@go-to-k/cdkd 0.285.7 → 0.285.9
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-B1EEjygi.js → asg-provider-hNkWDuAJ.js} +3 -3
- package/dist/{asg-provider-B1EEjygi.js.map → asg-provider-hNkWDuAJ.js.map} +1 -1
- package/dist/cli.js +2 -2
- package/dist/{deploy-engine-DmWPwiyQ.js → deploy-engine-BhBR2d3w.js} +568 -17
- package/dist/deploy-engine-BhBR2d3w.js.map +1 -0
- package/dist/index.d.ts +10 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/{logger-DyTk5GeO.js → logger-Dw-3y48G.js} +23 -13
- package/dist/{logger-DyTk5GeO.js.map → logger-Dw-3y48G.js.map} +1 -1
- package/dist/{program-CjlLza5f.js → program-B9vnKc3N.js} +498 -143
- package/dist/{program-CjlLza5f.js.map → program-B9vnKc3N.js.map} +1 -1
- package/dist/{version-DlUEyyGa.js → version-BWsyP2sq.js} +2 -2
- package/dist/{version-DlUEyyGa.js.map → version-BWsyP2sq.js.map} +1 -1
- package/package.json +1 -1
- package/dist/deploy-engine-DmWPwiyQ.js.map +0 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { $ as DEFAULT_STATE_PREFIX, $n as
|
|
2
|
-
import { _ as withStackName, c as PATTERN_B_NAME_OPTIONS, f as generateResourceName, g as withSkipPrefix, i as reserveStdoutForPayload, l as PATTERN_B_NAME_PROPERTIES, m as getCurrentStackName, n as getLogger, o as runStackBuffered, p as generateResourceNameWithFallback, s as getLiveRenderer, u as PATTERN_B_RESOURCE_TYPES } from "./logger-
|
|
1
|
+
import { $ as DEFAULT_STATE_PREFIX, $n as resolveUseCdkBootstrapAssets, $t as isSingleDynamicReferenceToken, A as bold, An as validateAssetBucketName, Ar as LocalStartServiceError, At as requireConfigObject, B as secretBearingStateKeyWarning, Bn as runDockerStreaming, Bt as DiffCalculator, C as unsupportedFinalSnapshotError, Cn as BOOTSTRAP_MARKER_PREFIX, Ct as assertRegionMatch, D as isStatefulRecreateTargetSync, Dn as isCrossRegionRedirect, Dr as DynamicReferenceRegionAmbiguousError, Dt as readConfigString, E as MULTI_REGION_RECREATE_BLOCKED_TYPES, En as getBootstrapMarkerKey, Er as DeployCancelledError, Et as configStringRefusal, F as yellow, Fn as dockerSpawnEnvWithSensitive, Fr as ProvisioningError, Ft as s3BucketDomainName, G as clearOnUpdateRemoval, Gn as getDefaultStateBucketName, Gr as withErrorHandling, Gt as TemplateParser, H as getCurrentResourceSecrets, Hn as getDockerImageBySourceHash, Ht as describeTypeWithThrottleRetry, I as collectDeclaredOutputNames, In as formatDockerLoginError, Ir as ResourceTimeoutError, It as s3BucketDualStackDomainName, J as findSilentDropProperties, Jn as resolveAutoAssetStorage, Jr as isThrottlingError, Jt as TEMPLATE_SOURCED_RULES, K as ProviderRegistry, Kn as getLegacyStateBucketName, Kr as isMarkedNonRetryable, Kt as STATE_SOURCED_CROSS_GENERATION_RULES, L as collectPublishedOutputNames, Ln as getDockerCmd, Lr as ResourceUpdateNotSupportedError, Lt as s3BucketRegionalDomainName, M as gray, Mn as buildDenyExternalAccessPolicy, Mr as MissingCdkCliError, Mt as classifyReplaySecretRegion, N as green, Nn as describeAwsFailure, Nr as NestedStackChildDirectDestroyError, Nt as producerRegionsFromState, O as renderStatefulReason, On as parseBootstrapMarker, Or as LocalInvokeBuildError$1, Ot as replayWarn, P as red, Pn as buildDockerImage, Pr as PartialFailureError, Pt as s3BucketArn, Q as CUSTOM_RESOURCE_RESPONSE_PREFIX, Qn as resolveStateBucketWithDefaultAndSource, Qr as retryClassificationText, Qt as errorCauseChain, R as exportAliasCollisionScrubWarning, Rn as partitionSensitiveEnv, Rr as StackHasActiveImportsError, Rt as s3BucketWebsiteUrl, S as refusesFinalSnapshot, Sn as AssetModeResolver, Sr as CdkdError, St as resolveExplicitPhysicalId, T as extractDeploymentEventError, Tn as ensureAssetStorage, Tt as configBooleanRefusal, U as IAMRoleProvider, Un as Synthesizer, Ut as withRetry, V as stateKeySecretExposure, Vn as AssetManifestLoader, Vt as INTRINSIC_KEYS, W as collectInlinePolicyNamesManagedBySiblings, Wn as synthesisStatusMessage, Wr as normalizeAwsError, Wt as DagBuilder, X as maskDeep, Xn as resolveSkipPrefix, Xr as markNonRetryable, Xt as createSecretMasker, Y as createMaskedRetryLogger, Yn as resolveCaptureObservedState, Yr as isTransientServerError, Yt as carriesSecretMask, Z as maskerOrIdentity, Zn as resolveStateBucketWithDefault, Zr as markRedactedCause, Zt as dynamicReferenceTokens, _ as PRE_DELETE_SNAPSHOT_TYPES, _n as createAssetRedirectResolver, _r as AwsClients, _t as isUnboundTemplateParameter, a as DeploymentEventsStore, an as scrubResourceRecord, ar as findLargeInlineResources, at as CloudControlProvider, b as createPreDeleteFinalSnapshot, bn as escapeRegExp, br as setAwsClients, bt as WAFv2WebACLProvider, c as replayFailedOperations, cn as rebuildClientForBucketRegion, cr as displaySafe, ct as deleteIndeterminateGuards, d as updatePartialReason, dn as importableOutputs, dr as canonicalizeRegion, dt as isTerminationProtectionPropagationError, en as maskSecretsInError, er as stateBucketExistenceConfirmed, et as beginCommandInterruptScope, f as withResourceDeadline, fn as shouldRetainResource, fr as derivePartitionAndUrlSuffix, ft as IntrinsicFunctionResolver, g as ATOMIC_FINAL_SNAPSHOT_TYPES, gn as buildAssetRedirectMap, gr as resolveBucketRegion, gt as getAccountInfo, h as computeImplicitDeleteEdges, hn as WorkGraph, ht as coerceParameterTypedValue, i as DeploymentEventsReader, in as redactSecretsForState, ir as MIGRATE_TMP_PREFIX, it as startInterruptWatch, j as cyan, jn as validateContainerRepoName, jt as requireConfigString, k as formatResourceLine, kn as readBootstrapMarkerBody, kr as LocalMigrateError, kt as requireConfigArray, l as replayRollback, ln as exportNamesCarriedFrom, lr as expectedOwnerParam, lt as deleteSkipReason, m as IMPLICIT_DELETE_DEPENDENCIES, mn as stringifyValue, mr as processStackMessages, mt as cfnRefValueFromPhysicalId, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as recordMaskOnlyValue, nr as CFN_TEMPLATE_BODY_LIMIT, nt as interruptWatchListenerCount, o as planFailedOps, on as LockManager, or as uploadCfnTemplate, ot as slowCcOperationTimeoutMs, p as maskingRetryLogger, pn as AssetPublisher, pr as AssemblyReader, pt as carriesDynamicReference, q as findActionableSilentDrops, qn as resolveApp, qr as isRetryableTransientError, qt as STATE_SOURCED_READBACK_RULES, r as DeployEngine, rn as recoverMaskedOutput, rr as CFN_TEMPLATE_URL_LIMIT, rt as isInterruptedWaitError, s as planRollback, sn as S3StateBackend, sr as CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION, st as UNSPECIFIED_SKIP_REASON, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as maskSecretsInText, tr as warnDeprecatedNoPrefixCliFlag, tt as endCommandInterruptScope, u as updatePartialMessage, un as importableOutputKeys, ur as PARTITION_TABLE, ut as disableInstanceApiTermination, v as buildFinalSnapshotIdentifier, vn as loadPublishableAssetManifest, vr as getAwsClients, vt as parameterTypeMayLoseSecretIdentity, w as makeCanonicalizePropertiesFn, wn as assertAssetBucketRegion, wr as CrossAccountSecretRefusalError, wt as coerceCfnBoolean, x as isFinalSnapshotError, xn as stripControlChars, xt as normalizeAwsTagsToCfn, y as ccRoutedFinalSnapshotError, yn as rewriteTemplateAssetReferences, yr as resetAwsClients, yt as refStateLookupFromResource, z as isExportAliasCollision, zn as runDockerForeground, zr as StackTerminationProtectionError, zt as applyRoleArnIfSet } from "./deploy-engine-BhBR2d3w.js";
|
|
2
|
+
import { _ as withStackName, c as PATTERN_B_NAME_OPTIONS, f as generateResourceName, g as withSkipPrefix, i as reserveStdoutForPayload, l as PATTERN_B_NAME_PROPERTIES, m as getCurrentStackName, n as getLogger, o as runStackBuffered, p as generateResourceNameWithFallback, s as getLiveRenderer, u as PATTERN_B_RESOURCE_TYPES } from "./logger-Dw-3y48G.js";
|
|
3
3
|
import { t as awsClientDefaults } from "./aws-client-defaults-D-iYiIJ6.js";
|
|
4
|
-
import { t as getCdkdVersion } from "./version-
|
|
5
|
-
import { t as ASGProvider } from "./asg-provider-
|
|
4
|
+
import { t as getCdkdVersion } from "./version-BWsyP2sq.js";
|
|
5
|
+
import { t as ASGProvider } from "./asg-provider-hNkWDuAJ.js";
|
|
6
6
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
7
7
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
8
8
|
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";
|
|
@@ -2675,14 +2675,32 @@ async function promptRecreateConfirm(input) {
|
|
|
2675
2675
|
//#endregion
|
|
2676
2676
|
//#region src/cli/commands/confirm-prompt.ts
|
|
2677
2677
|
/**
|
|
2678
|
-
*
|
|
2679
|
-
*
|
|
2680
|
-
*
|
|
2681
|
-
*
|
|
2682
|
-
*
|
|
2678
|
+
* The CLI's two shared confirmation-prompt helpers.
|
|
2679
|
+
*
|
|
2680
|
+
* They have OPPOSITE defaults and opposite non-interactive behaviour, which is
|
|
2681
|
+
* why both live here rather than one being expressed in terms of the other:
|
|
2682
|
+
*
|
|
2683
|
+
* - {@link promptYesNo} — default-YES (`[Y/n]`, empty input accepts). Used by
|
|
2684
|
+
* the issue #1007 asset-storage auto-create on interactive TTY deploys
|
|
2685
|
+
* without `--yes`. It deliberately carries NO non-interactive guard: its
|
|
2686
|
+
* only caller (`deploy.ts`, at the `options.yes || !process.stdin.isTTY`
|
|
2687
|
+
* short-circuit) never reaches it on a non-TTY stdin, and a deploy that
|
|
2688
|
+
* assumes "yes" is the one place in the CLI where auto-confirming is the
|
|
2689
|
+
* documented choice.
|
|
2690
|
+
* - {@link confirmOrRefuse} — default-NO (`[y/N]`), and REFUSES outright on a
|
|
2691
|
+
* non-interactive stdin. This is the one every MUTATING command uses.
|
|
2692
|
+
*
|
|
2693
|
+
* Kept in its own module (mirrors `recreate-confirm-prompt.ts`) so the
|
|
2694
|
+
* accept/decline parsing is unit-testable without importing whole command
|
|
2695
|
+
* files.
|
|
2696
|
+
*/
|
|
2697
|
+
/**
|
|
2698
|
+
* Default-YES confirmation prompt (`[Y/n]`, empty input = yes).
|
|
2699
|
+
*
|
|
2700
|
+
* See the module doc for why this one is deliberately unguarded.
|
|
2683
2701
|
*/
|
|
2684
2702
|
async function promptYesNo(prompt) {
|
|
2685
|
-
const rl = readline.createInterface({
|
|
2703
|
+
const rl = readline$1.createInterface({
|
|
2686
2704
|
input: process.stdin,
|
|
2687
2705
|
output: process.stdout
|
|
2688
2706
|
});
|
|
@@ -2693,6 +2711,79 @@ async function promptYesNo(prompt) {
|
|
|
2693
2711
|
rl.close();
|
|
2694
2712
|
}
|
|
2695
2713
|
}
|
|
2714
|
+
/** Suffix appended to a {@link confirmOrRefuse} prompt when none is given. */
|
|
2715
|
+
const DEFAULT_CONFIRM_SUFFIX = " [y/N] ";
|
|
2716
|
+
/**
|
|
2717
|
+
* Default-NO confirmation prompt that REFUSES a non-interactive run instead of
|
|
2718
|
+
* hanging on it (issue #2275, the class issue #2259 fixed for `cdkd destroy`).
|
|
2719
|
+
*
|
|
2720
|
+
* `rl.question` never settles when stdin is already at EOF, and EOF delivers
|
|
2721
|
+
* no signal, so nothing wakes it: a command that prompts without this guard
|
|
2722
|
+
* parks FOREVER in CI rather than failing, burning the job's whole timeout
|
|
2723
|
+
* budget. Measured on Node 24.15.0, the version `.node-version` pins, against
|
|
2724
|
+
* real `node:readline/promises`: `echo y |` resolves `"y"`, while both
|
|
2725
|
+
* `printf 'y' |` (a real answer with no trailing newline) and `< /dev/null`
|
|
2726
|
+
* stay pending indefinitely.
|
|
2727
|
+
*
|
|
2728
|
+
* REFUSE rather than auto-confirm, at every one of the nine sites this helper
|
|
2729
|
+
* replaced. Every one of them guards a MUTATION — a rollback replay, a state
|
|
2730
|
+
* record removal, an observed-property refresh, an orphan, an import, an
|
|
2731
|
+
* export-then-delete-state, a drift accept/revert, a CloudFormation stack
|
|
2732
|
+
* retirement, a state-bucket migration — so silently answering "yes" on
|
|
2733
|
+
* behalf of an absent operator is never the safe reading. `deploy.ts`'s
|
|
2734
|
+
* auto-confirm (`options.yes || !process.stdin.isTTY` -> proceed) stays the
|
|
2735
|
+
* deliberate exception, because a deploy that assumes "yes" is recoverable.
|
|
2736
|
+
*
|
|
2737
|
+
* A first cut of this guard (in `state.ts`, see the long comment at the
|
|
2738
|
+
* `state destroy --all` prompt) raced the question against readline's `close`
|
|
2739
|
+
* event and turned EOF into a decline. That lost three measured ways — a real
|
|
2740
|
+
* answer with no trailing newline was silently discarded, a delayed answer
|
|
2741
|
+
* lost the race, and an interactive Ctrl-C landed on the EOF arm — so do not
|
|
2742
|
+
* reintroduce it. A refusal has none of those.
|
|
2743
|
+
*
|
|
2744
|
+
* POSITION IS LOAD-BEARING, and this helper gets it for free: every call site
|
|
2745
|
+
* sits INSIDE the caller's `--yes` / `--force` short-circuit
|
|
2746
|
+
* (`if (!options.yes && !options.force) { ... }`), so a flagged
|
|
2747
|
+
* non-interactive run never reaches this function and never consults stdin at
|
|
2748
|
+
* all. Keep it that way — hoisting a call above its flag check would make
|
|
2749
|
+
* `--yes` refuse instead of proceed, which is the inverse bug.
|
|
2750
|
+
*
|
|
2751
|
+
* Throws {@link CdkdError} with the `NON_INTERACTIVE_CONFIRM` code, matching
|
|
2752
|
+
* `gc.ts` and `bootstrap-destroy.ts` — the only two of the five originally
|
|
2753
|
+
* guarded prompts that carry a code (the other three throw a bare `Error` or
|
|
2754
|
+
* a `LocalMigrateError`). `handleError` maps it to exit 1, so CI can branch on
|
|
2755
|
+
* it.
|
|
2756
|
+
*
|
|
2757
|
+
* TWO GUARDED PROMPTS DELIBERATELY DO NOT ROUTE THROUGH HERE:
|
|
2758
|
+
* `destroy-runner.ts`'s per-stack prompt (the issue #2259 fix) and
|
|
2759
|
+
* `state.ts`'s `state destroy --all` batch prompt (issue #2247). Both already
|
|
2760
|
+
* carry this exact guard inline, and both differ in ways a shared helper would
|
|
2761
|
+
* have to grow parameters for — `destroy-runner.ts` has a default-YES bare
|
|
2762
|
+
* form alongside a default-NO `--remove-protection` form, and `state.ts`
|
|
2763
|
+
* passes an abort `signal` for the issue #2117 Ctrl-C handling. Folding them
|
|
2764
|
+
* in would also drag `destroy-runner.ts` (in the `integ-destroy` AND
|
|
2765
|
+
* `integ-broad` gate scopes) into a pure refactor's blast radius, buying a
|
|
2766
|
+
* real-AWS run for no behaviour change. The other five pre-existing guarded
|
|
2767
|
+
* prompts (`gc.ts`, `bootstrap-destroy.ts`, `recreate-confirm-prompt.ts`,
|
|
2768
|
+
* `prefix-migration-check.ts`, `migrate-command.ts`) stay put for the same
|
|
2769
|
+
* reason: each guards its own flow with its own error type.
|
|
2770
|
+
*
|
|
2771
|
+
* @param prompt the question, WITHOUT its trailing `[y/N]` marker
|
|
2772
|
+
* @returns `true` only for `y` / `yes` (any case, trimmed)
|
|
2773
|
+
*/
|
|
2774
|
+
async function confirmOrRefuse(prompt, options) {
|
|
2775
|
+
if (process.stdin.isTTY !== true) throw new CdkdError(options.refusal, "NON_INTERACTIVE_CONFIRM");
|
|
2776
|
+
const rl = readline$1.createInterface({
|
|
2777
|
+
input: process.stdin,
|
|
2778
|
+
output: options.output ?? process.stdout
|
|
2779
|
+
});
|
|
2780
|
+
try {
|
|
2781
|
+
const ans = await rl.question(`${prompt}${options.suffix ?? " [y/N] "}`);
|
|
2782
|
+
return /^y(es)?$/i.test(ans.trim());
|
|
2783
|
+
} finally {
|
|
2784
|
+
rl.close();
|
|
2785
|
+
}
|
|
2786
|
+
}
|
|
2696
2787
|
|
|
2697
2788
|
//#endregion
|
|
2698
2789
|
//#region src/cli/commands/deployment-events-run.ts
|
|
@@ -60556,10 +60647,11 @@ var NestedStackProvider = class {
|
|
|
60556
60647
|
const resourceCount = Object.keys(childTemplate.Resources ?? {}).length;
|
|
60557
60648
|
this.logger.info(`Deploying nested stack ${childStackName} (logicalId=${logicalId}, ${resourceCount} resource(s))`);
|
|
60558
60649
|
await this.runChildDeploy(ctx, logicalId, childStackName, childRegion, childTemplate, childParameters, grandchildTemplates);
|
|
60559
|
-
const
|
|
60650
|
+
const childOutputs = await this.readChildOutputsAsAttributes(ctx, childStackName, childRegion);
|
|
60560
60651
|
return {
|
|
60561
60652
|
physicalId: this.synthesizeArn(ctx.accountId, childRegion, ctx.parentStackName, logicalId),
|
|
60562
|
-
attributes
|
|
60653
|
+
attributes: childOutputs.attributes,
|
|
60654
|
+
...childOutputs.noEchoAttributeNames.length > 0 && { noEchoAttributeNames: childOutputs.noEchoAttributeNames }
|
|
60563
60655
|
};
|
|
60564
60656
|
}
|
|
60565
60657
|
async update(logicalId, physicalId, _resourceType, properties, _previousProperties) {
|
|
@@ -60575,10 +60667,12 @@ var NestedStackProvider = class {
|
|
|
60575
60667
|
const resourceCount = Object.keys(childTemplate.Resources ?? {}).length;
|
|
60576
60668
|
this.logger.info(`Updating nested stack ${childStackName} (logicalId=${logicalId}, ${resourceCount} resource(s))`);
|
|
60577
60669
|
await this.runChildDeploy(ctx, logicalId, childStackName, childRegion, childTemplate, childParameters, grandchildTemplates);
|
|
60670
|
+
const updatedOutputs = await this.readChildOutputsAsAttributes(ctx, childStackName, childRegion);
|
|
60578
60671
|
return {
|
|
60579
60672
|
physicalId,
|
|
60580
60673
|
wasReplaced: false,
|
|
60581
|
-
attributes:
|
|
60674
|
+
attributes: updatedOutputs.attributes,
|
|
60675
|
+
...updatedOutputs.noEchoAttributeNames.length > 0 && { noEchoAttributeNames: updatedOutputs.noEchoAttributeNames }
|
|
60582
60676
|
};
|
|
60583
60677
|
}
|
|
60584
60678
|
async delete(logicalId, _physicalId, _resourceType, _properties, deleteContext) {
|
|
@@ -60651,10 +60745,50 @@ var NestedStackProvider = class {
|
|
|
60651
60745
|
nestedTemplates: grandchildTemplates
|
|
60652
60746
|
}, () => childEngine.deploy(childStackName, childTemplate));
|
|
60653
60747
|
}
|
|
60748
|
+
/**
|
|
60749
|
+
* The child's outputs, as `Outputs.<Key>` attributes for the parent's
|
|
60750
|
+
* `Fn::GetAtt` — plus the subset whose plaintext this run had to RECOVER
|
|
60751
|
+
* (issue [#2274](https://github.com/go-to-k/cdkd/issues/2274)).
|
|
60752
|
+
*
|
|
60753
|
+
* WHY THE RECOVERY IS HERE. This method reads the child's PERSISTED state,
|
|
60754
|
+
* and since #2274 an output whose value resolved a `NoEcho` custom resource's
|
|
60755
|
+
* `Data` is persisted as the redaction mask. Handing the parent `***` would
|
|
60756
|
+
* make the FIRST deploy of any such parent refuse — a template that deployed
|
|
60757
|
+
* before the feature — so where the child engine masked that output moments
|
|
60758
|
+
* ago IN THIS PROCESS, the plaintext is still available (see
|
|
60759
|
+
* `recoverMaskedOutput`) and is what the parent gets. The reported names are
|
|
60760
|
+
* what tells the deploy engine to re-register those values as MASK-ONLY
|
|
60761
|
+
* needles, so the parent's own record and every parent resource that consumes
|
|
60762
|
+
* one still persist `***`.
|
|
60763
|
+
*
|
|
60764
|
+
* PER ATTRIBUTE, not for the bag: a child typically exports one sensitive
|
|
60765
|
+
* output among several ordinary ones, and declaring the whole bag would mask
|
|
60766
|
+
* every unrelated output into the parent's state and into any parent resource
|
|
60767
|
+
* that reads one.
|
|
60768
|
+
*
|
|
60769
|
+
* A mask this run did NOT produce (the child was deployed by an earlier run
|
|
60770
|
+
* and its custom resource was `NO_CHANGE`, so no handler ran) is left as the
|
|
60771
|
+
* mask, and the resolver refuses the consumer rather than writing `***` to
|
|
60772
|
+
* AWS. That refusal is the feature's stated cost; recovering here removes it
|
|
60773
|
+
* for the in-run case only.
|
|
60774
|
+
*/
|
|
60654
60775
|
async readChildOutputsAsAttributes(ctx, childStackName, childRegion) {
|
|
60655
60776
|
const childStateData = await ctx.stateBackend.getState(childStackName, childRegion);
|
|
60656
60777
|
if (!childStateData) throw new Error(`Child stack state '${childStackName}' not found after deploy — NestedStackProvider invariant violated.`);
|
|
60657
|
-
|
|
60778
|
+
const attributes = this.buildOutputsAttributes(childStateData.state.outputs ?? {});
|
|
60779
|
+
const noEchoAttributeNames = [];
|
|
60780
|
+
for (const [outputKey, persisted] of Object.entries(childStateData.state.outputs ?? {})) {
|
|
60781
|
+
if (!carriesSecretMask(persisted)) continue;
|
|
60782
|
+
const recovered = recoverMaskedOutput(childStackName, childRegion, outputKey);
|
|
60783
|
+
if (recovered === void 0) continue;
|
|
60784
|
+
const attributeName = this.outputAttributeName(outputKey);
|
|
60785
|
+
attributes[attributeName] = recovered;
|
|
60786
|
+
noEchoAttributeNames.push(attributeName);
|
|
60787
|
+
}
|
|
60788
|
+
return {
|
|
60789
|
+
attributes,
|
|
60790
|
+
noEchoAttributeNames
|
|
60791
|
+
};
|
|
60658
60792
|
}
|
|
60659
60793
|
requireContext() {
|
|
60660
60794
|
const ctx = getCurrentNestedStackContext();
|
|
@@ -60777,9 +60911,17 @@ var NestedStackProvider = class {
|
|
|
60777
60911
|
* make EVERY consumer persist it — but the count is "one resolution path",
|
|
60778
60912
|
* not "one reader".
|
|
60779
60913
|
*/
|
|
60914
|
+
/**
|
|
60915
|
+
* The ONE spelling of a child output's attribute name. Both this class's
|
|
60916
|
+
* writers go through it, so the recovery pass (issue #2274) cannot name a key
|
|
60917
|
+
* `buildOutputsAttributes` did not write.
|
|
60918
|
+
*/
|
|
60919
|
+
outputAttributeName(outputKey) {
|
|
60920
|
+
return `Outputs.${outputKey}`;
|
|
60921
|
+
}
|
|
60780
60922
|
buildOutputsAttributes(outputs) {
|
|
60781
60923
|
const attributes = {};
|
|
60782
|
-
for (const [key, value] of Object.entries(outputs)) attributes[
|
|
60924
|
+
for (const [key, value] of Object.entries(outputs)) attributes[this.outputAttributeName(key)] = value;
|
|
60783
60925
|
return attributes;
|
|
60784
60926
|
}
|
|
60785
60927
|
};
|
|
@@ -62830,6 +62972,36 @@ const TEMPLATE_BODY_LIMIT = CFN_TEMPLATE_BODY_LIMIT;
|
|
|
62830
62972
|
*/
|
|
62831
62973
|
const TEMPLATE_URL_LIMIT = CFN_TEMPLATE_URL_LIMIT;
|
|
62832
62974
|
/**
|
|
62975
|
+
* Best-effort reaper for the transient template bodies this flow uploads to
|
|
62976
|
+
* the state bucket under `cdkd-migrate-tmp/`.
|
|
62977
|
+
*
|
|
62978
|
+
* ONE implementation because {@link retireCloudFormationStack} drains from
|
|
62979
|
+
* four different exits — the recursive walk's `catch`, the confirmation
|
|
62980
|
+
* gate's decline AND its non-interactive refusal, the no-modification fast
|
|
62981
|
+
* path, and the post-`UpdateStack` `finally`. Each used to carry its own copy
|
|
62982
|
+
* of this loop, and the copies were how the refusal exit came to have none at
|
|
62983
|
+
* all: a `throw` from the gate passes every one of the other three.
|
|
62984
|
+
*
|
|
62985
|
+
* Best-effort by design. A leaked transient object costs pennies and lives
|
|
62986
|
+
* under an explicitly-named prefix, while a failed DELETE that propagated
|
|
62987
|
+
* would turn a successful retire into a reported failure. The retire's
|
|
62988
|
+
* success is governed by CloudFormation, not by S3 — so each cleanup's own
|
|
62989
|
+
* error becomes a `warn` naming the prefix to sweep manually.
|
|
62990
|
+
*
|
|
62991
|
+
* `what` / `when` keep the four sites' distinct wording (a caller says
|
|
62992
|
+
* whether these are the parent's own upload or a nested child's, and whether
|
|
62993
|
+
* it is unwinding); the SENTENCE they sit in is shared.
|
|
62994
|
+
*/
|
|
62995
|
+
async function drainTemplateUploads(cleanups, stateBucket, labels) {
|
|
62996
|
+
const logger = getLogger();
|
|
62997
|
+
for (const cleanup of cleanups) try {
|
|
62998
|
+
await cleanup();
|
|
62999
|
+
} catch (cleanupErr) {
|
|
63000
|
+
const msg = cleanupErr instanceof Error ? cleanupErr.message : String(cleanupErr);
|
|
63001
|
+
logger.warn(`Failed to delete ${labels.what} from '${stateBucket}'${labels.when ? ` ${labels.when}` : ""}. Clean up manually under prefix '${MIGRATE_TMP_PREFIX}/'. Cause: ${msg}`);
|
|
63002
|
+
}
|
|
63003
|
+
}
|
|
63004
|
+
/**
|
|
62833
63005
|
* Retire a CloudFormation stack whose resources have just been adopted into
|
|
62834
63006
|
* cdkd state. The 4-step procedure is the one AWS recommends for handing
|
|
62835
63007
|
* over resources between management tools without deleting them:
|
|
@@ -62893,35 +63065,31 @@ async function retireCloudFormationStack(options) {
|
|
|
62893
63065
|
format = recursive.format;
|
|
62894
63066
|
nestedCleanups.push(...recursive.cleanups);
|
|
62895
63067
|
} catch (err) {
|
|
62896
|
-
if (err instanceof RecursiveRetainInjectionError)
|
|
62897
|
-
|
|
62898
|
-
|
|
62899
|
-
|
|
62900
|
-
logger.warn(`Failed to delete partial nested-template upload from '${stateBucket}' during error recovery. Clean up manually under prefix '${MIGRATE_TMP_PREFIX}/'. Cause: ${msg}`);
|
|
62901
|
-
}
|
|
63068
|
+
if (err instanceof RecursiveRetainInjectionError) await drainTemplateUploads(err.cleanups, stateBucket, {
|
|
63069
|
+
what: "partial nested-template upload",
|
|
63070
|
+
when: "during error recovery"
|
|
63071
|
+
});
|
|
62902
63072
|
throw err;
|
|
62903
63073
|
}
|
|
62904
63074
|
} else ({body: newBody, modified, format} = injectRetainPolicies(tpl.TemplateBody, cfnStackName));
|
|
62905
63075
|
if (!yes) {
|
|
62906
|
-
|
|
63076
|
+
let proceed = false;
|
|
63077
|
+
try {
|
|
63078
|
+
proceed = await confirmPrompt$6(`Set DeletionPolicy=Retain and UpdateReplacePolicy=Retain on every resource in CloudFormation stack '${cfnStackName}', then delete the stack? AWS resources will NOT be deleted (cdkd state has been written).`);
|
|
63079
|
+
} finally {
|
|
63080
|
+
if (!proceed) await drainTemplateUploads(nestedCleanups, stateBucket, {
|
|
63081
|
+
what: "temporary nested-template upload",
|
|
63082
|
+
when: "during cancel cleanup"
|
|
63083
|
+
});
|
|
63084
|
+
}
|
|
63085
|
+
if (!proceed) {
|
|
62907
63086
|
logger.info("CloudFormation stack retirement cancelled. cdkd state is unaffected.");
|
|
62908
|
-
for (const cleanup of nestedCleanups) try {
|
|
62909
|
-
await cleanup();
|
|
62910
|
-
} catch (cleanupErr) {
|
|
62911
|
-
const msg = cleanupErr instanceof Error ? cleanupErr.message : String(cleanupErr);
|
|
62912
|
-
logger.warn(`Failed to delete temporary nested-template upload from '${stateBucket}' during cancel cleanup. Clean up manually under prefix '${MIGRATE_TMP_PREFIX}/'. Cause: ${msg}`);
|
|
62913
|
-
}
|
|
62914
63087
|
return { outcome: "cancelled" };
|
|
62915
63088
|
}
|
|
62916
63089
|
}
|
|
62917
63090
|
if (!modified) {
|
|
62918
63091
|
logger.info(`[2/4] Template already has Retain on every resource — skipping UpdateStack.`);
|
|
62919
|
-
|
|
62920
|
-
await cleanup();
|
|
62921
|
-
} catch (cleanupErr) {
|
|
62922
|
-
const msg = cleanupErr instanceof Error ? cleanupErr.message : String(cleanupErr);
|
|
62923
|
-
logger.warn(`Failed to delete temporary nested-template upload from '${stateBucket}'. Clean up manually under prefix '${MIGRATE_TMP_PREFIX}/'. Cause: ${msg}`);
|
|
62924
|
-
}
|
|
63092
|
+
await drainTemplateUploads(nestedCleanups, stateBucket, { what: "temporary nested-template upload" });
|
|
62925
63093
|
} else {
|
|
62926
63094
|
logger.info(`[2/4] Injected DeletionPolicy=Retain and UpdateReplacePolicy=Retain.`);
|
|
62927
63095
|
if (newBody.length > TEMPLATE_URL_LIMIT) throw new Error(`Modified template is ${newBody.length} bytes, exceeds the CloudFormation UpdateStack TemplateURL limit (${TEMPLATE_URL_LIMIT}). cdkd state has already been written; retire the stack manually with (1) shrink the template, then (2) UpdateStack with Retain policies, (3) DeleteStack — or split the stack and retry.`);
|
|
@@ -62967,13 +63135,7 @@ async function retireCloudFormationStack(options) {
|
|
|
62967
63135
|
maxDelay: 10
|
|
62968
63136
|
}, { StackName: cfnStackName });
|
|
62969
63137
|
} finally {
|
|
62970
|
-
|
|
62971
|
-
for (const cleanup of allCleanups) try {
|
|
62972
|
-
await cleanup();
|
|
62973
|
-
} catch (cleanupErr) {
|
|
62974
|
-
const msg = cleanupErr instanceof Error ? cleanupErr.message : String(cleanupErr);
|
|
62975
|
-
logger.warn(`Failed to delete temporary template upload from '${stateBucket}'. Clean up manually under prefix '${MIGRATE_TMP_PREFIX}/'. Cause: ${msg}`);
|
|
62976
|
-
}
|
|
63138
|
+
await drainTemplateUploads([...s3Cleanup ? [s3Cleanup] : [], ...nestedCleanups], stateBucket, { what: "temporary template upload" });
|
|
62977
63139
|
}
|
|
62978
63140
|
}
|
|
62979
63141
|
logger.info(`[4/4] Deleting CloudFormation stack '${cfnStackName}' (resources retained)...`);
|
|
@@ -63284,17 +63446,30 @@ async function walkCfnStackTree(stackName, physicalId, cfnClient) {
|
|
|
63284
63446
|
nested
|
|
63285
63447
|
};
|
|
63286
63448
|
}
|
|
63449
|
+
/**
|
|
63450
|
+
* The CloudFormation-stack retirement confirmation prompt, reached from
|
|
63451
|
+
* `cdkd import --migrate-from-cloudformation` and `cdkd migrate
|
|
63452
|
+
* --retire-cfn-stack` (both spell the skip flag `-y` / `--yes`). Its only call
|
|
63453
|
+
* site is inside the `if (!yes)` block above, which is what keeps
|
|
63454
|
+
* `confirmOrRefuse`'s non-interactive refusal (issue #2275) from firing on a
|
|
63455
|
+
* `--yes` run.
|
|
63456
|
+
*
|
|
63457
|
+
* TWO commands reach it, so the refusal NAMES BOTH. Every other site names one
|
|
63458
|
+
* command because it has one; a refusal that named neither would leave a CI
|
|
63459
|
+
* user knowing a flag is missing without knowing which invocation to put it
|
|
63460
|
+
* on, and `confirmOrRefuse`'s own contract is that the refusal is the only
|
|
63461
|
+
* thing that user sees.
|
|
63462
|
+
*
|
|
63463
|
+
* It also states that cdkd state has ALREADY been written. This prompt is the
|
|
63464
|
+
* only one of the nine that fires AFTER its command's state write, so a
|
|
63465
|
+
* refusal here leaves a genuine split brain — cdkd claims the resources while
|
|
63466
|
+
* the CloudFormation stack is still live — and the operator has to know that
|
|
63467
|
+
* before deciding whether to re-run or finish by hand.
|
|
63468
|
+
*
|
|
63469
|
+
* Exported for unit testing — internal to the command flow otherwise.
|
|
63470
|
+
*/
|
|
63287
63471
|
async function confirmPrompt$6(prompt) {
|
|
63288
|
-
|
|
63289
|
-
input: process.stdin,
|
|
63290
|
-
output: process.stdout
|
|
63291
|
-
});
|
|
63292
|
-
try {
|
|
63293
|
-
const ans = await rl.question(`${prompt} [y/N] `);
|
|
63294
|
-
return /^y(es)?$/i.test(ans.trim());
|
|
63295
|
-
} finally {
|
|
63296
|
-
rl.close();
|
|
63297
|
-
}
|
|
63472
|
+
return confirmOrRefuse(prompt, { refusal: "The CloudFormation stack retirement confirmation prompt cannot run in a non-interactive environment. Pass -y / --yes on whichever command you ran — cdkd import --migrate-from-cloudformation or cdkd migrate --retire-cfn-stack — or run it from a real terminal. NOTE cdkd state has already been written: the resources are recorded in cdkd state while the CloudFormation stack is still live, so re-run with -y / --yes (or retire the stack by hand) to finish the migration." });
|
|
63298
63473
|
}
|
|
63299
63474
|
/**
|
|
63300
63475
|
* Flat `logicalId -> physicalId` map for ONE CloudFormation stack, or `null`
|
|
@@ -67724,6 +67899,37 @@ function collectDynamicReferencePaths(value, into, path = "") {
|
|
|
67724
67899
|
if (value !== null && typeof value === "object") for (const [k, v] of Object.entries(value)) collectDynamicReferencePaths(v, into, path === "" ? k : `${path}.${k}`);
|
|
67725
67900
|
}
|
|
67726
67901
|
/**
|
|
67902
|
+
* Record the dotted path of every leaf that IS {@link SECRET_MASK} — the
|
|
67903
|
+
* mask-only twin of {@link collectDynamicReferencePaths} (issue
|
|
67904
|
+
* [#2274](https://github.com/go-to-k/cdkd/issues/2274)).
|
|
67905
|
+
*
|
|
67906
|
+
* WITHOUT IT A NESTED MASK IS INVISIBLE, and that is a live disclosure rather
|
|
67907
|
+
* than a tidiness gap. `calculateResourceDrift` deliberately does not descend
|
|
67908
|
+
* arrays, so a masked leaf at `ContainerDefinitions.0.Environment.0.Value`
|
|
67909
|
+
* surfaces as ONE change at path `ContainerDefinitions` whose `stateValue` is
|
|
67910
|
+
* the WHOLE ARRAY — never equal to the mask. A whole-value equality test
|
|
67911
|
+
* therefore answers `false`, `secretBearing` stays false, the LIVE plaintext is
|
|
67912
|
+
* printed by the report, and `--accept` writes it into `state.json`, undoing
|
|
67913
|
+
* exactly the redaction this feature exists to perform.
|
|
67914
|
+
*
|
|
67915
|
+
* The expression class already answers this, and answers it this way: paths go
|
|
67916
|
+
* in at the LEAF's own coordinate and {@link isSecretBearingPath} bridges to the
|
|
67917
|
+
* comparator's coarser one by matching ANCESTORS. Seeded off the state bags,
|
|
67918
|
+
* exactly like the offline dynamic-reference seed, because a mask is a fact
|
|
67919
|
+
* about the RECORD and needs no AWS call to see.
|
|
67920
|
+
*/
|
|
67921
|
+
function collectSecretMaskPaths(value, into, path = "") {
|
|
67922
|
+
if (typeof value === "string") {
|
|
67923
|
+
if (value === "***") into.add(path);
|
|
67924
|
+
return;
|
|
67925
|
+
}
|
|
67926
|
+
if (Array.isArray(value)) {
|
|
67927
|
+
value.forEach((item, i) => collectSecretMaskPaths(item, into, path === "" ? String(i) : `${path}.${i}`));
|
|
67928
|
+
return;
|
|
67929
|
+
}
|
|
67930
|
+
if (value !== null && typeof value === "object") for (const [k, v] of Object.entries(value)) collectSecretMaskPaths(v, into, path === "" ? k : `${path}.${k}`);
|
|
67931
|
+
}
|
|
67932
|
+
/**
|
|
67727
67933
|
* The drift command's resolvers for ONE stack: the stack's own, plus one pinned
|
|
67728
67934
|
* sibling per FOREIGN region an ARN-named reference asks for (issue
|
|
67729
67935
|
* [#2108](https://github.com/go-to-k/cdkd/issues/2108)).
|
|
@@ -68093,7 +68299,7 @@ function redactDriftValue(value, secrets, secretBearing) {
|
|
|
68093
68299
|
* cannot disagree — a `--dry-run` that promises a write the real run refuses is
|
|
68094
68300
|
* worse than either behaviour on its own.
|
|
68095
68301
|
*
|
|
68096
|
-
*
|
|
68302
|
+
* Three refusals, all about a value cdkd could not identify rather than about
|
|
68097
68303
|
* secrecy as such:
|
|
68098
68304
|
*
|
|
68099
68305
|
* - A masked VALUE says AWS holds something at a known-secret path that is not
|
|
@@ -68103,6 +68309,10 @@ function redactDriftValue(value, secrets, secretBearing) {
|
|
|
68103
68309
|
* `setAtPath` would then create a key literally named `***` — and when
|
|
68104
68310
|
* `awsValue` is `undefined` it would INSERT that key rather than removing the
|
|
68105
68311
|
* real one, so the value check alone does not cover it.
|
|
68312
|
+
* - A masked BASELINE (issue #2274) says the state side itself is the mask —
|
|
68313
|
+
* a `NoEcho` custom resource's `Data` redacted out of `state.json`. Accepting
|
|
68314
|
+
* would write the live plaintext over a deliberate redaction, which is the
|
|
68315
|
+
* opposite of what the mask is for.
|
|
68106
68316
|
*
|
|
68107
68317
|
* The path check is what makes the value exemptions safe: `redactDriftValue`
|
|
68108
68318
|
* lets `undefined` / `null` / a whole expression through unmasked, which is
|
|
@@ -68112,6 +68322,7 @@ function acceptRefusalReason(change, maskedPaths) {
|
|
|
68112
68322
|
if (!maskedPaths.has(change.path)) return void 0;
|
|
68113
68323
|
if (change.path.includes("***")) return "its property NAME came back carrying a secret value, so cdkd cannot name the key to write — and no redaction pass can clear it either, since they all walk values and never keys. ROTATE the secret; that is the only remedy";
|
|
68114
68324
|
if (change.awsValue === void 0) return "AWS no longer reports it, and accepting an absence DELETES the key — which at or above a secret dynamic reference would erase the reference out of cdkd state. Use --revert to push it back, or re-deploy";
|
|
68325
|
+
if (change.stateValue === "***") return "cdkd does not know the value that belongs at this position — the baseline holds only the redaction mask, so accepting would write AWS-held plaintext over a deliberate redaction. Re-deploy to refresh it";
|
|
68115
68326
|
return "it carries a secret dynamic reference and the value AWS currently holds is not the one the reference resolves to";
|
|
68116
68327
|
}
|
|
68117
68328
|
/**
|
|
@@ -68139,8 +68350,8 @@ function acceptRefusalReason(change, maskedPaths) {
|
|
|
68139
68350
|
*
|
|
68140
68351
|
* Returns the input array by identity when there is nothing to act on.
|
|
68141
68352
|
*/
|
|
68142
|
-
function redactDriftChanges(changes, secrets, secretPaths) {
|
|
68143
|
-
if (secrets.size === 0 && secretPaths.size === 0) return {
|
|
68353
|
+
function redactDriftChanges(changes, secrets, secretPaths, maskPaths) {
|
|
68354
|
+
if (secrets.size === 0 && secretPaths.size === 0 && maskPaths.size === 0 && !changes.some((change) => carriesSecretMask(change.stateValue))) return {
|
|
68144
68355
|
changes,
|
|
68145
68356
|
maskedPaths: /* @__PURE__ */ new Set()
|
|
68146
68357
|
};
|
|
@@ -68148,9 +68359,10 @@ function redactDriftChanges(changes, secrets, secretPaths) {
|
|
|
68148
68359
|
const redacted = [];
|
|
68149
68360
|
for (const change of changes) {
|
|
68150
68361
|
const maskedPath = maskSecretsInText(change.path, secrets);
|
|
68151
|
-
const positionIsSecret = isSecretBearingPath(change.path, secretPaths);
|
|
68362
|
+
const positionIsSecret = isSecretBearingPath(change.path, secretPaths) || isSecretBearingPath(change.path, maskPaths);
|
|
68152
68363
|
const nameCarriesSecret = maskedPath !== change.path;
|
|
68153
|
-
const
|
|
68364
|
+
const stateValueIsMask = carriesSecretMask(change.stateValue);
|
|
68365
|
+
const secretBearing = positionIsSecret || nameCarriesSecret || stateValueIsMask;
|
|
68154
68366
|
if (secretPaths.has(change.path) && change.awsValue === void 0) continue;
|
|
68155
68367
|
const stateValue = redactDriftValue(change.stateValue, secrets, secretBearing);
|
|
68156
68368
|
const awsValue = redactDriftValue(change.awsValue, secrets, secretBearing);
|
|
@@ -68247,6 +68459,9 @@ async function runDriftForStack(stackName, region, stateBackend, providerRegistr
|
|
|
68247
68459
|
const seededSecretPaths = /* @__PURE__ */ new Set();
|
|
68248
68460
|
collectDynamicReferencePaths(baseline, seededSecretPaths);
|
|
68249
68461
|
if (useObserved) collectDynamicReferencePaths(resource.properties ?? {}, seededSecretPaths);
|
|
68462
|
+
const maskSecretPaths = /* @__PURE__ */ new Set();
|
|
68463
|
+
collectSecretMaskPaths(baseline, maskSecretPaths);
|
|
68464
|
+
if (useObserved) collectSecretMaskPaths(resource.properties ?? {}, maskSecretPaths);
|
|
68250
68465
|
const unresolvedTokens = /* @__PURE__ */ new Set();
|
|
68251
68466
|
const noteUnresolved = (tokens) => {
|
|
68252
68467
|
for (const token of tokens) unresolvedTokens.add(token);
|
|
@@ -68287,7 +68502,7 @@ async function runDriftForStack(stackName, region, stateBackend, providerRegistr
|
|
|
68287
68502
|
ignorePaths: observedIgnorePaths.length ? [...ignorePaths, ...observedIgnorePaths] : ignorePaths,
|
|
68288
68503
|
unionWalkObjects: useObserved,
|
|
68289
68504
|
unorderedPaths
|
|
68290
|
-
}), secrets, secretResolutionFailed ? seededSecretPaths : secretPaths);
|
|
68505
|
+
}), secrets, secretResolutionFailed ? seededSecretPaths : secretPaths, maskSecretPaths);
|
|
68291
68506
|
const notComparedCause = secretResolutionFailed ? "refused" : unresolvedTokens.size > 0 ? "unresolvedToken" : void 0;
|
|
68292
68507
|
if (reported.changes.length === 0) if (notComparedCause !== void 0) outcomes.push({
|
|
68293
68508
|
kind: "notCompared",
|
|
@@ -68889,6 +69104,101 @@ function preserveLiveValuesAtUnresolvedTokens(send, awsProperties, secrets) {
|
|
|
68889
69104
|
return walk(send, awsProperties);
|
|
68890
69105
|
}
|
|
68891
69106
|
/**
|
|
69107
|
+
* Keep `cdkd drift --revert` from pushing a REDACTION MASK to AWS (issue
|
|
69108
|
+
* [#2274](https://github.com/go-to-k/cdkd/issues/2274)).
|
|
69109
|
+
*
|
|
69110
|
+
* WHY THIS EXISTS AT ALL. A `NoEcho` custom resource's `Data` resolved into a
|
|
69111
|
+
* dependent's property is persisted as {@link SECRET_MASK}, because there is no
|
|
69112
|
+
* expression to store in its place. `--revert` pushes the BASELINE to AWS, so
|
|
69113
|
+
* without this a revert triggered by a SIBLING key would write the literal
|
|
69114
|
+
* `***` onto the live SSM parameter / secret / env var — the issue #1498 /
|
|
69115
|
+
* #1501 data-corruption class, and strictly worse than the disclosure the mask
|
|
69116
|
+
* exists to prevent. Shipping the mask without this guard would trade one for
|
|
69117
|
+
* the other.
|
|
69118
|
+
*
|
|
69119
|
+
* The sibling of {@link preserveLiveValuesAtUnresolvedTokens} and deliberately
|
|
69120
|
+
* a SEPARATE function rather than another branch inside it. That one is gated
|
|
69121
|
+
* on `unresolvedTokens.size > 0` at its call site, and this hazard is decided
|
|
69122
|
+
* by the SEND bag alone — a resource can carry a mask with no unresolved token
|
|
69123
|
+
* anywhere, so folding the two would put this check behind a condition that has
|
|
69124
|
+
* nothing to do with it.
|
|
69125
|
+
*
|
|
69126
|
+
* IT REGISTERS WHAT IT MOVES, exactly as the sibling does, and an earlier
|
|
69127
|
+
* revision's reason for not doing so was wrong: it said "the live value is one
|
|
69128
|
+
* cdkd never resolved and cannot recognise, so there is no needle to record".
|
|
69129
|
+
* The needle is `liveValue -> {@link SECRET_MASK}`, i.e. a MASK-ONLY entry, and
|
|
69130
|
+
* the evidence for it is the position — THE MASKED POSITION IS THE PROOF THE
|
|
69131
|
+
* VALUE IS SECRET, since nothing but this module's own redaction puts a mask at
|
|
69132
|
+
* a leaf. Without the registration this function copies live plaintext into the
|
|
69133
|
+
* send bag and then `collectNarrowedTopLevelKeys` persists that delta into
|
|
69134
|
+
* `observedProperties` against a `secrets` map holding no entry for it — the
|
|
69135
|
+
* same disclosure the mask exists to prevent, arriving through the mechanism
|
|
69136
|
+
* that is supposed to protect it — and the same omission un-masks it in
|
|
69137
|
+
* `maskSecretsInText(err.message, secrets)` and in the masker handed to
|
|
69138
|
+
* `provider.update`.
|
|
69139
|
+
*
|
|
69140
|
+
* `secrets` is therefore MUTATED, which is why it is a parameter rather than
|
|
69141
|
+
* something the caller could omit. `recordMaskOnlyValue` applies its own
|
|
69142
|
+
* refusals (an entry that already carries a real EXPRESSION wins; a plaintext
|
|
69143
|
+
* below the needle floor is not recorded), so this call site states only the
|
|
69144
|
+
* position fact and lets the module decide what is registrable.
|
|
69145
|
+
*
|
|
69146
|
+
* TWO OUTCOMES per masked leaf, and the second is why this returns a report
|
|
69147
|
+
* rather than just a bag:
|
|
69148
|
+
*
|
|
69149
|
+
* - AWS holds a value there -> copy the LIVE value in. The revert then leaves
|
|
69150
|
+
* that position exactly as AWS has it while still reverting every other
|
|
69151
|
+
* drifted key, which is the same trade `preserveLiveValuesAtUnresolvedTokens`
|
|
69152
|
+
* makes.
|
|
69153
|
+
* - AWS holds nothing there (the position could not be aligned, or the key is
|
|
69154
|
+
* absent live) -> there is no safe value to send, so the path is reported as
|
|
69155
|
+
* UNPRESERVABLE and the caller refuses the whole resource. Sending the mask
|
|
69156
|
+
* would be the corruption; dropping the key would delete a property the
|
|
69157
|
+
* resource may require.
|
|
69158
|
+
*
|
|
69159
|
+
* Array descent is POSITIONAL and bails to "no live value" on any length
|
|
69160
|
+
* mismatch, matching the sibling: a reordered readback cannot be positioned
|
|
69161
|
+
* against, and guessing would put one element's live value at another's index.
|
|
69162
|
+
*
|
|
69163
|
+
* Returns the input bag BY IDENTITY when it holds no mask, so the ordinary
|
|
69164
|
+
* revert is byte-identical and pays one walk.
|
|
69165
|
+
*/
|
|
69166
|
+
function preserveLiveValuesAtMaskedLeaves(send, awsProperties, secrets) {
|
|
69167
|
+
const unpreservablePaths = [];
|
|
69168
|
+
let changed = false;
|
|
69169
|
+
const walk = (value, live, path) => {
|
|
69170
|
+
if (value === "***") {
|
|
69171
|
+
if (live === void 0) {
|
|
69172
|
+
unpreservablePaths.push(path);
|
|
69173
|
+
return value;
|
|
69174
|
+
}
|
|
69175
|
+
changed = true;
|
|
69176
|
+
if (typeof live === "string") recordMaskOnlyValue(secrets, live);
|
|
69177
|
+
return live;
|
|
69178
|
+
}
|
|
69179
|
+
if (Array.isArray(value)) {
|
|
69180
|
+
const liveItems = Array.isArray(live) && live.length === value.length ? live : void 0;
|
|
69181
|
+
return value.map((item, i) => walk(item, liveItems?.[i], `${path}[${i}]`));
|
|
69182
|
+
}
|
|
69183
|
+
if (value !== null && typeof value === "object") {
|
|
69184
|
+
const liveObject = isPlainRecord(live) ? live : void 0;
|
|
69185
|
+
const out = {};
|
|
69186
|
+
for (const [k, v] of Object.entries(value)) out[k] = walk(v, liveObject?.[k], path === "" ? k : `${path}.${k}`);
|
|
69187
|
+
return out;
|
|
69188
|
+
}
|
|
69189
|
+
return value;
|
|
69190
|
+
};
|
|
69191
|
+
const properties = walk(send, awsProperties, "");
|
|
69192
|
+
if (!changed && unpreservablePaths.length === 0) return {
|
|
69193
|
+
properties: send,
|
|
69194
|
+
unpreservablePaths
|
|
69195
|
+
};
|
|
69196
|
+
return {
|
|
69197
|
+
properties,
|
|
69198
|
+
unpreservablePaths
|
|
69199
|
+
};
|
|
69200
|
+
}
|
|
69201
|
+
/**
|
|
68892
69202
|
* Build the `newProperties` object passed to `provider.update` during
|
|
68893
69203
|
* `--revert`. Strategy:
|
|
68894
69204
|
*
|
|
@@ -69130,7 +69440,13 @@ async function runRevert(reports, providerRegistry, stateBackend, stateConfig, a
|
|
|
69130
69440
|
let newProperties;
|
|
69131
69441
|
try {
|
|
69132
69442
|
const overlaid = buildRevertNewProperties(outcome.changes, desiredProperties, outcome.awsProperties, { preserveUntemplated: stateResource.observedProperties === void 0 });
|
|
69133
|
-
|
|
69443
|
+
const maskPreserved = preserveLiveValuesAtMaskedLeaves(unresolvedTokens.size > 0 ? preserveLiveValuesAtUnresolvedTokens(overlaid, outcome.awsProperties, secrets) : overlaid, outcome.awsProperties, secrets);
|
|
69444
|
+
if (maskPreserved.unpreservablePaths.length > 0) {
|
|
69445
|
+
totalUnresolvable++;
|
|
69446
|
+
logger.error(` ✗ ${report.stackName}/${outcome.logicalId} (${outcome.resourceType}): refused to revert ${maskPreserved.unpreservablePaths.join(", ")} — the recorded baseline holds only the redaction mask there (a NoEcho custom-resource value), and AWS reports nothing to preserve, so cdkd has no value it may write. Force that custom resource to update (change one of its properties, e.g. a nonce) and re-deploy, so its handler runs again and supplies the value — an ordinary re-deploy leaves it unchanged and the mask stays.`);
|
|
69447
|
+
return;
|
|
69448
|
+
}
|
|
69449
|
+
newProperties = maskPreserved.properties;
|
|
69134
69450
|
} catch (err) {
|
|
69135
69451
|
totalFailed++;
|
|
69136
69452
|
logger.error(` ✗ ${report.stackName}/${outcome.logicalId} (${outcome.resourceType}): could not build the revert payload — ${maskSecretsInText(err instanceof Error ? err.message : String(err), secrets)}`);
|
|
@@ -69335,24 +69651,26 @@ async function runWithConcurrency(tasks, concurrency) {
|
|
|
69335
69651
|
await Promise.all(workers);
|
|
69336
69652
|
}
|
|
69337
69653
|
/**
|
|
69338
|
-
*
|
|
69654
|
+
* `cdkd drift --accept` / `--revert`'s confirmation prompt: ask the operator to
|
|
69655
|
+
* confirm a mutation. Both call sites sit inside an `if (!options.yes)` block,
|
|
69656
|
+
* which is what keeps `confirmOrRefuse`'s non-interactive refusal (issue
|
|
69657
|
+
* #2275) from firing on a `--yes` run.
|
|
69658
|
+
*
|
|
69659
|
+
* Issue #2230 is why this site passes an explicit `output`: the prompt is
|
|
69660
|
+
* written to `out.stream`, not unconditionally to `process.stdout` — under
|
|
69661
|
+
* `--json` that stream carries the payload, and a bare `[y/N] ` spliced into
|
|
69662
|
+
* it is the same corruption as a status line, so the sink is `process.stderr`
|
|
69663
|
+
* there. The prompt is still SHOWN; only its stream changes. See the
|
|
69664
|
+
* `HumanTextSink` doc above for why the STREAM (not a closure) is what gets
|
|
69665
|
+
* handed to `createInterface`.
|
|
69339
69666
|
*
|
|
69340
|
-
*
|
|
69341
|
-
* `process.stdout` — under `--json` that stream carries the payload, and a
|
|
69342
|
-
* bare `[y/N] ` spliced into it is the same corruption as a status line.
|
|
69343
|
-
* The prompt is still SHOWN; only its stream changes.
|
|
69667
|
+
* Exported for unit testing — internal to the command flow otherwise.
|
|
69344
69668
|
*/
|
|
69345
69669
|
async function confirmPrompt$5(prompt, out) {
|
|
69346
|
-
|
|
69347
|
-
|
|
69348
|
-
|
|
69670
|
+
return confirmOrRefuse(prompt, {
|
|
69671
|
+
output: out.stream,
|
|
69672
|
+
refusal: "The cdkd drift confirmation prompt cannot run in a non-interactive environment. Pass -y / --yes to confirm, or run the command from a real terminal."
|
|
69349
69673
|
});
|
|
69350
|
-
try {
|
|
69351
|
-
const ans = await rl.question(`${prompt} [y/N] `);
|
|
69352
|
-
return /^y(es)?$/i.test(ans.trim());
|
|
69353
|
-
} finally {
|
|
69354
|
-
rl.close();
|
|
69355
|
-
}
|
|
69356
69674
|
}
|
|
69357
69675
|
function writeJsonReport(reports) {
|
|
69358
69676
|
const payload = reports.map((r) => {
|
|
@@ -70083,17 +70401,15 @@ async function emptyBucketAllVersions(s3, bucket) {
|
|
|
70083
70401
|
versionIdMarker = resp.NextVersionIdMarker;
|
|
70084
70402
|
} while (keyMarker || versionIdMarker);
|
|
70085
70403
|
}
|
|
70404
|
+
/**
|
|
70405
|
+
* `cdkd state migrate`'s confirmation prompt. Its only call site is inside the
|
|
70406
|
+
* `if (!options.yes)` block above, which is what keeps `confirmOrRefuse`'s
|
|
70407
|
+
* non-interactive refusal (issue #2275) from firing on a `--yes` run.
|
|
70408
|
+
*
|
|
70409
|
+
* Exported for unit testing — internal to the command flow otherwise.
|
|
70410
|
+
*/
|
|
70086
70411
|
async function confirmPrompt$4(prompt) {
|
|
70087
|
-
|
|
70088
|
-
input: process.stdin,
|
|
70089
|
-
output: process.stdout
|
|
70090
|
-
});
|
|
70091
|
-
try {
|
|
70092
|
-
const ans = await rl.question(`${prompt} [y/N] `);
|
|
70093
|
-
return /^y(es)?$/i.test(ans.trim());
|
|
70094
|
-
} finally {
|
|
70095
|
-
rl.close();
|
|
70096
|
-
}
|
|
70412
|
+
return confirmOrRefuse(prompt, { refusal: "The cdkd state migrate confirmation prompt cannot run in a non-interactive environment. Pass -y / --yes to confirm the migration, or run the command from a real terminal." });
|
|
70097
70413
|
}
|
|
70098
70414
|
/**
|
|
70099
70415
|
* Create the `cdkd state migrate` subcommand.
|
|
@@ -72010,6 +72326,14 @@ async function buildImportPlan(state, template, cfnClient, parentStackName, opti
|
|
|
72010
72326
|
});
|
|
72011
72327
|
continue;
|
|
72012
72328
|
}
|
|
72329
|
+
if (carriesSecretMask(stateEntry.properties)) {
|
|
72330
|
+
blocked.push({
|
|
72331
|
+
logicalId,
|
|
72332
|
+
resourceType,
|
|
72333
|
+
reason: "cdkd state holds only the redaction mask ('***') for at least one property — a NoEcho custom-resource value cdkd cannot re-derive. Forcing the custom resource to update does NOT clear this: the handler supplies the value to the deploy, and cdkd re-masks it on the way into state, so the export still has nothing to declare. Stop setting NoEcho on that response and re-deploy, then export again; or export this stack without that resource and adopt it into CloudFormation by hand. See https://github.com/go-to-k/cdkd/issues/2274."
|
|
72334
|
+
});
|
|
72335
|
+
continue;
|
|
72336
|
+
}
|
|
72013
72337
|
if (IMPORT_UNSUPPORTED_RECREATABLE_TYPES.has(resourceType)) {
|
|
72014
72338
|
if (options.recreateImportUnsupported) recreateBeforePhase2.push({
|
|
72015
72339
|
logicalId,
|
|
@@ -73705,17 +74029,18 @@ function printNextSteps(args) {
|
|
|
73705
74029
|
}
|
|
73706
74030
|
logger.info("");
|
|
73707
74031
|
}
|
|
74032
|
+
/**
|
|
74033
|
+
* `cdkd export`'s confirmation prompt, shared by its THREE call sites (the
|
|
74034
|
+
* rollback-journal override, the single-stack migration confirm, and the
|
|
74035
|
+
* nested-stack tree-wide confirm in `runPerStackImportLoop`). Every one of
|
|
74036
|
+
* them sits inside an `if (!options.yes ...)` block, which is what keeps
|
|
74037
|
+
* `confirmOrRefuse`'s non-interactive refusal (issue #2275) from firing on a
|
|
74038
|
+
* `--yes` run.
|
|
74039
|
+
*
|
|
74040
|
+
* Exported for unit testing — internal to the export flow otherwise.
|
|
74041
|
+
*/
|
|
73708
74042
|
async function confirmPrompt$3(prompt) {
|
|
73709
|
-
|
|
73710
|
-
input: process.stdin,
|
|
73711
|
-
output: process.stdout
|
|
73712
|
-
});
|
|
73713
|
-
try {
|
|
73714
|
-
const ans = await rl.question(`${prompt} [y/N] `);
|
|
73715
|
-
return /^y(es)?$/i.test(ans.trim());
|
|
73716
|
-
} finally {
|
|
73717
|
-
rl.close();
|
|
73718
|
-
}
|
|
74043
|
+
return confirmOrRefuse(prompt, { refusal: "The cdkd export confirmation prompt cannot run in a non-interactive environment. Pass -y / --yes to confirm, or run the command from a real terminal." });
|
|
73719
74044
|
}
|
|
73720
74045
|
function createExportCommand() {
|
|
73721
74046
|
const cmd = new Command("export").description("Hand a cdkd-managed stack over to CloudFormation via CFn IMPORT (changeset). AWS resources are unchanged; cdkd state for the stack is deleted on success. Mirror of `cdkd import` (AWS → cdkd) in the reverse direction (cdkd → CFn). Accepts JSON and YAML templates (YAML via a CFn-aware codec that preserves !Ref / !GetAtt / !Sub shorthand). Aborts if any resource is not CFn-importable.").argument("[stack]", "Stack name to export (auto-detected for single-stack apps)").option("--cfn-stack-name <name>", "Name of the destination CloudFormation stack for the root stack. Defaults to the cdkd stack name (or the cdkd2cfnStackName mapping when the name contains '~'). For per-nested-child overrides, use --cfn-child-stack-name.").option("--cfn-child-stack-name <pair...>", "Per-nested-child CFn stack-name override. Repeatable. Format: '<cdkdName>=<cfnName>' (e.g. --cfn-child-stack-name 'MyApp~Database=my-app-db'). The cdkd stack name for a nested child is '<parent>~<childLogicalId>' (v6 state-key form). Without an override the CFn name is derived by replacing '~' with '-'. Only consulted when the exported stack tree contains nested children; ignored for flat stacks.").option("--template <path>", "Path to a pre-rendered CloudFormation template (JSON or YAML — format auto-detected). Skips synth.").option("--stack-region <region>", "Region of the cdkd state record to operate on. Required when the same stack name has state in multiple regions.").option("--dry-run", "Print the import plan without creating a changeset.", false).option("--accept-transient-context", "Allow CLI -c key=value overrides at export time even though they are not persisted to cdk.json / cdk.context.json (default: refuse). When set, the user is responsible for passing the same -c flags to every future cdk deploy.", false).option("--include-non-importable", "Run a 2-phase migration when the stack contains non-importable resources (Custom::*). Phase 1 imports the importable resources; phase 2 CFn-CREATEs the non-importable ones, which re-invokes each Custom Resource's onCreate handler. Make sure onCreate is idempotent before enabling.", false).option("--parameter <key=value...>", "CFn template Parameter override, repeatable. Required when the synthesized template has Parameters without Default values; otherwise overrides the template's default value. Format: --parameter Key=Value.").option("--strict-cross-stack", "Refuse to export when sibling cdkd stacks in the same CDK app reference the exporting stack via Fn::GetStackOutput. Without the flag, cdkd warns and proceeds — the consumers keep resolving via the CloudFormation fallback (weak reference) unless they deploy with --no-cfn-fallback.", false).option("--skip-import-support-preflight", "Skip the pre-flight that refuses resource types whose CloudFormation registry schema says CFn cannot IMPORT them (no read handler AND NON_PROVISIONABLE), and let CreateChangeSet answer instead. The pre-flight is a registry HEURISTIC, not AWS's supported-for-import list, so this is the escape hatch for a type AWS has since made importable. Expect the changeset to be rejected if the heuristic was right.", false).option("--no-recreate-import-unsupported", "Block instead of auto-handling resource types AWS does NOT support in IMPORT changesets (currently only AWS::ApiGatewayV2::Stage, emitted by CDK HttpApi). Default behavior: cdkd skips these from phase 1, deletes the AWS-side resource between phases, and lets CFn re-CREATE in phase 2 (brief unavailability window). With this flag, the export aborts with a clear error instead.").action(withErrorHandling(exportCommand));
|
|
@@ -73739,6 +74064,26 @@ function formatStackRef(ref) {
|
|
|
73739
74064
|
return ref.region ? `${ref.stackName} (${ref.region})` : ref.stackName;
|
|
73740
74065
|
}
|
|
73741
74066
|
/**
|
|
74067
|
+
* {@link formatStackRef} for a sentence a CONFIRMATION PROMPT renders.
|
|
74068
|
+
*
|
|
74069
|
+
* Both halves come from an S3 key segment (or, for a legacy record, a state
|
|
74070
|
+
* body), so both are attacker-influenced in exactly the way issue #2170 round
|
|
74071
|
+
* 4 found for the lock error sixty lines below this helper's callers: a
|
|
74072
|
+
* planted `\n` forged a whole line inside that sentence. A forged line in a
|
|
74073
|
+
* PROMPT is strictly worse than one in an error, because the sentence it
|
|
74074
|
+
* corrupts is the one the operator answers `y` to.
|
|
74075
|
+
*
|
|
74076
|
+
* `asciiOnly` matches the lock error's own call on these same values — a
|
|
74077
|
+
* stack name and an AWS region both have a known charset — and is a no-op on
|
|
74078
|
+
* every ordinary input, so the shipped prompt strings are byte-identical.
|
|
74079
|
+
* `UNRENDERABLE` stands in when sanitising leaves nothing, since an empty
|
|
74080
|
+
* `()` would read as "no region" rather than "a region cdkd will not print".
|
|
74081
|
+
*/
|
|
74082
|
+
function formatStackRefSafe(ref) {
|
|
74083
|
+
const stack = displaySafe(ref.stackName, { asciiOnly: true }) || "<unrenderable>";
|
|
74084
|
+
return ref.region ? `${stack} (${displaySafe(ref.region, { asciiOnly: true }) || "<unrenderable>"})` : stack;
|
|
74085
|
+
}
|
|
74086
|
+
/**
|
|
73742
74087
|
* Resolve a stack name + optional region flag against the `listStacks` index
|
|
73743
74088
|
* built up front. When a name resolves to multiple regions and the caller
|
|
73744
74089
|
* didn't pin one, surface a clear error listing the candidates so the user
|
|
@@ -73842,7 +74187,7 @@ function sortRefs(refs) {
|
|
|
73842
74187
|
async function stateListCommand(options) {
|
|
73843
74188
|
const logger = getLogger();
|
|
73844
74189
|
if (options.verbose) logger.setLevel("debug");
|
|
73845
|
-
|
|
74190
|
+
reserveStdoutForPayload();
|
|
73846
74191
|
const setup = await setupStateBackend(options);
|
|
73847
74192
|
try {
|
|
73848
74193
|
const refs = sortRefs(await setup.stateBackend.listStacks());
|
|
@@ -74311,16 +74656,9 @@ async function stateOrphanCommand(stackArgs, options) {
|
|
|
74311
74656
|
}
|
|
74312
74657
|
}
|
|
74313
74658
|
if (!options.yes && !options.force) {
|
|
74314
|
-
const targetList = targets.map((t) =>
|
|
74659
|
+
const targetList = targets.map((t) => formatStackRefSafe(t)).join(", ");
|
|
74315
74660
|
process.stdout.write(`\nWARNING: This removes cdkd's state record for [${targetList}] only. AWS resources will NOT be deleted.\nUse 'cdkd destroy ${stackName}' if you want to delete the actual resources.\n\n`);
|
|
74316
|
-
|
|
74317
|
-
input: process.stdin,
|
|
74318
|
-
output: process.stdout
|
|
74319
|
-
});
|
|
74320
|
-
const answer = await rl.question(`Remove state for ${targetList} from s3://${setup.bucket}/${setup.prefix}/? (y/N): `);
|
|
74321
|
-
rl.close();
|
|
74322
|
-
const trimmed = answer.trim().toLowerCase();
|
|
74323
|
-
if (trimmed !== "y" && trimmed !== "yes") {
|
|
74661
|
+
if (!await confirmStateOrphanRemoval(`Remove state for ${targetList} from s3://${setup.bucket}/${setup.prefix}/?`)) {
|
|
74324
74662
|
logger.info(`Cancelled removal of state for stack: ${stackName}`);
|
|
74325
74663
|
continue;
|
|
74326
74664
|
}
|
|
@@ -74886,7 +75224,7 @@ async function stateRefreshObservedCommand(stackArgs, options) {
|
|
|
74886
75224
|
}
|
|
74887
75225
|
}
|
|
74888
75226
|
if (!options.yes && !options.dryRun) {
|
|
74889
|
-
const targetList = targets.map(
|
|
75227
|
+
const targetList = targets.map(formatStackRefSafe).join(", ");
|
|
74890
75228
|
if (!await confirmRefresh(`Refresh observedProperties for ${targets.length} stack(s) (${targetList})?`)) {
|
|
74891
75229
|
logger.info("Aborted.");
|
|
74892
75230
|
return;
|
|
@@ -75067,17 +75405,34 @@ async function refreshObservedForStack(stackName, region, stateBackend, lockMana
|
|
|
75067
75405
|
});
|
|
75068
75406
|
}
|
|
75069
75407
|
}
|
|
75070
|
-
|
|
75071
|
-
|
|
75072
|
-
|
|
75073
|
-
|
|
75408
|
+
/**
|
|
75409
|
+
* `cdkd state orphan`'s confirmation prompt. Its only call site is inside the
|
|
75410
|
+
* `if (!options.yes && !options.force)` block in `stateOrphanCommand`, which
|
|
75411
|
+
* is what keeps `confirmOrRefuse`'s non-interactive refusal (issue #2275)
|
|
75412
|
+
* from firing on a flagged run.
|
|
75413
|
+
*
|
|
75414
|
+
* The `(y/N): ` suffix is preserved verbatim from before issue #2275 folded
|
|
75415
|
+
* the guard in: it is user-visible output, and only this prompt and
|
|
75416
|
+
* `cdkd rollback` ever spelled it that way.
|
|
75417
|
+
*
|
|
75418
|
+
* Exported for unit testing — internal to the state-orphan flow otherwise.
|
|
75419
|
+
*/
|
|
75420
|
+
async function confirmStateOrphanRemoval(prompt) {
|
|
75421
|
+
return confirmOrRefuse(prompt, {
|
|
75422
|
+
suffix: " (y/N): ",
|
|
75423
|
+
refusal: "The cdkd state orphan confirmation prompt cannot run in a non-interactive environment. Pass -y / --yes (or -f / --force) to confirm the removal, or run the command from a real terminal."
|
|
75074
75424
|
});
|
|
75075
|
-
|
|
75076
|
-
|
|
75077
|
-
|
|
75078
|
-
|
|
75079
|
-
|
|
75080
|
-
|
|
75425
|
+
}
|
|
75426
|
+
/**
|
|
75427
|
+
* `cdkd state refresh-observed`'s confirmation prompt. Its only call site is
|
|
75428
|
+
* inside the `if (!options.yes && !options.dryRun)` block above, which is what
|
|
75429
|
+
* keeps `confirmOrRefuse`'s non-interactive refusal (issue #2275) from firing
|
|
75430
|
+
* on a `--yes` run.
|
|
75431
|
+
*
|
|
75432
|
+
* Exported for unit testing — internal to the refresh-observed flow otherwise.
|
|
75433
|
+
*/
|
|
75434
|
+
async function confirmRefresh(prompt) {
|
|
75435
|
+
return confirmOrRefuse(prompt, { refusal: "The cdkd state refresh-observed confirmation prompt cannot run in a non-interactive environment. Pass -y / --yes to confirm the refresh, or run the command from a real terminal." });
|
|
75081
75436
|
}
|
|
75082
75437
|
/**
|
|
75083
75438
|
* Create the `state refresh-observed` subcommand.
|
|
@@ -75208,17 +75563,20 @@ function failedActionLabel(item, skipFinalSnapshot) {
|
|
|
75208
75563
|
case "skip-failed-absent": return ` - skip ${op.logicalId} (${op.resourceType}) — no previous state available`;
|
|
75209
75564
|
}
|
|
75210
75565
|
}
|
|
75566
|
+
/**
|
|
75567
|
+
* `cdkd rollback`'s confirmation prompt. Exported for unit testing — internal
|
|
75568
|
+
* to the rollback flow otherwise, whose only call site is inside the
|
|
75569
|
+
* `if (!skipConfirmation)` block below.
|
|
75570
|
+
*
|
|
75571
|
+
* The `(y/N): ` suffix is preserved verbatim from before issue #2275 folded
|
|
75572
|
+
* the non-interactive guard into `confirmOrRefuse`: it is user-visible output,
|
|
75573
|
+
* and only this site and `cdkd state orphan` ever spelled it that way.
|
|
75574
|
+
*/
|
|
75211
75575
|
async function confirm(question) {
|
|
75212
|
-
|
|
75213
|
-
|
|
75214
|
-
|
|
75576
|
+
return confirmOrRefuse(question, {
|
|
75577
|
+
suffix: " (y/N): ",
|
|
75578
|
+
refusal: "The cdkd rollback confirmation prompt cannot run in a non-interactive environment. Pass --force (or -y / --yes) to confirm the rollback, or run the command from a real terminal."
|
|
75215
75579
|
});
|
|
75216
|
-
try {
|
|
75217
|
-
const t = (await rl.question(`${question} (y/N): `)).trim().toLowerCase();
|
|
75218
|
-
return t === "y" || t === "yes";
|
|
75219
|
-
} finally {
|
|
75220
|
-
rl.close();
|
|
75221
|
-
}
|
|
75222
75580
|
}
|
|
75223
75581
|
async function rollbackCommand(stackArg, options) {
|
|
75224
75582
|
const logger = getLogger();
|
|
@@ -77691,17 +78049,16 @@ function stringifyForAudit(value) {
|
|
|
77691
78049
|
if (typeof value === "string") return JSON.stringify(value);
|
|
77692
78050
|
return JSON.stringify(value);
|
|
77693
78051
|
}
|
|
78052
|
+
/**
|
|
78053
|
+
* `cdkd orphan`'s confirmation prompt. Its only call site is inside the
|
|
78054
|
+
* `if (!options.yes && !options.force)` block above, which is what keeps
|
|
78055
|
+
* `confirmOrRefuse`'s non-interactive refusal (issue #2275) from firing on a
|
|
78056
|
+
* flagged run.
|
|
78057
|
+
*
|
|
78058
|
+
* Exported for unit testing — internal to the command flow otherwise.
|
|
78059
|
+
*/
|
|
77694
78060
|
async function confirmPrompt$1(prompt) {
|
|
77695
|
-
|
|
77696
|
-
input: process.stdin,
|
|
77697
|
-
output: process.stdout
|
|
77698
|
-
});
|
|
77699
|
-
try {
|
|
77700
|
-
const ans = await rl.question(`${prompt} [y/N] `);
|
|
77701
|
-
return /^y(es)?$/i.test(ans.trim());
|
|
77702
|
-
} finally {
|
|
77703
|
-
rl.close();
|
|
77704
|
-
}
|
|
78061
|
+
return confirmOrRefuse(prompt, { refusal: "The cdkd orphan confirmation prompt cannot run in a non-interactive environment. Pass -y / --yes (or -f / --force) to confirm the orphan, or run the command from a real terminal." });
|
|
77705
78062
|
}
|
|
77706
78063
|
/**
|
|
77707
78064
|
* Create the top-level `cdkd orphan` command.
|
|
@@ -78714,17 +79071,15 @@ function formatOutcome(outcome) {
|
|
|
78714
79071
|
case "failed": return "✗";
|
|
78715
79072
|
}
|
|
78716
79073
|
}
|
|
79074
|
+
/**
|
|
79075
|
+
* `cdkd import`'s confirmation prompt. Its only call site is inside the
|
|
79076
|
+
* `if (!options.yes)` block above, which is what keeps `confirmOrRefuse`'s
|
|
79077
|
+
* non-interactive refusal (issue #2275) from firing on a `--yes` run.
|
|
79078
|
+
*
|
|
79079
|
+
* Exported for unit testing — internal to the command flow otherwise.
|
|
79080
|
+
*/
|
|
78717
79081
|
async function confirmPrompt(prompt) {
|
|
78718
|
-
|
|
78719
|
-
input: process.stdin,
|
|
78720
|
-
output: process.stdout
|
|
78721
|
-
});
|
|
78722
|
-
try {
|
|
78723
|
-
const ans = await rl.question(`${prompt} [y/N] `);
|
|
78724
|
-
return /^y(es)?$/i.test(ans.trim());
|
|
78725
|
-
} finally {
|
|
78726
|
-
rl.close();
|
|
78727
|
-
}
|
|
79082
|
+
return confirmOrRefuse(prompt, { refusal: "The cdkd import confirmation prompt cannot run in a non-interactive environment. Pass -y / --yes to confirm the state write, or run the command from a real terminal." });
|
|
78728
79083
|
}
|
|
78729
79084
|
/**
|
|
78730
79085
|
* Create the `cdkd import` top-level command.
|
|
@@ -89038,4 +89393,4 @@ function buildProgram() {
|
|
|
89038
89393
|
|
|
89039
89394
|
//#endregion
|
|
89040
89395
|
export { buildProgram };
|
|
89041
|
-
//# sourceMappingURL=program-
|
|
89396
|
+
//# sourceMappingURL=program-B9vnKc3N.js.map
|