@go-to-k/cdkd 0.290.14 → 0.290.16
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-BAYjqZuM.js → asg-provider-CvkcE8rH.js} +22 -5
- package/dist/asg-provider-CvkcE8rH.js.map +1 -0
- package/dist/cli.js +2 -2
- package/dist/{deploy-engine-v7h1H_Ac.js → deploy-engine-B91NMqm1.js} +54 -20
- package/dist/deploy-engine-B91NMqm1.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-DD0X9qLM.js → program-U03H43s7.js} +508 -74
- package/dist/{program-DD0X9qLM.js.map → program-U03H43s7.js.map} +1 -1
- package/dist/{version-CqnAWzcc.js → version-1FTpsWNc.js} +2 -2
- package/dist/{version-CqnAWzcc.js.map → version-1FTpsWNc.js.map} +1 -1
- package/package.json +1 -1
- package/dist/asg-provider-BAYjqZuM.js.map +0 -1
- package/dist/deploy-engine-v7h1H_Ac.js.map +0 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { $ as secretBearing, $n as BOOTSTRAP_MARKER_PREFIX, $r as getAwsClients, $t as s3BucketRegionalDomainName, A as extractDeploymentEventError, Ai as markNonRetryable, An as buildForceUnlockCommand, Ar as resolveAutoAssetStorage, At as parameterTypeMayLoseSecretIdentity, B as toCfnInteger, Bn as importableOutputs, Br as MIGRATE_TMP_PREFIX, Bt as configIntegerRefusal, C as buildFinalSnapshotIdentifier, Cn as recordMaskOnlyValue, Cr as AssetManifestLoader, Ct as IntrinsicFunctionResolver, D as refusesFinalSnapshot, Di as isRetryableTransientError, Dn as LockManager, Dr as getDefaultStateBucketName, Dt as getAccountInfo, E as isFinalSnapshotError, Ei as isMarkedNonRetryable, En as scrubResourceRecord, Er as synthesisStatusMessage, Et as coerceParameterTypedValue, F as isStatefulRecreateTargetSync, Fn as CUSTOM_RESOURCE_RESPONSE_PREFIX, Fr as resolveUseCdkBootstrapAssets, Ft as resolveExplicitPhysicalId, G as gray, Gn as AssetPublisher, Gr as PARTITION_TABLE, Gt as requireConfigObject, H as formatResourceLine, Hn as orphansAfterRollback, Hr as uploadCfnTemplate, Ht as readConfigString, I as renderStatefulReason, In as DEFAULT_STATE_PREFIX, Ir as stateBucketExistenceConfirmed, It as assertRegionMatch, J as yellow, Jn as buildAssetRedirectMap, Jr as AssemblyReader, Jt as producerRegionsFromState, K as green, Kn as stringifyValue, Kr as canonicalizeRegion, Kt as requireConfigString, L as WARM_THROUGHPUT_MEMBERS, Ln as exportNamesCarriedFrom, Lr as warnDeprecatedNoPrefixCliFlag, Lt as coerceCfnBoolean, M as planOrphanAdoption, Mi as retryClassificationText, Mn as forceQuitRecoveryClause, Mr as resolveSkipPrefix, Mt as isSettledInstanceState, N as MULTI_REGION_RECREATE_BLOCKED_TYPES, Nn as formatLockExpiry, Nr as resolveStateBucketWithDefault, Nt as WAFv2WebACLProvider, O as unsupportedFinalSnapshotError, Oi as isThrottlingError, On as S3StateBackend, Or as getLegacyStateBucketName, Ot as isImpossibleEmptyStoredAttribute, P as isStatefulRecreateTargetForReplace, Pn as shellQuote, Pr as resolveStateBucketWithDefaultAndSource, Pt as normalizeAwsTagsToCfn, Q as isExportAliasCollision, Qn as AssetModeResolver, Qr as AwsClients, Qt as s3BucketDualStackDomainName, R as coerceWarmThroughput$1, Rn as hasReadableExportSet, Rr as CFN_TEMPLATE_BODY_LIMIT, Rt as coerceCfnInteger, S as PRE_DELETE_SNAPSHOT_TYPES, Sn as maskSecretsInText, Sr as stripControlChars, St as isTerminationProtectionPropagationError, T as createPreDeleteFinalSnapshot, Ti as withErrorHandling, Tn as redactSecretsForState, Tr as Synthesizer, Tt as cfnRefValueFromPhysicalId, U as bold, Un as orphansCarriedFrom, Ur as CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION, Ut as replayWarn, V as toFiniteNumber, Vn as isReadableBag, Vr as findLargeInlineResources, Vt as configStringRefusal, W as cyan, Wn as shouldRetainResource, Wr as expectedOwnerParam, Wt as requireConfigArray, X as collectPublishedOutputNames, Xn as loadPublishableAssetManifest, Xt as s3BucketArn, Y as collectDeclaredOutputNames, Yn as createAssetRedirectResolver, Yr as processStackMessages, Yt as withSharedDrainBudget, Z as exportAliasCollisionScrubWarning, Zn as rewriteTemplateAssetReferences, Zr as resolveBucketRegion, Zt as s3BucketDomainName, _ as isSecretBearingReferenceString, _i as ResourceUpdateNotSupportedError, _n as errorCauseChain, _r as partitionSensitiveEnv, _t as slowCcOperationTimeoutMs, a as DeploymentEventsStore, ai as CrossAccountSecretRefusalError, an as findSilentDropProperties, ar as readBootstrapMarkerBody, at as clearOnUpdateRemoval, b as bindingSkippedOutputs, bn as markSameGenerationBag, br as runDockerStreaming, bt as deleteSkipReason, c as replayFailedOperations, ci as DynamicReferenceRegionAmbiguousError, cn as DagBuilder, cr as buildDenyExternalAccessPolicy, ct as createMaskedRetryLogger, d as updatePartialReason, di as LocalStartServiceError, dn as STATE_SOURCED_CROSS_GENERATION_RULES, dr as describeDockerCapturedOutput, dt as beginCommandInterruptScope, ei as resetAwsClients, en as s3BucketWebsiteUrl, er as assertAssetBucketRegion, et as secretBearingStateKeyWarning, f as withResourceDeadline, fn as STATE_SOURCED_READBACK_RULES, fr as describeDockerExecFailure, ft as endCommandInterruptScope, g as bagHoldsSecretExpression, gi as ResourceTimeoutError, gn as dynamicReferenceTokens, gr as getDockerCmd, gt as CloudControlProvider, h as computeImplicitDeleteEdges, hi as ProvisioningError, hn as createSecretMasker, hr as formatDockerLoginError, ht as startInterruptWatch, i as DeploymentEventsReader, in as findActionableSilentDrops, ir as parseBootstrapMarker, it as collectInlinePolicyNamesManagedBySiblings, j as makeSiblingClaimReader, ji as markRedactedCause, jn as buildLockContentionMessage, jr as resolveCaptureObservedState, jt as refStateLookupFromResource, k as makeCanonicalizePropertiesFn, ki as isTransientServerError, kn as rebuildClientForBucketRegion, kr as resolveApp, kt as isUnboundTemplateParameter, l as replayRollback, li as IntrinsicResolutionRefusalError, ln as TemplateParser, lr as describeAwsFailure, lt as maskDeep, m as IMPLICIT_DELETE_DEPENDENCIES, mi as PartialFailureError, mn as carriesSecretMask, mr as dockerSpawnEnvWithSensitive, mt as isInterruptedWaitError, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as DiffCalculator, nr as getBootstrapMarkerKey, nt as getCurrentResourceSecrets, o as planFailedOps, on as describeTypeWithThrottleRetry, or as validateAssetBucketName, ot as ProviderRegistry, p as maskingRetryLogger, pi as NestedStackChildDirectDestroyError, pn as TEMPLATE_SOURCED_RULES, pr as describeDockerFailure, pt as interruptWatchListenerCount, q as red, qn as WorkGraph, qr as derivePartitionAndUrlSuffix, qt as classifyReplaySecretRegion, r as DeployEngine, ri as CdkdError, rn as INTRINSIC_KEYS, rr as isCrossRegionRedirect, rt as IAMRoleProvider, s as planRollback, si as DeployCancelledError, sn as withRetry, sr as validateContainerRepoName, st as wouldReturnToSdkProvider, t as DEFAULT_RESOURCE_TIMEOUT_MS, ti as setAwsClients, tn as applyRoleArnIfSet, tr as ensureAssetStorage, tt as secretSafeKeyDisplay, u as updatePartialMessage, ui as LocalInvokeBuildError$1, un as STATE_SOURCED_BASELINE_RULES, ur as buildDockerImage, ut as maskerOrIdentity, v as keptWholeReasonText, vi as StackHasActiveImportsError, vn as identityKeyFor, vr as redactDockerArgvValues, vt as UNSPECIFIED_SKIP_REASON, w as ccRoutedFinalSnapshotError, wi as normalizeAwsError, wn as recoverMaskedOutput, wr as getDockerImageBySourceHash, wt as carriesDynamicReference, x as ATOMIC_FINAL_SNAPSHOT_TYPES, xn as maskSecretsInError, xr as escapeRegExp, xt as disableInstanceApiTermination, y as mergeNoChangeOutputs, yi as StackTerminationProtectionError, yn as isSingleDynamicReferenceToken, yr as runDockerForeground, yt as deleteIndeterminateGuards, z as isWarmThroughputDecrease, zn as importableOutputKeys, zr as CFN_TEMPLATE_URL_LIMIT, zt as configBooleanRefusal } from "./deploy-engine-
|
|
1
|
+
import { $ as secretBearing, $n as BOOTSTRAP_MARKER_PREFIX, $r as getAwsClients, $t as s3BucketRegionalDomainName, A as extractDeploymentEventError, Ai as markNonRetryable, An as buildForceUnlockCommand, Ar as resolveAutoAssetStorage, At as parameterTypeMayLoseSecretIdentity, B as toCfnInteger, Bn as importableOutputs, Br as MIGRATE_TMP_PREFIX, Bt as configIntegerRefusal, C as buildFinalSnapshotIdentifier, Cn as recordMaskOnlyValue, Cr as AssetManifestLoader, Ct as IntrinsicFunctionResolver, D as refusesFinalSnapshot, Di as isRetryableTransientError, Dn as LockManager, Dr as getDefaultStateBucketName, Dt as getAccountInfo, E as isFinalSnapshotError, Ei as isMarkedNonRetryable, En as scrubResourceRecord, Er as synthesisStatusMessage, Et as coerceParameterTypedValue, F as isStatefulRecreateTargetSync, Fn as CUSTOM_RESOURCE_RESPONSE_PREFIX, Fr as resolveUseCdkBootstrapAssets, Ft as resolveExplicitPhysicalId, G as gray, Gn as AssetPublisher, Gr as PARTITION_TABLE, Gt as requireConfigObject, H as formatResourceLine, Hn as orphansAfterRollback, Hr as uploadCfnTemplate, Ht as readConfigString, I as renderStatefulReason, In as DEFAULT_STATE_PREFIX, Ir as stateBucketExistenceConfirmed, It as assertRegionMatch, J as yellow, Jn as buildAssetRedirectMap, Jr as AssemblyReader, Jt as producerRegionsFromState, K as green, Kn as stringifyValue, Kr as canonicalizeRegion, Kt as requireConfigString, L as WARM_THROUGHPUT_MEMBERS, Ln as exportNamesCarriedFrom, Lr as warnDeprecatedNoPrefixCliFlag, Lt as coerceCfnBoolean, M as planOrphanAdoption, Mi as retryClassificationText, Mn as forceQuitRecoveryClause, Mr as resolveSkipPrefix, Mt as isSettledInstanceState, N as MULTI_REGION_RECREATE_BLOCKED_TYPES, Nn as formatLockExpiry, Nr as resolveStateBucketWithDefault, Nt as WAFv2WebACLProvider, O as unsupportedFinalSnapshotError, Oi as isThrottlingError, On as S3StateBackend, Or as getLegacyStateBucketName, Ot as isImpossibleEmptyStoredAttribute, P as isStatefulRecreateTargetForReplace, Pn as shellQuote, Pr as resolveStateBucketWithDefaultAndSource, Pt as normalizeAwsTagsToCfn, Q as isExportAliasCollision, Qn as AssetModeResolver, Qr as AwsClients, Qt as s3BucketDualStackDomainName, R as coerceWarmThroughput$1, Rn as hasReadableExportSet, Rr as CFN_TEMPLATE_BODY_LIMIT, Rt as coerceCfnInteger, S as PRE_DELETE_SNAPSHOT_TYPES, Sn as maskSecretsInText, Sr as stripControlChars, St as isTerminationProtectionPropagationError, T as createPreDeleteFinalSnapshot, Ti as withErrorHandling, Tn as redactSecretsForState, Tr as Synthesizer, Tt as cfnRefValueFromPhysicalId, U as bold, Un as orphansCarriedFrom, Ur as CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION, Ut as replayWarn, V as toFiniteNumber, Vn as isReadableBag, Vr as findLargeInlineResources, Vt as configStringRefusal, W as cyan, Wn as shouldRetainResource, Wr as expectedOwnerParam, Wt as requireConfigArray, X as collectPublishedOutputNames, Xn as loadPublishableAssetManifest, Xt as s3BucketArn, Y as collectDeclaredOutputNames, Yn as createAssetRedirectResolver, Yr as processStackMessages, Yt as withSharedDrainBudget, Z as exportAliasCollisionScrubWarning, Zn as rewriteTemplateAssetReferences, Zr as resolveBucketRegion, Zt as s3BucketDomainName, _ as isSecretBearingReferenceString, _i as ResourceUpdateNotSupportedError, _n as errorCauseChain, _r as partitionSensitiveEnv, _t as slowCcOperationTimeoutMs, a as DeploymentEventsStore, ai as CrossAccountSecretRefusalError, an as findSilentDropProperties, ar as readBootstrapMarkerBody, at as clearOnUpdateRemoval, b as bindingSkippedOutputs, bn as markSameGenerationBag, br as runDockerStreaming, bt as deleteSkipReason, c as replayFailedOperations, ci as DynamicReferenceRegionAmbiguousError, cn as DagBuilder, cr as buildDenyExternalAccessPolicy, ct as createMaskedRetryLogger, d as updatePartialReason, di as LocalStartServiceError, dn as STATE_SOURCED_CROSS_GENERATION_RULES, dr as describeDockerCapturedOutput, dt as beginCommandInterruptScope, ei as resetAwsClients, en as s3BucketWebsiteUrl, er as assertAssetBucketRegion, et as secretBearingStateKeyWarning, f as withResourceDeadline, fn as STATE_SOURCED_READBACK_RULES, fr as describeDockerExecFailure, ft as endCommandInterruptScope, g as bagHoldsSecretExpression, gi as ResourceTimeoutError, gn as dynamicReferenceTokens, gr as getDockerCmd, gt as CloudControlProvider, h as computeImplicitDeleteEdges, hi as ProvisioningError, hn as createSecretMasker, hr as formatDockerLoginError, ht as startInterruptWatch, i as DeploymentEventsReader, in as findActionableSilentDrops, ir as parseBootstrapMarker, it as collectInlinePolicyNamesManagedBySiblings, j as makeSiblingClaimReader, ji as markRedactedCause, jn as buildLockContentionMessage, jr as resolveCaptureObservedState, jt as refStateLookupFromResource, k as makeCanonicalizePropertiesFn, ki as isTransientServerError, kn as rebuildClientForBucketRegion, kr as resolveApp, kt as isUnboundTemplateParameter, l as replayRollback, li as IntrinsicResolutionRefusalError, ln as TemplateParser, lr as describeAwsFailure, lt as maskDeep, m as IMPLICIT_DELETE_DEPENDENCIES, mi as PartialFailureError, mn as carriesSecretMask, mr as dockerSpawnEnvWithSensitive, mt as isInterruptedWaitError, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as DiffCalculator, nr as getBootstrapMarkerKey, nt as getCurrentResourceSecrets, o as planFailedOps, on as describeTypeWithThrottleRetry, or as validateAssetBucketName, ot as ProviderRegistry, p as maskingRetryLogger, pi as NestedStackChildDirectDestroyError, pn as TEMPLATE_SOURCED_RULES, pr as describeDockerFailure, pt as interruptWatchListenerCount, q as red, qn as WorkGraph, qr as derivePartitionAndUrlSuffix, qt as classifyReplaySecretRegion, r as DeployEngine, ri as CdkdError, rn as INTRINSIC_KEYS, rr as isCrossRegionRedirect, rt as IAMRoleProvider, s as planRollback, si as DeployCancelledError, sn as withRetry, sr as validateContainerRepoName, st as wouldReturnToSdkProvider, t as DEFAULT_RESOURCE_TIMEOUT_MS, ti as setAwsClients, tn as applyRoleArnIfSet, tr as ensureAssetStorage, tt as secretSafeKeyDisplay, u as updatePartialMessage, ui as LocalInvokeBuildError$1, un as STATE_SOURCED_BASELINE_RULES, ur as buildDockerImage, ut as maskerOrIdentity, v as keptWholeReasonText, vi as StackHasActiveImportsError, vn as identityKeyFor, vr as redactDockerArgvValues, vt as UNSPECIFIED_SKIP_REASON, w as ccRoutedFinalSnapshotError, wi as normalizeAwsError, wn as recoverMaskedOutput, wr as getDockerImageBySourceHash, wt as carriesDynamicReference, x as ATOMIC_FINAL_SNAPSHOT_TYPES, xn as maskSecretsInError, xr as escapeRegExp, xt as disableInstanceApiTermination, y as mergeNoChangeOutputs, yi as StackTerminationProtectionError, yn as isSingleDynamicReferenceToken, yr as runDockerForeground, yt as deleteIndeterminateGuards, z as isWarmThroughputDecrease, zn as importableOutputKeys, zr as CFN_TEMPLATE_URL_LIMIT, zt as configBooleanRefusal } from "./deploy-engine-B91NMqm1.js";
|
|
2
2
|
import { C as displaySafe, S as displayIdent, _ as withSkipPrefix, b as STACK_REF_MAX_CODE_POINTS, c as PATTERN_B_NAME_OPTIONS, f as explicitNamePropertyFor, h as getCurrentStackName, i as reserveStdoutForPayload, l as PATTERN_B_NAME_PROPERTIES, m as generateResourceNameWithFallback, n as getLogger, o as runStackBuffered, p as generateResourceName, s as getLiveRenderer, u as PATTERN_B_RESOURCE_TYPES, v as withStackName, w as truncateCodePoints, x as UNRENDERABLE } from "./logger-C17H06QA.js";
|
|
3
3
|
import { t as awsClientDefaults } from "./aws-client-defaults-D-iYiIJ6.js";
|
|
4
|
-
import { t as getCdkdVersion } from "./version-
|
|
5
|
-
import { a as renderDisableCommand, i as protectedReplacementAdvice, r as UNNAMEABLE_ID_CLAUSE, t as ASGProvider } from "./asg-provider-
|
|
4
|
+
import { t as getCdkdVersion } from "./version-1FTpsWNc.js";
|
|
5
|
+
import { a as renderDisableCommand, i as protectedReplacementAdvice, r as UNNAMEABLE_ID_CLAUSE, t as ASGProvider } from "./asg-provider-CvkcE8rH.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";
|
|
@@ -7079,6 +7079,46 @@ function isTruthyCfnBoolean(value) {
|
|
|
7079
7079
|
//#endregion
|
|
7080
7080
|
//#region src/provisioning/providers/s3-bucket-provider.ts
|
|
7081
7081
|
/**
|
|
7082
|
+
* The "remove this bucket by hand" clause both partial-create cleanup arms end
|
|
7083
|
+
* with, rendered through the shared sanitize / shell-quote / SUPPRESS
|
|
7084
|
+
* (`renderDisableCommand`, the issue
|
|
7085
|
+
* [#2669](https://github.com/go-to-k/cdkd/issues/2669) shape; adopted here by
|
|
7086
|
+
* issue [#3136](https://github.com/go-to-k/cdkd/issues/3136)).
|
|
7087
|
+
*
|
|
7088
|
+
* `bucketName` is TEMPLATE-chosen — `Properties.BucketName`, or the name cdkd
|
|
7089
|
+
* generated for the resource — and until now both arms hand-quoted it as
|
|
7090
|
+
* `--bucket '<name>'`, so a `'` in the value broke out of the quoting and a
|
|
7091
|
+
* control byte forged lines on the operator's terminal. (A WARN, so that is
|
|
7092
|
+
* its whole reach — the recorder persists a thrown `error.message`, not this.)
|
|
7093
|
+
* When sanitizing CHANGES the name the whole command is
|
|
7094
|
+
* SUPPRESSED rather than shown with the sanitized value, because such a command
|
|
7095
|
+
* would delete a DIFFERENT bucket — the wrong-target harm the shared renderer
|
|
7096
|
+
* exists to prevent, and the reason emitting no command is the honest answer
|
|
7097
|
+
* rather than a fallback.
|
|
7098
|
+
*
|
|
7099
|
+
* ONE clause for both arms so the two cannot come to render the same value
|
|
7100
|
+
* differently. The prose `(${bucketName})` each arm carries BESIDE it is still
|
|
7101
|
+
* interpolated as-is: this clause takes only the pasteable half, and
|
|
7102
|
+
* display-sanitizing the PROSE of provider warnings is tracked as issue
|
|
7103
|
+
* [#3269](https://github.com/go-to-k/cdkd/issues/3269) (#3136 covers the
|
|
7104
|
+
* COMMAND half across providers, not prose).
|
|
7105
|
+
*
|
|
7106
|
+
* `maskSecrets` is REQUIRED rather than optional, and both call sites build it
|
|
7107
|
+
* from `context?.maskSecrets` (security review of #3136, which found these two
|
|
7108
|
+
* arms reaching no masker at all). It does two things: the arms now route
|
|
7109
|
+
* their whole warning through it, and it makes `renderDisableCommand` SUPPRESS
|
|
7110
|
+
* the command for a secret-bearing bucket name — which a message-level mask
|
|
7111
|
+
* could not catch once `shellQuote` has escaped an inner quote.
|
|
7112
|
+
*/
|
|
7113
|
+
function manualBucketDeletionClause(bucketName, maskSecrets) {
|
|
7114
|
+
const command = renderDisableCommand({
|
|
7115
|
+
before: "aws s3api delete-bucket --bucket",
|
|
7116
|
+
identifier: bucketName,
|
|
7117
|
+
maskSecrets
|
|
7118
|
+
});
|
|
7119
|
+
return command ? `Manual deletion may be required before the next deploy: ${command}` : "Manual deletion may be required before the next deploy, via the console: the bucket name cannot be reproduced safely on a command line, so any command shown here would act on a different bucket.";
|
|
7120
|
+
}
|
|
7121
|
+
/**
|
|
7082
7122
|
* A plain (non-array) object. `typeof x === 'object'` alone accepts arrays and
|
|
7083
7123
|
* `null`; a `Transition: []` or an unresolved intrinsic pushed through as a
|
|
7084
7124
|
* single transition entry makes S3 answer `MalformedXML` for the WHOLE
|
|
@@ -10440,11 +10480,13 @@ var S3BucketProvider = class S3BucketProvider {
|
|
|
10440
10480
|
this.logger.debug(`Cleaned up partially-created S3 bucket ${logicalId} (${bucketName}) after wiring failure`);
|
|
10441
10481
|
} catch (cleanupError) {
|
|
10442
10482
|
this.logger.debug(`DeleteBucket cleanup failed for S3 bucket ${logicalId} (${bucketName}): ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`);
|
|
10443
|
-
|
|
10483
|
+
const maskSecrets = maskerOrIdentity(context?.maskSecrets);
|
|
10484
|
+
this.logger.warn(maskSecrets(`Failed to clean up partially-created S3 bucket ${logicalId} (${bucketName}) (${cleanupError instanceof Error ? cleanupError.name : typeof cleanupError}). Re-run with --verbose for AWS's own message. ` + manualBucketDeletionClause(bucketName, maskSecrets)));
|
|
10444
10485
|
}
|
|
10445
10486
|
else if (preflight.kind === "indeterminate") {
|
|
10446
10487
|
this.logger.debug(`GetBucketLocation failed for S3 bucket ${bucketName} (${logicalId}) during create: ${preflight.reason}`);
|
|
10447
|
-
|
|
10488
|
+
const maskSecrets = maskerOrIdentity(context?.maskSecrets);
|
|
10489
|
+
this.logger.warn(maskSecrets(`Not cleaning up S3 bucket ${logicalId} (${bucketName}) after a wiring failure: this deploy could not confirm whether it created the bucket (region probe failed: ${preflight.errorName}), and in us-east-1 a successful CreateBucket does not prove it. Re-run with --verbose for AWS's own message. If cdkd created it, the bucket is an orphan. ` + manualBucketDeletionClause(bucketName, maskSecrets)));
|
|
10448
10490
|
}
|
|
10449
10491
|
throw innerError;
|
|
10450
10492
|
}
|
|
@@ -17881,16 +17923,39 @@ function mapSSESpecification(raw) {
|
|
|
17881
17923
|
* the index to the same warn-and-omit path an absent block takes, letting AWS
|
|
17882
17924
|
* name it — the CFn-handler outcome.
|
|
17883
17925
|
*
|
|
17884
|
-
* CFn is stringly typed, so a numeric string is accepted and coerced
|
|
17885
|
-
*
|
|
17926
|
+
* CFn is stringly typed, so a numeric string is accepted and coerced — through
|
|
17927
|
+
* {@link coerceCfnInteger}, CloudFormation's MEASURED DynamoDB Integer grammar
|
|
17928
|
+
* (issue [#3147](https://github.com/go-to-k/cdkd/issues/3147); the table is on
|
|
17929
|
+
* `toCfnInteger` in `../dynamodb-warm-throughput.ts`): an optional sign and
|
|
17930
|
+
* decimal digits, with NO trim, so `" 7 "` / `"7 "` / `""` / `"0x9"` / `"1e1"`
|
|
17931
|
+
* / `"6.5"` are every one `undefined` here because CloudFormation refuses the
|
|
17932
|
+
* template that spells them. The pre-#3147 reader was `Number()`, which
|
|
17933
|
+
* forwarded `7` / `9` / `10` for the first four and deployed an index at a
|
|
17934
|
+
* capacity CloudFormation would not have accepted. `NaN` / `Infinity` / a
|
|
17935
|
+
* non-integer number / objects / unresolved intrinsics were never usable and
|
|
17936
|
+
* still are not.
|
|
17886
17937
|
*/
|
|
17887
17938
|
function readCapacityNumber(block, member) {
|
|
17888
|
-
if (
|
|
17889
|
-
|
|
17890
|
-
|
|
17891
|
-
|
|
17892
|
-
|
|
17893
|
-
|
|
17939
|
+
if (!isPlainCapacityBlock(block)) return void 0;
|
|
17940
|
+
return coerceCfnInteger(block[member]);
|
|
17941
|
+
}
|
|
17942
|
+
/**
|
|
17943
|
+
* Is this a `ProvisionedThroughput` block whose MEMBERS can be read at all?
|
|
17944
|
+
*
|
|
17945
|
+
* One spelling, shared by {@link readCapacityNumber} — where a `false` answer
|
|
17946
|
+
* means "no usable member" — and by
|
|
17947
|
+
* {@link DynamoDBTableProvider.coerceIndexCapacityBlock}, where it means
|
|
17948
|
+
* "forward this VERBATIM and let AWS name the shape" (issue
|
|
17949
|
+
* [#3255](https://github.com/go-to-k/cdkd/issues/3255)). The two conclusions
|
|
17950
|
+
* differ; the shape test must not, or the create forwarder could rewrite a
|
|
17951
|
+
* block the flip's reader refuses to read (or vice versa).
|
|
17952
|
+
*
|
|
17953
|
+
* An ARRAY is excluded deliberately: `[]` indexes to `undefined` for both
|
|
17954
|
+
* members, so treating it as a block would DROP nothing and report nothing
|
|
17955
|
+
* while `CreateTable` rejects the list by name anyway.
|
|
17956
|
+
*/
|
|
17957
|
+
function isPlainCapacityBlock(value) {
|
|
17958
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
17894
17959
|
}
|
|
17895
17960
|
/**
|
|
17896
17961
|
* Whether a desired `GlobalSecondaryIndexes[]` entry declares BOTH capacity
|
|
@@ -17901,19 +17966,51 @@ function readCapacityNumber(block, member) {
|
|
|
17901
17966
|
* BOTH members are required, exactly as in the flip block: an entry declaring
|
|
17902
17967
|
* only one is treated like an absent one there, so accepting it here would let
|
|
17903
17968
|
* the pre-flip delete run ahead of a flip AWS still rejects.
|
|
17969
|
+
*
|
|
17970
|
+
* A MIRROR, so it calls {@link readCapacityNumber} — the function the flip's
|
|
17971
|
+
* per-index forwarder calls — rather than restating the rule. That is what kept
|
|
17972
|
+
* it correct across issue [#3147](https://github.com/go-to-k/cdkd/issues/3147):
|
|
17973
|
+
* the reader moved to CloudFormation's Integer grammar and this predicate moved
|
|
17974
|
+
* with it, so a padded `" 7 "` is "no usable declared capacity" here exactly
|
|
17975
|
+
* because the forwarder drops it there.
|
|
17904
17976
|
*/
|
|
17905
17977
|
function hasUsableDeclaredCapacity(entry) {
|
|
17906
17978
|
const declared = entry?.["ProvisionedThroughput"];
|
|
17907
17979
|
return readCapacityNumber(declared, "ReadCapacityUnits") !== void 0 && readCapacityNumber(declared, "WriteCapacityUnits") !== void 0;
|
|
17908
17980
|
}
|
|
17909
17981
|
/**
|
|
17982
|
+
* The two `ProvisionedThroughput` members, in the order the forwarders build
|
|
17983
|
+
* them. Named once so {@link hasUsableTableCapacity}, {@link tableCapacityForSend}
|
|
17984
|
+
* and the per-INDEX create forwarder
|
|
17985
|
+
* ({@link DynamoDBTableProvider.coerceIndexCapacityBlock}) cannot come to
|
|
17986
|
+
* disagree about the membership.
|
|
17987
|
+
*
|
|
17988
|
+
* Deliberately NOT `TABLE_CAPACITY_MEMBERS` any more (issue
|
|
17989
|
+
* [#3255](https://github.com/go-to-k/cdkd/issues/3255)): CloudFormation's
|
|
17990
|
+
* `ProvisionedThroughput` block has the same two members at the TABLE level and
|
|
17991
|
+
* inside every `GlobalSecondaryIndexes[]` entry, and a second list for the
|
|
17992
|
+
* per-index reader would be exactly the drift this constant exists to prevent.
|
|
17993
|
+
* What differs between the two levels is what an ABSENT member does — the table
|
|
17994
|
+
* level substitutes {@link DEFAULT_TABLE_CAPACITY_UNITS}, the per-index level
|
|
17995
|
+
* deliberately has no default ({@link readCapacityNumber}) — and that is a
|
|
17996
|
+
* property of each forwarder, not of the membership.
|
|
17997
|
+
*/
|
|
17998
|
+
const PROVISIONED_CAPACITY_MEMBERS = ["ReadCapacityUnits", "WriteCapacityUnits"];
|
|
17999
|
+
/**
|
|
18000
|
+
* The capacity both TABLE-level forwarders substitute for a member the
|
|
18001
|
+
* template does not declare at all — `create()`'s `CreateTable` and the
|
|
18002
|
+
* BillingMode flip's `UpdateTable`. It is the pre-issue-#3147 `?? 5`, kept
|
|
18003
|
+
* BYTE-identical in behaviour for the absent case; what changed is only what a
|
|
18004
|
+
* DECLARED-but-unusable member does.
|
|
18005
|
+
*/
|
|
18006
|
+
const DEFAULT_TABLE_CAPACITY_UNITS = 5;
|
|
18007
|
+
/**
|
|
17910
18008
|
* Whether the desired TABLE-level `ProvisionedThroughput` is one the flip can
|
|
17911
18009
|
* actually send (issue #1617 PR review).
|
|
17912
18010
|
*
|
|
17913
18011
|
* Deliberately NOT {@link hasUsableDeclaredCapacity}: the flip defaults each
|
|
17914
|
-
* member to 5
|
|
17915
|
-
*
|
|
17916
|
-
* is not.
|
|
18012
|
+
* ABSENT member to 5 ({@link tableCapacityForSend}), so a half-declared TABLE
|
|
18013
|
+
* capacity is sendable while a half-declared per-INDEX one is not.
|
|
17917
18014
|
*
|
|
17918
18015
|
* It mirrors the flip's GATE as well as its arithmetic, which the first version
|
|
17919
18016
|
* did not and a second review round caught — both halves of that miss are real:
|
|
@@ -17921,23 +18018,78 @@ function hasUsableDeclaredCapacity(entry) {
|
|
|
17921
18018
|
* - the gate is plain TRUTHINESS (`properties['ProvisionedThroughput']`), so a
|
|
17922
18019
|
* non-object truthy value sends `{5, 5}` and SUCCEEDS. Requiring a plain
|
|
17923
18020
|
* object refused it and skipped a removal that would have been fine.
|
|
17924
|
-
* - a DECLARED member that coerces to `0` (`
|
|
17925
|
-
*
|
|
17926
|
-
*
|
|
17927
|
-
*
|
|
18021
|
+
* - a DECLARED member that coerces to `0` (`0`, `'0'`) is a capacity AWS
|
|
18022
|
+
* rejects (below 1), which is precisely the deterministic rejection this
|
|
18023
|
+
* predicate exists to keep a delete from running ahead of.
|
|
18024
|
+
*
|
|
18025
|
+
* It reads through {@link tableCapacityForSend}'s OWN reader since issue
|
|
18026
|
+
* [#3147](https://github.com/go-to-k/cdkd/issues/3147) — `coerceCfnInteger`,
|
|
18027
|
+
* plus the `< 1` rule AWS adds on top of the grammar — so the two cannot
|
|
18028
|
+
* answer differently. On the pre-#3147 `Number()` reader they DID, in both
|
|
18029
|
+
* directions: a padded `" 7 "` counted as usable here while the forwarder now
|
|
18030
|
+
* drops it, and a DECLARED `null` counted as UNUSABLE here while the
|
|
18031
|
+
* forwarder's `?? 5` sent 5 and AWS accepted the flip — so this predicate
|
|
18032
|
+
* refused a pre-flip removal on a flip that would have succeeded, warning that
|
|
18033
|
+
* "AWS rejects the flip either way" when it does not. `null` therefore takes
|
|
18034
|
+
* the ABSENT arm here, exactly as it does at the forwarder; `''` / `false` /
|
|
18035
|
+
* `[]`, which `Number()` read as **0**, are now dropped-and-named by the
|
|
18036
|
+
* forwarder, so they make the flip fail and stay UNUSABLE here.
|
|
17928
18037
|
*/
|
|
17929
18038
|
function hasUsableTableCapacity(value) {
|
|
17930
18039
|
if (!value) return false;
|
|
17931
18040
|
if (typeof value !== "object" || Array.isArray(value)) return true;
|
|
17932
18041
|
const pt = value;
|
|
17933
|
-
for (const member of
|
|
17934
|
-
|
|
17935
|
-
|
|
17936
|
-
|
|
18042
|
+
for (const member of PROVISIONED_CAPACITY_MEMBERS) {
|
|
18043
|
+
const raw = pt[member];
|
|
18044
|
+
if (raw === void 0 || raw === null) continue;
|
|
18045
|
+
const n = coerceCfnInteger(raw);
|
|
18046
|
+
if (n === void 0 || n < 1) return false;
|
|
17937
18047
|
}
|
|
17938
18048
|
return true;
|
|
17939
18049
|
}
|
|
17940
18050
|
/**
|
|
18051
|
+
* Read one TABLE-level `ProvisionedThroughput` member for the wire (issue
|
|
18052
|
+
* [#3147](https://github.com/go-to-k/cdkd/issues/3147)).
|
|
18053
|
+
*
|
|
18054
|
+
* THREE answers, where the pre-#3147 `Number(pt[member] ?? 5)` had two:
|
|
18055
|
+
*
|
|
18056
|
+
* - ABSENT (`undefined` / `null`, matching `??`) -> the
|
|
18057
|
+
* {@link DEFAULT_TABLE_CAPACITY_UNITS} default, silently, exactly as before.
|
|
18058
|
+
* - a value {@link coerceCfnInteger} accepts -> that integer.
|
|
18059
|
+
* - anything else -> `undefined`, i.e. the member is OMITTED from the request,
|
|
18060
|
+
* and `onUnusable` announces it.
|
|
18061
|
+
*
|
|
18062
|
+
* The third answer is the decision this function exists for, and it is NOT the
|
|
18063
|
+
* announced-default one its `AWS::DynamoDB::GlobalTable` sibling takes. Two
|
|
18064
|
+
* reasons, both local to this type:
|
|
18065
|
+
*
|
|
18066
|
+
* - **This file's own precedent for the table-level capacity is "let AWS
|
|
18067
|
+
* reject it, by name".** A flip to PROVISIONED carrying NO
|
|
18068
|
+
* `ProvisionedThroughput` at all is deliberately left to fail at AWS as CFn
|
|
18069
|
+
* parity (see the comment at that gate), and the per-index arm below refuses
|
|
18070
|
+
* to invent a capacity in even stronger terms. Substituting 5 for a
|
|
18071
|
+
* DECLARED `" 7 "` would be the one place this type guesses.
|
|
18072
|
+
* - **A substituted capacity is invisible afterwards.** State records the
|
|
18073
|
+
* DESIRED bag, so a table created at 5 against a recorded `" 7 "` compares
|
|
18074
|
+
* EQUAL on every later `cdkd diff`: the divergence never surfaces and the
|
|
18075
|
+
* warning scrolls past once. Omitting the member fails the deploy with
|
|
18076
|
+
* DynamoDB naming the member, which is also what CloudFormation does with
|
|
18077
|
+
* this template (it refuses it at properties validation).
|
|
18078
|
+
*
|
|
18079
|
+
* `onUnusable` is called at most once per member and never for the absent case,
|
|
18080
|
+
* so an ordinary template is silent.
|
|
18081
|
+
*/
|
|
18082
|
+
function tableCapacityForSend(pt, member, onUnusable) {
|
|
18083
|
+
const raw = pt?.[member];
|
|
18084
|
+
if (raw === void 0 || raw === null) return DEFAULT_TABLE_CAPACITY_UNITS;
|
|
18085
|
+
const coerced = coerceCfnInteger(raw);
|
|
18086
|
+
if (coerced === void 0) {
|
|
18087
|
+
onUnusable(member, raw);
|
|
18088
|
+
return;
|
|
18089
|
+
}
|
|
18090
|
+
return coerced;
|
|
18091
|
+
}
|
|
18092
|
+
/**
|
|
17941
18093
|
* Does the capacity AWS currently holds for a GSI already equal the pair the
|
|
17942
18094
|
* template requests? (issue #1630)
|
|
17943
18095
|
*
|
|
@@ -18294,7 +18446,7 @@ function reverseMapSecondaryIndex(live, desired, bagInformative) {
|
|
|
18294
18446
|
* predicate EVERY per-index write path calls — the `Create` / `Update` GSI
|
|
18295
18447
|
* actions in `applyGsiUpdates` AND `create()`'s `CreateTable` forward, which
|
|
18296
18448
|
* maps each entry through
|
|
18297
|
-
* {@link DynamoDBTableProvider.
|
|
18449
|
+
* {@link DynamoDBTableProvider.coerceIndexThroughputForCreate}. It is
|
|
18298
18450
|
* TIGHTER than truthiness (at least one member resolving to a finite
|
|
18299
18451
|
* number), so `WarmThroughput: {}` is neither sent nor emitted on any path.
|
|
18300
18452
|
* An earlier version of this bullet carved out the TABLE-create path as an
|
|
@@ -18303,9 +18455,15 @@ function reverseMapSecondaryIndex(live, desired, bagInformative) {
|
|
|
18303
18455
|
* template failed on a fresh create and succeeded on a later GSI add. Fixed
|
|
18304
18456
|
* in PR review round 6; the carve-out is gone because the divergence is.
|
|
18305
18457
|
* - `ProvisionedThroughput` / `OnDemandThroughput` keep TRUTHINESS, which is
|
|
18306
|
-
* what their write path does:
|
|
18307
|
-
*
|
|
18308
|
-
*
|
|
18458
|
+
* still what their write path does: `applyGsiUpdates` gates on
|
|
18459
|
+
* `gsi.ProvisionedThroughput` alone, `OnDemandThroughput` is forwarded
|
|
18460
|
+
* verbatim on every path, and the `ProvisionedThroughput` coercion issue
|
|
18461
|
+
* [#3255](https://github.com/go-to-k/cdkd/issues/3255) added to ALL FOUR
|
|
18462
|
+
* per-index send sites (`create()`'s mapper plus `applyGsiUpdates`'
|
|
18463
|
+
* adopted-repair, `Create` and same-name `Update` actions) never
|
|
18464
|
+
* SUPPRESSES the block — it rewrites members inside a block that is sent
|
|
18465
|
+
* either way, so "would cdkd send this block" is unchanged by it, even
|
|
18466
|
+
* where both members were dropped and an empty block goes out. Widening
|
|
18309
18467
|
* `isSendableWarmThroughput` over them would be wrong twice over — it reads
|
|
18310
18468
|
* members those blocks do not have (`ReadUnitsPerSecond` vs
|
|
18311
18469
|
* `ReadCapacityUnits`), so every declared capacity would read as undeclared.
|
|
@@ -18497,9 +18655,10 @@ var DynamoDBTableProvider = class {
|
|
|
18497
18655
|
};
|
|
18498
18656
|
if (billingMode === "PROVISIONED") {
|
|
18499
18657
|
const pt = properties["ProvisionedThroughput"];
|
|
18658
|
+
const onUnusable = (member, raw) => this.warnUnusableProvisionedCapacity(`AWS::DynamoDB::Table ${logicalId}`, member, raw, maskSecrets);
|
|
18500
18659
|
createParams.ProvisionedThroughput = {
|
|
18501
|
-
ReadCapacityUnits:
|
|
18502
|
-
WriteCapacityUnits:
|
|
18660
|
+
ReadCapacityUnits: tableCapacityForSend(pt, "ReadCapacityUnits", onUnusable),
|
|
18661
|
+
WriteCapacityUnits: tableCapacityForSend(pt, "WriteCapacityUnits", onUnusable)
|
|
18503
18662
|
};
|
|
18504
18663
|
}
|
|
18505
18664
|
if (properties["OnDemandThroughput"]) createParams.OnDemandThroughput = properties["OnDemandThroughput"];
|
|
@@ -18512,7 +18671,7 @@ var DynamoDBTableProvider = class {
|
|
|
18512
18671
|
};
|
|
18513
18672
|
if (properties["GlobalSecondaryIndexes"]) {
|
|
18514
18673
|
const declaredGsis = properties["GlobalSecondaryIndexes"];
|
|
18515
|
-
createParams.GlobalSecondaryIndexes = Array.isArray(declaredGsis) ? declaredGsis.map((entry) => this.
|
|
18674
|
+
createParams.GlobalSecondaryIndexes = Array.isArray(declaredGsis) ? declaredGsis.map((entry) => this.coerceIndexThroughputForCreate(logicalId, entry, maskSecrets)) : declaredGsis;
|
|
18516
18675
|
}
|
|
18517
18676
|
if (properties["LocalSecondaryIndexes"]) createParams.LocalSecondaryIndexes = properties["LocalSecondaryIndexes"];
|
|
18518
18677
|
const sse = mapSSESpecification(properties["SSESpecification"]);
|
|
@@ -18526,7 +18685,7 @@ var DynamoDBTableProvider = class {
|
|
|
18526
18685
|
tableCreated = true;
|
|
18527
18686
|
this.logger.debug(`CreateTable initiated for ${tableName}, waiting for ACTIVE status`);
|
|
18528
18687
|
const tableInfo = await this.waitForTableActive(tableName);
|
|
18529
|
-
await this.applyPointInTimeRecovery(tableName, properties["PointInTimeRecoverySpecification"]);
|
|
18688
|
+
await this.applyPointInTimeRecovery(tableName, properties["PointInTimeRecoverySpecification"], void 0, maskSecrets);
|
|
18530
18689
|
await this.applyTimeToLive(tableName, properties["TimeToLiveSpecification"]);
|
|
18531
18690
|
await this.applyKinesisStreamingDestination(tableName, properties["KinesisStreamSpecification"], void 0, maskSecrets);
|
|
18532
18691
|
await this.applyContributorInsights(tableName, properties["ContributorInsightsSpecification"]);
|
|
@@ -18630,9 +18789,10 @@ var DynamoDBTableProvider = class {
|
|
|
18630
18789
|
if (tableClassChanged) updateInput.TableClass = normalizeTableClass(properties["TableClass"]);
|
|
18631
18790
|
if (billingOrThroughputChanged && billingMode !== "PAY_PER_REQUEST" && properties["ProvisionedThroughput"]) {
|
|
18632
18791
|
const pt = properties["ProvisionedThroughput"];
|
|
18792
|
+
const onUnusable = (member, raw) => this.warnUnusableProvisionedCapacity(`AWS::DynamoDB::Table ${logicalId}`, member, raw, maskSecrets);
|
|
18633
18793
|
updateInput.ProvisionedThroughput = {
|
|
18634
|
-
ReadCapacityUnits:
|
|
18635
|
-
WriteCapacityUnits:
|
|
18794
|
+
ReadCapacityUnits: tableCapacityForSend(pt, "ReadCapacityUnits", onUnusable),
|
|
18795
|
+
WriteCapacityUnits: tableCapacityForSend(pt, "WriteCapacityUnits", onUnusable)
|
|
18636
18796
|
};
|
|
18637
18797
|
}
|
|
18638
18798
|
const flipLiveIndexes = liveIndexes.filter((live) => typeof live.IndexName !== "string" || !preFlipDeletedIndexNames.has(live.IndexName));
|
|
@@ -18740,7 +18900,7 @@ var DynamoDBTableProvider = class {
|
|
|
18740
18900
|
}
|
|
18741
18901
|
}
|
|
18742
18902
|
if (JSON.stringify(properties["GlobalSecondaryIndexes"]) !== JSON.stringify(previousProperties["GlobalSecondaryIndexes"])) await this.applyGsiUpdates(physicalId, resourceType, logicalId, previousProperties["GlobalSecondaryIndexes"], properties["GlobalSecondaryIndexes"], properties["AttributeDefinitions"], gsiHandledByBillingFlip, currentLiveIndexNames, currentLiveIndexByName, liveCapacityComparable, maskSecrets);
|
|
18743
|
-
if (JSON.stringify(properties["PointInTimeRecoverySpecification"]) !== JSON.stringify(previousProperties["PointInTimeRecoverySpecification"])) await this.applyPointInTimeRecovery(physicalId, properties["PointInTimeRecoverySpecification"], previousProperties["PointInTimeRecoverySpecification"]);
|
|
18903
|
+
if (JSON.stringify(properties["PointInTimeRecoverySpecification"]) !== JSON.stringify(previousProperties["PointInTimeRecoverySpecification"])) await this.applyPointInTimeRecovery(physicalId, properties["PointInTimeRecoverySpecification"], previousProperties["PointInTimeRecoverySpecification"], maskSecrets);
|
|
18744
18904
|
if (JSON.stringify(properties["TimeToLiveSpecification"]) !== JSON.stringify(previousProperties["TimeToLiveSpecification"])) {
|
|
18745
18905
|
this.assertNoActiveTtlAttributeNameChange(logicalId, resourceType, physicalId, properties["TimeToLiveSpecification"], previousProperties["TimeToLiveSpecification"]);
|
|
18746
18906
|
await this.applyTimeToLive(physicalId, properties["TimeToLiveSpecification"], previousProperties["TimeToLiveSpecification"]);
|
|
@@ -18933,13 +19093,20 @@ var DynamoDBTableProvider = class {
|
|
|
18933
19093
|
* change", so a dropped block must be turned into an explicit
|
|
18934
19094
|
* `PointInTimeRecoveryEnabled: false`).
|
|
18935
19095
|
*/
|
|
18936
|
-
async applyPointInTimeRecovery(tableName, spec, previousSpec) {
|
|
19096
|
+
async applyPointInTimeRecovery(tableName, spec, previousSpec, maskSecrets = (text) => text) {
|
|
18937
19097
|
let enabled;
|
|
18938
19098
|
let recoveryPeriodInDays;
|
|
18939
19099
|
if (spec !== void 0 && spec !== null) {
|
|
18940
19100
|
const s = spec;
|
|
18941
19101
|
enabled = Boolean(s["PointInTimeRecoveryEnabled"]);
|
|
18942
|
-
if (enabled && s["RecoveryPeriodInDays"] !== void 0)
|
|
19102
|
+
if (enabled && s["RecoveryPeriodInDays"] !== void 0) {
|
|
19103
|
+
const rawRecoveryPeriod = s["RecoveryPeriodInDays"];
|
|
19104
|
+
recoveryPeriodInDays = coerceCfnInteger(rawRecoveryPeriod);
|
|
19105
|
+
if (recoveryPeriodInDays === void 0) {
|
|
19106
|
+
const warn = (message) => this.logger.warn(maskSecrets(message));
|
|
19107
|
+
warn(`DynamoDB table ${tableName}: PointInTimeRecoverySpecification.RecoveryPeriodInDays ${JSON.stringify(maskLeafValue$1(rawRecoveryPeriod, maskSecrets))} is not an Integer CloudFormation accepts (an optional sign and decimal digits only — no surrounding whitespace, hex, exponent or decimal point), so it was DROPPED from the UpdateContinuousBackups request: point-in-time recovery is enabled with DynamoDB's default recovery period instead of the declared one. Check for an unresolved intrinsic, or spell the value as a decimal integer.`);
|
|
19108
|
+
}
|
|
19109
|
+
}
|
|
18943
19110
|
} else if (previousSpec !== void 0 && previousSpec !== null) enabled = false;
|
|
18944
19111
|
if (enabled === void 0) return;
|
|
18945
19112
|
const pitrSpec = { PointInTimeRecoveryEnabled: enabled };
|
|
@@ -19314,7 +19481,7 @@ var DynamoDBTableProvider = class {
|
|
|
19314
19481
|
const adopted = { IndexName: name };
|
|
19315
19482
|
let adoptedHasMember = false;
|
|
19316
19483
|
if (liveCapacityComparable && gsi.ProvisionedThroughput && !handledByBillingFlip.has(name) && !liveCapacityAlreadyMatches(live.ProvisionedThroughput, gsi.ProvisionedThroughput) && !this.skipZeroCapacityIndexUpdate(name, physicalId, gsi.ProvisionedThroughput, maskSecrets)) {
|
|
19317
|
-
adopted.ProvisionedThroughput = gsi.ProvisionedThroughput;
|
|
19484
|
+
adopted.ProvisionedThroughput = this.indexCapacityForSend(this.indexScopeAt(name, physicalId, maskSecrets), gsi.ProvisionedThroughput, maskSecrets);
|
|
19318
19485
|
adoptedHasMember = true;
|
|
19319
19486
|
}
|
|
19320
19487
|
const adoptedWarm = this.warmThroughputOpFor(name, physicalId, gsi, void 0, live, maskSecrets);
|
|
@@ -19326,13 +19493,13 @@ var DynamoDBTableProvider = class {
|
|
|
19326
19493
|
warn(`GSI ${maskSecrets(name)} was adopted from AWS rather than created, so cdkd could not verify its KeySchema / Projection match the template. Run \`cdkd drift ${physicalId}\` to confirm, and \`cdkd drift --accept\` if AWS is the source of truth.`);
|
|
19327
19494
|
continue;
|
|
19328
19495
|
}
|
|
19329
|
-
this.warnRefusedWarmThroughput(
|
|
19330
|
-
const createWarm = this.coerceWarmThroughputForSend(
|
|
19496
|
+
this.warnRefusedWarmThroughput(this.indexScopeAt(name, physicalId, maskSecrets), gsi.WarmThroughput, maskSecrets);
|
|
19497
|
+
const createWarm = this.coerceWarmThroughputForSend(this.indexScopeAt(name, physicalId, maskSecrets), gsi.WarmThroughput, maskSecrets);
|
|
19331
19498
|
ops.push({ Create: {
|
|
19332
19499
|
IndexName: name,
|
|
19333
19500
|
KeySchema: gsi.KeySchema,
|
|
19334
19501
|
Projection: gsi.Projection,
|
|
19335
|
-
...gsi.ProvisionedThroughput ? { ProvisionedThroughput: gsi.ProvisionedThroughput } : {},
|
|
19502
|
+
...gsi.ProvisionedThroughput ? { ProvisionedThroughput: this.indexCapacityForSend(this.indexScopeAt(name, physicalId, maskSecrets), gsi.ProvisionedThroughput, maskSecrets) } : {},
|
|
19336
19503
|
...gsi.OnDemandThroughput ? { OnDemandThroughput: gsi.OnDemandThroughput } : {},
|
|
19337
19504
|
...createWarm ? { WarmThroughput: createWarm } : {}
|
|
19338
19505
|
} });
|
|
@@ -19344,7 +19511,7 @@ var DynamoDBTableProvider = class {
|
|
|
19344
19511
|
if (gsi.ProvisionedThroughput && !handledByBillingFlip.has(name) && JSON.stringify(before.ProvisionedThroughput) !== JSON.stringify(gsi.ProvisionedThroughput)) {
|
|
19345
19512
|
if (liveCapacityComparable && liveCapacityAlreadyMatches(liveIndexByName?.get(name)?.ProvisionedThroughput, gsi.ProvisionedThroughput)) this.logger.debug(`GSI ${name} on DynamoDB table ${physicalId} already carries the requested capacity in AWS; skipping its throughput Update`);
|
|
19346
19513
|
else if (!this.skipZeroCapacityIndexUpdate(name, physicalId, gsi.ProvisionedThroughput, maskSecrets)) {
|
|
19347
|
-
update.ProvisionedThroughput = gsi.ProvisionedThroughput;
|
|
19514
|
+
update.ProvisionedThroughput = this.indexCapacityForSend(this.indexScopeAt(name, physicalId, maskSecrets), gsi.ProvisionedThroughput, maskSecrets);
|
|
19348
19515
|
updateHasMember = true;
|
|
19349
19516
|
}
|
|
19350
19517
|
}
|
|
@@ -19376,35 +19543,211 @@ var DynamoDBTableProvider = class {
|
|
|
19376
19543
|
return coerced.spec;
|
|
19377
19544
|
}
|
|
19378
19545
|
/**
|
|
19379
|
-
*
|
|
19380
|
-
*
|
|
19546
|
+
* `GSI <name> on AWS::DynamoDB::Table <logicalId>` — the scope every
|
|
19547
|
+
* create-path per-index warning interpolates.
|
|
19548
|
+
*
|
|
19549
|
+
* The index NAME is a RESOLVED property value, so it is masked before it
|
|
19550
|
+
* becomes part of a message (issue #1997). It is also an UNCHECKED cast off
|
|
19551
|
+
* the template: `IndexName: 2024` (unquoted YAML) reaches here as a NUMBER,
|
|
19552
|
+
* and the real masker is a `String.prototype.replace` call that THROWS on
|
|
19553
|
+
* one — which would take the whole deploy down from a diagnostic path. So
|
|
19554
|
+
* the type test is the guard, not merely `!== undefined`: anything that is
|
|
19555
|
+
* not a string has no name to print and takes the `<unnamed>` arm, exactly
|
|
19556
|
+
* as an absent one does. (Reached by the review of issue
|
|
19557
|
+
* [#3255](https://github.com/go-to-k/cdkd/issues/3255): before it, the scope
|
|
19558
|
+
* was built only for an entry declaring `WarmThroughput`, so the crash was
|
|
19559
|
+
* unreachable for an ordinary PROVISIONED template.)
|
|
19560
|
+
*/
|
|
19561
|
+
indexScope(indexName, logicalId, maskSecrets) {
|
|
19562
|
+
return `GSI ${typeof indexName === "string" ? maskSecrets(indexName) : "<unnamed>"} on AWS::DynamoDB::Table ${logicalId}`;
|
|
19563
|
+
}
|
|
19564
|
+
/**
|
|
19565
|
+
* `GSI <name> on DynamoDB table <physicalId>` — {@link indexScope}'s twin for
|
|
19566
|
+
* the UPDATE path, which names the LIVE table rather than the template's
|
|
19567
|
+
* logical id.
|
|
19568
|
+
*
|
|
19569
|
+
* Same guard, same reason, and the reason is why this exists at all rather
|
|
19570
|
+
* than the five call sites spelling `maskSecrets(name)` inline: `name` is a
|
|
19571
|
+
* key of a map built from `g.IndexName!` off an UNCHECKED template cast, so
|
|
19572
|
+
* `IndexName: 2024` (unquoted YAML) is truthy, becomes the key, and arrives
|
|
19573
|
+
* here as a NUMBER — on which the real masker's `String.prototype.replace`
|
|
19574
|
+
* THROWS. The create path was guarded when {@link indexScope} was added
|
|
19575
|
+
* (the review of issue #3255); the update sites that now build a scope
|
|
19576
|
+
* EAGERLY for the capacity coercion were not, which newly exposed the same
|
|
19577
|
+
* crash on an ordinary PROVISIONED capacity update with a non-empty secret
|
|
19578
|
+
* bag (found by the review of that round).
|
|
19579
|
+
*/
|
|
19580
|
+
indexScopeAt(indexName, physicalId, maskSecrets) {
|
|
19581
|
+
return `GSI ${typeof indexName === "string" ? maskSecrets(indexName) : "<unnamed>"} on DynamoDB table ${physicalId}`;
|
|
19582
|
+
}
|
|
19583
|
+
/**
|
|
19584
|
+
* The per-index `ProvisionedThroughput` an `UpdateTable` action should put on
|
|
19585
|
+
* the wire (the review of issue
|
|
19586
|
+
* [#3255](https://github.com/go-to-k/cdkd/issues/3255)).
|
|
19587
|
+
*
|
|
19588
|
+
* Exists because coercing only `create()`'s forward left the file
|
|
19589
|
+
* INTERNALLY divergent in the direction the issue is about: the same index
|
|
19590
|
+
* entry succeeded on a fresh create and was rejected by AWS when added by a
|
|
19591
|
+
* later update, which is the `WarmThroughput` round-6 story with the
|
|
19592
|
+
* polarity flipped. All three `applyGsiUpdates` send sites — the adopted
|
|
19593
|
+
* index's repair `Update`, the `Create` action and the same-name `Update`
|
|
19594
|
+
* action — now read through {@link coerceIndexCapacityBlock}, the same
|
|
19595
|
+
* reader `create()` uses.
|
|
19596
|
+
*
|
|
19597
|
+
* **The GUARDS around those sites deliberately stay on `toFiniteNumber`**,
|
|
19598
|
+
* and that is the split `dynamodb-warm-throughput.ts`'s header already
|
|
19599
|
+
* records: a FORWARDER reads CloudFormation's grammar, a GUARD reads the
|
|
19600
|
+
* wider one, because over-acceptance is the safe side of an
|
|
19601
|
+
* already-matches / zero-capacity / change-detection test. Nothing moves as
|
|
19602
|
+
* a result — `liveCapacityAlreadyMatches` and `skipZeroCapacityIndexUpdate`
|
|
19603
|
+
* are asked about the DECLARED block exactly as before, and the change
|
|
19604
|
+
* detector still compares the declared block against the recorded one, both
|
|
19605
|
+
* raw. Only what reaches AWS changes.
|
|
19606
|
+
*/
|
|
19607
|
+
indexCapacityForSend(scope, declared, maskSecrets) {
|
|
19608
|
+
if (declared === void 0) return void 0;
|
|
19609
|
+
return this.coerceIndexCapacityBlock(scope, declared, maskSecrets) ?? declared;
|
|
19610
|
+
}
|
|
19611
|
+
/**
|
|
19612
|
+
* One `CreateTable` index entry with BOTH of its throughput blocks coerced —
|
|
19613
|
+
* `WarmThroughput` (PR review round 6) and `ProvisionedThroughput` (issue
|
|
19614
|
+
* [#3255](https://github.com/go-to-k/cdkd/issues/3255)).
|
|
19381
19615
|
*
|
|
19382
19616
|
* `create()` forwards the declared `GlobalSecondaryIndexes` array to
|
|
19383
|
-
* `CreateTable`, so it is a SEND SITE for the same per-index
|
|
19384
|
-
* update path coerces
|
|
19385
|
-
*
|
|
19386
|
-
*
|
|
19387
|
-
*
|
|
19388
|
-
*
|
|
19389
|
-
*
|
|
19390
|
-
*
|
|
19391
|
-
*
|
|
19392
|
-
|
|
19393
|
-
|
|
19617
|
+
* `CreateTable`, so it is a SEND SITE for the same per-index values the
|
|
19618
|
+
* update path coerces. `WarmThroughput` was the one the round-5 sweep
|
|
19619
|
+
* missed; `ProvisionedThroughput` was missed by round 6 AND by issue #3147's
|
|
19620
|
+
* own enumeration, because this forwarder is a CAST rather than a call, so
|
|
19621
|
+
* neither sweep's grep could see it. The BillingMode flip's per-index reader
|
|
19622
|
+
* ({@link readCapacityNumber}) has been on the grammar since #3147, so until
|
|
19623
|
+
* now create and flip disagreed about the same property: a CFn-legal
|
|
19624
|
+
* `ReadCapacityUnits: '7'` reached `CreateTable` as a STRING in a `number`
|
|
19625
|
+
* field, and a `' 7 '` CloudFormation refuses the template for was forwarded
|
|
19626
|
+
* unchanged.
|
|
19627
|
+
*
|
|
19628
|
+
* Entries are rebuilt rather than mutated: the input bag belongs to the
|
|
19629
|
+
* caller (the resolved template, which the engine also records into state),
|
|
19630
|
+
* and a provider that edits it in place would change what state reports cdkd
|
|
19631
|
+
* sent.
|
|
19632
|
+
*
|
|
19633
|
+
* An entry that is not a plain object, and one declaring NEITHER throughput
|
|
19634
|
+
* block, is returned UNCHANGED — identity included. An entry that DOES
|
|
19635
|
+
* declare one is rebuilt whenever that block's coercion changes anything;
|
|
19636
|
+
* the `WarmThroughput` half rebuilds even for an already-numeric value,
|
|
19637
|
+
* which is pre-existing behaviour and costs one spread per declared block.
|
|
19638
|
+
*/
|
|
19639
|
+
coerceIndexThroughputForCreate(logicalId, entry, maskSecrets = (text) => text) {
|
|
19394
19640
|
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) return entry;
|
|
19395
|
-
const
|
|
19396
|
-
|
|
19397
|
-
const
|
|
19398
|
-
|
|
19399
|
-
|
|
19400
|
-
|
|
19401
|
-
|
|
19402
|
-
|
|
19641
|
+
const bag = entry;
|
|
19642
|
+
const declaredWarm = bag["WarmThroughput"];
|
|
19643
|
+
const declaredCapacity = bag["ProvisionedThroughput"];
|
|
19644
|
+
if (declaredWarm === void 0 && !isPlainCapacityBlock(declaredCapacity)) return entry;
|
|
19645
|
+
const scope = this.indexScope(entry.IndexName, logicalId, maskSecrets);
|
|
19646
|
+
let out = entry;
|
|
19647
|
+
if (declaredWarm !== void 0) {
|
|
19648
|
+
const coerced = this.coerceWarmThroughputForSend(scope, declaredWarm, maskSecrets);
|
|
19649
|
+
if (coerced === void 0) {
|
|
19650
|
+
this.warnRefusedWarmThroughput(scope, declaredWarm, maskSecrets);
|
|
19651
|
+
const { WarmThroughput: _dropped, ...rest } = out;
|
|
19652
|
+
out = rest;
|
|
19653
|
+
} else out = {
|
|
19654
|
+
...out,
|
|
19655
|
+
WarmThroughput: coerced
|
|
19656
|
+
};
|
|
19403
19657
|
}
|
|
19404
|
-
|
|
19405
|
-
|
|
19406
|
-
|
|
19658
|
+
const capacity = this.coerceIndexCapacityBlock(scope, declaredCapacity, maskSecrets);
|
|
19659
|
+
if (capacity !== void 0) out = {
|
|
19660
|
+
...out,
|
|
19661
|
+
ProvisionedThroughput: capacity
|
|
19407
19662
|
};
|
|
19663
|
+
return out;
|
|
19664
|
+
}
|
|
19665
|
+
/**
|
|
19666
|
+
* One `CreateTable` index entry's `ProvisionedThroughput`, with each DECLARED
|
|
19667
|
+
* member read through CloudFormation's DynamoDB Integer grammar (issue
|
|
19668
|
+
* [#3255](https://github.com/go-to-k/cdkd/issues/3255)). Returns `undefined`
|
|
19669
|
+
* when the block needs no rewriting at all.
|
|
19670
|
+
*
|
|
19671
|
+
* It reads through {@link readCapacityNumber} — the function the BillingMode
|
|
19672
|
+
* flip's per-index forwarder calls — rather than restating the grammar, for
|
|
19673
|
+
* the same reason {@link hasUsableDeclaredCapacity} does: that is what keeps
|
|
19674
|
+
* create and flip answering identically about one template value.
|
|
19675
|
+
*
|
|
19676
|
+
* THREE decisions, each of which had a plausible alternative:
|
|
19677
|
+
*
|
|
19678
|
+
* - **A rejected spelling drops the MEMBER, not the whole block.** This is
|
|
19679
|
+
* what {@link tableCapacityForSend} already does one level up for the
|
|
19680
|
+
* identical grammar, so create's two capacity forwarders answer the same
|
|
19681
|
+
* way; it keeps a sibling member the template spelled LEGALLY, which
|
|
19682
|
+
* dropping the block would discard; and it is the more informative
|
|
19683
|
+
* failure at AWS — `CreateTable` names the missing member at its exact
|
|
19684
|
+
* index position, where a missing block yields only
|
|
19685
|
+
* `ProvisionedThroughput must be specified for index: <name>`. Both
|
|
19686
|
+
* directions FAIL the deploy (DynamoDB requires both members on a
|
|
19687
|
+
* PROVISIONED index), so this is a choice of error TEXT, never of
|
|
19688
|
+
* outcome — what matters is that neither silently succeeds at a capacity
|
|
19689
|
+
* CloudFormation refuses the template for.
|
|
19690
|
+
*
|
|
19691
|
+
* **That is also why this drop needs NO `effectiveProperties`**, which
|
|
19692
|
+
* `.claude/rules/provider-property-fidelity.md` otherwise requires of any
|
|
19693
|
+
* announced narrowing: the rule exists because a SUCCEEDING call plus a
|
|
19694
|
+
* record describing the declared value is permanent phantom drift. Here
|
|
19695
|
+
* `CreateTable` FAILS, so `create()` never returns and nothing is
|
|
19696
|
+
* recorded — the same reasoning `tableCapacityForSend` states for the
|
|
19697
|
+
* table-level omit one level up.
|
|
19698
|
+
* - **An ABSENT member stays absent — no `5` default.** The per-index
|
|
19699
|
+
* reader deliberately has none ({@link readCapacityNumber}'s doc, issue
|
|
19700
|
+
* #1588): a defaulted per-index capacity would land in cdkd state as if
|
|
19701
|
+
* the template had declared it, with no later call to correct it. Only
|
|
19702
|
+
* the TABLE level defaults.
|
|
19703
|
+
* - **A block that is not a plain object is forwarded VERBATIM**, the
|
|
19704
|
+
* fail-OPEN direction this file takes everywhere: an unresolved intrinsic
|
|
19705
|
+
* or a mis-nested template value is AWS's to reject by name, and that is
|
|
19706
|
+
* also the pre-existing behaviour of this forwarder.
|
|
19707
|
+
*
|
|
19708
|
+
* A member name the grammar does not know is preserved untouched, for the
|
|
19709
|
+
* same fail-open reason — a misspelled `ReadCapacityUnit` reaches AWS and is
|
|
19710
|
+
* named there rather than vanishing here.
|
|
19711
|
+
*/
|
|
19712
|
+
coerceIndexCapacityBlock(scope, declared, maskSecrets = (text) => text) {
|
|
19713
|
+
if (!isPlainCapacityBlock(declared)) return void 0;
|
|
19714
|
+
const block = declared;
|
|
19715
|
+
let rewritten;
|
|
19716
|
+
for (const member of PROVISIONED_CAPACITY_MEMBERS) {
|
|
19717
|
+
if (!(member in block)) continue;
|
|
19718
|
+
const coerced = readCapacityNumber(block, member);
|
|
19719
|
+
if (coerced === block[member]) continue;
|
|
19720
|
+
rewritten ??= { ...block };
|
|
19721
|
+
if (coerced === void 0) {
|
|
19722
|
+
this.warnUnusableProvisionedCapacity(scope, member, block[member], maskSecrets);
|
|
19723
|
+
delete rewritten[member];
|
|
19724
|
+
} else rewritten[member] = coerced;
|
|
19725
|
+
}
|
|
19726
|
+
return rewritten;
|
|
19727
|
+
}
|
|
19728
|
+
/**
|
|
19729
|
+
* Announce a `ProvisionedThroughput` member a forwarder DROPPED (issue
|
|
19730
|
+
* [#3147](https://github.com/go-to-k/cdkd/issues/3147); widened to the
|
|
19731
|
+
* per-index create forwarder by issue
|
|
19732
|
+
* [#3255](https://github.com/go-to-k/cdkd/issues/3255)).
|
|
19733
|
+
*
|
|
19734
|
+
* Called from all THREE forwarders that can drop one — `create()`'s
|
|
19735
|
+
* `CreateTable` table-level block, the BillingMode flip's `UpdateTable`
|
|
19736
|
+
* table-level block, and `create()`'s per-`GlobalSecondaryIndexes[]` block —
|
|
19737
|
+
* because the member vanishes at each independently, and a drop that is
|
|
19738
|
+
* never announced is the silent-narrowing class this file's other warn arms
|
|
19739
|
+
* exist to end. `scope` names the resource or the index, so a table with
|
|
19740
|
+
* several GSIs says WHICH one.
|
|
19741
|
+
*
|
|
19742
|
+
* It says NO capacity was substituted, which is the whole decision: the
|
|
19743
|
+
* sibling `AWS::DynamoDB::GlobalTable` forwarders announce a 5/5 FALLBACK,
|
|
19744
|
+
* and saying the wrong one of those two would send a user looking for a
|
|
19745
|
+
* table that does not exist. {@link tableCapacityForSend}'s doc carries why
|
|
19746
|
+
* this type differs.
|
|
19747
|
+
*/
|
|
19748
|
+
warnUnusableProvisionedCapacity(scope, member, raw, maskSecrets = (text) => text) {
|
|
19749
|
+
const warn = (message) => this.logger.warn(maskSecrets(message));
|
|
19750
|
+
warn(`${scope}: ProvisionedThroughput.${member} ${JSON.stringify(maskLeafValue$1(raw, maskSecrets))} is not an Integer CloudFormation accepts (an optional sign and decimal digits only — no surrounding whitespace, hex, exponent or decimal point), so it was NOT sent and NO capacity was substituted for it. DynamoDB requires both members wherever a PROVISIONED ProvisionedThroughput block is declared — the table itself, and every GlobalSecondaryIndexes[] entry of a PROVISIONED table — and will reject the request naming this one; CloudFormation refuses the same template at properties validation. Check for an unresolved intrinsic, or spell the value as a decimal integer.`);
|
|
19408
19751
|
}
|
|
19409
19752
|
/**
|
|
19410
19753
|
* Announce a `WarmThroughput` the send rule REFUSES (PR review of issue
|
|
@@ -23953,10 +24296,19 @@ var LogsLogGroupProvider = class {
|
|
|
23953
24296
|
const rawPreviousRetention = previousProperties["RetentionInDays"];
|
|
23954
24297
|
const renderedRetention = JSON.stringify(rawRetention);
|
|
23955
24298
|
const sameRawRetention = Object.is(rawRetention, rawPreviousRetention) || renderedRetention !== void 0 && renderedRetention !== "null" && renderedRetention === JSON.stringify(rawPreviousRetention);
|
|
23956
|
-
|
|
24299
|
+
let retentionInDays = toCfnInteger(rawRetention);
|
|
23957
24300
|
const oldRetentionInDays = toCfnInteger(rawPreviousRetention);
|
|
23958
24301
|
const desiredFromAwsReadback = context?.desiredFromAwsReadback === true;
|
|
23959
|
-
if (!sameRawRetention && !isAbsentRetention(rawRetention, desiredFromAwsReadback))
|
|
24302
|
+
if (!sameRawRetention && !isAbsentRetention(rawRetention, desiredFromAwsReadback)) try {
|
|
24303
|
+
assertUsableRetention(rawRetention, retentionInDays, logicalId, resourceType);
|
|
24304
|
+
} catch (refusal) {
|
|
24305
|
+
if (context?.replayingState !== true || !(refusal instanceof ProvisioningError)) throw refusal;
|
|
24306
|
+
const legacyReading = toFiniteNumber(rawRetention);
|
|
24307
|
+
if (legacyReading !== void 0 && Number.isInteger(legacyReading) && legacyReading > 0) {
|
|
24308
|
+
retentionInDays = legacyReading;
|
|
24309
|
+
this.logger.warn(`${refusal.message}. This update replays a cdkd state record (rollback) written by a cdkd that read the value as ${legacyReading} and applied it, so the retention is RESTORED as ${legacyReading} days rather than refused. Fix the template spelling and re-deploy.`);
|
|
24310
|
+
} else this.logger.warn(`${refusal.message}. This update replays a cdkd state record (rollback), so the value is read as the "no retention" the record meant rather than refused: the log group's retention policy is REMOVED, which is what the cdkd that wrote the record left in place. Re-apply it by hand (aws logs put-retention-policy) or fix the template and re-deploy.`);
|
|
24311
|
+
}
|
|
23960
24312
|
const newKmsKeyId = properties["KmsKeyId"];
|
|
23961
24313
|
if (newKmsKeyId !== previousProperties["KmsKeyId"]) {
|
|
23962
24314
|
if (newKmsKeyId) await this.logsClient.send(new AssociateKmsKeyCommand({
|
|
@@ -25630,7 +25982,13 @@ var SSMParameterProvider = class {
|
|
|
25630
25982
|
await this.ssmClient.send(new DeleteParameterCommand({ Name: name }));
|
|
25631
25983
|
debug(`Cleaned up partially-created SSM parameter ${logicalId} (${name}) after wiring failure`);
|
|
25632
25984
|
} catch (cleanupError) {
|
|
25633
|
-
|
|
25985
|
+
const deleteCommand = renderDisableCommand({
|
|
25986
|
+
before: "aws ssm delete-parameter --name",
|
|
25987
|
+
identifier: name,
|
|
25988
|
+
maskSecrets: mask
|
|
25989
|
+
});
|
|
25990
|
+
const manualStep = deleteCommand ? `Manual deletion may be required before the next deploy: ${deleteCommand}` : "Manual deletion may be required before the next deploy, via the console: the parameter name cannot be reproduced safely on a command line, and a command naming the sanitized form would delete a DIFFERENT parameter.";
|
|
25991
|
+
warn(`Failed to clean up partially-created SSM parameter ${logicalId} (${name}): ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}. ${manualStep}`);
|
|
25634
25992
|
}
|
|
25635
25993
|
throw innerError;
|
|
25636
25994
|
}
|
|
@@ -25858,7 +26216,13 @@ var SSMParameterProvider = class {
|
|
|
25858
26216
|
const shape = isArn ? "an ARN" : "a version / label selector";
|
|
25859
26217
|
const remedy = input.knownPhysicalId === explicit ? `pass the parameter NAME instead: --resource ${input.logicalId}=<parameterName>` : `set Properties.Name to the parameter NAME rather than ${shape}`;
|
|
25860
26218
|
const whyNotDerived = isArn ? " cdkd deliberately does NOT derive the name from the ARN: the leading \"/\" is not recoverable (arn:...:parameter/foo is the ARN of BOTH the simple name \"foo\" and the hierarchical name \"/foo\"), and a parameter shared from another account has no name form at all — so a derived name could silently address a DIFFERENT parameter." : "";
|
|
25861
|
-
|
|
26219
|
+
const readCommand = renderDisableCommand({
|
|
26220
|
+
before: "aws ssm get-parameter --name",
|
|
26221
|
+
identifier: explicit,
|
|
26222
|
+
after: "--query Parameter.Name --output text"
|
|
26223
|
+
});
|
|
26224
|
+
const howToRead = readCommand ? ` Read the name AWS holds with: ${readCommand}` : " Read the name AWS holds via the console: the value cdkd was given cannot be reproduced safely on a command line, so any command shown here would read a different parameter.";
|
|
26225
|
+
throw new ProvisioningError(`Cannot adopt SSM parameter ${input.logicalId} from ${shape} ('${explicit}'): cdkd records a parameter's NAME as its physical id, because SSM's write APIs accept only the name (PutParameter and DeleteParameter both reject an ARN, and a name cannot contain ':'), so the next cdkd deploy and cdkd destroy would fail with a ValidationException. ${remedy}.${whyNotDerived}${howToRead}`, input.resourceType, input.logicalId);
|
|
25862
26226
|
}
|
|
25863
26227
|
/**
|
|
25864
26228
|
* Adopt an existing SSM parameter into cdkd state.
|
|
@@ -65936,7 +66300,7 @@ function indexNestedChildTemplates(template, templatePath) {
|
|
|
65936
66300
|
if (resource?.Type !== "AWS::CloudFormation::Stack") continue;
|
|
65937
66301
|
const assetPath = resource.Metadata?.["aws:asset:path"];
|
|
65938
66302
|
if (typeof assetPath !== "string" || assetPath.length === 0) continue;
|
|
65939
|
-
if (isAbsoluteCrossPlatform(assetPath)) throw new Error(`Nested stack '${logicalId}' has Metadata['aws:asset:path']='${assetPath}' which is absolute. CDK emits relative asset paths for nested templates; an absolute path indicates the synth output was hand-modified or generated by a non-CDK toolchain. Refusing to load.`);
|
|
66303
|
+
if (isAbsoluteCrossPlatform(assetPath)) throw new Error(`Nested stack '${displaySafe(logicalId)}' has Metadata['aws:asset:path']='${displaySafe(assetPath)}' which is absolute. CDK emits relative asset paths for nested templates; an absolute path indicates the synth output was hand-modified or generated by a non-CDK toolchain. Refusing to load.`);
|
|
65940
66304
|
result[logicalId] = path.join(dir, assetPath);
|
|
65941
66305
|
}
|
|
65942
66306
|
return result;
|
|
@@ -66236,7 +66600,7 @@ async function resolveChildStackParameters(parentStackRow, parentTemplate, paren
|
|
|
66236
66600
|
* fail at deploy time.
|
|
66237
66601
|
*/
|
|
66238
66602
|
async function buildDiffTree(args) {
|
|
66239
|
-
const { stackName, displayName, region, template, nestedTemplates, recursive, stateBackend, diffCalculator, parentHasSecretReference, parameters, canonicalizeProperties, assetRedirect, cfnFallback, previewOrphanAdoption } = args;
|
|
66603
|
+
const { stackName, displayName, region, template, nestedTemplates, recursive, stateBackend, diffCalculator, parentHasSecretReference, parameters, canonicalizeProperties, assetRedirect, cfnFallback, previewOrphanAdoption, ancestorTemplatePaths } = args;
|
|
66240
66604
|
const state = await loadStateOrEmpty(stackName, region, stateBackend);
|
|
66241
66605
|
const secretBearingAbove = parentHasSecretReference === true || templateHasSecretDynamicReference(template);
|
|
66242
66606
|
const { changes, outputChanges, adoptedOrphans, adoptedRecords, blocking } = await computeStackDiff(state, template, region, stackName, stateBackend, diffCalculator, {
|
|
@@ -66272,6 +66636,9 @@ async function buildDiffTree(args) {
|
|
|
66272
66636
|
const childTemplatePath = nestedTemplates[logicalId];
|
|
66273
66637
|
if (!childTemplatePath) throw new Error(`Nested template file not found for ${NESTED_STACK_RESOURCE_TYPE} '${logicalId}' under stack '${stackName}'. Verify the synth output emits Metadata['aws:asset:path'] on this resource (CDK 2.x cdk.NestedStack does so by default), then re-run synth.`);
|
|
66274
66638
|
const childStackName = `${stackName}~${logicalId}`;
|
|
66639
|
+
const resolvedChildPath = path.resolve(childTemplatePath);
|
|
66640
|
+
if (ancestorTemplatePaths?.has(resolvedChildPath) === true) throw new Error(`Nested stack '${displaySafe(logicalId)}' under stack '${displaySafe(stackName)}' resolves to nested template '${displaySafe(resolvedChildPath)}', which is already being diffed higher up the same nesting chain. Its Metadata['aws:asset:path'] closes a cycle; CDK emits an acyclic nested template tree, so this indicates the synth output was hand-modified or generated by a non-CDK toolchain. Refusing to diff.`);
|
|
66641
|
+
const childAncestorTemplatePaths = new Set(ancestorTemplatePaths ?? []).add(resolvedChildPath);
|
|
66275
66642
|
const childTemplate = readNestedTemplate(childTemplatePath);
|
|
66276
66643
|
if (assetRedirect) rewriteTemplateAssetReferences(childTemplate, assetRedirect);
|
|
66277
66644
|
const grandchildTemplates = indexNestedChildTemplates(childTemplate, childTemplatePath);
|
|
@@ -66290,6 +66657,7 @@ async function buildDiffTree(args) {
|
|
|
66290
66657
|
...assetRedirect && { assetRedirect },
|
|
66291
66658
|
...cfnFallback !== void 0 && { cfnFallback },
|
|
66292
66659
|
...previewOrphanAdoption && { previewOrphanAdoption },
|
|
66660
|
+
ancestorTemplatePaths: childAncestorTemplatePaths,
|
|
66293
66661
|
parentHasSecretReference: secretBearingAbove
|
|
66294
66662
|
}));
|
|
66295
66663
|
}
|
|
@@ -74531,6 +74899,39 @@ function createStateMigrateCommand() {
|
|
|
74531
74899
|
//#endregion
|
|
74532
74900
|
//#region src/cli/commands/state-list-tree.ts
|
|
74533
74901
|
/**
|
|
74902
|
+
* How deep a node may sit below its root, counting the root as 0.
|
|
74903
|
+
*
|
|
74904
|
+
* A node that would sit deeper is made a root instead (see
|
|
74905
|
+
* {@link buildStackTree}). The value is far beyond any nesting an app is
|
|
74906
|
+
* written with: CloudFormation stops at five levels of nested stacks, so an
|
|
74907
|
+
* app that also has to deploy through CloudFormation cannot exceed that.
|
|
74908
|
+
* cdkd deploys nested stacks through its own engine and imposes no limit of
|
|
74909
|
+
* its own, so a deliberately deeper app CAN produce a tree this reshapes —
|
|
74910
|
+
* which is the point. The view stays whole either way.
|
|
74911
|
+
*
|
|
74912
|
+
* It exists because every step AFTER the tree is built recurses once per
|
|
74913
|
+
* level: this module's own child sort, the box-drawing renderer and
|
|
74914
|
+
* {@link stackTreeToJson}, plus the `JSON.stringify` that serializes that
|
|
74915
|
+
* shape for `--tree --json`. Capping the tree bounds all four at once, which
|
|
74916
|
+
* an iterative rewrite of any of them could not: `JSON.stringify` recurses
|
|
74917
|
+
* into whatever nesting it is handed, and the text view indents four
|
|
74918
|
+
* characters per level, so a chain deep enough would need more indentation
|
|
74919
|
+
* than a string can hold.
|
|
74920
|
+
*
|
|
74921
|
+
* It bounds the DEPTH, not the bucket, and what it does to the failure at the
|
|
74922
|
+
* far end is change its KIND. Before the cap, one deep chain exhausted the
|
|
74923
|
+
* CALL STACK, at a record count nothing fixes — V8's budget moves with frame
|
|
74924
|
+
* size and with what else is on the stack. After it, every recursion is at
|
|
74925
|
+
* most a hundred frames and the remaining ceiling is V8's maximum STRING
|
|
74926
|
+
* length: a capped tree still costs a roughly constant number of characters
|
|
74927
|
+
* per record (the cap's own pretty-print indent dominates in `--tree --json`),
|
|
74928
|
+
* so `JSON.stringify` refuses a large enough bucket deterministically instead
|
|
74929
|
+
* of at a moving depth. Both are far past `renderTreeMode`'s unbounded
|
|
74930
|
+
* one-read-per-reference fan-out, which gives out first. Read the cap as what
|
|
74931
|
+
* stops a SINGLE chain taking the view down, not as a total bound.
|
|
74932
|
+
*/
|
|
74933
|
+
const MAX_STACK_TREE_DEPTH = 100;
|
|
74934
|
+
/**
|
|
74534
74935
|
* Build a parent → child tree from the flat list of state records.
|
|
74535
74936
|
*
|
|
74536
74937
|
* Children are linked to their parent by `(parentStack, parentRegion)`
|
|
@@ -74546,6 +74947,17 @@ function createStateMigrateCommand() {
|
|
|
74546
74947
|
* from both the text and the JSON view. A node hanging off a loop still sits
|
|
74547
74948
|
* under its parent, which is now visible (issue #3069).
|
|
74548
74949
|
*
|
|
74950
|
+
* A node that would sit deeper than {@link MAX_STACK_TREE_DEPTH} levels below
|
|
74951
|
+
* its root is placed at the root too, so the chain resumes there as its own
|
|
74952
|
+
* tree. Every record still appears exactly once, and a tree within the cap is
|
|
74953
|
+
* built as before. Without it a long enough chain took the whole view down with
|
|
74954
|
+
* a `RangeError` instead of costing only its own rows, since the recursive
|
|
74955
|
+
* steps AFTER this function are not covered by the per-stack guard around the
|
|
74956
|
+
* state read. Reaching the depths that failed takes thousands of records — in
|
|
74957
|
+
* practice a hand-edited bucket, or records planted by someone holding
|
|
74958
|
+
* `s3:PutObject` on it — but nothing in cdkd bounds the depth, so the cap does
|
|
74959
|
+
* (issue #3155).
|
|
74960
|
+
*
|
|
74549
74961
|
* The roots and every child list are sorted alphabetically by `stackName`,
|
|
74550
74962
|
* then by `region` (legacy `undefined` last), so output is stable across
|
|
74551
74963
|
* runs.
|
|
@@ -74572,10 +74984,26 @@ function buildStackTree(entries) {
|
|
|
74572
74984
|
if (current !== void 0 && onPath.has(current)) for (const member of path.slice(onPath.get(current))) loopMembers.add(member);
|
|
74573
74985
|
for (const node of path) settled.add(node);
|
|
74574
74986
|
}
|
|
74987
|
+
const depthOf = /* @__PURE__ */ new Map();
|
|
74988
|
+
for (const member of loopMembers) depthOf.set(member, 0);
|
|
74989
|
+
for (const start of byKey.values()) {
|
|
74990
|
+
if (depthOf.has(start)) continue;
|
|
74991
|
+
const path = [];
|
|
74992
|
+
let current = start;
|
|
74993
|
+
while (current !== void 0 && !depthOf.has(current)) {
|
|
74994
|
+
path.push(current);
|
|
74995
|
+
current = parentOf(current);
|
|
74996
|
+
}
|
|
74997
|
+
let depth = current === void 0 ? -1 : depthOf.get(current);
|
|
74998
|
+
for (let i = path.length - 1; i >= 0; i--) {
|
|
74999
|
+
depth = depth + 1 > 100 ? 0 : depth + 1;
|
|
75000
|
+
depthOf.set(path[i], depth);
|
|
75001
|
+
}
|
|
75002
|
+
}
|
|
74575
75003
|
const roots = [];
|
|
74576
75004
|
for (const node of byKey.values()) {
|
|
74577
75005
|
const parent = parentOf(node);
|
|
74578
|
-
if (parent &&
|
|
75006
|
+
if (parent && depthOf.get(node) > 0) {
|
|
74579
75007
|
parent.children.push(node);
|
|
74580
75008
|
continue;
|
|
74581
75009
|
}
|
|
@@ -78609,6 +79037,12 @@ async function stateListCommand(options) {
|
|
|
78609
79037
|
*
|
|
78610
79038
|
* `tree --json` emits the nested {@link import('./state-list-tree.js').StackTreeJson}
|
|
78611
79039
|
* shape; plain `tree` renders `tree(1)`-style box-drawing.
|
|
79040
|
+
*
|
|
79041
|
+
* The `JSON.stringify` below recurses once per level of the shape it is handed,
|
|
79042
|
+
* and so does the box-drawing renderer. Neither needs a depth guard here:
|
|
79043
|
+
* `buildStackTree` caps how deep a node may sit
|
|
79044
|
+
* ({@link import('./state-list-tree.js').MAX_STACK_TREE_DEPTH}), which bounds
|
|
79045
|
+
* both (issue #3155).
|
|
78612
79046
|
*/
|
|
78613
79047
|
async function renderTreeMode(refs, stateBackend, asJson) {
|
|
78614
79048
|
const entries = await Promise.all(refs.map(async (ref) => {
|
|
@@ -94054,4 +94488,4 @@ function buildProgram() {
|
|
|
94054
94488
|
|
|
94055
94489
|
//#endregion
|
|
94056
94490
|
export { buildProgram };
|
|
94057
|
-
//# sourceMappingURL=program-
|
|
94491
|
+
//# sourceMappingURL=program-U03H43s7.js.map
|