@go-to-k/cdkd 0.288.3 → 0.288.5
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-CQrHElrl.js → asg-provider-C_AHAmgD.js} +2 -2
- package/dist/{asg-provider-CQrHElrl.js.map → asg-provider-C_AHAmgD.js.map} +1 -1
- package/dist/cli.js +2 -2
- package/dist/{deploy-engine-CfxcC3q3.js → deploy-engine-Dsf9bTqH.js} +1085 -215
- package/dist/deploy-engine-Dsf9bTqH.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/{program-BXH3FeeV.js → program-CMhpftan.js} +558 -116
- package/dist/{program-BXH3FeeV.js.map → program-CMhpftan.js.map} +1 -1
- package/dist/{version-CEyZvTcB.js → version-BFmq8S23.js} +2 -2
- package/dist/{version-CEyZvTcB.js.map → version-BFmq8S23.js.map} +1 -1
- package/package.json +1 -1
- package/dist/deploy-engine-CfxcC3q3.js.map +0 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { $ as ProviderRegistry, $n as
|
|
1
|
+
import { $ as ProviderRegistry, $n as formatDockerLoginError, $r as PartialFailureError, $t as STATE_SOURCED_BASELINE_RULES, A as renderStatefulReason, An as stringifyValue, Ar as PARTITION_TABLE, At as configStringRefusal, B as red, Bn as getBootstrapMarkerKey, Br as setAwsClients, Bt as s3BucketDomainName, C as refusesFinalSnapshot, Cn as CUSTOM_RESOURCE_RESPONSE_PREFIX, Cr as CFN_TEMPLATE_URL_LIMIT, Ct as refStateLookupFromResource, D as MULTI_REGION_RECREATE_BLOCKED_TYPES, Dn as importableOutputs, Dr as CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION, Dt as assertRegionMatch, E as extractDeploymentEventError, En as importableOutputKeys, Er as uploadCfnTemplate, Et as resolveExplicitPhysicalId, F as formatResourceLine, Fn as rewriteTemplateAssetReferences, Ft as requireConfigString, G as isExportAliasCollision, Gn as validateContainerRepoName, Gt as DiffCalculator, H as collectDeclaredOutputNames, Hn as parseBootstrapMarker, Hr as CdkdError, Ht as s3BucketRegionalDomainName, I as bold, In as AssetModeResolver, Ir as resolveBucketRegion, It as classifyReplaySecretRegion, J as secretSafeKeyDisplay, Jn as buildDockerImage, Jr as IntrinsicResolutionRefusalError, Jt as findSilentDropProperties, K as secretBearing, Kn as buildDenyExternalAccessPolicy, Kr as DeployCancelledError, Kt as INTRINSIC_KEYS, L as cyan, Ln as BOOTSTRAP_MARKER_PREFIX, Lr as AwsClients, Lt as producerRegionsFromState, M as coerceWarmThroughput$1, Mn as buildAssetRedirectMap, Mr as derivePartitionAndUrlSuffix, Mt as replayWarn, N as isWarmThroughputDecrease, Nn as createAssetRedirectResolver, Nr as AssemblyReader, Nt as requireConfigArray, O as isStatefulRecreateTargetForReplace, On as shouldRetainResource, Or as displaySafe, Ot as coerceCfnBoolean, P as toFiniteNumber, Pn as loadPublishableAssetManifest, Pr as processStackMessages, Pt as requireConfigObject, Q as clearOnUpdateRemoval, Qn as dockerSpawnEnvWithSensitive, Qr as NestedStackChildDirectDestroyError, Qt as TemplateParser, R as gray, Rn as assertAssetBucketRegion, Rr as getAwsClients, Rt as withSharedDrainBudget, S as isFinalSnapshotError, Sn as shellQuote, Sr as CFN_TEMPLATE_BODY_LIMIT, St as parameterTypeMayLoseSecretIdentity, T as makeCanonicalizePropertiesFn, Tn as exportNamesCarriedFrom, Tr as findLargeInlineResources, Tt as normalizeAwsTagsToCfn, U as collectPublishedOutputNames, Un as readBootstrapMarkerBody, Ut as s3BucketWebsiteUrl, V as yellow, Vn as isCrossRegionRedirect, Vt as s3BucketDualStackDomainName, W as exportAliasCollisionScrubWarning, Wn as validateAssetBucketName, Wr as CrossAccountSecretRefusalError, Wt as applyRoleArnIfSet, X as IAMRoleProvider, Xn as describeDockerExecFailure, Xr as LocalStartServiceError, Xt as withRetry, Y as getCurrentResourceSecrets, Yn as describeDockerCapturedOutput, Yr as LocalInvokeBuildError$1, Yt as describeTypeWithThrottleRetry, Z as collectInlinePolicyNamesManagedBySiblings, Zn as describeDockerFailure, Zt as DagBuilder, _ as ATOMIC_FINAL_SNAPSHOT_TYPES, _i as retryClassificationText, _n as rebuildClientForBucketRegion, _r as resolveStateBucketWithDefault, _t as carriesDynamicReference, a as DeploymentEventsStore, an as dynamicReferenceTokens, ar as escapeRegExp, at as endCommandInterruptScope, b as ccRoutedFinalSnapshotError, bn as buildLockContentionMessage, br as stateBucketExistenceConfirmed, bt as getAccountInfo, c as replayFailedOperations, cn as isSingleDynamicReferenceToken, cr as getDockerImageBySourceHash, ct as startInterruptWatch, d as updatePartialReason, di as isMarkedNonRetryable, dn as recordMaskOnlyValue, dr as getDefaultStateBucketName, dt as UNSPECIFIED_SKIP_REASON, ei as ProvisioningError, en as STATE_SOURCED_CROSS_GENERATION_RULES, er as getDockerCmd, et as wouldReturnToSdkProvider, f as withResourceDeadline, fi as isRetryableTransientError, fn as recoverMaskedOutput, fr as getLegacyStateBucketName, ft as deleteIndeterminateGuards, g as bindingSkippedOutputs, gi as markRedactedCause, gn as S3StateBackend, gr as resolveSkipPrefix, gt as IntrinsicFunctionResolver, h as computeImplicitDeleteEdges, hi as markNonRetryable, hn as LockManager, hr as resolveCaptureObservedState, ht as isTerminationProtectionPropagationError, i as DeploymentEventsReader, ii as StackTerminationProtectionError, in as createSecretMasker, ir as runDockerStreaming, it as beginCommandInterruptScope, j as WARM_THROUGHPUT_MEMBERS, jn as WorkGraph, jr as canonicalizeRegion, jt as readConfigString, k as isStatefulRecreateTargetSync, kn as AssetPublisher, kr as expectedOwnerParam, kt as configBooleanRefusal, l as replayRollback, li as normalizeAwsError, ln as maskSecretsInError, lr as Synthesizer, lt as CloudControlProvider, m as IMPLICIT_DELETE_DEPENDENCIES, mi as isTransientServerError, mn as scrubResourceRecord, mr as resolveAutoAssetStorage, mt as disableInstanceApiTermination, n as DEFAULT_RESOURCE_WARN_AFTER_MS, ni as ResourceUpdateNotSupportedError, nn as TEMPLATE_SOURCED_RULES, nr as redactDockerArgvValues, nt as maskDeep, o as planFailedOps, on as errorCauseChain, or as stripControlChars, ot as interruptWatchListenerCount, p as maskingRetryLogger, pi as isThrottlingError, pn as redactSecretsForState, pr as resolveApp, pt as deleteSkipReason, q as secretBearingStateKeyWarning, qn as describeAwsFailure, qr as DynamicReferenceRegionAmbiguousError, qt as findActionableSilentDrops, r as DeployEngine, ri as StackHasActiveImportsError, rn as carriesSecretMask, rr as runDockerForeground, rt as maskerOrIdentity, s as planRollback, sn as identityKeyFor, sr as AssetManifestLoader, st as isInterruptedWaitError, t as DEFAULT_RESOURCE_TIMEOUT_MS, ti as ResourceTimeoutError, tn as STATE_SOURCED_READBACK_RULES, tr as partitionSensitiveEnv, tt as createMaskedRetryLogger, u as updatePartialMessage, ui as withErrorHandling, un as maskSecretsInText, ur as synthesisStatusMessage, ut as slowCcOperationTimeoutMs, v as PRE_DELETE_SNAPSHOT_TYPES, vn as UNRENDERABLE, vr as resolveStateBucketWithDefaultAndSource, vt as cfnRefValueFromPhysicalId, w as unsupportedFinalSnapshotError, wn as DEFAULT_STATE_PREFIX, wr as MIGRATE_TMP_PREFIX, wt as WAFv2WebACLProvider, x as createPreDeleteFinalSnapshot, xn as forceQuitRecoveryClause, xr as warnDeprecatedNoPrefixCliFlag, xt as isUnboundTemplateParameter, y as buildFinalSnapshotIdentifier, yn as buildForceUnlockCommand, yr as resolveUseCdkBootstrapAssets, yt as coerceParameterTypedValue, z as green, zn as ensureAssetStorage, zr as resetAwsClients, zt as s3BucketArn } from "./deploy-engine-Dsf9bTqH.js";
|
|
2
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 { r as protectedReplacementAdvice, t as ASGProvider } from "./asg-provider-
|
|
4
|
+
import { t as getCdkdVersion } from "./version-BFmq8S23.js";
|
|
5
|
+
import { r as protectedReplacementAdvice, t as ASGProvider } from "./asg-provider-C_AHAmgD.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, NotFound, 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";
|
|
@@ -44136,7 +44136,11 @@ var CognitoUserPoolProvider = class {
|
|
|
44136
44136
|
* Retry a Cognito control-plane call on transient "settling" errors. A
|
|
44137
44137
|
* SetUserPoolMfaConfig issued immediately after CreateUserPool (or another
|
|
44138
44138
|
* control-plane write) can briefly hit `ConcurrentModificationException` /
|
|
44139
|
-
* "please retry". Backoff
|
|
44139
|
+
* "please retry". Backoff is capped at 4s, but the DEFAULT 3 attempts means
|
|
44140
|
+
* only TWO sleeps -- 1s then 2s, 3s total -- so the 4s step is unreachable
|
|
44141
|
+
* unless a caller raises `maxAttempts`. (`retryable-errors.ts`'s Cognito
|
|
44142
|
+
* SMS-role entry depends on that arithmetic; an earlier revision of it
|
|
44143
|
+
* copied a 7s figure out of this comment's previous wording.)
|
|
44140
44144
|
*/
|
|
44141
44145
|
async retryOnTransientControlPlane(fn, label, maxAttempts = 3) {
|
|
44142
44146
|
for (let attempt = 1;; attempt++) try {
|
|
@@ -67696,9 +67700,14 @@ function makeCrossStackPrePass(deps) {
|
|
|
67696
67700
|
/**
|
|
67697
67701
|
* Scrub one stack's state. Re-resolves the template's per-resource properties to
|
|
67698
67702
|
* learn the resolved secret VALUES, then replaces those values in the state
|
|
67699
|
-
* record with their `{{resolve:...}}` expressions. Returns counts
|
|
67700
|
-
* AWS
|
|
67701
|
-
* `dryRun
|
|
67703
|
+
* record with their `{{resolve:...}}` expressions. Returns counts. It mutates no
|
|
67704
|
+
* AWS RESOURCE, but it does write S3: the state object itself, and the stack
|
|
67705
|
+
* lock it acquires for the read-modify-write unless `dryRun` (issue #2667 added
|
|
67706
|
+
* a third write, the cross-stack exports index, in `repairExportIndexForStack` —
|
|
67707
|
+
* a separate step outside this function). This comment is no longer the
|
|
67708
|
+
* "performs no AWS mutation" it used to say (go-to-k/cdkd#2878): that was FALSE
|
|
67709
|
+
* about the lock even before #2667, and it wrapped across a line break, which
|
|
67710
|
+
* is why a line-oriented grep for the claim did not return it.
|
|
67702
67711
|
*
|
|
67703
67712
|
* The stack OUTPUTS bag is scrubbed too, in two passes: today's declared
|
|
67704
67713
|
* outputs are redacted BY POSITION against the template, and a stored key
|
|
@@ -67763,102 +67772,104 @@ async function scrubStack(stack, region, stateBackend, lockManager, opts) {
|
|
|
67763
67772
|
logger.debug(`Condition evaluation skipped for ${stack.stackName}: ${maskSecretsInText(err instanceof Error ? err.message : String(err), outputSecrets)}`);
|
|
67764
67773
|
}
|
|
67765
67774
|
const templateResources = stack.template.Resources ?? {};
|
|
67766
|
-
for (const logicalId of Object.keys(state.resources)) {
|
|
67767
|
-
const templateResource = templateResources[logicalId];
|
|
67768
|
-
if (!templateResource?.Properties) continue;
|
|
67769
|
-
const recordedSecretValues = /* @__PURE__ */ new Map();
|
|
67770
|
-
perResourceSecrets.set(logicalId, recordedSecretValues);
|
|
67771
|
-
const resolveInput = await pinCrossRegionSecrets(templateResource.Properties, stack.stackName, {
|
|
67772
|
-
stackRegion: region,
|
|
67773
|
-
producerRegions,
|
|
67774
|
-
resolvers,
|
|
67775
|
-
recordedSecretValues,
|
|
67776
|
-
origin: `resource '${logicalId}'`
|
|
67777
|
-
});
|
|
67778
|
-
const resourceContext = resolverContext(recordedSecretValues);
|
|
67779
|
-
await resolveCrossStackReads(resolveInput, resourceContext, `resource '${logicalId}'`);
|
|
67780
|
-
try {
|
|
67781
|
-
await resolver.resolve(resolveInput, resourceContext);
|
|
67782
|
-
} catch (err) {
|
|
67783
|
-
if (isRegionAmbiguousRefusal(err)) throw err;
|
|
67784
|
-
logger.debug(`Resolution of ${logicalId} during scrub was partial: ${maskSecretsInText(err instanceof Error ? err.message : String(err), recordedSecretValues)}`);
|
|
67785
|
-
}
|
|
67786
|
-
perResourceTemplateProps.set(logicalId, templateResource.Properties);
|
|
67787
|
-
}
|
|
67788
67775
|
const templateOutputs = stack.template.Outputs ?? {};
|
|
67789
67776
|
const declaredOutputNames = collectDeclaredOutputNames(templateOutputs);
|
|
67790
67777
|
const accountedOutputKeys = new Set(declaredOutputNames);
|
|
67791
67778
|
const ambiguousKeys = /* @__PURE__ */ new Set();
|
|
67792
67779
|
const collisions = [];
|
|
67793
67780
|
let outputsSourceUntrusted = false;
|
|
67794
|
-
|
|
67795
|
-
const
|
|
67796
|
-
|
|
67797
|
-
|
|
67798
|
-
const
|
|
67799
|
-
|
|
67800
|
-
|
|
67801
|
-
const nameSource = await pinCrossRegionSecrets(declaredExportName, stack.stackName, {
|
|
67781
|
+
await withSharedDrainBudget(async () => {
|
|
67782
|
+
for (const logicalId of Object.keys(state.resources)) {
|
|
67783
|
+
const templateResource = templateResources[logicalId];
|
|
67784
|
+
if (!templateResource?.Properties) continue;
|
|
67785
|
+
const recordedSecretValues = /* @__PURE__ */ new Map();
|
|
67786
|
+
perResourceSecrets.set(logicalId, recordedSecretValues);
|
|
67787
|
+
const resolveInput = await pinCrossRegionSecrets(templateResource.Properties, stack.stackName, {
|
|
67802
67788
|
stackRegion: region,
|
|
67803
67789
|
producerRegions,
|
|
67804
67790
|
resolvers,
|
|
67805
|
-
recordedSecretValues
|
|
67806
|
-
origin: `
|
|
67791
|
+
recordedSecretValues,
|
|
67792
|
+
origin: `resource '${logicalId}'`
|
|
67807
67793
|
});
|
|
67808
|
-
const
|
|
67809
|
-
await resolveCrossStackReads(
|
|
67794
|
+
const resourceContext = resolverContext(recordedSecretValues);
|
|
67795
|
+
await resolveCrossStackReads(resolveInput, resourceContext, `resource '${logicalId}'`);
|
|
67810
67796
|
try {
|
|
67811
|
-
|
|
67797
|
+
await resolver.resolve(resolveInput, resourceContext);
|
|
67812
67798
|
} catch (err) {
|
|
67813
67799
|
if (isRegionAmbiguousRefusal(err)) throw err;
|
|
67814
|
-
|
|
67815
|
-
|
|
67800
|
+
logger.debug(`Resolution of ${logicalId} during scrub was partial: ${maskSecretsInText(err instanceof Error ? err.message : String(err), recordedSecretValues)}`);
|
|
67801
|
+
}
|
|
67802
|
+
perResourceTemplateProps.set(logicalId, templateResource.Properties);
|
|
67803
|
+
}
|
|
67804
|
+
for (const [name, output] of Object.entries(templateOutputs)) {
|
|
67805
|
+
const declaredExportName = output.Export?.Name;
|
|
67806
|
+
let exportName = declaredExportName;
|
|
67807
|
+
if (declaredExportName !== void 0 && typeof declaredExportName !== "string") {
|
|
67808
|
+
const nameSecrets = new SharedEntriesSecrets(outputSecrets);
|
|
67809
|
+
let nameFailed = false;
|
|
67810
|
+
let nameError;
|
|
67811
|
+
const nameSource = await pinCrossRegionSecrets(declaredExportName, stack.stackName, {
|
|
67812
|
+
stackRegion: region,
|
|
67813
|
+
producerRegions,
|
|
67814
|
+
resolvers,
|
|
67815
|
+
recordedSecretValues: nameSecrets,
|
|
67816
|
+
origin: `Export.Name of output '${name}'`
|
|
67817
|
+
});
|
|
67818
|
+
const nameContext = resolverContext(nameSecrets);
|
|
67819
|
+
await resolveCrossStackReads(nameSource, nameContext, `Export.Name of output '${name}'`, { canRefuse: !isOutputSuppressed(name, output, conditions, state.outputs ?? {}) });
|
|
67820
|
+
try {
|
|
67821
|
+
exportName = await resolver.resolve(nameSource, nameContext);
|
|
67822
|
+
} catch (err) {
|
|
67823
|
+
if (isRegionAmbiguousRefusal(err)) throw err;
|
|
67824
|
+
nameFailed = true;
|
|
67825
|
+
nameError = err;
|
|
67826
|
+
}
|
|
67827
|
+
if (nameFailed) {
|
|
67828
|
+
outputsSourceUntrusted = true;
|
|
67829
|
+
logger.warn(`Export.Name of output ${name} could not be resolved during scrub (${maskSecretsInText(nameError instanceof Error ? nameError.message : String(nameError), outputSecrets)}) — redacting this stack's outputs by value match instead of by template position, since state may be keyed under a name this run cannot reproduce.`);
|
|
67830
|
+
}
|
|
67816
67831
|
}
|
|
67817
|
-
if (
|
|
67832
|
+
if (declaredExportName !== void 0 && typeof exportName !== "string") outputsSourceUntrusted = true;
|
|
67833
|
+
const exportNameUnresolved = declaredExportName !== void 0 && typeof exportName === "string" && isUnresolvedValue(exportName, templateUsesSub(declaredExportName));
|
|
67834
|
+
if (typeof exportName === "string" && !exportNameUnresolved) accountedOutputKeys.add(exportName);
|
|
67835
|
+
if (exportNameUnresolved) {
|
|
67818
67836
|
outputsSourceUntrusted = true;
|
|
67819
|
-
logger.warn(`Export.Name of output ${name}
|
|
67837
|
+
logger.warn(`Export.Name of output ${name} did not fully resolve during scrub — redacting this stack's outputs by value match instead of by template position, since state may be keyed under a name this run cannot reproduce.`);
|
|
67838
|
+
} else if (typeof exportName === "string" && isExportAliasCollision(exportName, name, declaredOutputNames)) {
|
|
67839
|
+
ambiguousKeys.add(exportName);
|
|
67840
|
+
collisions.push([name, exportName]);
|
|
67820
67841
|
}
|
|
67821
67842
|
}
|
|
67822
|
-
|
|
67823
|
-
|
|
67824
|
-
|
|
67825
|
-
|
|
67826
|
-
|
|
67827
|
-
|
|
67828
|
-
|
|
67829
|
-
|
|
67830
|
-
|
|
67831
|
-
|
|
67832
|
-
|
|
67833
|
-
|
|
67834
|
-
|
|
67835
|
-
|
|
67836
|
-
|
|
67837
|
-
|
|
67838
|
-
|
|
67839
|
-
|
|
67840
|
-
|
|
67841
|
-
|
|
67842
|
-
|
|
67843
|
-
recordedSecretValues: outputSecrets,
|
|
67844
|
-
origin: `output '${name}'`
|
|
67845
|
-
});
|
|
67846
|
-
const valueContext = resolverContext(outputSecrets);
|
|
67847
|
-
await resolveCrossStackReads(valueSource, valueContext, `output '${name}'`, { canRefuse: !isOutputSuppressed(name, output, conditions, state.outputs ?? {}) });
|
|
67848
|
-
try {
|
|
67849
|
-
await resolver.resolve(valueSource, valueContext);
|
|
67850
|
-
} catch (err) {
|
|
67851
|
-
if (isRegionAmbiguousRefusal(err)) throw err;
|
|
67852
|
-
logger.debug(`Resolution of output ${name} during scrub was partial: ${maskSecretsInText(err instanceof Error ? err.message : String(err), outputSecrets)}`);
|
|
67843
|
+
for (const [name, output] of Object.entries(templateOutputs)) {
|
|
67844
|
+
const value = output.Value;
|
|
67845
|
+
if (value === void 0) continue;
|
|
67846
|
+
if (!ambiguousKeys.has(name)) outputsTemplateSource[name] = value;
|
|
67847
|
+
const exportName = output.Export?.Name;
|
|
67848
|
+
if (typeof exportName === "string" && !ambiguousKeys.has(exportName)) outputsTemplateSource[exportName] = value;
|
|
67849
|
+
const valueSource = await pinCrossRegionSecrets(value, stack.stackName, {
|
|
67850
|
+
stackRegion: region,
|
|
67851
|
+
producerRegions,
|
|
67852
|
+
resolvers,
|
|
67853
|
+
recordedSecretValues: outputSecrets,
|
|
67854
|
+
origin: `output '${name}'`
|
|
67855
|
+
});
|
|
67856
|
+
const valueContext = resolverContext(outputSecrets);
|
|
67857
|
+
await resolveCrossStackReads(valueSource, valueContext, `output '${name}'`, { canRefuse: !isOutputSuppressed(name, output, conditions, state.outputs ?? {}) });
|
|
67858
|
+
try {
|
|
67859
|
+
await resolver.resolve(valueSource, valueContext);
|
|
67860
|
+
} catch (err) {
|
|
67861
|
+
if (isRegionAmbiguousRefusal(err)) throw err;
|
|
67862
|
+
logger.debug(`Resolution of output ${name} during scrub was partial: ${maskSecretsInText(err instanceof Error ? err.message : String(err), outputSecrets)}`);
|
|
67863
|
+
}
|
|
67853
67864
|
}
|
|
67854
|
-
}
|
|
67865
|
+
});
|
|
67855
67866
|
for (const [name, exportName] of collisions) logger.warn(exportAliasCollisionScrubWarning(name, exportName, outputSecrets));
|
|
67856
67867
|
const secretBearingKeys = [];
|
|
67857
67868
|
for (const key of Object.keys(state.outputs ?? {})) {
|
|
67858
|
-
const
|
|
67859
|
-
if (!
|
|
67869
|
+
const display = secretSafeKeyDisplay(key, outputSecrets);
|
|
67870
|
+
if (!secretBearing(display)) continue;
|
|
67860
67871
|
secretBearingKeys.push(key);
|
|
67861
|
-
logger.warn(secretBearingStateKeyWarning(stack.stackName,
|
|
67872
|
+
logger.warn(secretBearingStateKeyWarning(stack.stackName, display));
|
|
67862
67873
|
}
|
|
67863
67874
|
const totalSecrets = outputSecrets.size + [...perResourceSecrets.values()].reduce((n, m) => n + m.size, 0);
|
|
67864
67875
|
if (totalSecrets === 0) return {
|
|
@@ -70039,7 +70050,7 @@ async function runDriftForStack(stackName, region, stateBackend, providerRegistr
|
|
|
70039
70050
|
logger.warn(`${logicalId} (${resource.resourceType}): ` + (refused ? "refused to resolve a dynamic reference this resource's state records, so its secret-bearing properties are NOT compared — " : "could not resolve the dynamic reference(s) this resource's state records, so its secret-bearing properties are NOT compared — ") + `${maskSecretsInText(err instanceof Error ? err.message : String(err), secrets)}`);
|
|
70040
70051
|
secrets.clear();
|
|
70041
70052
|
}
|
|
70042
|
-
if (unresolvedTokens.size > 0) logger.warn(`${logicalId} (${resource.resourceType}): cdkd cannot resolve ${maskSecretsInText([...unresolvedTokens].join(", "), secrets)} — those properties are NOT compared. A revert leaves a property whose WHOLE value is one of these tokens untouched; where a token is EMBEDDED in a longer string, the revert writes
|
|
70053
|
+
if (unresolvedTokens.size > 0) logger.warn(`${logicalId} (${resource.resourceType}): cdkd cannot resolve ${maskSecretsInText([...unresolvedTokens].join(", "), secrets)} — those properties are NOT compared. A revert leaves a property whose WHOLE value is one of these tokens untouched where its position can be matched against AWS's report (a list element pairs by an identity field, or by the list's own unchanged literal values); where it cannot be matched, and where a token is EMBEDDED in a longer string, the revert writes the token literal, exactly as 'cdkd deploy' does — so a resolved value AWS holds there WOULD be overwritten. cdkd resolves 'secretsmanager', 'ssm' and 'ssm-secure' references; anything else is left as written.`);
|
|
70043
70054
|
const observedIgnorePaths = useObserved ? undeclaredEmptyObservedKeys(resource.observedProperties, resource.properties ?? {}) : [];
|
|
70044
70055
|
const normalized = await canonicalizePrincipalUniqueIds(comparisonBaseline, aws, resolvePrincipalUniqueId);
|
|
70045
70056
|
const protocolNormalized = canonicalizeIpProtocols(normalized.baseline, normalized.aws, resource.resourceType);
|
|
@@ -70516,6 +70527,22 @@ function isPlainRecord(value) {
|
|
|
70516
70527
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
70517
70528
|
}
|
|
70518
70529
|
/**
|
|
70530
|
+
* Does this value carry an ORDINARY object prototype?
|
|
70531
|
+
*
|
|
70532
|
+
* {@link isPlainRecord} admits `Date` / `Map` / `Set` / class instances, whose
|
|
70533
|
+
* own enumerable keys are `[]` — so a key-walk over one reports "no
|
|
70534
|
+
* contradiction" between two values that actually differ. The redaction walk's
|
|
70535
|
+
* `deepEqualJsonValue` carries the same guard for the same reason
|
|
70536
|
+
* (`secret-redaction.ts`, issue #2869): a `Date` anchor corroborates NOTHING.
|
|
70537
|
+
* Kept file-local rather than imported because `secret-redaction.js` is
|
|
70538
|
+
* `vi.mock`ed by several drift suites, and a value import from it reds them
|
|
70539
|
+
* with a missing-export failure.
|
|
70540
|
+
*/
|
|
70541
|
+
function hasPlainPrototype(value) {
|
|
70542
|
+
const proto = Object.getPrototypeOf(value);
|
|
70543
|
+
return proto === Object.prototype || proto === null;
|
|
70544
|
+
}
|
|
70545
|
+
/**
|
|
70519
70546
|
* Merge the AWS-current value with the revert baseline, KEEPING every path the
|
|
70520
70547
|
* baseline does not declare (issue
|
|
70521
70548
|
* [#1626](https://github.com/go-to-k/cdkd/issues/1626) items 2 + 3).
|
|
@@ -70599,7 +70626,8 @@ function mergeUntemplatedValue(awsValue, desiredValue) {
|
|
|
70599
70626
|
return merged;
|
|
70600
70627
|
}
|
|
70601
70628
|
if (!isPlainRecord(awsValue) || !isPlainRecord(desiredValue)) return desiredValue;
|
|
70602
|
-
|
|
70629
|
+
if (!hasPlainPrototype(awsValue) || !hasPlainPrototype(desiredValue)) return desiredValue;
|
|
70630
|
+
const merged = Object.create(null);
|
|
70603
70631
|
for (const [key, value] of Object.entries(awsValue)) merged[key] = key in desiredValue ? mergeUntemplatedValue(value, desiredValue[key]) : value;
|
|
70604
70632
|
for (const [key, value] of Object.entries(desiredValue)) if (!(key in awsValue)) merged[key] = value;
|
|
70605
70633
|
return merged;
|
|
@@ -70633,9 +70661,26 @@ function mergeUntemplatedValue(awsValue, desiredValue) {
|
|
|
70633
70661
|
*
|
|
70634
70662
|
* Falls back to KEEPING the token wherever AWS has nothing at that position —
|
|
70635
70663
|
* that is what `cdkd deploy` sends, so it is the safe residual rather than
|
|
70636
|
-
* dropping a property the resource may require. Array descent
|
|
70637
|
-
*
|
|
70638
|
-
*
|
|
70664
|
+
* dropping a property the resource may require. Array descent pairs live
|
|
70665
|
+
* elements through {@link pairedLiveItems} — an identity field first, else the
|
|
70666
|
+
* array's own literal frame corroborated per LEAF — never by blind index
|
|
70667
|
+
* (issue [#2893](https://github.com/go-to-k/cdkd/issues/2893)): until that
|
|
70668
|
+
* issue the descent was positional behind a length test, so a readback AWS
|
|
70669
|
+
* REORDERED copied element *i*'s live value into element *i*'s token position
|
|
70670
|
+
* and `provider.update` shipped a value belonging to a DIFFERENT element. The
|
|
70671
|
+
* wildcard handed to the pairing is "whole token OR the redaction mask": a
|
|
70672
|
+
* token leaf cannot corroborate an order (its live counterpart is the resolved
|
|
70673
|
+
* value, so comparing would refuse every array this pass exists to serve), and
|
|
70674
|
+
* a mask leaf belongs to the SIBLING mask walk but must abstain here rather
|
|
70675
|
+
* than contradict. Where the pairing refuses, the token is KEPT — the same
|
|
70676
|
+
* residual as everywhere else in this pass, and deliberately NOT the mask
|
|
70677
|
+
* walk's drop-the-resource refusal: that one would abandon every other drifted
|
|
70678
|
+
* property over an alignment this pass never needed, while the kept token is
|
|
70679
|
+
* what `cdkd deploy` sends. The trade this accepts: an array whose OTHER
|
|
70680
|
+
* leaves genuinely drifted no longer takes a positional copy (the frame cannot
|
|
70681
|
+
* corroborate an order it no longer matches), so the token ships there —
|
|
70682
|
+
* a no-op for a record cdkd deployed, and for any other record a preserved
|
|
70683
|
+
* pre-existing breakage rather than another element's value written live.
|
|
70639
70684
|
*/
|
|
70640
70685
|
function preserveLiveValuesAtUnresolvedTokens(send, awsProperties) {
|
|
70641
70686
|
const walk = (value, live) => {
|
|
@@ -70645,12 +70690,13 @@ function preserveLiveValuesAtUnresolvedTokens(send, awsProperties) {
|
|
|
70645
70690
|
return live;
|
|
70646
70691
|
}
|
|
70647
70692
|
if (Array.isArray(value)) {
|
|
70648
|
-
const liveItems =
|
|
70693
|
+
const liveItems = pairedLiveItems(value, live, isTokenOrMaskLeaf, carriesTokenOrMask, true);
|
|
70649
70694
|
return value.map((item, i) => walk(item, liveItems?.[i]));
|
|
70650
70695
|
}
|
|
70651
70696
|
if (value !== null && typeof value === "object") {
|
|
70697
|
+
if (!hasPlainPrototype(value)) return value;
|
|
70652
70698
|
const liveObject = isPlainRecord(live) ? live : void 0;
|
|
70653
|
-
const out =
|
|
70699
|
+
const out = Object.create(null);
|
|
70654
70700
|
for (const [k, v] of Object.entries(value)) out[k] = walk(v, liveObject?.[k]);
|
|
70655
70701
|
return out;
|
|
70656
70702
|
}
|
|
@@ -70659,6 +70705,335 @@ function preserveLiveValuesAtUnresolvedTokens(send, awsProperties) {
|
|
|
70659
70705
|
return walk(send, awsProperties);
|
|
70660
70706
|
}
|
|
70661
70707
|
/**
|
|
70708
|
+
* Paths in the bag `--revert` is about to send whose value is an unresolved
|
|
70709
|
+
* CloudFormation intrinsic OBJECT — `{ Ref: ... }` or a single-key
|
|
70710
|
+
* `{ 'Fn::*': ... }` (issue
|
|
70711
|
+
* [#2855](https://github.com/go-to-k/cdkd/issues/2855)).
|
|
70712
|
+
*
|
|
70713
|
+
* WHY THE SHAPE IS REACHABLE. `cdkd import`'s warn path persists raw intrinsic
|
|
70714
|
+
* objects into a record's `properties`, and issue #2842's baseline refusal is
|
|
70715
|
+
* what routes such a record here: it leaves `observedProperties` undefined, so
|
|
70716
|
+
* `runRevert`'s `revertBaseline` falls back to the raw bag.
|
|
70717
|
+
* `resolveStateSecretExpressions` re-resolves only `{{resolve:...}}` STRINGS,
|
|
70718
|
+
* so the OBJECT walks through it untouched, and the drift comparator's
|
|
70719
|
+
* `{{resolve:` skip is a string test too — the object simply differs from the
|
|
70720
|
+
* readback, drifts, and `buildRevertNewProperties` overlays it into the send
|
|
70721
|
+
* bag.
|
|
70722
|
+
*
|
|
70723
|
+
* WHY IT MUST BE REFUSED rather than shipped or live-preserved. Measured
|
|
70724
|
+
* (issue #2855's open question) with mocked SDK clients on both routes: NO
|
|
70725
|
+
* route fails loudly on cdkd's side. `SSMParameterProvider.update` puts the
|
|
70726
|
+
* raw object straight into `PutParameterCommand.input.Value`;
|
|
70727
|
+
* `CloudControlProvider.update` emits it verbatim as a JSON-patch value, and
|
|
70728
|
+
* for a `JSON_STRING_PROPERTIES` type (`AWS::Events::Rule.EventPattern`) it is
|
|
70729
|
+
* `JSON.stringify`ed into a schema-VALID string — a payload AWS cannot even
|
|
70730
|
+
* reject on type, i.e. guaranteed silent corruption. Live-preserving (the
|
|
70731
|
+
* token pass's answer) is no better: the intrinsic's real value depends on
|
|
70732
|
+
* template context a synth-free command does not have, and copying whatever
|
|
70733
|
+
* AWS holds would silently bless an out-of-band edit as the baseline.
|
|
70734
|
+
*
|
|
70735
|
+
* The predicate mirrors the resolver's own rule (a CloudFormation intrinsic is
|
|
70736
|
+
* ALWAYS a single-key object; `detectUnknownIntrinsicKey` in
|
|
70737
|
+
* `intrinsic-function-resolver.ts` states why single-key is what keeps a real
|
|
70738
|
+
* property literally named `Ref` from false-positiving). A non-plain object
|
|
70739
|
+
* (`Date`, `Uint8Array`) cannot be an intrinsic and is not descended. The scan
|
|
70740
|
+
* is scoped to `topLevelKeys` — the DRIFTED keys, the only ones
|
|
70741
|
+
* `buildRevertNewProperties` sources from the baseline — so a value that
|
|
70742
|
+
* merely looks intrinsic under a non-drifted key cannot refuse a revert it
|
|
70743
|
+
* never participates in. The caller hands it the BASELINE
|
|
70744
|
+
* (`desiredProperties`), never the post-overlay send bag: on the very
|
|
70745
|
+
* population this refusal exists for, `mergeUntemplatedValue`'s key-merge can
|
|
70746
|
+
* FUSE a single-key intrinsic with a plain-record live value into a multi-key
|
|
70747
|
+
* object the predicate no longer matches, while every overlay arm re-emits
|
|
70748
|
+
* every baseline path into the send bag in some shape — so the baseline scan
|
|
70749
|
+
* is complete where the merged-bag scan was dilutable, and an AWS-echoed
|
|
70750
|
+
* lookalike (which lives only in the overlay) stays exempt by construction.
|
|
70751
|
+
* The caller additionally gates the whole scan on
|
|
70752
|
+
* `observedProperties === undefined` — intrinsic objects reach a revert
|
|
70753
|
+
* baseline only through the raw-`properties` fallback, while an
|
|
70754
|
+
* `observedProperties` baseline is READBACK-derived, where a single-key map
|
|
70755
|
+
* named like an intrinsic is a real AWS value and a refusal would be
|
|
70756
|
+
* permanent (the call site's comment carries the full derivation).
|
|
70757
|
+
*
|
|
70758
|
+
* The whole flagged leaf is reported once; nothing beneath it is separately
|
|
70759
|
+
* scanned, since the caller refuses the resource on the first path anyway.
|
|
70760
|
+
*/
|
|
70761
|
+
function collectUnresolvedIntrinsicObjectPaths(bag, topLevelKeys) {
|
|
70762
|
+
const found = [];
|
|
70763
|
+
const visit = (value, path) => {
|
|
70764
|
+
if (Array.isArray(value)) {
|
|
70765
|
+
value.forEach((item, i) => visit(item, `${path}[${i}]`));
|
|
70766
|
+
return;
|
|
70767
|
+
}
|
|
70768
|
+
if (value !== null && typeof value === "object") {
|
|
70769
|
+
if (!hasPlainPrototype(value)) return;
|
|
70770
|
+
const record = value;
|
|
70771
|
+
const keys = Object.keys(record);
|
|
70772
|
+
const key = keys[0];
|
|
70773
|
+
if (keys.length === 1 && key !== void 0 && (key === "Ref" || key.startsWith("Fn::"))) {
|
|
70774
|
+
found.push(path);
|
|
70775
|
+
return;
|
|
70776
|
+
}
|
|
70777
|
+
for (const [k, v] of Object.entries(record)) visit(v, `${path}.${k}`);
|
|
70778
|
+
}
|
|
70779
|
+
};
|
|
70780
|
+
for (const key of topLevelKeys) if (Object.hasOwn(bag, key)) visit(bag[key], key);
|
|
70781
|
+
return found.sort();
|
|
70782
|
+
}
|
|
70783
|
+
/**
|
|
70784
|
+
* The live array to read a preserved leaf's replacement from, aligned to
|
|
70785
|
+
* `send`'s indices — or `undefined` when the two lists cannot be aligned at
|
|
70786
|
+
* all, which routes every mask beneath them to `unpreservablePaths` in the
|
|
70787
|
+
* mask walk and keeps the token in the token walk (see the wildcard
|
|
70788
|
+
* paragraph below for the split).
|
|
70789
|
+
*
|
|
70790
|
+
* WHY THIS IS NOT INDEX ALIGNMENT ANY MORE (issue
|
|
70791
|
+
* [#2884](https://github.com/go-to-k/cdkd/issues/2884)). Until issue
|
|
70792
|
+
* [#2852](https://github.com/go-to-k/cdkd/issues/2852) the only mask in a
|
|
70793
|
+
* persisted bag came from a `NoEcho` custom resource's `Data` (issue #2274),
|
|
70794
|
+
* which sits at a scalar leaf of a property the template names. #2852 added a
|
|
70795
|
+
* second source, and it lands in exactly the population index alignment is
|
|
70796
|
+
* WRONG on: a leaf is masked BECAUSE `refuseUncertifiedReadbackPositions` could
|
|
70797
|
+
* not pair the two lists — an unkeyed array AWS reordered, or a keyed element
|
|
70798
|
+
* whose identity AWS normalised. A resource that produced a mask is by
|
|
70799
|
+
* construction one whose list does not come back in a stable order.
|
|
70800
|
+
*
|
|
70801
|
+
* Traced consequence, which is the issue #1498 / #1501 data-loss class this
|
|
70802
|
+
* module's own doc calls "strictly worse than the disclosure": for
|
|
70803
|
+
* `Environment: [{Name:'DB_PASS', Value:<ref>}, {Name:'REGION', Value:'...'}]`
|
|
70804
|
+
* echoed back case-normalised and reordered, the baseline becomes
|
|
70805
|
+
* `[{Name:'REGION',...}, {Name:'***', Value:'***'}]`, and an index-aligned
|
|
70806
|
+
* revert copies `live[1]` into the masked slot — shipping a duplicated `REGION`
|
|
70807
|
+
* and DELETING the password variable from the live task definition.
|
|
70808
|
+
*
|
|
70809
|
+
* So the alignment must be one AWS's own reordering cannot break:
|
|
70810
|
+
* {@link identityKeyFor} — the same rule the redaction walk pairs by, so the
|
|
70811
|
+
* two can never disagree about which elements correspond. It is asked FIRST and
|
|
70812
|
+
* with NO length test, which is a change from the first cut of this function: a
|
|
70813
|
+
* length test ahead of it refuses a list AWS legitimately ADDED an element to,
|
|
70814
|
+
* while the keyed lookup needs no such partner — a `send` element whose identity
|
|
70815
|
+
* AWS does not report simply yields no live value, which is already the
|
|
70816
|
+
* refusing arm.
|
|
70817
|
+
*
|
|
70818
|
+
* WITHOUT AN IDENTITY FIELD the array's own literal FRAME has to vouch for the
|
|
70819
|
+
* order — the same corroboration `unkeyedArrayPairsByAnchors` requires on the
|
|
70820
|
+
* redaction side — and the test is PER-LEAF. Three rules, and the first cut of
|
|
70821
|
+
* this function had none of them right:
|
|
70822
|
+
*
|
|
70823
|
+
* - **the lengths must match**, positional alignment being all that is left;
|
|
70824
|
+
* - **every leaf of `send` that is not a mask must equal `live`'s leaf at the
|
|
70825
|
+
* same position** ({@link corroboratedLeafCount}), masked leaves wildcarded.
|
|
70826
|
+
* A whole-ELEMENT exemption is what shipped first, and it corroborated
|
|
70827
|
+
* NOTHING: {@link carriesSecretMask} is a DEEP, whole-subtree test, so an
|
|
70828
|
+
* element carrying one mask anywhere skipped the comparison entirely.
|
|
70829
|
+
* Measured on `[{Pw:'***',Role:'reader'},{Pw:'***',Role:'admin'}]` against a
|
|
70830
|
+
* live list AWS returned in the other order — the admin password was written
|
|
70831
|
+
* onto the reader entry and `provider.update` shipped it;
|
|
70832
|
+
* - **at most ONE element may carry a mask, and at least one non-mask leaf must
|
|
70833
|
+
* actually have been COMPARED.** Two masked slots leave the surviving literal
|
|
70834
|
+
* frame unable to say which mask goes where (two secret environment variables
|
|
70835
|
+
* in one container definition is the ordinary shape), and zero compared
|
|
70836
|
+
* leaves is the all-masked array {@link SECRET_MASK} produces when AWS
|
|
70837
|
+
* normalised the identity field too — there the old `every` was vacuously
|
|
70838
|
+
* true and the alignment rested on no evidence at all. With ONE masked slot
|
|
70839
|
+
* and every other position literally equal, the single live element left over
|
|
70840
|
+
* is the only value that can belong there.
|
|
70841
|
+
*
|
|
70842
|
+
* The `compared > 0` floor DELIBERATELY also refuses a single-element scalar
|
|
70843
|
+
* array — `[SECRET_MASK]` against one live value — even though one masked slot
|
|
70844
|
+
* against one live slot looks unambiguous. It is unambiguous only IF the live
|
|
70845
|
+
* list corresponds to this one, and with zero corroborated leaves the only
|
|
70846
|
+
* evidence for that is the length-1 match, which is no evidence: this is the
|
|
70847
|
+
* all-masked reading applied to its smallest shape, and the cost is a refusal
|
|
70848
|
+
* (the safe residual), not a wrong write. THAT COST SENTENCE IS THE MASK
|
|
70849
|
+
* WALK'S ALONE — for the token walk a refusal WRITES the token, so the same
|
|
70850
|
+
* floor inverts there and the singleton is accepted instead; see
|
|
70851
|
+
* `pairedLiveItems`' `acceptForcedSingleton` for the derivation.
|
|
70852
|
+
*
|
|
70853
|
+
* THE EVIDENCE MUST NOT BE MANUFACTURED BY A SIBLING PASS. `send` here must be
|
|
70854
|
+
* values the caller did not itself copy from `live` — see
|
|
70855
|
+
* {@link preserveLiveValuesAtMaskedLeaves}'s `corroborationSource` parameter
|
|
70856
|
+
* for the round-4 #2884 defect where the token pass's index-copied live values
|
|
70857
|
+
* corroborated their own indices.
|
|
70858
|
+
*
|
|
70859
|
+
* Refusing is the correct outcome for the MASK caller — it drops the whole
|
|
70860
|
+
* resource rather than send a guess. The TOKEN caller's refusal residual is
|
|
70861
|
+
* KEEPING the token (see below); both are the refusal `undefined` spells here,
|
|
70862
|
+
* and what it costs is the CALLER's contract, not this function's.
|
|
70863
|
+
*
|
|
70864
|
+
* PARAMETERISED WILDCARDS (issue #2893). This pairing now serves BOTH preserve
|
|
70865
|
+
* walks, because the two questions are identical — "which live element belongs
|
|
70866
|
+
* to this send element?" — and a second hand-rolled copy of the rule is
|
|
70867
|
+
* exactly how the `maskDeep` class of divergence starts. What differs per
|
|
70868
|
+
* caller is only WHICH leaf is the one being paired FOR (and so must be
|
|
70869
|
+
* wildcarded rather than compared) and what a refusal costs:
|
|
70870
|
+
*
|
|
70871
|
+
* - the MASK walk wildcards {@link SECRET_MASK} alone; a refusal drops the
|
|
70872
|
+
* resource, so over-refusal is a safe posture there;
|
|
70873
|
+
* - the TOKEN walk wildcards a WHOLE `{{resolve:...}}` token (its live
|
|
70874
|
+
* counterpart is the resolved value, so comparing would contradict on every
|
|
70875
|
+
* array that pass exists to serve) AND the mask (owned by the LATER mask
|
|
70876
|
+
* walk — comparing `***` against live would contradict spuriously, and
|
|
70877
|
+
* treating it as evidence would be wrong in the other direction). Its
|
|
70878
|
+
* refusal residual is keeping the token, which `provider.update` ships —
|
|
70879
|
+
* a no-op for a record cdkd deployed, a preserved pre-existing breakage
|
|
70880
|
+
* otherwise, and in both cases better than another element's live value
|
|
70881
|
+
* copied into the wrong position.
|
|
70882
|
+
*
|
|
70883
|
+
* `carriesWildcard` must be the DEEP form of `wildcardLeaf` (an element
|
|
70884
|
+
* carrying a wildcard anywhere is a slot whose alignment part-rests on
|
|
70885
|
+
* abstention), and the one-slot bound applies to it: with two such elements
|
|
70886
|
+
* the surviving literal frame cannot say which wildcard goes where.
|
|
70887
|
+
*
|
|
70888
|
+
* `acceptForcedSingleton` (PR #2912 review, blocker 1) is where the two
|
|
70889
|
+
* callers' refusal COSTS meet the evidence rules, and it exists because a
|
|
70890
|
+
* rule that is sound for one arm BECAUSE of what its refusal costs cannot be
|
|
70891
|
+
* shared without re-deriving that cost. At `send.length === 1` against
|
|
70892
|
+
* `live.length === 1` no evidence question remains OPEN: the position is
|
|
70893
|
+
* FORCED (there is no other candidate to mis-pair with), and a positional
|
|
70894
|
+
* copy writes each live value back to its own position, so no
|
|
70895
|
+
* cross-POSITION donation — the credential-swap class every bound above
|
|
70896
|
+
* exists to stop — is constructible. Cross-IDENTITY donation is: the
|
|
70897
|
+
* bypass sits above the identity arm and its wildcard-identity guard, so
|
|
70898
|
+
* `[{Name:'DB', Value:<token>}]` against a live `[{Name:'OTHER', Value:'X'}]`
|
|
70899
|
+
* ships `{Name:'DB', Value:'X'}` — a value from a differently-identified
|
|
70900
|
+
* element. That is position-neutral (X lands at the exact position AWS
|
|
70901
|
+
* holds it), it is what the pre-#2893 positional descent did for every such
|
|
70902
|
+
* array, and the alternative writes the token over X; the trade is stated
|
|
70903
|
+
* here rather than claimed away. What the evidence rules still doubt at
|
|
70904
|
+
* 1-vs-1 is only "does this live list correspond to this send list at
|
|
70905
|
+
* all", and the two callers answer that doubt oppositely because their
|
|
70906
|
+
* refusal residuals invert:
|
|
70907
|
+
*
|
|
70908
|
+
* - the MASK walk refuses (`false`, the default): its residual is dropping
|
|
70909
|
+
* the resource, so doubt costs a refusal — the safe direction, and the
|
|
70910
|
+
* `[SECRET_MASK]`-vs-one-live-value reading its own doc defends;
|
|
70911
|
+
* - the TOKEN walk accepts (`true`): its residual WRITES the literal token
|
|
70912
|
+
* over whatever AWS holds (`provider.update` ships it — the #1914
|
|
70913
|
+
* corruption), so refusing on a forced position DESTROYS the live value a
|
|
70914
|
+
* pairing would have preserved, while accepting is write-neutral even when
|
|
70915
|
+
* the correspondence doubt is real (the copied value lands at the exact
|
|
70916
|
+
* position AWS already holds it). Refusal buys nothing and costs a
|
|
70917
|
+
* destructive write, so the token walk takes the pairing — which is also
|
|
70918
|
+
* exactly the pre-#2893 behaviour for every 1-vs-1 array.
|
|
70919
|
+
*
|
|
70920
|
+
* The bypass sits ABOVE the identity arm deliberately: at 1-vs-1 an identity
|
|
70921
|
+
* mismatch (AWS normalised the one element's Name) changes which EVIDENCE
|
|
70922
|
+
* rule fails, not the forced position, and routing it through the identity
|
|
70923
|
+
* arm would re-open the same inverted cost through a different door. Every
|
|
70924
|
+
* bound below is untouched for `length >= 2`, where mis-pairing is real.
|
|
70925
|
+
*/
|
|
70926
|
+
function pairedLiveItems(send, live, wildcardLeaf = isMaskLeaf, carriesWildcard = carriesSecretMask, acceptForcedSingleton = false) {
|
|
70927
|
+
if (!Array.isArray(live)) return void 0;
|
|
70928
|
+
if (acceptForcedSingleton && send.length === 1 && live.length === 1) return live;
|
|
70929
|
+
const key = identityKeyFor(send, live);
|
|
70930
|
+
if (key !== void 0) {
|
|
70931
|
+
const byIdentity = /* @__PURE__ */ new Map();
|
|
70932
|
+
for (const item of live) byIdentity.set(item[key], item);
|
|
70933
|
+
return send.map((item) => {
|
|
70934
|
+
const identity = item[key];
|
|
70935
|
+
return wildcardLeaf(identity) ? void 0 : byIdentity.get(identity);
|
|
70936
|
+
});
|
|
70937
|
+
}
|
|
70938
|
+
if (live.length !== send.length) return void 0;
|
|
70939
|
+
let wildcardSlots = 0;
|
|
70940
|
+
let compared = 0;
|
|
70941
|
+
for (let i = 0; i < send.length; i++) {
|
|
70942
|
+
if (carriesWildcard(send[i])) wildcardSlots++;
|
|
70943
|
+
const corroborated = corroboratedLeafCount(send[i], live[i], wildcardLeaf);
|
|
70944
|
+
if (corroborated === void 0) return void 0;
|
|
70945
|
+
compared += corroborated;
|
|
70946
|
+
}
|
|
70947
|
+
if (wildcardSlots > 1 || compared === 0) return void 0;
|
|
70948
|
+
return live;
|
|
70949
|
+
}
|
|
70950
|
+
/** The MASK walk's wildcard: the redaction mask alone. */
|
|
70951
|
+
function isMaskLeaf(leaf) {
|
|
70952
|
+
return leaf === "***";
|
|
70953
|
+
}
|
|
70954
|
+
/**
|
|
70955
|
+
* The TOKEN walk's wildcard: a leaf whose WHOLE value is a `{{resolve:...}}`
|
|
70956
|
+
* token, plus the mask (which that walk never touches but must abstain on —
|
|
70957
|
+
* see {@link pairedLiveItems}). A MIXED leaf (a token embedded in a longer
|
|
70958
|
+
* string) is deliberately NOT a wildcard: the token walk does not preserve it
|
|
70959
|
+
* either, and its live counterpart genuinely differs, so letting it abstain
|
|
70960
|
+
* would make every partially-resolved string an unbounded free pass.
|
|
70961
|
+
*/
|
|
70962
|
+
function isTokenOrMaskLeaf(leaf) {
|
|
70963
|
+
if (leaf === "***") return true;
|
|
70964
|
+
return typeof leaf === "string" && leaf.includes("{{resolve:") && isSingleDynamicReferenceToken(leaf);
|
|
70965
|
+
}
|
|
70966
|
+
/**
|
|
70967
|
+
* Deep form of {@link isTokenOrMaskLeaf}, for the one-slot bound.
|
|
70968
|
+
*
|
|
70969
|
+
* Cycle-guarded like its sibling `carriesSecretMask`, via the same inner
|
|
70970
|
+
* closure so the visited set is not a callable parameter (PR #2912 review):
|
|
70971
|
+
* the value walked here is an element of `buildRevertNewProperties`' output,
|
|
70972
|
+
* which carries RAW `readCurrentState` returns — provider-authored, not
|
|
70973
|
+
* JSON-round-tripped — so a self-referential object must answer `false`
|
|
70974
|
+
* rather than throw `RangeError` out of the pairing. The guard bounds the
|
|
70975
|
+
* PAIRING frame only: a cyclic PLAIN element still overflows the enclosing
|
|
70976
|
+
* preserve walk one frame later (pre-existing, caught by `runRevert`'s
|
|
70977
|
+
* per-resource payload-build catch); what this guard keeps alive is the
|
|
70978
|
+
* NON-PLAIN cyclic element, which that walk returns by identity and only
|
|
70979
|
+
* this slot count ever descends.
|
|
70980
|
+
*/
|
|
70981
|
+
function carriesTokenOrMask(value) {
|
|
70982
|
+
const seen = /* @__PURE__ */ new Set();
|
|
70983
|
+
const walkNode = (node) => {
|
|
70984
|
+
if (isTokenOrMaskLeaf(node)) return true;
|
|
70985
|
+
if (node === null || typeof node !== "object") return false;
|
|
70986
|
+
if (seen.has(node)) return false;
|
|
70987
|
+
seen.add(node);
|
|
70988
|
+
if (Array.isArray(node)) return node.some(walkNode);
|
|
70989
|
+
return Object.values(node).some(walkNode);
|
|
70990
|
+
};
|
|
70991
|
+
return walkNode(value);
|
|
70992
|
+
}
|
|
70993
|
+
/**
|
|
70994
|
+
* How many of `send`'s leaves the live side CORROBORATES, or `undefined` the
|
|
70995
|
+
* moment one of them CONTRADICTS it.
|
|
70996
|
+
*
|
|
70997
|
+
* A WILDCARD leaf (the caller's `wildcardLeaf` — the mask for the mask walk,
|
|
70998
|
+
* a whole token or the mask for the token walk) contributes 0 and is never
|
|
70999
|
+
* compared: it is the value being paired FOR, so requiring it to match would
|
|
71000
|
+
* refuse every array {@link pairedLiveItems} exists to align. Everything else
|
|
71001
|
+
* must agree — including the container SHAPE around it, since a subtree of a
|
|
71002
|
+
* different shape is a contradiction rather than an absent comparison.
|
|
71003
|
+
*
|
|
71004
|
+
* A COUNT rather than a boolean, because "nothing contradicted me" is exactly
|
|
71005
|
+
* the answer an all-wildcard array gives and it is not evidence. The caller
|
|
71006
|
+
* requires a non-zero count before it reads the order as corroborated.
|
|
71007
|
+
*/
|
|
71008
|
+
function corroboratedLeafCount(send, live, wildcardLeaf = isMaskLeaf) {
|
|
71009
|
+
if (wildcardLeaf(send)) return 0;
|
|
71010
|
+
if (Array.isArray(send)) {
|
|
71011
|
+
if (!Array.isArray(live) || live.length !== send.length) return void 0;
|
|
71012
|
+
let total = 0;
|
|
71013
|
+
for (let i = 0; i < send.length; i++) {
|
|
71014
|
+
const corroborated = corroboratedLeafCount(send[i], live[i], wildcardLeaf);
|
|
71015
|
+
if (corroborated === void 0) return void 0;
|
|
71016
|
+
total += corroborated;
|
|
71017
|
+
}
|
|
71018
|
+
return total;
|
|
71019
|
+
}
|
|
71020
|
+
if (isPlainRecord(send)) {
|
|
71021
|
+
if (!hasPlainPrototype(send)) return void 0;
|
|
71022
|
+
if (!isPlainRecord(live) || !hasPlainPrototype(live)) return void 0;
|
|
71023
|
+
const keys = Object.keys(send);
|
|
71024
|
+
if (keys.length !== Object.keys(live).length) return void 0;
|
|
71025
|
+
let total = 0;
|
|
71026
|
+
for (const k of keys) {
|
|
71027
|
+
if (!Object.prototype.hasOwnProperty.call(live, k)) return void 0;
|
|
71028
|
+
const corroborated = corroboratedLeafCount(send[k], live[k], wildcardLeaf);
|
|
71029
|
+
if (corroborated === void 0) return void 0;
|
|
71030
|
+
total += corroborated;
|
|
71031
|
+
}
|
|
71032
|
+
return total;
|
|
71033
|
+
}
|
|
71034
|
+
return deepEqualUnordered(send, live) ? 1 : void 0;
|
|
71035
|
+
}
|
|
71036
|
+
/**
|
|
70662
71037
|
* Keep `cdkd drift --revert` from pushing a REDACTION MASK to AWS (issue
|
|
70663
71038
|
* [#2274](https://github.com/go-to-k/cdkd/issues/2274)).
|
|
70664
71039
|
*
|
|
@@ -70682,9 +71057,16 @@ function preserveLiveValuesAtUnresolvedTokens(send, awsProperties) {
|
|
|
70682
71057
|
* revision's reason for not doing so was wrong: it said "the live value is one
|
|
70683
71058
|
* cdkd never resolved and cannot recognise, so there is no needle to record".
|
|
70684
71059
|
* The needle is `liveValue -> {@link SECRET_MASK}`, i.e. a MASK-ONLY entry, and
|
|
70685
|
-
* the evidence for it is the position —
|
|
70686
|
-
*
|
|
70687
|
-
* a leaf.
|
|
71060
|
+
* the evidence for it is the position — the masked position proves the value
|
|
71061
|
+
* is ONE CDKD CHOSE TO MASK, since nothing but this module's own redaction
|
|
71062
|
+
* puts a mask at a leaf. That is deliberately weaker than "proof the value is
|
|
71063
|
+
* secret" (issue #2881): a #2852 fail-closed mask deliberately over-masks a
|
|
71064
|
+
* value the walk merely could not certify — a normalised `US-EAST-1` as
|
|
71065
|
+
* readily as a credential — so the live value registered here may be an
|
|
71066
|
+
* ordinary literal. The consequence is bounded to over-masking log output,
|
|
71067
|
+
* the safe direction, and registering it is still right: when the position
|
|
71068
|
+
* DOES hold a secret, the registration is what keeps the moved plaintext out
|
|
71069
|
+
* of every reader below. Without it this function copies live plaintext into the
|
|
70688
71070
|
* send bag and then `collectNarrowedTopLevelKeys` persists that delta into
|
|
70689
71071
|
* `observedProperties` against a `secrets` map holding no entry for it — the
|
|
70690
71072
|
* same disclosure the mask exists to prevent, arriving through the mechanism
|
|
@@ -70711,17 +71093,58 @@ function preserveLiveValuesAtUnresolvedTokens(send, awsProperties) {
|
|
|
70711
71093
|
* would be the corruption; dropping the key would delete a property the
|
|
70712
71094
|
* resource may require.
|
|
70713
71095
|
*
|
|
70714
|
-
* Array descent is
|
|
70715
|
-
*
|
|
70716
|
-
*
|
|
70717
|
-
*
|
|
70718
|
-
*
|
|
70719
|
-
*
|
|
70720
|
-
|
|
70721
|
-
|
|
71096
|
+
* Array descent is NOT positional and does NOT bail on a length mismatch —
|
|
71097
|
+
* {@link pairedLiveItems} owns that question, for BOTH walks since issue
|
|
71098
|
+
* #2893 (the sibling's earlier answer, index alignment behind a length test,
|
|
71099
|
+
* was wrong by construction on the population that produces these masks, and
|
|
71100
|
+
* wrong under any reordered readback for the sibling's own tokens). An
|
|
71101
|
+
* earlier revision of this paragraph still claimed the sibling's rule after
|
|
71102
|
+
* the pairing had changed underneath it.
|
|
71103
|
+
*
|
|
71104
|
+
* `corroborationSource` is the bag the PAIRING EVIDENCE is read from, and it
|
|
71105
|
+
* exists because of a round-4 #2884 defect: `runRevert` runs
|
|
71106
|
+
* {@link preserveLiveValuesAtUnresolvedTokens} FIRST, and that pass copies
|
|
71107
|
+
* `live` values into whole-token leaves — BY INDEX when the defect was found;
|
|
71108
|
+
* by certified pairing since issue #2893, which does not retire this
|
|
71109
|
+
* parameter, because a copied leaf is trivially deep-equal to `live` at its
|
|
71110
|
+
* own position HOWEVER it was chosen, so corroborating `send` (the post-token
|
|
71111
|
+
* bag) against that same `live` still counts evidence a sibling pass
|
|
71112
|
+
* manufactured rather than evidence AWS's readback supplied. Under the
|
|
71113
|
+
* original index copies, an unkeyed array
|
|
71114
|
+
* whose only non-mask leaves were tokens satisfied both the
|
|
71115
|
+
* `compared > 0` floor and the one-mask bound on evidence the sibling pass
|
|
71116
|
+
* manufactured, and a reordered `live` wrote another element's secret into the
|
|
71117
|
+
* masked slot — the credential-swap class the guards were added for. The same
|
|
71118
|
+
* fabrication reached the IDENTITY arm: an identity field that was itself a
|
|
71119
|
+
* token took its live value by index, and {@link pairedLiveItems}' keyed
|
|
71120
|
+
* lookup then "paired" it right back to that index. So the caller passes the
|
|
71121
|
+
* PRE-token bag here; both the identity lookup and the per-leaf corroboration
|
|
71122
|
+
* run over values no sibling pass copied from `live`, a token leaf compares
|
|
71123
|
+
* against the resolved live value and CONTRADICTS (its live counterpart is
|
|
71124
|
+
* unknowable, and an abstention would make token leaves unbounded wildcards
|
|
71125
|
+
* beside the deliberately bounded one-mask rule), and the array refuses. The
|
|
71126
|
+
* masked VALUES the walk writes still come from `send` — only the evidence
|
|
71127
|
+
* source changes. Defaults to `send` (self-corroboration, the pre-round-4
|
|
71128
|
+
* behaviour) for a caller with no sibling pass; a structural mismatch between
|
|
71129
|
+
* the two bags — which the token pass CAN produce, by returning the live
|
|
71130
|
+
* CONTAINER AWS holds over a whole-token string leaf — refuses the masks
|
|
71131
|
+
* beneath it rather than falling back to the fabricable bag. That refusal is
|
|
71132
|
+
* a deliberate fail-closed cost, not a reachable loss: the mismatching
|
|
71133
|
+
* subtree is live-derived and carries no mask, so nothing preservable is
|
|
71134
|
+
* refused today.
|
|
71135
|
+
*
|
|
71136
|
+
* Returns the input bag BY IDENTITY when it holds no mask — checked FIRST via
|
|
71137
|
+
* {@link carriesSecretMask}, so a mask-free revert (the ordinary case) pays
|
|
71138
|
+
* one boolean walk instead of a full rebuild plus per-array pairing.
|
|
71139
|
+
*/
|
|
71140
|
+
function preserveLiveValuesAtMaskedLeaves(send, awsProperties, secrets, corroborationSource = send) {
|
|
71141
|
+
if (!carriesSecretMask(send)) return {
|
|
71142
|
+
properties: send,
|
|
71143
|
+
unpreservablePaths: []
|
|
71144
|
+
};
|
|
70722
71145
|
const unpreservablePaths = [];
|
|
70723
71146
|
let changed = false;
|
|
70724
|
-
const walk = (value, live, path) => {
|
|
71147
|
+
const walk = (value, corr, live, path) => {
|
|
70725
71148
|
if (value === "***") {
|
|
70726
71149
|
if (live === void 0) {
|
|
70727
71150
|
unpreservablePaths.push(path);
|
|
@@ -70732,18 +71155,24 @@ function preserveLiveValuesAtMaskedLeaves(send, awsProperties, secrets) {
|
|
|
70732
71155
|
return live;
|
|
70733
71156
|
}
|
|
70734
71157
|
if (Array.isArray(value)) {
|
|
70735
|
-
const
|
|
70736
|
-
|
|
71158
|
+
const corrItems = Array.isArray(corr) && corr.length === value.length ? corr : void 0;
|
|
71159
|
+
const liveItems = corrItems === void 0 ? void 0 : pairedLiveItems(corrItems, live);
|
|
71160
|
+
return value.map((item, i) => walk(item, corrItems?.[i], liveItems?.[i], `${path}[${i}]`));
|
|
70737
71161
|
}
|
|
70738
71162
|
if (value !== null && typeof value === "object") {
|
|
71163
|
+
if (!hasPlainPrototype(value)) {
|
|
71164
|
+
if (carriesSecretMask(value)) unpreservablePaths.push(path);
|
|
71165
|
+
return value;
|
|
71166
|
+
}
|
|
70739
71167
|
const liveObject = isPlainRecord(live) ? live : void 0;
|
|
70740
|
-
const
|
|
70741
|
-
|
|
71168
|
+
const corrObject = isPlainRecord(corr) ? corr : void 0;
|
|
71169
|
+
const out = Object.create(null);
|
|
71170
|
+
for (const [k, v] of Object.entries(value)) out[k] = walk(v, corrObject?.[k], liveObject?.[k], path === "" ? k : `${path}.${k}`);
|
|
70742
71171
|
return out;
|
|
70743
71172
|
}
|
|
70744
71173
|
return value;
|
|
70745
71174
|
};
|
|
70746
|
-
const properties = walk(send, awsProperties, "");
|
|
71175
|
+
const properties = walk(send, corroborationSource, awsProperties, "");
|
|
70747
71176
|
if (!changed && unpreservablePaths.length === 0) return {
|
|
70748
71177
|
properties: send,
|
|
70749
71178
|
unpreservablePaths
|
|
@@ -70990,15 +71419,28 @@ async function runRevert(reports, providerRegistry, stateBackend, stateConfig, a
|
|
|
70990
71419
|
return;
|
|
70991
71420
|
}
|
|
70992
71421
|
const unrevertablePaths = outcome.changes.map((c) => c.path).filter((path) => (path.split(".", 1)[0] ?? "").includes("***"));
|
|
70993
|
-
if (
|
|
70994
|
-
|
|
71422
|
+
if (stateResource.observedProperties === void 0) {
|
|
71423
|
+
const driftedTopLevelKeys = /* @__PURE__ */ new Set();
|
|
71424
|
+
for (const change of outcome.changes) {
|
|
71425
|
+
const topLevelKey = change.path.split(".", 1)[0];
|
|
71426
|
+
if (topLevelKey) driftedTopLevelKeys.add(topLevelKey);
|
|
71427
|
+
}
|
|
71428
|
+
const intrinsicObjectPaths = collectUnresolvedIntrinsicObjectPaths(desiredProperties, driftedTopLevelKeys);
|
|
71429
|
+
if (intrinsicObjectPaths.length > 0) {
|
|
71430
|
+
totalUnresolvable++;
|
|
71431
|
+
logger.error(` ✗ ${report.stackName}/${outcome.logicalId} (${outcome.resourceType}): refused to revert ${maskSecretsInText(intrinsicObjectPaths.join(", "), secrets)} — the recorded baseline holds an unresolved CloudFormation intrinsic there (e.g. Fn::Join, Ref), which cdkd cannot resolve outside a deploy; writing it would set the live property to the raw intrinsic object instead of its value. Run 'cdkd deploy' for this stack — the deploy resolves the template and records a resolvable baseline — then re-run the revert if drift remains.`);
|
|
71432
|
+
return;
|
|
71433
|
+
}
|
|
71434
|
+
}
|
|
71435
|
+
if (unrevertablePaths.length > 0) logger.warn(` ! ${report.stackName}/${outcome.logicalId} (${outcome.resourceType}): cannot revert ${maskSecretsInText(unrevertablePaths.join(", "), secrets)} — cdkd cannot name the property, so it is left as AWS has it. ROTATE the secret that leaked into the property name.`);
|
|
71436
|
+
if (unresolvedTokens.size > 0) logger.warn(` ! [revert] ${report.stackName}/${outcome.logicalId} (${outcome.resourceType}): cdkd cannot resolve ${maskSecretsInText([...unresolvedTokens].join(", "), secrets)} — a property whose WHOLE value is one of these tokens is left UNCHANGED by this revert when cdkd can pair its position against AWS's report (a list element by an identity field, or by its list's own unchanged literal values). Where it cannot pair, and where a token is EMBEDDED in a longer string, the token is written literally, exactly as 'cdkd deploy' does, so a resolved value AWS holds there is overwritten.`);
|
|
70995
71437
|
let newProperties;
|
|
70996
71438
|
try {
|
|
70997
71439
|
const overlaid = buildRevertNewProperties(outcome.changes, desiredProperties, outcome.awsProperties, { preserveUntemplated: stateResource.observedProperties === void 0 });
|
|
70998
|
-
const maskPreserved = preserveLiveValuesAtMaskedLeaves(unresolvedTokens.size > 0 ? preserveLiveValuesAtUnresolvedTokens(overlaid, outcome.awsProperties) : overlaid, outcome.awsProperties, secrets);
|
|
71440
|
+
const maskPreserved = preserveLiveValuesAtMaskedLeaves(unresolvedTokens.size > 0 ? preserveLiveValuesAtUnresolvedTokens(overlaid, outcome.awsProperties) : overlaid, outcome.awsProperties, secrets, overlaid);
|
|
70999
71441
|
if (maskPreserved.unpreservablePaths.length > 0) {
|
|
71000
71442
|
totalUnresolvable++;
|
|
71001
|
-
logger.error(` ✗ ${report.stackName}/${outcome.logicalId} (${outcome.resourceType}): refused to revert ${maskPreserved.unpreservablePaths.join(", ")} — the recorded baseline holds only the redaction mask there
|
|
71443
|
+
logger.error(` ✗ ${report.stackName}/${outcome.logicalId} (${outcome.resourceType}): refused to revert ${maskSecretsInText(maskPreserved.unpreservablePaths.join(", "), secrets)} — the recorded baseline holds only the redaction mask there, and AWS reports nothing to preserve, so cdkd has no value it may write. Two causes leave such a mask, and the record does not say which: a NoEcho custom-resource value (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), or a readback position cdkd could not certify when the baseline was captured (deploy a change to this resource, so the baseline is recaptured against the template). An ordinary no-change re-deploy clears neither.`);
|
|
71002
71444
|
return;
|
|
71003
71445
|
}
|
|
71004
71446
|
newProperties = maskPreserved.properties;
|
|
@@ -71095,7 +71537,7 @@ async function runRevert(reports, providerRegistry, stateBackend, stateConfig, a
|
|
|
71095
71537
|
if (totalFailed > 0) summaryParts.push(`${totalFailed} failed`);
|
|
71096
71538
|
logger.info(`\nRevert summary: ${summaryParts.join(", ")}.`);
|
|
71097
71539
|
if (totalUnsupported > 0) logger.warn(`${totalUnsupported} resource(s) cannot be reverted in place — re-deploy the stack with cdkd deploy --replace, or destroy + redeploy to push the cdkd-state values back into AWS.`);
|
|
71098
|
-
if (totalFailed > 0 || totalUnsupported > 0 || totalUnresolvable > 0) throw new PartialFailureError(`Revert completed with ${totalFailed + totalUnsupported + totalUnresolvable} resource error(s) (${totalFailed} AWS update failure(s), ${totalUnsupported} update-not-supported, ${totalUnresolvable}
|
|
71540
|
+
if (totalFailed > 0 || totalUnsupported > 0 || totalUnresolvable > 0) throw new PartialFailureError(`Revert completed with ${totalFailed + totalUnsupported + totalUnresolvable} resource error(s) (${totalFailed} AWS update failure(s), ${totalUnsupported} update-not-supported, ${totalUnresolvable} refused or unresolvable — those never reached provider.update; each per-resource message above names its cause and remedy, e.g. missing secretsmanager:GetSecretValue / ssm:GetParameter grants for an unresolvable reference). Re-run 'cdkd drift <stack>' to see the remaining drift, then 'cdkd drift <stack> --revert' to retry.`);
|
|
71099
71541
|
}
|
|
71100
71542
|
function humanTextSink(json) {
|
|
71101
71543
|
return json ? {
|
|
@@ -75288,7 +75730,7 @@ async function runPerStackImportLoop(args) {
|
|
|
75288
75730
|
const uploadCleanups = [];
|
|
75289
75731
|
const importedStacks = [];
|
|
75290
75732
|
const paramResolver = new IntrinsicFunctionResolver(rootRegion);
|
|
75291
|
-
const { paramsByCdkdName, intrinsicSkippedByCdkdName: sessionIntrinsicSkipped } = await buildResolvedParametersPerStack({
|
|
75733
|
+
const { paramsByCdkdName, intrinsicSkippedByCdkdName: sessionIntrinsicSkipped } = await withSharedDrainBudget(() => buildResolvedParametersPerStack({
|
|
75292
75734
|
rootStackName,
|
|
75293
75735
|
rootParameters,
|
|
75294
75736
|
perStackNodes: perStackPlans.map((p) => ({
|
|
@@ -75299,7 +75741,7 @@ async function runPerStackImportLoop(args) {
|
|
|
75299
75741
|
tree,
|
|
75300
75742
|
resolver: paramResolver,
|
|
75301
75743
|
stateBackend: deps.stateBackend
|
|
75302
|
-
});
|
|
75744
|
+
}));
|
|
75303
75745
|
for (const [cdkdName, skipped] of sessionIntrinsicSkipped) logger.warn(` Child '${cdkdName}': could not resolve intrinsic-valued Parameter(s) ${skipped.join(", ")} at IMPORT time. The child template's Parameter Default values must cover these — otherwise CFn will reject the IMPORT.`);
|
|
75304
75746
|
try {
|
|
75305
75747
|
for (let i = 0; i < perStackPlans.length; i++) {
|
|
@@ -76973,7 +77415,7 @@ async function refreshObservedForStack(stackName, region, stateBackend, lockMana
|
|
|
76973
77415
|
unsupported++;
|
|
76974
77416
|
return;
|
|
76975
77417
|
}
|
|
76976
|
-
resource.observedProperties = redactSecretsForState(observed, NO_RECORDED_SECRETS$1, resource.properties ?? {},
|
|
77418
|
+
resource.observedProperties = redactSecretsForState(observed, NO_RECORDED_SECRETS$1, resource.properties ?? {}, STATE_SOURCED_BASELINE_RULES);
|
|
76977
77419
|
refreshed++;
|
|
76978
77420
|
} catch (err) {
|
|
76979
77421
|
failed++;
|
|
@@ -80263,7 +80705,7 @@ async function importCommand(stackArg, options) {
|
|
|
80263
80705
|
}
|
|
80264
80706
|
}
|
|
80265
80707
|
const stackState = buildStackState(stackInfo.stackName, targetRegion, rows, templateParser, stateTemplate, existingState, selectiveMode);
|
|
80266
|
-
await captureObservedForImportedResources(stackState, providerRegistry, logger, await resolveImportedProperties(stackState, stateTemplate, targetRegion, stateBackend, logger));
|
|
80708
|
+
await captureObservedForImportedResources(stackState, providerRegistry, logger, await withSharedDrainBudget(() => resolveImportedProperties(stackState, stateTemplate, targetRegion, stateBackend, logger)));
|
|
80267
80709
|
const saveOptions = {};
|
|
80268
80710
|
if (existingEtag) saveOptions.expectedEtag = existingEtag;
|
|
80269
80711
|
if (migrationPending) saveOptions.migrateLegacy = true;
|
|
@@ -81179,7 +81621,7 @@ async function importNestedStackChildrenRecursive(args) {
|
|
|
81179
81621
|
childStackState.parentStack = parentStackName;
|
|
81180
81622
|
childStackState.parentLogicalId = childLogicalId;
|
|
81181
81623
|
childStackState.parentRegion = parentRegion;
|
|
81182
|
-
await captureObservedForImportedResources(childStackState, providerRegistry, logger, await resolveImportedProperties(childStackState, childStateTemplate, childRegion, stateBackend, logger));
|
|
81624
|
+
await captureObservedForImportedResources(childStackState, providerRegistry, logger, await withSharedDrainBudget(() => resolveImportedProperties(childStackState, childStateTemplate, childRegion, stateBackend, logger)));
|
|
81183
81625
|
await stateBackend.saveState(childStackName, childRegion, childStackState);
|
|
81184
81626
|
logger.info(`✓ Nested stack state written: ${childStackName} (${childRegion}) — ${rows.filter((r) => r.outcome === "imported").length} resource(s) imported.`);
|
|
81185
81627
|
if (childTreeNode.nested.size > 0) await importNestedStackChildrenRecursive({
|
|
@@ -90410,4 +90852,4 @@ function buildProgram() {
|
|
|
90410
90852
|
|
|
90411
90853
|
//#endregion
|
|
90412
90854
|
export { buildProgram };
|
|
90413
|
-
//# sourceMappingURL=program-
|
|
90855
|
+
//# sourceMappingURL=program-CMhpftan.js.map
|