@go-to-k/cdkd 0.280.4 → 0.280.6
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-Cyuov8l5.js → asg-provider-6Nts8xtc.js} +2 -2
- package/dist/{asg-provider-Cyuov8l5.js.map → asg-provider-6Nts8xtc.js.map} +1 -1
- package/dist/cli.js +208 -20
- package/dist/cli.js.map +1 -1
- package/dist/{deploy-engine-CttwgBIB.js → deploy-engine-FY8IzgQM.js} +3 -3
- package/dist/{deploy-engine-CttwgBIB.js.map → deploy-engine-FY8IzgQM.js.map} +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { $ as DiffCalculator, $t as resolveBucketRegion, A as red, At as getDockerImageBySourceHash, B as isTerminationProtectionPropagationError, Bt as resolveStateBucketWithDefaultAndSource, C as isStatefulRecreateTargetSync, Ct as validateContainerRepoName, D as cyan, Dt as runDockerForeground, E as bold, En as withErrorHandling, Et as getDockerCmd, F as ProviderRegistry, Ft as resolveApp, G as normalizeAwsTagsToCfn, Gt as CFN_TEMPLATE_URL_LIMIT, H as cfnRefValueFromPhysicalId, Ht as stateBucketExistenceConfirmed, I as findActionableSilentDrops, It as resolveAutoAssetStorage, J as readConfigString, Jt as uploadCfnTemplate, K as resolveExplicitPhysicalId, Kt as MIGRATE_TMP_PREFIX, L as CloudControlProvider, Lt as resolveCaptureObservedState, M as IAMRoleProvider, Mt as synthesisStatusMessage, N as collectInlinePolicyNamesManagedBySiblings, Nt as getDefaultStateBucketName, O as gray, Ot as runDockerStreaming, P as clearOnUpdateRemoval, Pt as getLegacyStateBucketName, Q as applyRoleArnIfSet, R as slowCcOperationTimeoutMs, Rt as resolveSkipPrefix, S as MULTI_REGION_RECREATE_BLOCKED_TYPES, St as validateAssetBucketName, T as formatResourceLine, Tn as normalizeAwsError, Tt as formatDockerLoginError, U as refStateLookupFromResource, Ut as warnDeprecatedNoPrefixCliFlag, V as IntrinsicFunctionResolver, Vt as resolveUseCdkBootstrapAssets, W as WAFv2WebACLProvider, Wt as CFN_TEMPLATE_BODY_LIMIT, X as requireConfigArray, Xt as AssemblyReader, Y as replayWarn, Yt as expectedOwnerParam, Z as requireConfigString, Zt as processStackMessages, _ as createPreDeleteFinalSnapshot, _n as ResourceTimeoutError, _t as AssetModeResolver, a as DeploymentEventsStore, an as CdkdError, at as TemplateParser, b as unsupportedFinalSnapshotError, bn as StackTerminationProtectionError, bt as getBootstrapMarkerKey, c as replayFailedOperations, cn as DeployCancelledError, ct as rebuildClientForBucketRegion, d as IMPLICIT_DELETE_DEPENDENCIES, dn as LocalStartServiceError, dt as stringifyValue, en as AwsClients, et as describeTypeWithThrottleRetry, f as computeImplicitDeleteEdges, ft as WorkGraph, g as ccRoutedFinalSnapshotError, gn as ProvisioningError, gt as rewriteTemplateAssetReferences, h as buildFinalSnapshotIdentifier, hn as PartialFailureError, ht as loadPublishableAssetManifest, i as DeploymentEventsReader, it as DagBuilder, j as yellow, jt as Synthesizer, k as green, kt as AssetManifestLoader, l as replayRollback, ln as LocalInvokeBuildError$1, lt as shouldRetainResource, m as PRE_DELETE_SNAPSHOT_TYPES, mn as NestedStackChildDirectDestroyError, mt as createAssetRedirectResolver, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as resetAwsClients, nt as isRetryableTransientError, o as planFailedOps, ot as LockManager, p as ATOMIC_FINAL_SNAPSHOT_TYPES, pn as MissingCdkCliError, pt as buildAssetRedirectMap, q as assertRegionMatch, qt as findLargeInlineResources, r as DeployEngine, rn as setAwsClients, rt as isThrottlingError, s as planRollback, st as S3StateBackend, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as getAwsClients, tt as withRetry, u as withResourceDeadline, un as LocalMigrateError, ut as AssetPublisher, v as isFinalSnapshotError, vn as ResourceUpdateNotSupportedError, vt as BOOTSTRAP_MARKER_PREFIX, w as renderStatefulReason, wt as buildDockerImage, x as extractDeploymentEventError, xt as parseBootstrapMarker, y as refusesFinalSnapshot, yn as StackHasActiveImportsError, yt as ensureAssetStorage, z as disableInstanceApiTermination, zt as resolveStateBucketWithDefault } from "./deploy-engine-
|
|
2
|
+
import { $ as DiffCalculator, $t as resolveBucketRegion, A as red, At as getDockerImageBySourceHash, B as isTerminationProtectionPropagationError, Bt as resolveStateBucketWithDefaultAndSource, C as isStatefulRecreateTargetSync, Ct as validateContainerRepoName, D as cyan, Dt as runDockerForeground, E as bold, En as withErrorHandling, Et as getDockerCmd, F as ProviderRegistry, Ft as resolveApp, G as normalizeAwsTagsToCfn, Gt as CFN_TEMPLATE_URL_LIMIT, H as cfnRefValueFromPhysicalId, Ht as stateBucketExistenceConfirmed, I as findActionableSilentDrops, It as resolveAutoAssetStorage, J as readConfigString, Jt as uploadCfnTemplate, K as resolveExplicitPhysicalId, Kt as MIGRATE_TMP_PREFIX, L as CloudControlProvider, Lt as resolveCaptureObservedState, M as IAMRoleProvider, Mt as synthesisStatusMessage, N as collectInlinePolicyNamesManagedBySiblings, Nt as getDefaultStateBucketName, O as gray, Ot as runDockerStreaming, P as clearOnUpdateRemoval, Pt as getLegacyStateBucketName, Q as applyRoleArnIfSet, R as slowCcOperationTimeoutMs, Rt as resolveSkipPrefix, S as MULTI_REGION_RECREATE_BLOCKED_TYPES, St as validateAssetBucketName, T as formatResourceLine, Tn as normalizeAwsError, Tt as formatDockerLoginError, U as refStateLookupFromResource, Ut as warnDeprecatedNoPrefixCliFlag, V as IntrinsicFunctionResolver, Vt as resolveUseCdkBootstrapAssets, W as WAFv2WebACLProvider, Wt as CFN_TEMPLATE_BODY_LIMIT, X as requireConfigArray, Xt as AssemblyReader, Y as replayWarn, Yt as expectedOwnerParam, Z as requireConfigString, Zt as processStackMessages, _ as createPreDeleteFinalSnapshot, _n as ResourceTimeoutError, _t as AssetModeResolver, a as DeploymentEventsStore, an as CdkdError, at as TemplateParser, b as unsupportedFinalSnapshotError, bn as StackTerminationProtectionError, bt as getBootstrapMarkerKey, c as replayFailedOperations, cn as DeployCancelledError, ct as rebuildClientForBucketRegion, d as IMPLICIT_DELETE_DEPENDENCIES, dn as LocalStartServiceError, dt as stringifyValue, en as AwsClients, et as describeTypeWithThrottleRetry, f as computeImplicitDeleteEdges, ft as WorkGraph, g as ccRoutedFinalSnapshotError, gn as ProvisioningError, gt as rewriteTemplateAssetReferences, h as buildFinalSnapshotIdentifier, hn as PartialFailureError, ht as loadPublishableAssetManifest, i as DeploymentEventsReader, it as DagBuilder, j as yellow, jt as Synthesizer, k as green, kt as AssetManifestLoader, l as replayRollback, ln as LocalInvokeBuildError$1, lt as shouldRetainResource, m as PRE_DELETE_SNAPSHOT_TYPES, mn as NestedStackChildDirectDestroyError, mt as createAssetRedirectResolver, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as resetAwsClients, nt as isRetryableTransientError, o as planFailedOps, ot as LockManager, p as ATOMIC_FINAL_SNAPSHOT_TYPES, pn as MissingCdkCliError, pt as buildAssetRedirectMap, q as assertRegionMatch, qt as findLargeInlineResources, r as DeployEngine, rn as setAwsClients, rt as isThrottlingError, s as planRollback, st as S3StateBackend, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as getAwsClients, tt as withRetry, u as withResourceDeadline, un as LocalMigrateError, ut as AssetPublisher, v as isFinalSnapshotError, vn as ResourceUpdateNotSupportedError, vt as BOOTSTRAP_MARKER_PREFIX, w as renderStatefulReason, wt as buildDockerImage, x as extractDeploymentEventError, xt as parseBootstrapMarker, y as refusesFinalSnapshot, yn as StackHasActiveImportsError, yt as ensureAssetStorage, z as disableInstanceApiTermination, zt as resolveStateBucketWithDefault } from "./deploy-engine-FY8IzgQM.js";
|
|
3
3
|
import { a as getLiveRenderer, c as PATTERN_B_RESOURCE_TYPES, d as generateResourceNameWithFallback, f as withSkipPrefix, i as runStackBuffered, n as getLogger, o as PATTERN_B_NAME_OPTIONS, p as withStackName, s as PATTERN_B_NAME_PROPERTIES, u as generateResourceName } from "./logger-BYMEE-BS.js";
|
|
4
|
-
import { t as ASGProvider } from "./asg-provider-
|
|
4
|
+
import { t as ASGProvider } from "./asg-provider-6Nts8xtc.js";
|
|
5
5
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
6
6
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
7
7
|
import { CopyObjectCommand, CreateBucketCommand, DeleteBucketAnalyticsConfigurationCommand, DeleteBucketCommand, DeleteBucketCorsCommand, DeleteBucketEncryptionCommand, DeleteBucketIntelligentTieringConfigurationCommand, DeleteBucketInventoryConfigurationCommand, DeleteBucketLifecycleCommand, DeleteBucketMetricsConfigurationCommand, DeleteBucketOwnershipControlsCommand, DeleteBucketPolicyCommand, DeleteBucketReplicationCommand, DeleteBucketTaggingCommand, DeleteBucketWebsiteCommand, DeleteObjectsCommand, GetBucketAccelerateConfigurationCommand, GetBucketCorsCommand, GetBucketEncryptionCommand, GetBucketLifecycleConfigurationCommand, GetBucketLocationCommand, GetBucketLoggingCommand, GetBucketNotificationConfigurationCommand, GetBucketOwnershipControlsCommand, GetBucketPolicyCommand, GetBucketReplicationCommand, GetBucketTaggingCommand, GetBucketVersioningCommand, GetBucketWebsiteCommand, GetObjectCommand, GetObjectLockConfigurationCommand, GetPublicAccessBlockCommand, HeadBucketCommand, ListBucketAnalyticsConfigurationsCommand, ListBucketIntelligentTieringConfigurationsCommand, ListBucketInventoryConfigurationsCommand, ListBucketMetricsConfigurationsCommand, ListObjectVersionsCommand, ListObjectsV2Command, NoSuchBucket, PutBucketAccelerateConfigurationCommand, PutBucketAnalyticsConfigurationCommand, PutBucketCorsCommand, PutBucketEncryptionCommand, PutBucketIntelligentTieringConfigurationCommand, PutBucketInventoryConfigurationCommand, PutBucketLifecycleConfigurationCommand, PutBucketLoggingCommand, PutBucketMetricsConfigurationCommand, PutBucketNotificationConfigurationCommand, PutBucketOwnershipControlsCommand, PutBucketPolicyCommand, PutBucketReplicationCommand, PutBucketTaggingCommand, PutBucketVersioningCommand, PutBucketWebsiteCommand, PutObjectCommand, PutObjectLockConfigurationCommand, PutPublicAccessBlockCommand, S3Client, S3ServiceException } from "@aws-sdk/client-s3";
|
|
@@ -11647,6 +11647,19 @@ function mapSSESpecification(raw) {
|
|
|
11647
11647
|
if (typeof cfn.KMSMasterKeyId === "string") out.KMSMasterKeyId = cfn.KMSMasterKeyId;
|
|
11648
11648
|
return out;
|
|
11649
11649
|
}
|
|
11650
|
+
/**
|
|
11651
|
+
* `DescribeTable` poll budget (seconds, 1 poll/s) after an ordinary
|
|
11652
|
+
* `UpdateTable` — capacity, TTL, tags, table class. Calibrated on those and
|
|
11653
|
+
* sufficient for them.
|
|
11654
|
+
*/
|
|
11655
|
+
const TABLE_ACTIVE_WAIT_ATTEMPTS = 60;
|
|
11656
|
+
/**
|
|
11657
|
+
* The same budget for a BillingMode FLIP, which DynamoDB settles on a
|
|
11658
|
+
* different order of magnitude (measured 2026-08-11: a PAY_PER_REQUEST ->
|
|
11659
|
+
* PROVISIONED flip exceeded 60s and failed the deploy). Kept SEPARATE so a
|
|
11660
|
+
* genuinely wedged capacity edit still fails in a minute rather than ten.
|
|
11661
|
+
*/
|
|
11662
|
+
const BILLING_FLIP_ACTIVE_WAIT_ATTEMPTS = 600;
|
|
11650
11663
|
var DynamoDBTableProvider = class {
|
|
11651
11664
|
dynamoDBClient;
|
|
11652
11665
|
logger = getLogger().child("DynamoDBTableProvider");
|
|
@@ -11762,20 +11775,22 @@ var DynamoDBTableProvider = class {
|
|
|
11762
11775
|
if (table?.TableArn) await this.applyTagDiff(table.TableArn, previousProperties["Tags"], properties["Tags"]);
|
|
11763
11776
|
const normalizeTableClass = (v) => typeof v === "string" && v.length > 0 ? v : "STANDARD";
|
|
11764
11777
|
const tableClassChanged = normalizeTableClass(properties["TableClass"]) !== normalizeTableClass(previousProperties["TableClass"]);
|
|
11778
|
+
const liveBillingMode = table?.BillingModeSummary?.BillingMode ?? "PROVISIONED";
|
|
11765
11779
|
const recordedPrevBillingMode = previousProperties["BillingMode"];
|
|
11766
11780
|
const recordedPrevBillingModeUsable = recordedPrevBillingMode === void 0 || typeof recordedPrevBillingMode === "string" && recordedPrevBillingMode.trim() !== "";
|
|
11767
|
-
const prevBillingMode = recordedPrevBillingModeUsable ? recordedPrevBillingMode
|
|
11781
|
+
const prevBillingMode = recordedPrevBillingModeUsable ? recordedPrevBillingMode ?? (properties["BillingMode"] !== void 0 ? liveBillingMode : "PROVISIONED") : liveBillingMode;
|
|
11768
11782
|
if (!recordedPrevBillingModeUsable) this.logger.warn(`AWS::DynamoDB::Table ${logicalId}: the recorded previous BillingMode is unusable (${JSON.stringify(recordedPrevBillingMode)}) — using the table's actual billing mode (${prevBillingMode}) as the comparison baseline for this update so a corrected template does not issue a same-mode UpdateTable.`);
|
|
11769
11783
|
let billingModeUnusable = false;
|
|
11770
|
-
const requestedBillingMode = properties["BillingMode"] === void 0 ?
|
|
11784
|
+
const requestedBillingMode = properties["BillingMode"] === void 0 ? "PROVISIONED" : requireConfigString(properties["BillingMode"], "PROVISIONED", "AWS::DynamoDB::Table BillingMode", { onUnusable: (message) => {
|
|
11771
11785
|
billingModeUnusable = true;
|
|
11772
|
-
this.logger.warn(`${message} The table's current billing mode
|
|
11786
|
+
this.logger.warn(`${message} The table's current billing mode (${prevBillingMode}) is kept for this update rather than flipped to the default.`);
|
|
11773
11787
|
} });
|
|
11774
11788
|
const billingMode = billingModeUnusable ? prevBillingMode : requestedBillingMode;
|
|
11775
|
-
const billingOrThroughputChanged =
|
|
11789
|
+
const billingOrThroughputChanged = billingMode !== prevBillingMode || JSON.stringify(properties["ProvisionedThroughput"]) !== JSON.stringify(previousProperties["ProvisionedThroughput"]);
|
|
11790
|
+
if (billingMode === "PROVISIONED" && prevBillingMode === "PAY_PER_REQUEST" && properties["OnDemandThroughput"] !== void 0) throw new ProvisioningError(`AWS::DynamoDB::Table ${logicalId}: the template flips BillingMode to PROVISIONED${properties["BillingMode"] === void 0 ? " (BillingMode is absent from the template, which CloudFormation resets to PROVISIONED)" : ""} while still declaring OnDemandThroughput, which AWS accepts only on a PAY_PER_REQUEST table. Nothing was applied. Remove OnDemandThroughput, or keep BillingMode: PAY_PER_REQUEST.`, logicalId, resourceType);
|
|
11776
11791
|
if (billingOrThroughputChanged || tableClassChanged) {
|
|
11777
11792
|
const updateInput = { TableName: physicalId };
|
|
11778
|
-
if (billingOrThroughputChanged
|
|
11793
|
+
if (billingOrThroughputChanged) updateInput.BillingMode = billingMode;
|
|
11779
11794
|
if (tableClassChanged) updateInput.TableClass = normalizeTableClass(properties["TableClass"]);
|
|
11780
11795
|
if (billingOrThroughputChanged && billingMode !== "PAY_PER_REQUEST" && properties["ProvisionedThroughput"]) {
|
|
11781
11796
|
const pt = properties["ProvisionedThroughput"];
|
|
@@ -11785,7 +11800,8 @@ var DynamoDBTableProvider = class {
|
|
|
11785
11800
|
};
|
|
11786
11801
|
}
|
|
11787
11802
|
await this.dynamoDBClient.send(new UpdateTableCommand(updateInput));
|
|
11788
|
-
await this.
|
|
11803
|
+
if (billingMode !== liveBillingMode) await this.waitForTableAndIndexesActive(physicalId, BILLING_FLIP_ACTIVE_WAIT_ATTEMPTS);
|
|
11804
|
+
else await this.waitForTableActiveAfterUpdate(physicalId, TABLE_ACTIVE_WAIT_ATTEMPTS);
|
|
11789
11805
|
this.logger.debug(`Updated BillingMode/ProvisionedThroughput/TableClass on DynamoDB table ${physicalId}`);
|
|
11790
11806
|
}
|
|
11791
11807
|
if (JSON.stringify(properties["OnDemandThroughput"]) !== JSON.stringify(previousProperties["OnDemandThroughput"])) {
|
|
@@ -12233,13 +12249,27 @@ var DynamoDBTableProvider = class {
|
|
|
12233
12249
|
throw new Error(`Table ${tableName} did not reach ACTIVE status within ${maxAttempts} seconds`);
|
|
12234
12250
|
}
|
|
12235
12251
|
/**
|
|
12236
|
-
* Poll DescribeTable until the table
|
|
12237
|
-
*
|
|
12238
|
-
*
|
|
12239
|
-
*
|
|
12240
|
-
*
|
|
12252
|
+
* Poll `DescribeTable` until the table leaves `UPDATING`. Distinct from
|
|
12253
|
+
* `waitForTableActive` because `UpdateTable` transitions the table to
|
|
12254
|
+
* `UPDATING` (not `CREATING`), so a status mismatch must keep polling
|
|
12255
|
+
* rather than throw, and the call may return immediately ACTIVE.
|
|
12256
|
+
*
|
|
12257
|
+
* The 60-attempt (60s) default was calibrated on capacity / TTL / tag edits,
|
|
12258
|
+
* which settle in seconds. A **BillingMode flip does not** — measured
|
|
12259
|
+
* 2026-08-11 on the `dynamodb-ondemand` fixture, a PAY_PER_REQUEST ->
|
|
12260
|
+
* PROVISIONED flip exceeded 60s and failed the deploy with
|
|
12261
|
+
* `did not reach ACTIVE status within 60 seconds`, rolling the whole stack
|
|
12262
|
+
* back. It was invisible before issue #1553 because the removal that
|
|
12263
|
+
* triggers the flip never produced a valid `UpdateTable` at all; making the
|
|
12264
|
+
* call correct is what put a real flip on this path.
|
|
12265
|
+
*
|
|
12266
|
+
* So the timeout is per-OPERATION rather than global: a flip gets 600s (the
|
|
12267
|
+
* same order as the GlobalTable provider's own settle waits), everything
|
|
12268
|
+
* else keeps the 60s that has been sufficient for two years of integs. A
|
|
12269
|
+
* blanket raise would turn every genuinely wedged capacity edit into a
|
|
12270
|
+
* 10-minute hang.
|
|
12241
12271
|
*/
|
|
12242
|
-
async waitForTableActiveAfterUpdate(tableName, maxAttempts =
|
|
12272
|
+
async waitForTableActiveAfterUpdate(tableName, maxAttempts = TABLE_ACTIVE_WAIT_ATTEMPTS) {
|
|
12243
12273
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
12244
12274
|
if ((await this.dynamoDBClient.send(new DescribeTableCommand({ TableName: tableName }))).Table?.TableStatus === "ACTIVE") return;
|
|
12245
12275
|
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
@@ -12954,7 +12984,8 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
12954
12984
|
if (tableArn) await this.applyTagDiff(tableArn, extractLocalTags(previousProperties), extractLocalTags(properties));
|
|
12955
12985
|
const recordedOldBilling = previousProperties["BillingMode"];
|
|
12956
12986
|
const recordedOldBillingUsable = recordedOldBilling === void 0 || typeof recordedOldBilling === "string" && recordedOldBilling.trim() !== "";
|
|
12957
|
-
const
|
|
12987
|
+
const liveBillingMode = describeResp.Table?.BillingModeSummary?.BillingMode ?? "PROVISIONED";
|
|
12988
|
+
const oldBilling = recordedOldBillingUsable ? recordedOldBilling ?? "PAY_PER_REQUEST" : liveBillingMode;
|
|
12958
12989
|
if (!recordedOldBillingUsable) this.logger.warn(`AWS::DynamoDB::GlobalTable ${logicalId}: the recorded previous BillingMode is unusable (${JSON.stringify(recordedOldBilling)}) — using the table's actual billing mode (${oldBilling}) as the comparison baseline for this update so a corrected template does not issue a same-mode UpdateTable.`);
|
|
12959
12990
|
let billingUnusable = false;
|
|
12960
12991
|
const requestedBilling = requireConfigString(properties["BillingMode"], "PAY_PER_REQUEST", "AWS::DynamoDB::GlobalTable BillingMode", { onUnusable: (message) => {
|
|
@@ -12970,12 +13001,12 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
12970
13001
|
let desiredGsiUnusable = false;
|
|
12971
13002
|
const onUnusableGsiBlock = (message) => {
|
|
12972
13003
|
desiredGsiUnusable = true;
|
|
12973
|
-
this.logger.warn(
|
|
13004
|
+
this.logger.warn(`AWS::DynamoDB::GlobalTable ${logicalId}: ${message} No GlobalSecondaryIndexes change is applied by this update; the table's existing indexes are left untouched.`);
|
|
12974
13005
|
};
|
|
12975
13006
|
let previousGsiUnusable = false;
|
|
12976
13007
|
const onUnusablePreviousGsiBlock = (message) => {
|
|
12977
13008
|
previousGsiUnusable = true;
|
|
12978
|
-
this.logger.warn(
|
|
13009
|
+
this.logger.warn(`AWS::DynamoDB::GlobalTable ${logicalId}: ${message} (recorded in cdkd state, not in the template) — using the table's LIVE indexes as the comparison baseline for this update, so a corrected template still applies.`);
|
|
12979
13010
|
};
|
|
12980
13011
|
const liveIndexNames = new Set((describeResp.Table?.GlobalSecondaryIndexes ?? []).map((live) => live.IndexName).filter((name) => typeof name === "string"));
|
|
12981
13012
|
const desiredSdkIndexes = toSdkGlobalSecondaryIndexes(properties, currentRegion, newBilling, "min", flippingToProvisioned ? void 0 : diagnostics, onUnusableGsiBlock);
|
|
@@ -13182,11 +13213,15 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
13182
13213
|
await this.waitForReplicaActive(physicalId, region, logicalId);
|
|
13183
13214
|
}
|
|
13184
13215
|
const previousTranslatedIndexes = toSdkGlobalSecondaryIndexes(previousProperties, currentRegion, oldBilling, "min", void 0, onUnusablePreviousGsiBlock);
|
|
13185
|
-
const previousSdkIndexes = previousGsiUnusable ? desiredSdkIndexes
|
|
13216
|
+
const previousSdkIndexes = previousGsiUnusable ? buildLiveRecoveryGsiBaseline(desiredSdkIndexes, describeResp.Table?.GlobalSecondaryIndexes, {
|
|
13217
|
+
carryLiveValues: oldBilling === newBilling && liveBillingMode === newBilling,
|
|
13218
|
+
liveBillingMode,
|
|
13219
|
+
uncomparableCapacityIndexNames: collectUncomparableCapacityGsiNames(properties, currentRegion)
|
|
13220
|
+
}) : previousTranslatedIndexes;
|
|
13186
13221
|
if (previousGsiUnusable) {
|
|
13187
13222
|
const desiredNames = new Set(desiredSdkIndexes.map((gsi) => gsi.IndexName).filter((name) => typeof name === "string"));
|
|
13188
13223
|
const liveOnly = [...liveIndexNames].filter((name) => !desiredNames.has(name));
|
|
13189
|
-
if (liveOnly.length > 0) this.logger.warn(`AWS::DynamoDB::GlobalTable ${logicalId}: ${liveOnly.join(", ")} exist(s) on the table but not in the template. No Delete is issued while the recorded GlobalSecondaryIndexes are unusable — a junk state record cannot prove cdkd created them.
|
|
13224
|
+
if (liveOnly.length > 0) this.logger.warn(`AWS::DynamoDB::GlobalTable ${logicalId}: ${liveOnly.join(", ")} exist(s) on the table but not in the template. No Delete is issued while the recorded GlobalSecondaryIndexes are unusable — a junk state record cannot prove cdkd created them, and an index delete is irreversible. This does NOT self-heal, and no later cdkd deploy will remove them: delete the index directly, e.g. aws dynamodb update-table --table-name ${physicalId} --global-secondary-index-updates '[{"Delete":{"IndexName":"<name>"}}]'.`);
|
|
13190
13225
|
}
|
|
13191
13226
|
const gsiDiff = desiredGsiUnusable ? {
|
|
13192
13227
|
added: [],
|
|
@@ -14941,6 +14976,159 @@ function collectRawOnDemandDeclarations(properties, region) {
|
|
|
14941
14976
|
return out;
|
|
14942
14977
|
}
|
|
14943
14978
|
/**
|
|
14979
|
+
* Names of the GSIs whose PROVISIONED capacity must NOT be compared against
|
|
14980
|
+
* the live table (issue #1571). Two disjoint reasons, and both are about the
|
|
14981
|
+
* DESIRED side being a number the template did not actually choose:
|
|
14982
|
+
*
|
|
14983
|
+
* - **auto-scaled** — the live number is owned by Application Auto Scaling
|
|
14984
|
+
* while the translation takes `MinCapacity` (see {@link CapacitySource}), so
|
|
14985
|
+
* a live 10 against a template min of 1 reads as a scale-down NOBODY ASKED
|
|
14986
|
+
* FOR. This was one of the three measured failure modes of the first
|
|
14987
|
+
* value-carrying baseline (#1551), and the only one that cannot be resolved
|
|
14988
|
+
* by reading the live side more carefully — both numbers are correct, they
|
|
14989
|
+
* answer different questions.
|
|
14990
|
+
* - **not template-determined** — no capacity resolves to a finite number, so
|
|
14991
|
+
* `toSdkGlobalSecondaryIndexes` substitutes `DEFAULT_CAPACITY_UNITS` (5/5,
|
|
14992
|
+
* the issue #1511 class). Comparing that invented default against the live
|
|
14993
|
+
* value turns it into a WRITE. Found by review, not by the first cut.
|
|
14994
|
+
*
|
|
14995
|
+
* Walks the same two sources as {@link toSdkGlobalSecondaryIndexes} and
|
|
14996
|
+
* {@link collectRawOnDemandDeclarations} — the LOCAL replica's index entry
|
|
14997
|
+
* first (the canonical CDK spelling), the GSI-level block as the
|
|
14998
|
+
* hand-authored fallback for read, the top-level GSI for write. A divergence
|
|
14999
|
+
* between the three is what would re-open the bug, so they are kept adjacent.
|
|
15000
|
+
*
|
|
15001
|
+
* The auto-scaling arm tests PRESENCE of the `*CapacityAutoScalingSettings`
|
|
15002
|
+
* key, not whether its members coerce: a block that is itself an unresolved
|
|
15003
|
+
* intrinsic still means "this index is autoscaled", and "might be" must read
|
|
15004
|
+
* as "is" — a false negative issues an unrequested scale-down.
|
|
15005
|
+
*
|
|
15006
|
+
* KNOWN BOUND: detection is TEMPLATE-side only. An index autoscaled OUT OF
|
|
15007
|
+
* BAND (no `*CapacityAutoScalingSettings` in the template) has a
|
|
15008
|
+
* template-determined capacity, so its live value IS compared and the recovery
|
|
15009
|
+
* deploy re-asserts the template's number — which is what a clean previous
|
|
15010
|
+
* side would also have done, so this matches ordinary cdkd semantics rather
|
|
15011
|
+
* than being a recovery-path defect.
|
|
15012
|
+
*
|
|
15013
|
+
* And the cost of a false positive is a PERMANENT divergence, not "one deploy
|
|
15014
|
+
* of lag": the deploy records the template value as state, so the next diff
|
|
15015
|
+
* sees previous == desired and never revisits it. That is the same self-heal
|
|
15016
|
+
* fallacy this PR corrects in the live-only warning; it is accepted here
|
|
15017
|
+
* because the alternative is writing a number the user never asked for.
|
|
15018
|
+
*/
|
|
15019
|
+
function collectUncomparableCapacityGsiNames(properties, region) {
|
|
15020
|
+
const out = /* @__PURE__ */ new Set();
|
|
15021
|
+
const rawIndexes = properties["GlobalSecondaryIndexes"];
|
|
15022
|
+
if (!Array.isArray(rawIndexes)) return out;
|
|
15023
|
+
const replicas = properties["Replicas"];
|
|
15024
|
+
const localReplicaIndexes = asRecord$1(Array.isArray(replicas) ? replicas.find((r) => asRecord$1(r)?.["Region"] === region) : void 0)?.["GlobalSecondaryIndexes"];
|
|
15025
|
+
const localByName = /* @__PURE__ */ new Map();
|
|
15026
|
+
if (Array.isArray(localReplicaIndexes)) for (const entry of localReplicaIndexes) {
|
|
15027
|
+
const record = asRecord$1(entry);
|
|
15028
|
+
const name = record?.["IndexName"];
|
|
15029
|
+
if (record && typeof name === "string") localByName.set(name, record);
|
|
15030
|
+
}
|
|
15031
|
+
for (const entry of rawIndexes) {
|
|
15032
|
+
const gsi = asRecord$1(entry);
|
|
15033
|
+
const name = gsi?.["IndexName"];
|
|
15034
|
+
if (!gsi || typeof name !== "string") continue;
|
|
15035
|
+
const readBlocks = [localByName.get(name)?.["ReadProvisionedThroughputSettings"], gsi["ReadProvisionedThroughputSettings"]];
|
|
15036
|
+
const writeBlock = gsi["WriteProvisionedThroughputSettings"];
|
|
15037
|
+
if (readBlocks.some((block) => asRecord$1(block)?.["ReadCapacityAutoScalingSettings"] !== void 0 || isUnresolvedIntrinsicBlock(block)) || asRecord$1(writeBlock)?.["WriteCapacityAutoScalingSettings"] !== void 0 || isUnresolvedIntrinsicBlock(writeBlock)) {
|
|
15038
|
+
out.add(name);
|
|
15039
|
+
continue;
|
|
15040
|
+
}
|
|
15041
|
+
const explicitProvisioned = asRecord$1(gsi["ProvisionedThroughput"]);
|
|
15042
|
+
const derivedRead = toFiniteNumber(explicitProvisioned?.["ReadCapacityUnits"]) ?? deriveReadCapacityUnits(asRecord$1(readBlocks[0])) ?? deriveReadCapacityUnits(asRecord$1(readBlocks[1]));
|
|
15043
|
+
const derivedWrite = toFiniteNumber(explicitProvisioned?.["WriteCapacityUnits"]) ?? deriveWriteCapacityUnits(asRecord$1(writeBlock));
|
|
15044
|
+
if (derivedRead === void 0 || derivedWrite === void 0) out.add(name);
|
|
15045
|
+
}
|
|
15046
|
+
return out;
|
|
15047
|
+
}
|
|
15048
|
+
/**
|
|
15049
|
+
* Build the GSI diff baseline used when the cdkd STATE record's
|
|
15050
|
+
* `GlobalSecondaryIndexes` is present-but-unusable (issue #1571, closing the
|
|
15051
|
+
* residual #1551 / PR #1562 measured and deliberately left open).
|
|
15052
|
+
*
|
|
15053
|
+
* The entries are the DESIRED indexes filtered to the names that EXIST live —
|
|
15054
|
+
* the live table is the only side that can answer "does this index exist",
|
|
15055
|
+
* which is what stops the permanent index loss #1562 was about. What #1562
|
|
15056
|
+
* could not settle was the VALUES: it copied the desired entry verbatim, so
|
|
15057
|
+
* the diff could only ever produce ADDs and every capacity / ceiling edit
|
|
15058
|
+
* silently lagged one deploy.
|
|
15059
|
+
*
|
|
15060
|
+
* This carries live VALUES, but only the ones that are safe to compare, and
|
|
15061
|
+
* the exclusions are the three measured failure modes of the first attempt
|
|
15062
|
+
* (recorded at the `liveIndexNames` call site) rather than a guess:
|
|
15063
|
+
*
|
|
15064
|
+
* - **`ProvisionedThroughput` is gated on the LIVE billing mode.**
|
|
15065
|
+
* `DescribeTable` reports `{ReadCapacityUnits: 0, WriteCapacityUnits: 0}`
|
|
15066
|
+
* for every index of a PAY_PER_REQUEST table, so an ungated read produced a
|
|
15067
|
+
* "modified" on every index and re-sent `{0, 0}`, which AWS rejects.
|
|
15068
|
+
* - **An AUTOSCALED index keeps identity-only capacity.** The live number
|
|
15069
|
+
* belongs to Application Auto Scaling; the desired side is `MinCapacity`.
|
|
15070
|
+
* Comparing them reads as a scale-down the user never asked for.
|
|
15071
|
+
* - **`KeySchema` / `Projection` / `WarmThroughput` are always the desired
|
|
15072
|
+
* side.** All three are copied from `desired`, so they can never DIFFER —
|
|
15073
|
+
* deliberately. `KeySchema` and `Projection` are immutable on an existing
|
|
15074
|
+
* index (a difference produces only a "recreate the index" warning, and
|
|
15075
|
+
* AWS's `Projection.NonKeyAttributes` READBACK ORDER is not guaranteed, so
|
|
15076
|
+
* the comparison would be a coin flip); `WarmThroughput` reads back with a
|
|
15077
|
+
* `Status` member the translated shape does not carry, and DynamoDB warm
|
|
15078
|
+
* throughput is increase-only, so a manufactured difference is a real
|
|
15079
|
+
* AWS-side risk.
|
|
15080
|
+
*
|
|
15081
|
+
* `OnDemandThroughput` IS carried from live on an on-demand table, and that is
|
|
15082
|
+
* the second thing this fixes: the `modified` loop derives the `-1` ceiling
|
|
15083
|
+
* RESET (the #1160 absent-field-removal class) from the PREVIOUS side, so with
|
|
15084
|
+
* a desired-copy baseline a template that dropped `MaxReadRequestUnits` left
|
|
15085
|
+
* the old ceiling live in AWS forever while cdkd reported success.
|
|
15086
|
+
*
|
|
15087
|
+
* REMOVES are still not derivable here and are deliberately absent: an index
|
|
15088
|
+
* live but not in the template is in NEITHER side, so no `Delete` is issued.
|
|
15089
|
+
* A junk state record cannot distinguish "cdkd created this and the template
|
|
15090
|
+
* dropped it" from "somebody added it out of band", the delete is irreversible
|
|
15091
|
+
* and a re-create costs a full backfill. The caller names the live-only
|
|
15092
|
+
* indexes and points at the remedy.
|
|
15093
|
+
*/
|
|
15094
|
+
function buildLiveRecoveryGsiBaseline(desiredSdkIndexes, liveIndexes, options) {
|
|
15095
|
+
const liveByName = /* @__PURE__ */ new Map();
|
|
15096
|
+
for (const live of liveIndexes ?? []) if (typeof live.IndexName === "string") liveByName.set(live.IndexName, live);
|
|
15097
|
+
const baseline = [];
|
|
15098
|
+
for (const desired of desiredSdkIndexes) {
|
|
15099
|
+
if (typeof desired.IndexName !== "string") continue;
|
|
15100
|
+
const live = liveByName.get(desired.IndexName);
|
|
15101
|
+
if (!live) continue;
|
|
15102
|
+
const entry = { ...desired };
|
|
15103
|
+
if (!options.carryLiveValues) {
|
|
15104
|
+
baseline.push(entry);
|
|
15105
|
+
continue;
|
|
15106
|
+
}
|
|
15107
|
+
if (options.liveBillingMode === "PROVISIONED") {
|
|
15108
|
+
if (options.uncomparableCapacityIndexNames.has(desired.IndexName)) {
|
|
15109
|
+
baseline.push(entry);
|
|
15110
|
+
continue;
|
|
15111
|
+
}
|
|
15112
|
+
const liveRead = toFiniteNumber(live.ProvisionedThroughput?.ReadCapacityUnits);
|
|
15113
|
+
const liveWrite = toFiniteNumber(live.ProvisionedThroughput?.WriteCapacityUnits);
|
|
15114
|
+
if (desired.ProvisionedThroughput && liveRead !== void 0 && liveWrite !== void 0) entry.ProvisionedThroughput = {
|
|
15115
|
+
ReadCapacityUnits: liveRead,
|
|
15116
|
+
WriteCapacityUnits: liveWrite
|
|
15117
|
+
};
|
|
15118
|
+
} else {
|
|
15119
|
+
const liveRead = toFiniteNumber(live.OnDemandThroughput?.MaxReadRequestUnits);
|
|
15120
|
+
const liveWrite = toFiniteNumber(live.OnDemandThroughput?.MaxWriteRequestUnits);
|
|
15121
|
+
if (liveRead === void 0 && liveWrite === void 0) delete entry.OnDemandThroughput;
|
|
15122
|
+
else entry.OnDemandThroughput = {
|
|
15123
|
+
...liveRead !== void 0 && { MaxReadRequestUnits: liveRead },
|
|
15124
|
+
...liveWrite !== void 0 && { MaxWriteRequestUnits: liveWrite }
|
|
15125
|
+
};
|
|
15126
|
+
}
|
|
15127
|
+
baseline.push(entry);
|
|
15128
|
+
}
|
|
15129
|
+
return baseline;
|
|
15130
|
+
}
|
|
15131
|
+
/**
|
|
14944
15132
|
* Translate a CFn `Replicas[].GlobalSecondaryIndexes[]` blob (the
|
|
14945
15133
|
* `ReplicaGlobalSecondaryIndexSpecification` shape) into the SDK's
|
|
14946
15134
|
* `ReplicaGlobalSecondaryIndex[]` used by `CreateReplicationGroupMemberAction`
|
|
@@ -68549,7 +68737,7 @@ function createMigrateCommand() {
|
|
|
68549
68737
|
*/
|
|
68550
68738
|
function buildProgram() {
|
|
68551
68739
|
const program = new Command();
|
|
68552
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.280.
|
|
68740
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.280.6");
|
|
68553
68741
|
program.hook("preAction", (_thisCommand, actionCommand) => {
|
|
68554
68742
|
const { profile } = actionCommand.optsWithGlobals();
|
|
68555
68743
|
if (profile !== void 0) process.env["AWS_PROFILE"] = profile;
|