@go-to-k/cdkd 0.278.30 → 0.278.32
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-CfVwvyCi.js → asg-provider-DLlrKwbd.js} +2 -2
- package/dist/{asg-provider-CfVwvyCi.js.map → asg-provider-DLlrKwbd.js.map} +1 -1
- package/dist/cli.js +530 -91
- package/dist/cli.js.map +1 -1
- package/dist/{deploy-engine-BLIg6xfm.js → deploy-engine-CEVFHtcP.js} +3 -3
- package/dist/{deploy-engine-BLIg6xfm.js.map → deploy-engine-CEVFHtcP.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 withRetry, $t as getAwsClients, A as red, At as synthesisStatusMessage, B as isTerminationProtectionPropagationError, Bt as stateBucketExistenceConfirmed, C as isStatefulRecreateTargetSync, Cn as normalizeAwsError, Ct as formatDockerLoginError, D as cyan, Dt as AssetManifestLoader, E as bold, Et as runDockerStreaming, F as ProviderRegistry, Ft as resolveCaptureObservedState, G as normalizeAwsTagsToCfn, Gt as findLargeInlineResources, H as cfnRefValueFromPhysicalId, Ht as CFN_TEMPLATE_BODY_LIMIT, I as findActionableSilentDrops, It as resolveSkipPrefix, J as readConfigString, Jt as AssemblyReader, K as resolveExplicitPhysicalId, Kt as uploadCfnTemplate, L as CloudControlProvider, Lt as resolveStateBucketWithDefault, M as IAMRoleProvider, Mt as getLegacyStateBucketName, N as collectInlinePolicyNamesManagedBySiblings, Nt as resolveApp, O as gray, Ot as getDockerImageBySourceHash, P as clearOnUpdateRemoval, Pt as resolveAutoAssetStorage, Q as describeTypeWithThrottleRetry, Qt as AwsClients, R as slowCcOperationTimeoutMs, Rt as resolveStateBucketWithDefaultAndSource, S as MULTI_REGION_RECREATE_BLOCKED_TYPES, St as buildDockerImage, T as formatResourceLine, Tt as runDockerForeground, U as refStateLookupFromResource, Ut as CFN_TEMPLATE_URL_LIMIT, V as IntrinsicFunctionResolver, Vt as warnDeprecatedNoPrefixCliFlag, W as WAFv2WebACLProvider, Wt as MIGRATE_TMP_PREFIX, X as applyRoleArnIfSet, Y as requireConfigString, Yt as processStackMessages, Z as DiffCalculator, Zt as resolveBucketRegion, _ as createPreDeleteFinalSnapshot, _n as StackHasActiveImportsError, _t as ensureAssetStorage, a as DeploymentEventsStore, at as S3StateBackend, b as unsupportedFinalSnapshotError, bt as validateAssetBucketName, c as replayFailedOperations, cn as LocalMigrateError, ct as AssetPublisher, d as IMPLICIT_DELETE_DEPENDENCIES, dn as MissingCdkCliError, dt as buildAssetRedirectMap, en as resetAwsClients, et as isRetryableTransientError, f as computeImplicitDeleteEdges, fn as NestedStackChildDirectDestroyError, ft as createAssetRedirectResolver, g as ccRoutedFinalSnapshotError, gn as ResourceUpdateNotSupportedError, gt as BOOTSTRAP_MARKER_PREFIX, h as buildFinalSnapshotIdentifier, hn as ResourceTimeoutError, ht as AssetModeResolver, i as DeploymentEventsReader, it as LockManager, j as yellow, jt as getDefaultStateBucketName, k as green, kt as Synthesizer, l as replayRollback, ln as LocalStartServiceError, lt as stringifyValue, m as PRE_DELETE_SNAPSHOT_TYPES, mn as ProvisioningError, mt as rewriteTemplateAssetReferences, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nt as DagBuilder, o as planFailedOps, on as DeployCancelledError, ot as rebuildClientForBucketRegion, p as ATOMIC_FINAL_SNAPSHOT_TYPES, pn as PartialFailureError, pt as loadPublishableAssetManifest, q as assertRegionMatch, qt as expectedOwnerParam, r as DeployEngine, rn as CdkdError, rt as TemplateParser, s as planRollback, sn as LocalInvokeBuildError$1, st as shouldRetainResource, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as setAwsClients, tt as isThrottlingError, u as withResourceDeadline, ut as WorkGraph, v as isFinalSnapshotError, vn as StackTerminationProtectionError, vt as getBootstrapMarkerKey, w as renderStatefulReason, wn as withErrorHandling, wt as getDockerCmd, x as extractDeploymentEventError, xt as validateContainerRepoName, y as refusesFinalSnapshot, yt as parseBootstrapMarker, z as disableInstanceApiTermination, zt as resolveUseCdkBootstrapAssets } from "./deploy-engine-
|
|
2
|
+
import { $ as withRetry, $t as getAwsClients, A as red, At as synthesisStatusMessage, B as isTerminationProtectionPropagationError, Bt as stateBucketExistenceConfirmed, C as isStatefulRecreateTargetSync, Cn as normalizeAwsError, Ct as formatDockerLoginError, D as cyan, Dt as AssetManifestLoader, E as bold, Et as runDockerStreaming, F as ProviderRegistry, Ft as resolveCaptureObservedState, G as normalizeAwsTagsToCfn, Gt as findLargeInlineResources, H as cfnRefValueFromPhysicalId, Ht as CFN_TEMPLATE_BODY_LIMIT, I as findActionableSilentDrops, It as resolveSkipPrefix, J as readConfigString, Jt as AssemblyReader, K as resolveExplicitPhysicalId, Kt as uploadCfnTemplate, L as CloudControlProvider, Lt as resolveStateBucketWithDefault, M as IAMRoleProvider, Mt as getLegacyStateBucketName, N as collectInlinePolicyNamesManagedBySiblings, Nt as resolveApp, O as gray, Ot as getDockerImageBySourceHash, P as clearOnUpdateRemoval, Pt as resolveAutoAssetStorage, Q as describeTypeWithThrottleRetry, Qt as AwsClients, R as slowCcOperationTimeoutMs, Rt as resolveStateBucketWithDefaultAndSource, S as MULTI_REGION_RECREATE_BLOCKED_TYPES, St as buildDockerImage, T as formatResourceLine, Tt as runDockerForeground, U as refStateLookupFromResource, Ut as CFN_TEMPLATE_URL_LIMIT, V as IntrinsicFunctionResolver, Vt as warnDeprecatedNoPrefixCliFlag, W as WAFv2WebACLProvider, Wt as MIGRATE_TMP_PREFIX, X as applyRoleArnIfSet, Y as requireConfigString, Yt as processStackMessages, Z as DiffCalculator, Zt as resolveBucketRegion, _ as createPreDeleteFinalSnapshot, _n as StackHasActiveImportsError, _t as ensureAssetStorage, a as DeploymentEventsStore, at as S3StateBackend, b as unsupportedFinalSnapshotError, bt as validateAssetBucketName, c as replayFailedOperations, cn as LocalMigrateError, ct as AssetPublisher, d as IMPLICIT_DELETE_DEPENDENCIES, dn as MissingCdkCliError, dt as buildAssetRedirectMap, en as resetAwsClients, et as isRetryableTransientError, f as computeImplicitDeleteEdges, fn as NestedStackChildDirectDestroyError, ft as createAssetRedirectResolver, g as ccRoutedFinalSnapshotError, gn as ResourceUpdateNotSupportedError, gt as BOOTSTRAP_MARKER_PREFIX, h as buildFinalSnapshotIdentifier, hn as ResourceTimeoutError, ht as AssetModeResolver, i as DeploymentEventsReader, it as LockManager, j as yellow, jt as getDefaultStateBucketName, k as green, kt as Synthesizer, l as replayRollback, ln as LocalStartServiceError, lt as stringifyValue, m as PRE_DELETE_SNAPSHOT_TYPES, mn as ProvisioningError, mt as rewriteTemplateAssetReferences, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nt as DagBuilder, o as planFailedOps, on as DeployCancelledError, ot as rebuildClientForBucketRegion, p as ATOMIC_FINAL_SNAPSHOT_TYPES, pn as PartialFailureError, pt as loadPublishableAssetManifest, q as assertRegionMatch, qt as expectedOwnerParam, r as DeployEngine, rn as CdkdError, rt as TemplateParser, s as planRollback, sn as LocalInvokeBuildError$1, st as shouldRetainResource, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as setAwsClients, tt as isThrottlingError, u as withResourceDeadline, ut as WorkGraph, v as isFinalSnapshotError, vn as StackTerminationProtectionError, vt as getBootstrapMarkerKey, w as renderStatefulReason, wn as withErrorHandling, wt as getDockerCmd, x as extractDeploymentEventError, xt as validateContainerRepoName, y as refusesFinalSnapshot, yt as parseBootstrapMarker, z as disableInstanceApiTermination, zt as resolveUseCdkBootstrapAssets } from "./deploy-engine-CEVFHtcP.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-DLlrKwbd.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";
|
|
@@ -12328,9 +12328,10 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
12328
12328
|
StreamViewType: "NEW_AND_OLD_IMAGES"
|
|
12329
12329
|
};
|
|
12330
12330
|
}
|
|
12331
|
+
const diagnostics = [];
|
|
12331
12332
|
let sdkIndexes;
|
|
12332
12333
|
try {
|
|
12333
|
-
sdkIndexes = toSdkGlobalSecondaryIndexes(properties, currentRegion, billingMode);
|
|
12334
|
+
sdkIndexes = toSdkGlobalSecondaryIndexes(properties, currentRegion, billingMode, "min", diagnostics);
|
|
12334
12335
|
} catch (error) {
|
|
12335
12336
|
throw new ProvisioningError(error instanceof Error ? error.message : String(error), resourceType, logicalId, void 0, error instanceof Error ? error : void 0);
|
|
12336
12337
|
}
|
|
@@ -12345,10 +12346,21 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
12345
12346
|
const dpeResolved = extractLocalDeletionProtection(properties, currentRegion);
|
|
12346
12347
|
if (dpeResolved !== void 0) createParams.DeletionProtectionEnabled = dpeResolved;
|
|
12347
12348
|
if (properties["TableClass"]) createParams.TableClass = properties["TableClass"];
|
|
12348
|
-
const
|
|
12349
|
-
|
|
12349
|
+
const createCeilings = collectTableOnDemandCeilings(properties, currentRegion, diagnostics);
|
|
12350
|
+
const createOnDemand = {
|
|
12351
|
+
...createCeilings.read.value !== void 0 && { MaxReadRequestUnits: createCeilings.read.value },
|
|
12352
|
+
...createCeilings.write.value !== void 0 && { MaxWriteRequestUnits: createCeilings.write.value }
|
|
12353
|
+
};
|
|
12354
|
+
if (billingMode !== "PROVISIONED" && Object.keys(createOnDemand).length > 0) createParams.OnDemandThroughput = createOnDemand;
|
|
12350
12355
|
const localReplicaTags = replicas.find((r) => r["Region"] === currentRegion)?.["Tags"];
|
|
12351
12356
|
if (localReplicaTags && localReplicaTags.length > 0) createParams.Tags = localReplicaTags;
|
|
12357
|
+
for (const replica of replicas) {
|
|
12358
|
+
const replicaRegion = replica["Region"];
|
|
12359
|
+
if (!replicaRegion || replicaRegion === currentRegion) continue;
|
|
12360
|
+
toSdkReplicaGlobalSecondaryIndexes(replica["GlobalSecondaryIndexes"], billingMode, diagnostics);
|
|
12361
|
+
toSdkReplicaThroughputOverrides(replica, billingMode, diagnostics, {});
|
|
12362
|
+
}
|
|
12363
|
+
this.reportThroughputDiagnostics(diagnostics, logicalId, tableName);
|
|
12352
12364
|
try {
|
|
12353
12365
|
await this.dynamoDBClient.send(new CreateTableCommand(createParams));
|
|
12354
12366
|
this.logger.debug(`CreateTable initiated for ${tableName}, waiting for ACTIVE`);
|
|
@@ -12361,7 +12373,7 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
12361
12373
|
for (const replica of replicas) {
|
|
12362
12374
|
const region = replica["Region"];
|
|
12363
12375
|
if (!region || region === currentRegion) continue;
|
|
12364
|
-
await this.addReplica(tableName, replica, region, logicalId);
|
|
12376
|
+
await this.addReplica(tableName, replica, region, logicalId, billingMode);
|
|
12365
12377
|
}
|
|
12366
12378
|
for (const replica of replicas) {
|
|
12367
12379
|
const region = replica["Region"];
|
|
@@ -12425,6 +12437,24 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
12425
12437
|
}
|
|
12426
12438
|
}
|
|
12427
12439
|
/**
|
|
12440
|
+
* Log a batch of {@link ThroughputDiagnostic}s at WARN, de-duplicated.
|
|
12441
|
+
*
|
|
12442
|
+
* The translators run over every index on both the create and the update
|
|
12443
|
+
* path, so the same malformed block can be reported more than once in one
|
|
12444
|
+
* call; a repeated warning reads as repeated damage and is worse than
|
|
12445
|
+
* useless. Kept as ONE formatter so the five translation call sites cannot
|
|
12446
|
+
* word the same defect differently.
|
|
12447
|
+
*/
|
|
12448
|
+
reportThroughputDiagnostics(diagnostics, logicalId, physicalId) {
|
|
12449
|
+
const seen = /* @__PURE__ */ new Set();
|
|
12450
|
+
for (const diagnostic of diagnostics) {
|
|
12451
|
+
const line = `DynamoDB GlobalTable ${logicalId}: ${diagnostic.indexName ? `GSI '${diagnostic.indexName}' on ${physicalId}` : physicalId}: ${diagnostic.message}`;
|
|
12452
|
+
if (seen.has(line)) continue;
|
|
12453
|
+
seen.add(line);
|
|
12454
|
+
this.logger.warn(line);
|
|
12455
|
+
}
|
|
12456
|
+
}
|
|
12457
|
+
/**
|
|
12428
12458
|
* Add a single replica region. Issues one `UpdateTableCommand` with
|
|
12429
12459
|
* `ReplicaUpdates: [{ Create: { RegionName, ... } }]` and polls
|
|
12430
12460
|
* `DescribeTable` until the replica's `ReplicaStatus` flips to ACTIVE.
|
|
@@ -12445,11 +12475,14 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
12445
12475
|
* is the granularity cdkd's pre-flight property-coverage check works at;
|
|
12446
12476
|
* these nested keys are tracked as a known gap, not a supported surface.
|
|
12447
12477
|
*/
|
|
12448
|
-
async addReplica(tableName, replica, region, logicalId) {
|
|
12478
|
+
async addReplica(tableName, replica, region, logicalId, billingMode, diagnostics) {
|
|
12449
12479
|
const create = { RegionName: region };
|
|
12450
12480
|
if (replica["KMSMasterKeyId"]) create.KMSMasterKeyId = replica["KMSMasterKeyId"];
|
|
12451
|
-
const replicaIndexes = toSdkReplicaGlobalSecondaryIndexes(replica["GlobalSecondaryIndexes"]);
|
|
12481
|
+
const replicaIndexes = toSdkReplicaGlobalSecondaryIndexes(replica["GlobalSecondaryIndexes"], billingMode, diagnostics);
|
|
12452
12482
|
if (replicaIndexes) create.GlobalSecondaryIndexes = replicaIndexes;
|
|
12483
|
+
const tableOverrides = toSdkReplicaThroughputOverrides(replica, billingMode, diagnostics, {});
|
|
12484
|
+
if (tableOverrides.ProvisionedThroughputOverride) create.ProvisionedThroughputOverride = tableOverrides.ProvisionedThroughputOverride;
|
|
12485
|
+
if (tableOverrides.OnDemandThroughputOverride) create.OnDemandThroughputOverride = tableOverrides.OnDemandThroughputOverride;
|
|
12453
12486
|
if (replica["TableClassOverride"]) create.TableClassOverride = replica["TableClassOverride"];
|
|
12454
12487
|
const replicaUpdates = [{ Create: create }];
|
|
12455
12488
|
await this.dynamoDBClient.send(new UpdateTableCommand({
|
|
@@ -12494,8 +12527,9 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
12494
12527
|
if (properties["LocalSecondaryIndexes"] !== void 0 && previousProperties["LocalSecondaryIndexes"] !== void 0 && !deepEqual$1(properties["LocalSecondaryIndexes"], previousProperties["LocalSecondaryIndexes"])) throw new ProvisioningError(`LocalSecondaryIndexes is immutable on AWS::DynamoDB::GlobalTable; replacement required (deploy with --replace, or destroy + redeploy)`, resourceType, logicalId, physicalId);
|
|
12495
12528
|
const oldAttrs = previousProperties["AttributeDefinitions"] ?? [];
|
|
12496
12529
|
const newAttrs = properties["AttributeDefinitions"] ?? [];
|
|
12497
|
-
const
|
|
12498
|
-
|
|
12530
|
+
const desiredKeyAttrNames = collectDesiredKeyAttributeNames(properties);
|
|
12531
|
+
const removedButStillReferenced = oldAttrs.filter((o) => !newAttrs.some((n) => n.AttributeName === o.AttributeName)).filter((a) => a.AttributeName !== void 0 && desiredKeyAttrNames.has(a.AttributeName));
|
|
12532
|
+
if (removedButStillReferenced.length > 0) throw new ProvisioningError(`AttributeDefinitions removals are immutable on AWS::DynamoDB::GlobalTable while a key schema still references them (offenders: ${removedButStillReferenced.map((a) => a.AttributeName).join(", ")}); replacement required`, resourceType, logicalId, physicalId);
|
|
12499
12533
|
const currentRegion = await this.dynamoDBClient.config.region() ?? "";
|
|
12500
12534
|
try {
|
|
12501
12535
|
await this.waitForTableActiveAfterUpdate(physicalId, logicalId);
|
|
@@ -12508,6 +12542,16 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
12508
12542
|
const oldBilling = previousProperties["BillingMode"] ?? "PAY_PER_REQUEST";
|
|
12509
12543
|
const newBilling = properties["BillingMode"] ?? "PAY_PER_REQUEST";
|
|
12510
12544
|
const onDemandCeilingLive = oldBilling === "PAY_PER_REQUEST" && newBilling === "PAY_PER_REQUEST";
|
|
12545
|
+
const diagnostics = [];
|
|
12546
|
+
const desiredCeilings = collectTableOnDemandCeilings(properties, currentRegion, diagnostics);
|
|
12547
|
+
const desiredSdkIndexes = toSdkGlobalSecondaryIndexes(properties, currentRegion, newBilling, "min", diagnostics);
|
|
12548
|
+
for (const replica of properties["Replicas"] ?? []) {
|
|
12549
|
+
const replicaRegion = asRecord$1(replica)?.["Region"];
|
|
12550
|
+
if (typeof replicaRegion !== "string" || replicaRegion === currentRegion) continue;
|
|
12551
|
+
toSdkReplicaGlobalSecondaryIndexes(replica["GlobalSecondaryIndexes"], newBilling, diagnostics);
|
|
12552
|
+
toSdkReplicaThroughputOverrides(replica, newBilling, diagnostics, {});
|
|
12553
|
+
}
|
|
12554
|
+
this.reportThroughputDiagnostics(diagnostics, logicalId, physicalId);
|
|
12511
12555
|
const newDpe = extractLocalDeletionProtection(properties, currentRegion);
|
|
12512
12556
|
const oldDpe = extractLocalDeletionProtection(previousProperties, currentRegion);
|
|
12513
12557
|
const awsDpe = describeResp.Table?.DeletionProtectionEnabled;
|
|
@@ -12537,17 +12581,23 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
12537
12581
|
};
|
|
12538
12582
|
flatChanged = true;
|
|
12539
12583
|
}
|
|
12540
|
-
|
|
12541
|
-
|
|
12542
|
-
|
|
12543
|
-
|
|
12544
|
-
|
|
12545
|
-
|
|
12546
|
-
flatChanged = true;
|
|
12547
|
-
} else if (onDemandCeilingLive && previousWodts?.["MaxWriteRequestUnits"] !== void 0) {
|
|
12548
|
-
flatUpdate.OnDemandThroughput = { MaxWriteRequestUnits: ON_DEMAND_LIMIT_RESET };
|
|
12549
|
-
flatChanged = true;
|
|
12584
|
+
const awsOnDemand = describeResp.Table?.OnDemandThroughput;
|
|
12585
|
+
const onDemandPayload = {};
|
|
12586
|
+
const resolveCeiling = (desired, live) => {
|
|
12587
|
+
if (desired.declared) {
|
|
12588
|
+
if (desired.value === void 0) return void 0;
|
|
12589
|
+
return desired.value !== live ? desired.value : void 0;
|
|
12550
12590
|
}
|
|
12591
|
+
if (onDemandCeilingLive && live !== void 0) return ON_DEMAND_LIMIT_RESET;
|
|
12592
|
+
};
|
|
12593
|
+
const nextMaxRead = resolveCeiling(desiredCeilings.read, awsOnDemand?.MaxReadRequestUnits);
|
|
12594
|
+
const nextMaxWrite = resolveCeiling(desiredCeilings.write, awsOnDemand?.MaxWriteRequestUnits);
|
|
12595
|
+
if (nextMaxRead !== void 0) onDemandPayload.MaxReadRequestUnits = nextMaxRead;
|
|
12596
|
+
if (nextMaxWrite !== void 0) onDemandPayload.MaxWriteRequestUnits = nextMaxWrite;
|
|
12597
|
+
const deferCeilingsPastFlip = oldBilling === "PROVISIONED" && newBilling === "PAY_PER_REQUEST" && Object.keys(onDemandPayload).length > 0;
|
|
12598
|
+
if (newBilling !== "PROVISIONED" && !deferCeilingsPastFlip && Object.keys(onDemandPayload).length > 0) {
|
|
12599
|
+
flatUpdate.OnDemandThroughput = onDemandPayload;
|
|
12600
|
+
flatChanged = true;
|
|
12551
12601
|
}
|
|
12552
12602
|
if (flatChanged) {
|
|
12553
12603
|
await this.dynamoDBClient.send(new UpdateTableCommand(flatUpdate));
|
|
@@ -12591,6 +12641,13 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
12591
12641
|
await this.dynamoDBClient.send(new UpdateTableCommand(billingUpdate));
|
|
12592
12642
|
await this.waitForTableActiveAfterUpdate(physicalId, logicalId);
|
|
12593
12643
|
}
|
|
12644
|
+
if (deferCeilingsPastFlip) {
|
|
12645
|
+
await this.dynamoDBClient.send(new UpdateTableCommand({
|
|
12646
|
+
TableName: physicalId,
|
|
12647
|
+
OnDemandThroughput: onDemandPayload
|
|
12648
|
+
}));
|
|
12649
|
+
await this.waitForTableActiveAfterUpdate(physicalId, logicalId);
|
|
12650
|
+
}
|
|
12594
12651
|
const writeAutoScalingOld = (previousProperties["WriteProvisionedThroughputSettings"] ?? {})["WriteCapacityAutoScalingSettings"];
|
|
12595
12652
|
const writeAutoScalingNew = (properties["WriteProvisionedThroughputSettings"] ?? {})["WriteCapacityAutoScalingSettings"];
|
|
12596
12653
|
const effectiveNewAutoScaling = oldBilling === "PROVISIONED" && newBilling === "PAY_PER_REQUEST" ? void 0 : writeAutoScalingNew;
|
|
@@ -12624,7 +12681,7 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
12624
12681
|
for (const replica of replicaDiff.added) {
|
|
12625
12682
|
const region = replica["Region"];
|
|
12626
12683
|
if (!region || region === currentRegion) continue;
|
|
12627
|
-
await this.addReplica(physicalId, replica, region, logicalId);
|
|
12684
|
+
await this.addReplica(physicalId, replica, region, logicalId, newBilling);
|
|
12628
12685
|
const newReplicaTags = replica["Tags"];
|
|
12629
12686
|
await this.applyCrossRegionReplicaTagsDiff(tableArn, region, void 0, newReplicaTags, physicalId);
|
|
12630
12687
|
const newReadAutoScaling = (replica["ReadProvisionedThroughputSettings"] ?? {})["ReadCapacityAutoScalingSettings"];
|
|
@@ -12657,10 +12714,20 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
12657
12714
|
}
|
|
12658
12715
|
const updateAction = { RegionName: region };
|
|
12659
12716
|
if (replica["KMSMasterKeyId"] !== void 0) updateAction.KMSMasterKeyId = replica["KMSMasterKeyId"];
|
|
12660
|
-
const replicaIndexes = toSdkReplicaGlobalSecondaryIndexes(replica["GlobalSecondaryIndexes"]);
|
|
12717
|
+
const replicaIndexes = toSdkReplicaGlobalSecondaryIndexes(replica["GlobalSecondaryIndexes"], newBilling);
|
|
12661
12718
|
if (replicaIndexes) updateAction.GlobalSecondaryIndexes = replicaIndexes;
|
|
12719
|
+
const replicaOverrides = toSdkReplicaThroughputOverrides(replica, newBilling, void 0, {});
|
|
12720
|
+
if (replicaOverrides.ProvisionedThroughputOverride) updateAction.ProvisionedThroughputOverride = replicaOverrides.ProvisionedThroughputOverride;
|
|
12721
|
+
if (replicaOverrides.OnDemandThroughputOverride) updateAction.OnDemandThroughputOverride = replicaOverrides.OnDemandThroughputOverride;
|
|
12722
|
+
if (oldBilling === newBilling) {
|
|
12723
|
+
const oldReplicaOverrides = toSdkReplicaThroughputOverrides(oldReplica ?? {}, oldBilling, void 0, {});
|
|
12724
|
+
const droppedOverrides = [];
|
|
12725
|
+
if (oldReplicaOverrides.OnDemandThroughputOverride && !replicaOverrides.OnDemandThroughputOverride) droppedOverrides.push("ReadOnDemandThroughputSettings");
|
|
12726
|
+
if (oldReplicaOverrides.ProvisionedThroughputOverride && !replicaOverrides.ProvisionedThroughputOverride) droppedOverrides.push("ReadProvisionedThroughputSettings");
|
|
12727
|
+
if (droppedOverrides.length > 0) this.logger.warn(`Cross-region replica ${region} of ${physicalId}: the template no longer declares ${droppedOverrides.join(" / ")}, but DynamoDB offers no way to CLEAR a replica-level throughput override — a -1 sentinel is stored literally, and an empty override block wedges the table in UPDATING (both live-probed, issue #1436). The previous override is STILL IN EFFECT on AWS. Set an explicit value, or remove and re-add the replica, to change it.`);
|
|
12728
|
+
}
|
|
12662
12729
|
if (replica["TableClassOverride"]) updateAction.TableClassOverride = replica["TableClassOverride"];
|
|
12663
|
-
if (!(updateAction.KMSMasterKeyId !== void 0 || updateAction.GlobalSecondaryIndexes !== void 0 || updateAction.TableClassOverride !== void 0)) {
|
|
12730
|
+
if (!(updateAction.KMSMasterKeyId !== void 0 || updateAction.GlobalSecondaryIndexes !== void 0 || updateAction.TableClassOverride !== void 0 || updateAction.ProvisionedThroughputOverride !== void 0 || updateAction.OnDemandThroughputOverride !== void 0)) {
|
|
12664
12731
|
this.logger.debug(`Cross-region replica ${region} of ${physicalId}: only Tags-style changes detected; UpdateReplica skipped (AWS rejects empty Update actions). Tags propagation handled above via per-region client.`);
|
|
12665
12732
|
continue;
|
|
12666
12733
|
}
|
|
@@ -12671,7 +12738,7 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
12671
12738
|
await this.waitForReplicaActive(physicalId, region, logicalId);
|
|
12672
12739
|
}
|
|
12673
12740
|
const previousSdkIndexes = toSdkGlobalSecondaryIndexes(previousProperties, currentRegion, oldBilling);
|
|
12674
|
-
const gsiDiff = diffGlobalSecondaryIndexes(previousSdkIndexes,
|
|
12741
|
+
const gsiDiff = diffGlobalSecondaryIndexes(previousSdkIndexes, desiredSdkIndexes);
|
|
12675
12742
|
const rawOnDemandDeclarations = collectRawOnDemandDeclarations(properties, currentRegion);
|
|
12676
12743
|
const previousSdkByName = /* @__PURE__ */ new Map();
|
|
12677
12744
|
for (const prev of previousSdkIndexes) if (prev.IndexName) previousSdkByName.set(prev.IndexName, prev);
|
|
@@ -12716,7 +12783,6 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
12716
12783
|
if (previousOnDemand.MaxWriteRequestUnits !== void 0 && onDemand.MaxWriteRequestUnits === void 0) if (declared?.writeDeclared) unresolvedMembers.push("MaxWriteRequestUnits");
|
|
12717
12784
|
else onDemand.MaxWriteRequestUnits = ON_DEMAND_LIMIT_RESET;
|
|
12718
12785
|
}
|
|
12719
|
-
if (unresolvedMembers.length > 0) this.logger.warn(`GSI '${gsi.IndexName}' on ${physicalId}: the template declares ${unresolvedMembers.join(" / ")} but the value did not resolve to a number, so the on-demand limit was left UNCHANGED on AWS rather than applied or cleared. This is usually an unresolved intrinsic in the template; resolve it to a literal to change the ceiling, or remove the property entirely to clear it.`);
|
|
12720
12786
|
if (Object.keys(onDemand).length > 0) update.OnDemandThroughput = onDemand;
|
|
12721
12787
|
if (!billingFlipped && gsi.ProvisionedThroughput) update.ProvisionedThroughput = gsi.ProvisionedThroughput;
|
|
12722
12788
|
const previousSdk = previousSdkByName.get(gsi.IndexName);
|
|
@@ -13279,7 +13345,6 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
13279
13345
|
if (table.AttributeDefinitions) result["AttributeDefinitions"] = table.AttributeDefinitions;
|
|
13280
13346
|
const billingMode = table.BillingModeSummary?.BillingMode;
|
|
13281
13347
|
if (billingMode) result["BillingMode"] = billingMode;
|
|
13282
|
-
if (table.GlobalSecondaryIndexes && table.GlobalSecondaryIndexes.length > 0) result["GlobalSecondaryIndexes"] = table.GlobalSecondaryIndexes;
|
|
13283
13348
|
if (table.LocalSecondaryIndexes && table.LocalSecondaryIndexes.length > 0) result["LocalSecondaryIndexes"] = table.LocalSecondaryIndexes;
|
|
13284
13349
|
if (table.StreamSpecification?.StreamEnabled && table.StreamSpecification.StreamViewType) result["StreamSpecification"] = {
|
|
13285
13350
|
StreamEnabled: true,
|
|
@@ -13294,7 +13359,8 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
13294
13359
|
if (table.DeletionProtectionEnabled !== void 0) result["DeletionProtectionEnabled"] = table.DeletionProtectionEnabled;
|
|
13295
13360
|
const currentRegion = await this.dynamoDBClient.config.region() ?? "";
|
|
13296
13361
|
const tableNameForSubs = table.TableName ?? physicalId;
|
|
13297
|
-
|
|
13362
|
+
const sdkReplicas = table.Replicas && table.Replicas.length > 0 ? table.Replicas : [{ RegionName: currentRegion }];
|
|
13363
|
+
const replicas = await Promise.all(sdkReplicas.map(async (r) => {
|
|
13298
13364
|
const entry = { Region: r.RegionName };
|
|
13299
13365
|
if (r.KMSMasterKeyId !== void 0) entry["KMSMasterKeyId"] = r.KMSMasterKeyId;
|
|
13300
13366
|
if (!r.RegionName) return entry;
|
|
@@ -13324,8 +13390,38 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
13324
13390
|
}
|
|
13325
13391
|
return entry;
|
|
13326
13392
|
}));
|
|
13393
|
+
result["Replicas"] = replicas;
|
|
13394
|
+
const localReplicaEntry = replicas.find((r) => r["Region"] === currentRegion);
|
|
13395
|
+
if (table.GlobalSecondaryIndexes && table.GlobalSecondaryIndexes.length > 0) {
|
|
13396
|
+
const cfnIndexes = [];
|
|
13397
|
+
const replicaIndexEntries = [];
|
|
13398
|
+
for (const gsi of table.GlobalSecondaryIndexes) {
|
|
13399
|
+
if (!gsi.IndexName) continue;
|
|
13400
|
+
const entry = { IndexName: gsi.IndexName };
|
|
13401
|
+
if (gsi.KeySchema) entry["KeySchema"] = gsi.KeySchema;
|
|
13402
|
+
if (gsi.Projection) entry["Projection"] = gsi.Projection;
|
|
13403
|
+
if (gsi.WarmThroughput) entry["WarmThroughput"] = gsi.WarmThroughput;
|
|
13404
|
+
const replicaEntry = { IndexName: gsi.IndexName };
|
|
13405
|
+
if (billingMode === "PROVISIONED") {
|
|
13406
|
+
const writeAutoScaling = await this.readAutoScalingSettings(tableNameForSubs, "dynamodb:index:WriteCapacityUnits", void 0, gsi.IndexName);
|
|
13407
|
+
if (writeAutoScaling) entry["WriteProvisionedThroughputSettings"] = { WriteCapacityAutoScalingSettings: writeAutoScaling };
|
|
13408
|
+
else if (gsi.ProvisionedThroughput?.WriteCapacityUnits !== void 0) entry["WriteProvisionedThroughputSettings"] = { WriteCapacityUnits: gsi.ProvisionedThroughput.WriteCapacityUnits };
|
|
13409
|
+
const readAutoScaling = await this.readAutoScalingSettings(tableNameForSubs, "dynamodb:index:ReadCapacityUnits", void 0, gsi.IndexName);
|
|
13410
|
+
if (readAutoScaling) replicaEntry["ReadProvisionedThroughputSettings"] = { ReadCapacityAutoScalingSettings: readAutoScaling };
|
|
13411
|
+
else if (gsi.ProvisionedThroughput?.ReadCapacityUnits !== void 0) replicaEntry["ReadProvisionedThroughputSettings"] = { ReadCapacityUnits: gsi.ProvisionedThroughput.ReadCapacityUnits };
|
|
13412
|
+
} else {
|
|
13413
|
+
if (gsi.OnDemandThroughput?.MaxWriteRequestUnits !== void 0) entry["WriteOnDemandThroughputSettings"] = { MaxWriteRequestUnits: gsi.OnDemandThroughput.MaxWriteRequestUnits };
|
|
13414
|
+
if (gsi.OnDemandThroughput?.MaxReadRequestUnits !== void 0) replicaEntry["ReadOnDemandThroughputSettings"] = { MaxReadRequestUnits: gsi.OnDemandThroughput.MaxReadRequestUnits };
|
|
13415
|
+
}
|
|
13416
|
+
cfnIndexes.push(entry);
|
|
13417
|
+
if (Object.keys(replicaEntry).length > 1) replicaIndexEntries.push(replicaEntry);
|
|
13418
|
+
}
|
|
13419
|
+
result["GlobalSecondaryIndexes"] = cfnIndexes;
|
|
13420
|
+
if (localReplicaEntry && replicaIndexEntries.length > 0) localReplicaEntry["GlobalSecondaryIndexes"] = replicaIndexEntries;
|
|
13421
|
+
}
|
|
13327
13422
|
if (table.OnDemandThroughput?.MaxWriteRequestUnits !== void 0) result["WriteOnDemandThroughputSettings"] = { MaxWriteRequestUnits: table.OnDemandThroughput.MaxWriteRequestUnits };
|
|
13328
13423
|
else result["WriteOnDemandThroughputSettings"] = {};
|
|
13424
|
+
if (localReplicaEntry && table.OnDemandThroughput?.MaxReadRequestUnits !== void 0) localReplicaEntry["ReadOnDemandThroughputSettings"] = { MaxReadRequestUnits: table.OnDemandThroughput.MaxReadRequestUnits };
|
|
13329
13425
|
if (billingMode === "PROVISIONED") {
|
|
13330
13426
|
const autoScaling = await this.readAutoScalingSettings(tableNameForSubs, "dynamodb:table:WriteCapacityUnits");
|
|
13331
13427
|
if (autoScaling) result["WriteProvisionedThroughputSettings"] = { WriteCapacityAutoScalingSettings: autoScaling };
|
|
@@ -13446,18 +13542,24 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
13446
13542
|
* under `--verbose`.
|
|
13447
13543
|
*
|
|
13448
13544
|
* @param tableName DynamoDB table name (the `physicalId`).
|
|
13449
|
-
* @param scalableDimension
|
|
13450
|
-
* `
|
|
13545
|
+
* @param scalableDimension Any of the four dimensions a GlobalTable can
|
|
13546
|
+
* register — the two `dynamodb:table:*` ones and, since issue #1420's
|
|
13547
|
+
* GSI reverse map, the two `dynamodb:index:*` ones.
|
|
13451
13548
|
* @param client Pre-built region-scoped autoscaling client. Defaults to
|
|
13452
13549
|
* a fresh client in the local region — pass an explicit client when
|
|
13453
13550
|
* reading from a cross-region replica.
|
|
13551
|
+
* @param indexName Required for a `dynamodb:index:*` dimension; the
|
|
13552
|
+
* `ResourceId` is built through the shared {@link autoScalingResourceId}
|
|
13553
|
+
* so the read cannot spell it differently from the register / deregister
|
|
13554
|
+
* calls.
|
|
13454
13555
|
*/
|
|
13455
|
-
async readAutoScalingSettings(tableName, scalableDimension, client) {
|
|
13556
|
+
async readAutoScalingSettings(tableName, scalableDimension, client, indexName) {
|
|
13557
|
+
const resourceId = autoScalingResourceId(tableName, indexName);
|
|
13456
13558
|
try {
|
|
13457
13559
|
const asClient = client ?? new ApplicationAutoScalingClient({ region: await this.dynamoDBClient.config.region() ?? "" });
|
|
13458
13560
|
const targets = (await asClient.send(new DescribeScalableTargetsCommand({
|
|
13459
13561
|
ServiceNamespace: "dynamodb",
|
|
13460
|
-
ResourceIds: [
|
|
13562
|
+
ResourceIds: [resourceId],
|
|
13461
13563
|
ScalableDimension: scalableDimension
|
|
13462
13564
|
}))).ScalableTargets ?? [];
|
|
13463
13565
|
if (targets.length === 0) return null;
|
|
@@ -13465,7 +13567,7 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
13465
13567
|
if (target.MinCapacity === void 0 || target.MaxCapacity === void 0) return null;
|
|
13466
13568
|
const targetTracking = ((await asClient.send(new DescribeScalingPoliciesCommand({
|
|
13467
13569
|
ServiceNamespace: "dynamodb",
|
|
13468
|
-
ResourceId:
|
|
13570
|
+
ResourceId: resourceId,
|
|
13469
13571
|
ScalableDimension: scalableDimension
|
|
13470
13572
|
}))).ScalingPolicies ?? []).find((p) => p.PolicyType === "TargetTrackingScaling");
|
|
13471
13573
|
if (!targetTracking) return null;
|
|
@@ -13481,7 +13583,7 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
13481
13583
|
TargetTrackingScalingPolicyConfiguration: tttConfig
|
|
13482
13584
|
};
|
|
13483
13585
|
} catch (err) {
|
|
13484
|
-
this.logger.debug(`Could not read application-autoscaling settings for ${
|
|
13586
|
+
this.logger.debug(`Could not read application-autoscaling settings for ${resourceId} (${scalableDimension}): ${err instanceof Error ? err.message : String(err)}`);
|
|
13485
13587
|
return null;
|
|
13486
13588
|
}
|
|
13487
13589
|
}
|
|
@@ -13507,6 +13609,17 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
13507
13609
|
* - `TimeToLiveSpecification` is reverse-mapped via
|
|
13508
13610
|
* `DescribeTimeToLive` in `readCurrentState`.
|
|
13509
13611
|
*
|
|
13612
|
+
* Issue #1420 + #1436 kept it empty rather than growing it:
|
|
13613
|
+
* - `GlobalSecondaryIndexes` is a real CFn-shaped reverse map (SDK-only
|
|
13614
|
+
* members stripped, write throughput at the top level, read throughput
|
|
13615
|
+
* on the local replica's index entry, auto-scaling recovered per index
|
|
13616
|
+
* via `dynamodb:index:*`). The alternative #1420 offered — declaring the
|
|
13617
|
+
* subtree here — would have turned drift OFF for every GSI, which is a
|
|
13618
|
+
* strictly worse trade than the phantom drift it removes.
|
|
13619
|
+
* - The table-level on-demand READ ceiling is reverse-mapped onto
|
|
13620
|
+
* `Replicas[local].ReadOnDemandThroughputSettings`, the CFn location it
|
|
13621
|
+
* is authored at.
|
|
13622
|
+
*
|
|
13510
13623
|
* Returning an empty list is the canonical "no known-unknown paths"
|
|
13511
13624
|
* shape — keeping the method present (rather than removing it) for
|
|
13512
13625
|
* future additions and for backward compat on test reflection.
|
|
@@ -13691,8 +13804,8 @@ function extractLocalDeletionProtection(props, region) {
|
|
|
13691
13804
|
*/
|
|
13692
13805
|
function derivePerCallProvisionedThroughput(properties, region, source = "min") {
|
|
13693
13806
|
return {
|
|
13694
|
-
ReadCapacityUnits: deriveReadCapacityUnits(asRecord((properties["Replicas"] ?? []).find((r) => r["Region"] === region)?.["ReadProvisionedThroughputSettings"]), source) ?? DEFAULT_CAPACITY_UNITS,
|
|
13695
|
-
WriteCapacityUnits: deriveWriteCapacityUnits(asRecord(properties["WriteProvisionedThroughputSettings"]), source) ?? DEFAULT_CAPACITY_UNITS
|
|
13807
|
+
ReadCapacityUnits: deriveReadCapacityUnits(asRecord$1((properties["Replicas"] ?? []).find((r) => r["Region"] === region)?.["ReadProvisionedThroughputSettings"]), source) ?? DEFAULT_CAPACITY_UNITS,
|
|
13808
|
+
WriteCapacityUnits: deriveWriteCapacityUnits(asRecord$1(properties["WriteProvisionedThroughputSettings"]), source) ?? DEFAULT_CAPACITY_UNITS
|
|
13696
13809
|
};
|
|
13697
13810
|
}
|
|
13698
13811
|
/**
|
|
@@ -13757,7 +13870,7 @@ const DESCRIBE_SCALABLE_TARGETS_BATCH = 50;
|
|
|
13757
13870
|
*/
|
|
13758
13871
|
function collectAutoScalingTargets(properties, localRegion) {
|
|
13759
13872
|
const specs = [];
|
|
13760
|
-
const tableWrite = asRecord(asRecord(properties["WriteProvisionedThroughputSettings"])?.["WriteCapacityAutoScalingSettings"]);
|
|
13873
|
+
const tableWrite = asRecord$1(asRecord$1(properties["WriteProvisionedThroughputSettings"])?.["WriteCapacityAutoScalingSettings"]);
|
|
13761
13874
|
if (tableWrite) specs.push({
|
|
13762
13875
|
dimension: "dynamodb:table:WriteCapacityUnits",
|
|
13763
13876
|
region: localRegion,
|
|
@@ -13765,10 +13878,10 @@ function collectAutoScalingTargets(properties, localRegion) {
|
|
|
13765
13878
|
});
|
|
13766
13879
|
const cfnIndexes = properties["GlobalSecondaryIndexes"];
|
|
13767
13880
|
if (Array.isArray(cfnIndexes)) for (const entry of cfnIndexes) {
|
|
13768
|
-
const gsi = asRecord(entry);
|
|
13881
|
+
const gsi = asRecord$1(entry);
|
|
13769
13882
|
const indexName = gsi?.["IndexName"];
|
|
13770
13883
|
if (!gsi || typeof indexName !== "string") continue;
|
|
13771
|
-
const indexWrite = asRecord(asRecord(gsi["WriteProvisionedThroughputSettings"])?.["WriteCapacityAutoScalingSettings"]);
|
|
13884
|
+
const indexWrite = asRecord$1(asRecord$1(gsi["WriteProvisionedThroughputSettings"])?.["WriteCapacityAutoScalingSettings"]);
|
|
13772
13885
|
if (indexWrite) specs.push({
|
|
13773
13886
|
dimension: "dynamodb:index:WriteCapacityUnits",
|
|
13774
13887
|
indexName,
|
|
@@ -13778,10 +13891,10 @@ function collectAutoScalingTargets(properties, localRegion) {
|
|
|
13778
13891
|
}
|
|
13779
13892
|
const replicas = properties["Replicas"];
|
|
13780
13893
|
if (Array.isArray(replicas)) for (const replicaEntry of replicas) {
|
|
13781
|
-
const replica = asRecord(replicaEntry);
|
|
13894
|
+
const replica = asRecord$1(replicaEntry);
|
|
13782
13895
|
const region = replica?.["Region"];
|
|
13783
13896
|
if (!replica || typeof region !== "string") continue;
|
|
13784
|
-
const replicaRead = asRecord(asRecord(replica["ReadProvisionedThroughputSettings"])?.["ReadCapacityAutoScalingSettings"]);
|
|
13897
|
+
const replicaRead = asRecord$1(asRecord$1(replica["ReadProvisionedThroughputSettings"])?.["ReadCapacityAutoScalingSettings"]);
|
|
13785
13898
|
if (replicaRead) specs.push({
|
|
13786
13899
|
dimension: "dynamodb:table:ReadCapacityUnits",
|
|
13787
13900
|
region,
|
|
@@ -13790,10 +13903,10 @@ function collectAutoScalingTargets(properties, localRegion) {
|
|
|
13790
13903
|
const replicaIndexes = replica["GlobalSecondaryIndexes"];
|
|
13791
13904
|
if (!Array.isArray(replicaIndexes)) continue;
|
|
13792
13905
|
for (const entry of replicaIndexes) {
|
|
13793
|
-
const replicaIndex = asRecord(entry);
|
|
13906
|
+
const replicaIndex = asRecord$1(entry);
|
|
13794
13907
|
const indexName = replicaIndex?.["IndexName"];
|
|
13795
13908
|
if (!replicaIndex || typeof indexName !== "string") continue;
|
|
13796
|
-
const indexRead = asRecord(asRecord(replicaIndex["ReadProvisionedThroughputSettings"])?.["ReadCapacityAutoScalingSettings"]);
|
|
13909
|
+
const indexRead = asRecord$1(asRecord$1(replicaIndex["ReadProvisionedThroughputSettings"])?.["ReadCapacityAutoScalingSettings"]);
|
|
13797
13910
|
if (indexRead) specs.push({
|
|
13798
13911
|
dimension: "dynamodb:index:ReadCapacityUnits",
|
|
13799
13912
|
indexName,
|
|
@@ -13804,8 +13917,38 @@ function collectAutoScalingTargets(properties, localRegion) {
|
|
|
13804
13917
|
}
|
|
13805
13918
|
return specs;
|
|
13806
13919
|
}
|
|
13920
|
+
/**
|
|
13921
|
+
* Every attribute name some KEY SCHEMA in the desired template references:
|
|
13922
|
+
* the table's own `KeySchema`, each GSI's, and each LSI's. This is the set
|
|
13923
|
+
* the `AttributeDefinitions`-removal guard in `update()` tests membership
|
|
13924
|
+
* against — a removed attribute still in this set is a genuine
|
|
13925
|
+
* redefine-in-place (replacement required); one outside it is the normal
|
|
13926
|
+
* bookkeeping of a GSI deletion and must NOT force a replacement.
|
|
13927
|
+
*
|
|
13928
|
+
* Malformed / intrinsic-valued entries contribute nothing, which errs on the
|
|
13929
|
+
* PERMISSIVE side of the guard — correct here, because the guard's failure
|
|
13930
|
+
* mode is a forced replacement of a live table, and AWS itself still rejects
|
|
13931
|
+
* a genuinely-bad `UpdateTable` loudly.
|
|
13932
|
+
*/
|
|
13933
|
+
function collectDesiredKeyAttributeNames(properties) {
|
|
13934
|
+
const names = /* @__PURE__ */ new Set();
|
|
13935
|
+
const addKeySchema = (value) => {
|
|
13936
|
+
if (!Array.isArray(value)) return;
|
|
13937
|
+
for (const element of value) {
|
|
13938
|
+
const name = asRecord$1(element)?.["AttributeName"];
|
|
13939
|
+
if (typeof name === "string") names.add(name);
|
|
13940
|
+
}
|
|
13941
|
+
};
|
|
13942
|
+
addKeySchema(properties["KeySchema"]);
|
|
13943
|
+
for (const indexListKey of ["GlobalSecondaryIndexes", "LocalSecondaryIndexes"]) {
|
|
13944
|
+
const indexes = properties[indexListKey];
|
|
13945
|
+
if (!Array.isArray(indexes)) continue;
|
|
13946
|
+
for (const index of indexes) addKeySchema(asRecord$1(index)?.["KeySchema"]);
|
|
13947
|
+
}
|
|
13948
|
+
return names;
|
|
13949
|
+
}
|
|
13807
13950
|
/** Narrow an unknown CFn sub-object to a record (or `undefined`). */
|
|
13808
|
-
function asRecord(value) {
|
|
13951
|
+
function asRecord$1(value) {
|
|
13809
13952
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
13810
13953
|
}
|
|
13811
13954
|
/**
|
|
@@ -13820,10 +13963,93 @@ const ON_DEMAND_LIMIT_RESET = -1;
|
|
|
13820
13963
|
/** Coerce a CFn numeric (CFn is stringly-typed) to a finite number. */
|
|
13821
13964
|
function toFiniteNumber(value) {
|
|
13822
13965
|
if (value === void 0 || value === null || value === "") return void 0;
|
|
13966
|
+
if (typeof value !== "number" && typeof value !== "string") return void 0;
|
|
13823
13967
|
const n = Number(value);
|
|
13824
13968
|
return Number.isFinite(n) ? n : void 0;
|
|
13825
13969
|
}
|
|
13826
13970
|
/**
|
|
13971
|
+
* Merge an explicitly-supplied, already-SDK-shaped throughput block over a
|
|
13972
|
+
* derived one, PER MEMBER (issue #1428).
|
|
13973
|
+
*
|
|
13974
|
+
* The CFn schema forbids `ProvisionedThroughput` / `OnDemandThroughput` /
|
|
13975
|
+
* `*ThroughputOverride` on `AWS::DynamoDB::GlobalTable`, so this only fires
|
|
13976
|
+
* for pre-#1387 cdkd state and hand-authored templates — but those ARE a
|
|
13977
|
+
* first-class cdkd surface (`cdkd import --migrate-from-cloudformation`,
|
|
13978
|
+
* `cdkd migrate --from-cfn-stack`), and the pre-fix code forwarded such a
|
|
13979
|
+
* block VERBATIM. Three defects followed, and fixing any subset leaves a
|
|
13980
|
+
* live one, which is why this helper does all three at once:
|
|
13981
|
+
*
|
|
13982
|
+
* 1. **Coercion.** The verbatim forward was the ONE path that skipped
|
|
13983
|
+
* {@link toFiniteNumber}, so a stringly-typed CFn `"5"` reached the SDK
|
|
13984
|
+
* unnormalized while every derived value was a number.
|
|
13985
|
+
* 2. **Per-member merge.** Whole-block replacement meant a PARTIAL explicit
|
|
13986
|
+
* block SUPPRESSED valid derived siblings — an explicit
|
|
13987
|
+
* `{MaxReadRequestUnits: 41}` discarded a perfectly good
|
|
13988
|
+
* `WriteOnDemandThroughputSettings.MaxWriteRequestUnits` from the same
|
|
13989
|
+
* template.
|
|
13990
|
+
* 3. **Billing gate.** That is the CALLER's job (it owns `billingMode`), but
|
|
13991
|
+
* it only works because this helper never emits a block the caller did
|
|
13992
|
+
* not ask for.
|
|
13993
|
+
*
|
|
13994
|
+
* WARN-AND-FALL-BACK, never throw. Three earlier designs are recorded on
|
|
13995
|
+
* issue #1428 and each was broken by the same lever: a present-but-
|
|
13996
|
+
* uncoercible member that THREW made a stack whose STATE carried the garbage
|
|
13997
|
+
* permanently undeployable (the helper also runs on `previousProperties`),
|
|
13998
|
+
* and throwing from the replica path threw AFTER `CreateTable` had already
|
|
13999
|
+
* committed a real table. So an uncoercible member is dropped, reported
|
|
14000
|
+
* through `diagnostics`, and the derived value stands in.
|
|
14001
|
+
*
|
|
14002
|
+
* Returns `undefined` when neither side contributes a single member — an
|
|
14003
|
+
* EMPTY block is not a safe stand-in, because AWS documents an empty
|
|
14004
|
+
* `ProvisionedThroughputOverride` / `OnDemandThroughputOverride` as "inherit
|
|
14005
|
+
* the source table's settings".
|
|
14006
|
+
*/
|
|
14007
|
+
function mergeExplicitThroughputBlock(explicit, derived, members, diagnostics, context) {
|
|
14008
|
+
const out = {};
|
|
14009
|
+
for (const member of members) {
|
|
14010
|
+
if (explicit && explicit[member] !== void 0) {
|
|
14011
|
+
const coerced = toFiniteNumber(explicit[member]);
|
|
14012
|
+
if (coerced !== void 0) {
|
|
14013
|
+
out[member] = coerced;
|
|
14014
|
+
continue;
|
|
14015
|
+
}
|
|
14016
|
+
diagnostics?.push({
|
|
14017
|
+
kind: "unresolved-member",
|
|
14018
|
+
...context.indexName !== void 0 && { indexName: context.indexName },
|
|
14019
|
+
member,
|
|
14020
|
+
message: `${context.blockName}.${member} is present but did not resolve to a number (${JSON.stringify(explicit[member])?.slice(0, 80)}), so it was ignored. This is usually an unresolved intrinsic; resolve it to a literal.`
|
|
14021
|
+
});
|
|
14022
|
+
}
|
|
14023
|
+
const derivedValue = derived?.[member];
|
|
14024
|
+
if (typeof derivedValue === "number") out[member] = derivedValue;
|
|
14025
|
+
}
|
|
14026
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
14027
|
+
}
|
|
14028
|
+
/**
|
|
14029
|
+
* Report a member the template DECLARED whose value did not coerce (#1444 A).
|
|
14030
|
+
*
|
|
14031
|
+
* The diagnostic has to be raised WHERE THE VALUE IS LOST — inside the
|
|
14032
|
+
* translation — not from the per-GSI reset gate in `update()`. That gate only
|
|
14033
|
+
* runs for an index the diff already classified `modified` AND only when a
|
|
14034
|
+
* previous ceiling happened to exist AND only when the billing mode did not
|
|
14035
|
+
* flip, so the very same template defect was silent in the two most likely
|
|
14036
|
+
* cases: a FIRST-TIME set (both translated sides lack the member, so the index
|
|
14037
|
+
* is not `modified` at all and the loop never runs) and a BILLING FLIP (the
|
|
14038
|
+
* gate short-circuits). The defect is identical in all three; only the
|
|
14039
|
+
* diagnostic was conditional.
|
|
14040
|
+
*/
|
|
14041
|
+
function reportUnresolvedRawMember(rawValue, diagnostics, context) {
|
|
14042
|
+
if (!diagnostics) return;
|
|
14043
|
+
if (rawValue === void 0) return;
|
|
14044
|
+
if (toFiniteNumber(rawValue) !== void 0) return;
|
|
14045
|
+
diagnostics.push({
|
|
14046
|
+
kind: "unresolved-member",
|
|
14047
|
+
...context.indexName !== void 0 && { indexName: context.indexName },
|
|
14048
|
+
member: context.member,
|
|
14049
|
+
message: `${context.blockName}.${context.member} is declared but did not resolve to a number (${JSON.stringify(rawValue)?.slice(0, 80)}), so no throughput value was sent for it and the live AWS setting was left unchanged. This is usually an unresolved intrinsic; resolve it to a literal to apply the limit, or remove the property entirely to clear it.`
|
|
14050
|
+
});
|
|
14051
|
+
}
|
|
14052
|
+
/**
|
|
13827
14053
|
* Derive a single WRITE capacity number from a CFn
|
|
13828
14054
|
* `WriteProvisionedThroughputSettings` block.
|
|
13829
14055
|
*
|
|
@@ -13849,7 +14075,7 @@ function deriveWriteCapacityUnits(settings, source = "min") {
|
|
|
13849
14075
|
if (!settings) return void 0;
|
|
13850
14076
|
const literal = toFiniteNumber(settings["WriteCapacityUnits"]);
|
|
13851
14077
|
if (literal !== void 0) return literal;
|
|
13852
|
-
const autoScaling = asRecord(settings["WriteCapacityAutoScalingSettings"]);
|
|
14078
|
+
const autoScaling = asRecord$1(settings["WriteCapacityAutoScalingSettings"]);
|
|
13853
14079
|
if (!autoScaling) return void 0;
|
|
13854
14080
|
return pickAutoScalingCapacity(autoScaling, source);
|
|
13855
14081
|
}
|
|
@@ -13865,7 +14091,7 @@ function deriveReadCapacityUnits(settings, source = "min") {
|
|
|
13865
14091
|
if (!settings) return void 0;
|
|
13866
14092
|
const literal = toFiniteNumber(settings["ReadCapacityUnits"]);
|
|
13867
14093
|
if (literal !== void 0) return literal;
|
|
13868
|
-
const autoScaling = asRecord(settings["ReadCapacityAutoScalingSettings"]);
|
|
14094
|
+
const autoScaling = asRecord$1(settings["ReadCapacityAutoScalingSettings"]);
|
|
13869
14095
|
if (!autoScaling) return void 0;
|
|
13870
14096
|
return pickAutoScalingCapacity(autoScaling, source);
|
|
13871
14097
|
}
|
|
@@ -13912,22 +14138,22 @@ function pickAutoScalingCapacity(autoScaling, source) {
|
|
|
13912
14138
|
* written before this fix (and hand-authored templates) can carry them, and
|
|
13913
14139
|
* silently re-deriving over an explicit value would be a regression.
|
|
13914
14140
|
*/
|
|
13915
|
-
function toSdkGlobalSecondaryIndexes(properties, region, billingMode, source = "min") {
|
|
14141
|
+
function toSdkGlobalSecondaryIndexes(properties, region, billingMode, source = "min", diagnostics) {
|
|
13916
14142
|
const rawIndexes = properties["GlobalSecondaryIndexes"];
|
|
13917
14143
|
if (rawIndexes !== void 0 && !Array.isArray(rawIndexes)) throw new Error(`AWS::DynamoDB::GlobalTable GlobalSecondaryIndexes must be an array, got ${typeof rawIndexes} (${JSON.stringify(rawIndexes)?.slice(0, 200)}). An unresolved intrinsic here would otherwise deploy a table with no indexes.`);
|
|
13918
14144
|
const cfnIndexes = rawIndexes ?? [];
|
|
13919
14145
|
if (cfnIndexes.length === 0) return [];
|
|
13920
14146
|
const replicas = properties["Replicas"] ?? [];
|
|
13921
|
-
const localReplicaIndexes = (Array.isArray(replicas) ? replicas.find((r) => asRecord(r)?.["Region"] === region) : void 0)?.["GlobalSecondaryIndexes"] ?? [];
|
|
14147
|
+
const localReplicaIndexes = (Array.isArray(replicas) ? replicas.find((r) => asRecord$1(r)?.["Region"] === region) : void 0)?.["GlobalSecondaryIndexes"] ?? [];
|
|
13922
14148
|
const localByName = /* @__PURE__ */ new Map();
|
|
13923
14149
|
if (Array.isArray(localReplicaIndexes)) for (const entry of localReplicaIndexes) {
|
|
13924
|
-
const record = asRecord(entry);
|
|
14150
|
+
const record = asRecord$1(entry);
|
|
13925
14151
|
const name = record?.["IndexName"];
|
|
13926
14152
|
if (record && typeof name === "string") localByName.set(name, record);
|
|
13927
14153
|
}
|
|
13928
14154
|
const result = [];
|
|
13929
14155
|
for (const entry of cfnIndexes) {
|
|
13930
|
-
const gsi = asRecord(entry);
|
|
14156
|
+
const gsi = asRecord$1(entry);
|
|
13931
14157
|
if (!gsi) {
|
|
13932
14158
|
result.push(entry);
|
|
13933
14159
|
continue;
|
|
@@ -13940,21 +14166,51 @@ function toSdkGlobalSecondaryIndexes(properties, region, billingMode, source = "
|
|
|
13940
14166
|
Projection: gsi["Projection"]
|
|
13941
14167
|
};
|
|
13942
14168
|
if (gsi["WarmThroughput"] !== void 0) sdk.WarmThroughput = gsi["WarmThroughput"];
|
|
13943
|
-
const explicitProvisioned = asRecord(gsi["ProvisionedThroughput"]);
|
|
13944
|
-
const explicitOnDemand = asRecord(gsi["OnDemandThroughput"]);
|
|
13945
|
-
if (explicitProvisioned
|
|
13946
|
-
|
|
13947
|
-
|
|
13948
|
-
|
|
13949
|
-
|
|
13950
|
-
|
|
13951
|
-
|
|
13952
|
-
|
|
13953
|
-
|
|
13954
|
-
|
|
14169
|
+
const explicitProvisioned = asRecord$1(gsi["ProvisionedThroughput"]);
|
|
14170
|
+
const explicitOnDemand = asRecord$1(gsi["OnDemandThroughput"]);
|
|
14171
|
+
if (explicitProvisioned && billingMode !== "PROVISIONED") diagnostics?.push({
|
|
14172
|
+
kind: "billing-mode-mismatch",
|
|
14173
|
+
...typeof indexName === "string" && { indexName },
|
|
14174
|
+
member: "ProvisionedThroughput",
|
|
14175
|
+
message: `an explicit ProvisionedThroughput block was dropped because the table's BillingMode is ${billingMode}; AWS rejects provisioned capacity on an on-demand table. Remove the block, or set BillingMode: PROVISIONED.`
|
|
14176
|
+
});
|
|
14177
|
+
if (explicitOnDemand && billingMode === "PROVISIONED") diagnostics?.push({
|
|
14178
|
+
kind: "billing-mode-mismatch",
|
|
14179
|
+
...typeof indexName === "string" && { indexName },
|
|
14180
|
+
member: "OnDemandThroughput",
|
|
14181
|
+
message: "an explicit OnDemandThroughput block was dropped because the table's BillingMode is PROVISIONED; AWS rejects on-demand ceilings on a provisioned table. Remove the block, or set BillingMode: PAY_PER_REQUEST."
|
|
14182
|
+
});
|
|
14183
|
+
if (billingMode === "PROVISIONED") sdk.ProvisionedThroughput = mergeExplicitThroughputBlock(explicitProvisioned, {
|
|
14184
|
+
ReadCapacityUnits: deriveReadCapacityUnits(asRecord$1(localEntry?.["ReadProvisionedThroughputSettings"]), source) ?? deriveReadCapacityUnits(asRecord$1(gsi["ReadProvisionedThroughputSettings"]), source) ?? DEFAULT_CAPACITY_UNITS,
|
|
14185
|
+
WriteCapacityUnits: deriveWriteCapacityUnits(asRecord$1(gsi["WriteProvisionedThroughputSettings"]), source) ?? DEFAULT_CAPACITY_UNITS
|
|
14186
|
+
}, ["ReadCapacityUnits", "WriteCapacityUnits"], diagnostics, {
|
|
14187
|
+
...typeof indexName === "string" && { indexName },
|
|
14188
|
+
blockName: "ProvisionedThroughput"
|
|
14189
|
+
});
|
|
14190
|
+
else {
|
|
14191
|
+
const rawWrite = asRecord$1(gsi["WriteOnDemandThroughputSettings"])?.["MaxWriteRequestUnits"];
|
|
14192
|
+
const rawReadLocal = asRecord$1(localEntry?.["ReadOnDemandThroughputSettings"])?.["MaxReadRequestUnits"];
|
|
14193
|
+
const rawReadGsi = asRecord$1(gsi["ReadOnDemandThroughputSettings"])?.["MaxReadRequestUnits"];
|
|
14194
|
+
reportUnresolvedRawMember(rawWrite, diagnostics, {
|
|
14195
|
+
...typeof indexName === "string" && { indexName },
|
|
14196
|
+
blockName: "WriteOnDemandThroughputSettings",
|
|
14197
|
+
member: "MaxWriteRequestUnits"
|
|
14198
|
+
});
|
|
14199
|
+
reportUnresolvedRawMember(rawReadLocal !== void 0 ? rawReadLocal : rawReadGsi, diagnostics, {
|
|
14200
|
+
...typeof indexName === "string" && { indexName },
|
|
14201
|
+
blockName: rawReadLocal !== void 0 ? "Replicas[local].GlobalSecondaryIndexes[].ReadOnDemandThroughputSettings" : "ReadOnDemandThroughputSettings",
|
|
14202
|
+
member: "MaxReadRequestUnits"
|
|
14203
|
+
});
|
|
14204
|
+
const maxWrite = toFiniteNumber(rawWrite);
|
|
14205
|
+
const maxRead = toFiniteNumber(rawReadLocal) ?? toFiniteNumber(rawReadGsi);
|
|
14206
|
+
const merged = mergeExplicitThroughputBlock(explicitOnDemand, {
|
|
13955
14207
|
...maxRead !== void 0 && { MaxReadRequestUnits: maxRead },
|
|
13956
14208
|
...maxWrite !== void 0 && { MaxWriteRequestUnits: maxWrite }
|
|
13957
|
-
}
|
|
14209
|
+
}, ["MaxReadRequestUnits", "MaxWriteRequestUnits"], diagnostics, {
|
|
14210
|
+
...typeof indexName === "string" && { indexName },
|
|
14211
|
+
blockName: "OnDemandThroughput"
|
|
14212
|
+
});
|
|
14213
|
+
if (merged) sdk.OnDemandThroughput = merged;
|
|
13958
14214
|
}
|
|
13959
14215
|
result.push(sdk);
|
|
13960
14216
|
}
|
|
@@ -13972,7 +14228,7 @@ function toSdkGlobalSecondaryIndexes(properties, region, billingMode, source = "
|
|
|
13972
14228
|
* is issue #1440 one level up from where the member test looks.
|
|
13973
14229
|
*/
|
|
13974
14230
|
function isUnresolvedIntrinsicBlock(value) {
|
|
13975
|
-
const record = asRecord(value);
|
|
14231
|
+
const record = asRecord$1(value);
|
|
13976
14232
|
if (!record) return false;
|
|
13977
14233
|
const keys = Object.keys(record);
|
|
13978
14234
|
return keys.length > 0 && keys.every((k) => k === "Ref" || k.startsWith("Fn::"));
|
|
@@ -14014,31 +14270,25 @@ function collectRawOnDemandDeclarations(properties, region) {
|
|
|
14014
14270
|
const rawIndexes = properties["GlobalSecondaryIndexes"];
|
|
14015
14271
|
if (!Array.isArray(rawIndexes)) return out;
|
|
14016
14272
|
const replicas = properties["Replicas"];
|
|
14017
|
-
const localReplicaIndexes = asRecord(Array.isArray(replicas) ? replicas.find((r) => asRecord(r)?.["Region"] === region) : void 0)?.["GlobalSecondaryIndexes"];
|
|
14273
|
+
const localReplicaIndexes = asRecord$1(Array.isArray(replicas) ? replicas.find((r) => asRecord$1(r)?.["Region"] === region) : void 0)?.["GlobalSecondaryIndexes"];
|
|
14018
14274
|
const localByName = /* @__PURE__ */ new Map();
|
|
14019
14275
|
if (Array.isArray(localReplicaIndexes)) for (const entry of localReplicaIndexes) {
|
|
14020
|
-
const record = asRecord(entry);
|
|
14276
|
+
const record = asRecord$1(entry);
|
|
14021
14277
|
const name = record?.["IndexName"];
|
|
14022
14278
|
if (record && typeof name === "string") localByName.set(name, record);
|
|
14023
14279
|
}
|
|
14024
14280
|
for (const entry of rawIndexes) {
|
|
14025
|
-
const gsi = asRecord(entry);
|
|
14281
|
+
const gsi = asRecord$1(entry);
|
|
14026
14282
|
const name = gsi?.["IndexName"];
|
|
14027
14283
|
if (!gsi || typeof name !== "string") continue;
|
|
14028
14284
|
const localEntry = localByName.get(name);
|
|
14029
|
-
const explicitOnDemand = asRecord(gsi["OnDemandThroughput"]);
|
|
14030
|
-
|
|
14031
|
-
out.set(name, {
|
|
14032
|
-
readDeclared: explicitOnDemand["MaxReadRequestUnits"] !== void 0,
|
|
14033
|
-
writeDeclared: explicitOnDemand["MaxWriteRequestUnits"] !== void 0
|
|
14034
|
-
});
|
|
14035
|
-
continue;
|
|
14036
|
-
}
|
|
14285
|
+
const explicitOnDemand = asRecord$1(gsi["OnDemandThroughput"]);
|
|
14286
|
+
const explicitIsIntrinsic = isUnresolvedIntrinsicBlock(gsi["OnDemandThroughput"]);
|
|
14037
14287
|
const readBlocks = [localEntry?.["ReadOnDemandThroughputSettings"], gsi["ReadOnDemandThroughputSettings"]];
|
|
14038
14288
|
const writeBlock = gsi["WriteOnDemandThroughputSettings"];
|
|
14039
14289
|
out.set(name, {
|
|
14040
|
-
readDeclared: readBlocks.some((block) => asRecord(block)?.["MaxReadRequestUnits"] !== void 0 || isUnresolvedIntrinsicBlock(block)),
|
|
14041
|
-
writeDeclared: asRecord(writeBlock)?.["MaxWriteRequestUnits"] !== void 0 || isUnresolvedIntrinsicBlock(writeBlock)
|
|
14290
|
+
readDeclared: explicitOnDemand?.["MaxReadRequestUnits"] !== void 0 || explicitIsIntrinsic || readBlocks.some((block) => asRecord$1(block)?.["MaxReadRequestUnits"] !== void 0 || isUnresolvedIntrinsicBlock(block)),
|
|
14291
|
+
writeDeclared: explicitOnDemand?.["MaxWriteRequestUnits"] !== void 0 || explicitIsIntrinsic || asRecord$1(writeBlock)?.["MaxWriteRequestUnits"] !== void 0 || isUnresolvedIntrinsicBlock(writeBlock)
|
|
14042
14292
|
});
|
|
14043
14293
|
}
|
|
14044
14294
|
return out;
|
|
@@ -14063,23 +14313,113 @@ function collectRawOnDemandDeclarations(properties, region) {
|
|
|
14063
14313
|
* status back in `readCurrentState`). Recorded here rather than silently
|
|
14064
14314
|
* dropped; tracked with the other GlobalTable nested-key gaps.
|
|
14065
14315
|
*/
|
|
14066
|
-
function toSdkReplicaGlobalSecondaryIndexes(replicaIndexes) {
|
|
14316
|
+
function toSdkReplicaGlobalSecondaryIndexes(replicaIndexes, billingMode, diagnostics) {
|
|
14067
14317
|
if (!Array.isArray(replicaIndexes) || replicaIndexes.length === 0) return void 0;
|
|
14068
14318
|
return replicaIndexes.map((entry) => {
|
|
14069
|
-
const cfn = asRecord(entry);
|
|
14319
|
+
const cfn = asRecord$1(entry);
|
|
14070
14320
|
if (!cfn) return entry;
|
|
14071
|
-
const
|
|
14072
|
-
const
|
|
14073
|
-
const
|
|
14074
|
-
|
|
14075
|
-
|
|
14076
|
-
if (explicitProvisioned) sdk.ProvisionedThroughputOverride = explicitProvisioned;
|
|
14077
|
-
else if (readCapacity !== void 0) sdk.ProvisionedThroughputOverride = { ReadCapacityUnits: readCapacity };
|
|
14078
|
-
if (explicitOnDemand) sdk.OnDemandThroughputOverride = explicitOnDemand;
|
|
14079
|
-
else if (maxReadRequestUnits !== void 0) sdk.OnDemandThroughputOverride = { MaxReadRequestUnits: maxReadRequestUnits };
|
|
14321
|
+
const indexName = cfn["IndexName"];
|
|
14322
|
+
const sdk = { IndexName: indexName };
|
|
14323
|
+
const overrides = toSdkReplicaThroughputOverrides(cfn, billingMode, diagnostics, { ...typeof indexName === "string" && { indexName } });
|
|
14324
|
+
if (overrides.ProvisionedThroughputOverride) sdk.ProvisionedThroughputOverride = overrides.ProvisionedThroughputOverride;
|
|
14325
|
+
if (overrides.OnDemandThroughputOverride) sdk.OnDemandThroughputOverride = overrides.OnDemandThroughputOverride;
|
|
14080
14326
|
return sdk;
|
|
14081
14327
|
});
|
|
14082
14328
|
}
|
|
14329
|
+
/**
|
|
14330
|
+
* The `{Provisioned,OnDemand}ThroughputOverride` pair for ONE replica-scoped
|
|
14331
|
+
* CFn block — shared by the per-GSI replica entries and, since issue #1436,
|
|
14332
|
+
* by the replica's own TABLE-level entry, which carries the very same two CFn
|
|
14333
|
+
* spellings (`ReadProvisionedThroughputSettings` /
|
|
14334
|
+
* `ReadOnDemandThroughputSettings`) and maps to the very same two SDK members
|
|
14335
|
+
* on `{Create,Update}ReplicationGroupMemberAction`. One helper so the two
|
|
14336
|
+
* levels cannot disagree about the mapping.
|
|
14337
|
+
*
|
|
14338
|
+
* Same #1428 treatment as the GSI-level blocks: coerce, merge an explicit
|
|
14339
|
+
* SDK-shaped override PER MEMBER over the derived value, and gate on billing
|
|
14340
|
+
* mode. Both override members are READ-side only — a replica has no
|
|
14341
|
+
* independent write capacity, since writes replicate from the source table.
|
|
14342
|
+
*/
|
|
14343
|
+
function toSdkReplicaThroughputOverrides(cfn, billingMode, diagnostics, context) {
|
|
14344
|
+
const explicitProvisioned = asRecord$1(cfn["ProvisionedThroughputOverride"]);
|
|
14345
|
+
const explicitOnDemand = asRecord$1(cfn["OnDemandThroughputOverride"]);
|
|
14346
|
+
if (explicitProvisioned && billingMode !== "PROVISIONED") diagnostics?.push({
|
|
14347
|
+
kind: "billing-mode-mismatch",
|
|
14348
|
+
...context,
|
|
14349
|
+
member: "ProvisionedThroughputOverride",
|
|
14350
|
+
message: `an explicit ProvisionedThroughputOverride was dropped because the table's BillingMode is ${billingMode}; AWS rejects provisioned capacity on an on-demand table.`
|
|
14351
|
+
});
|
|
14352
|
+
if (explicitOnDemand && billingMode === "PROVISIONED") diagnostics?.push({
|
|
14353
|
+
kind: "billing-mode-mismatch",
|
|
14354
|
+
...context,
|
|
14355
|
+
member: "OnDemandThroughputOverride",
|
|
14356
|
+
message: "an explicit OnDemandThroughputOverride was dropped because the table's BillingMode is PROVISIONED; AWS rejects on-demand ceilings on a provisioned table."
|
|
14357
|
+
});
|
|
14358
|
+
if (billingMode === "PROVISIONED") {
|
|
14359
|
+
const readCapacity = deriveReadCapacityUnits(asRecord$1(cfn["ReadProvisionedThroughputSettings"]));
|
|
14360
|
+
const merged = mergeExplicitThroughputBlock(explicitProvisioned, readCapacity !== void 0 ? { ReadCapacityUnits: readCapacity } : void 0, ["ReadCapacityUnits"], diagnostics, {
|
|
14361
|
+
...context,
|
|
14362
|
+
blockName: "ProvisionedThroughputOverride"
|
|
14363
|
+
});
|
|
14364
|
+
return merged ? { ProvisionedThroughputOverride: merged } : {};
|
|
14365
|
+
}
|
|
14366
|
+
const rawMaxRead = asRecord$1(cfn["ReadOnDemandThroughputSettings"])?.["MaxReadRequestUnits"];
|
|
14367
|
+
reportUnresolvedRawMember(rawMaxRead, diagnostics, {
|
|
14368
|
+
...context,
|
|
14369
|
+
blockName: "ReadOnDemandThroughputSettings",
|
|
14370
|
+
member: "MaxReadRequestUnits"
|
|
14371
|
+
});
|
|
14372
|
+
const maxReadRequestUnits = toFiniteNumber(rawMaxRead);
|
|
14373
|
+
const merged = mergeExplicitThroughputBlock(explicitOnDemand, maxReadRequestUnits !== void 0 ? { MaxReadRequestUnits: maxReadRequestUnits } : void 0, ["MaxReadRequestUnits"], diagnostics, {
|
|
14374
|
+
...context,
|
|
14375
|
+
blockName: "OnDemandThroughputOverride"
|
|
14376
|
+
});
|
|
14377
|
+
return merged ? { OnDemandThroughputOverride: merged } : {};
|
|
14378
|
+
}
|
|
14379
|
+
/**
|
|
14380
|
+
* The TABLE-level on-demand ceilings, gathered from the two DIFFERENT places
|
|
14381
|
+
* the CFn `AWS::DynamoDB::GlobalTable` shape puts them (issue #1436).
|
|
14382
|
+
*
|
|
14383
|
+
* The write ceiling is top-level `WriteOnDemandThroughputSettings`, but the
|
|
14384
|
+
* READ ceiling lives on the LOCAL REPLICA as `ReadOnDemandThroughputSettings`
|
|
14385
|
+
* — the same write-here / read-on-the-replica split #1387 established one
|
|
14386
|
+
* level down for a GSI. The provider read only the write half, so the plain
|
|
14387
|
+
* CDK spelling
|
|
14388
|
+
*
|
|
14389
|
+
* billing: Billing.onDemand({ maxReadRequestUnits: 100, maxWriteRequestUnits: 200 })
|
|
14390
|
+
*
|
|
14391
|
+
* deployed a table with NO read ceiling and reported success. That is a
|
|
14392
|
+
* drop on the way IN, not the absent-field-reset class of #1423 / #1434.
|
|
14393
|
+
*
|
|
14394
|
+
* The registry schema (`cloudformation:DescribeType`, 2026-08-09) confirms the
|
|
14395
|
+
* member on `ReplicaSpecification`:
|
|
14396
|
+
* `ReadOnDemandThroughputSettings: { MaxReadRequestUnits: integer, minimum 1 }`.
|
|
14397
|
+
*/
|
|
14398
|
+
function collectTableOnDemandCeilings(properties, region, diagnostics) {
|
|
14399
|
+
const writeBlock = properties["WriteOnDemandThroughputSettings"];
|
|
14400
|
+
const replicas = properties["Replicas"];
|
|
14401
|
+
const readBlock = asRecord$1(Array.isArray(replicas) ? replicas.find((r) => asRecord$1(r)?.["Region"] === region) : void 0)?.["ReadOnDemandThroughputSettings"];
|
|
14402
|
+
const rawRead = asRecord$1(readBlock)?.["MaxReadRequestUnits"];
|
|
14403
|
+
const rawWrite = asRecord$1(writeBlock)?.["MaxWriteRequestUnits"];
|
|
14404
|
+
reportUnresolvedRawMember(rawRead, diagnostics, {
|
|
14405
|
+
blockName: "Replicas[local].ReadOnDemandThroughputSettings",
|
|
14406
|
+
member: "MaxReadRequestUnits"
|
|
14407
|
+
});
|
|
14408
|
+
reportUnresolvedRawMember(rawWrite, diagnostics, {
|
|
14409
|
+
blockName: "WriteOnDemandThroughputSettings",
|
|
14410
|
+
member: "MaxWriteRequestUnits"
|
|
14411
|
+
});
|
|
14412
|
+
return {
|
|
14413
|
+
read: {
|
|
14414
|
+
declared: rawRead !== void 0 || isUnresolvedIntrinsicBlock(readBlock),
|
|
14415
|
+
value: toFiniteNumber(rawRead)
|
|
14416
|
+
},
|
|
14417
|
+
write: {
|
|
14418
|
+
declared: rawWrite !== void 0 || isUnresolvedIntrinsicBlock(writeBlock),
|
|
14419
|
+
value: toFiniteNumber(rawWrite)
|
|
14420
|
+
}
|
|
14421
|
+
};
|
|
14422
|
+
}
|
|
14083
14423
|
function diffReplicas(oldReplicas, newReplicas) {
|
|
14084
14424
|
const oldByRegion = /* @__PURE__ */ new Map();
|
|
14085
14425
|
const newByRegion = /* @__PURE__ */ new Map();
|
|
@@ -33237,6 +33577,7 @@ var GlueProvider = class {
|
|
|
33237
33577
|
try {
|
|
33238
33578
|
const builtTableInput = this.buildTableInput(tableInput, tableName);
|
|
33239
33579
|
this.preserveAwsManagedParameters(builtTableInput, tableInput["Parameters"], (previousProperties?.["TableInput"])?.["Parameters"], live.parameters);
|
|
33580
|
+
this.preserveAwsManagedStorageDescriptor(builtTableInput, tableInput, previousProperties?.["TableInput"], live.storageDescriptor);
|
|
33240
33581
|
await this.getClient().send(new UpdateTableCommand$1({
|
|
33241
33582
|
...catalogId !== void 0 && { CatalogId: catalogId },
|
|
33242
33583
|
DatabaseName: databaseName,
|
|
@@ -33253,7 +33594,7 @@ var GlueProvider = class {
|
|
|
33253
33594
|
let message = `Failed to update Glue Table ${logicalId}: ${error instanceof Error ? error.message : String(error)}`;
|
|
33254
33595
|
if (error instanceof ConcurrentModificationException) {
|
|
33255
33596
|
message += ". Another writer changed the table between cdkd's pre-update read and its UpdateTable call.";
|
|
33256
|
-
message += live.versionId !== void 0 ? ` cdkd sent the version it read (${live.versionId}) as a precondition and AWS rejected the write, rather than let it put back the AWS-managed Parameters read a moment earlier — which for an Apache Iceberg table would have pinned it to an older metadata_location (an effective snapshot rollback).` : " AWS returned no VersionId on the pre-update read, so cdkd could not attach a precondition; AWS rejected the write on its own.";
|
|
33597
|
+
message += live.versionId !== void 0 ? ` cdkd sent the version it read (${live.versionId}) as a precondition and AWS rejected the write, rather than let it put back the AWS-managed Parameters and StorageDescriptor members read a moment earlier — which for an Apache Iceberg table would have pinned it to an older metadata_location (an effective snapshot rollback).` : " AWS returned no VersionId on the pre-update read, so cdkd could not attach a precondition; AWS rejected the write on its own.";
|
|
33257
33598
|
message += ` Re-run the deploy to pick up the current values.`;
|
|
33258
33599
|
}
|
|
33259
33600
|
throw new ProvisioningError(message, resourceType, logicalId, physicalId, cause);
|
|
@@ -33343,8 +33684,91 @@ var GlueProvider = class {
|
|
|
33343
33684
|
};
|
|
33344
33685
|
}
|
|
33345
33686
|
/**
|
|
33346
|
-
*
|
|
33347
|
-
*
|
|
33687
|
+
* Read-merge-write for out-of-band-authored `TableInput.StorageDescriptor`
|
|
33688
|
+
* members (issue #1479) — the subtree sibling of
|
|
33689
|
+
* {@link preserveAwsManagedParameters}, applied per SD MEMBER, with the two
|
|
33690
|
+
* nested bags (`StorageDescriptor.Parameters`, `SerdeInfo.Parameters`)
|
|
33691
|
+
* getting the same per-KEY rule one level down.
|
|
33692
|
+
*
|
|
33693
|
+
* Live probes (2026-08-10, us-east-1 — recorded on issue #1479) established
|
|
33694
|
+
* WHO authors what, which is why this is per-member rather than a blanket
|
|
33695
|
+
* merge:
|
|
33696
|
+
*
|
|
33697
|
+
* - A USER-authored `EXTERNAL_TABLE`: re-sending the template verbatim
|
|
33698
|
+
* reads back identical — the service itself authors only scalar defaults
|
|
33699
|
+
* (`Compressed: false`, `NumberOfBuckets: 0`, ...). Full-replace is
|
|
33700
|
+
* CORRECT for template-declared members, and `drift --revert` depends on
|
|
33701
|
+
* it to clear console-side edits.
|
|
33702
|
+
* - A CRAWLER-populated table: the crawler authors `Columns`,
|
|
33703
|
+
* `InputFormat` / `OutputFormat`, `SerdeInfo` (incl. its `Parameters`
|
|
33704
|
+
* bag), and `StorageDescriptor.Parameters`; an UpdateTable restating only
|
|
33705
|
+
* a minimal template wiped ALL of them (`Columns` -> `[]`, `SerdeInfo`
|
|
33706
|
+
* gone) while reporting success.
|
|
33707
|
+
* - An ICEBERG table: an UpdateTable omitting `Columns` was ACCEPTED and
|
|
33708
|
+
* wiped the catalog schema to `[]` (Glue re-derives Iceberg columns from
|
|
33709
|
+
* table metadata, so the catalog copy is metadata-authored between
|
|
33710
|
+
* engine commits).
|
|
33711
|
+
*
|
|
33712
|
+
* The preservation key is the SAME "present in NEITHER template side" test
|
|
33713
|
+
* as {@link preserveAwsManagedParameters}, per member:
|
|
33714
|
+
*
|
|
33715
|
+
* | member in desired | member in previous | outcome |
|
|
33716
|
+
* | ----------------- | ------------------ | ---------------------------------- |
|
|
33717
|
+
* | yes | (either) | template wins |
|
|
33718
|
+
* | no | yes | the USER REMOVED it -> stays removed |
|
|
33719
|
+
* | no | no | out-of-band authored -> carried forward from live |
|
|
33720
|
+
*
|
|
33721
|
+
* A template that never declared `StorageDescriptor` on EITHER side (a
|
|
33722
|
+
* crawler-managed table adopted into a minimal template) carries the whole
|
|
33723
|
+
* live block forward. Deliberately scoped to the `StorageDescriptor`
|
|
33724
|
+
* subtree per issue #1479 — the crawler also authors `PartitionKeys` /
|
|
33725
|
+
* `Owner`, but widening beyond the issue's member set is a separate
|
|
33726
|
+
* decision (recorded there).
|
|
33727
|
+
*
|
|
33728
|
+
* TOCTOU: the write rides the same unconditional `VersionId` precondition
|
|
33729
|
+
* as the Parameters merge (see {@link readLiveTableState}), so a concurrent
|
|
33730
|
+
* out-of-band write fails the update loudly instead of being clobbered.
|
|
33731
|
+
*/
|
|
33732
|
+
preserveAwsManagedStorageDescriptor(built, desiredTableInput, previousTableInput, liveSd) {
|
|
33733
|
+
if (!liveSd || Object.keys(liveSd).length === 0) return;
|
|
33734
|
+
const desiredSdRaw = desiredTableInput["StorageDescriptor"];
|
|
33735
|
+
const previousSdRaw = asRecord(previousTableInput)?.["StorageDescriptor"];
|
|
33736
|
+
if (desiredSdRaw === void 0 && previousSdRaw === void 0) {
|
|
33737
|
+
built.StorageDescriptor = liveSd;
|
|
33738
|
+
return;
|
|
33739
|
+
}
|
|
33740
|
+
const desiredKeys = parameterKeySet(desiredSdRaw);
|
|
33741
|
+
const previousKeys = parameterKeySet(previousSdRaw);
|
|
33742
|
+
const sdOut = { ...built.StorageDescriptor ?? {} };
|
|
33743
|
+
for (const [member, liveValue] of Object.entries(liveSd)) {
|
|
33744
|
+
if (liveValue === void 0) continue;
|
|
33745
|
+
if (desiredKeys.has(member) || previousKeys.has(member)) continue;
|
|
33746
|
+
sdOut[member] = liveValue;
|
|
33747
|
+
}
|
|
33748
|
+
const desiredSd = asRecord(desiredSdRaw);
|
|
33749
|
+
const previousSd = asRecord(previousSdRaw);
|
|
33750
|
+
if (desiredKeys.has("Parameters") || previousKeys.has("Parameters")) this.preserveAwsManagedParameters(sdOut, desiredSd?.["Parameters"], previousSd?.["Parameters"], liveSd.Parameters);
|
|
33751
|
+
if (desiredKeys.has("SerdeInfo") && liveSd.SerdeInfo) {
|
|
33752
|
+
const desiredSerde = asRecord(desiredSd?.["SerdeInfo"]);
|
|
33753
|
+
const previousSerde = asRecord(previousSd?.["SerdeInfo"]);
|
|
33754
|
+
const desiredSerdeKeys = parameterKeySet(desiredSerde);
|
|
33755
|
+
const previousSerdeKeys = parameterKeySet(previousSerde);
|
|
33756
|
+
const serdeOut = { ...sdOut["SerdeInfo"] ?? {} };
|
|
33757
|
+
for (const [member, liveValue] of Object.entries(liveSd.SerdeInfo)) {
|
|
33758
|
+
if (liveValue === void 0) continue;
|
|
33759
|
+
if (desiredSerdeKeys.has(member) || previousSerdeKeys.has(member)) continue;
|
|
33760
|
+
serdeOut[member] = liveValue;
|
|
33761
|
+
}
|
|
33762
|
+
if (desiredSerdeKeys.has("Parameters") || previousSerdeKeys.has("Parameters")) this.preserveAwsManagedParameters(serdeOut, desiredSerde?.["Parameters"], previousSerde?.["Parameters"], liveSd.SerdeInfo.Parameters);
|
|
33763
|
+
if (Object.keys(serdeOut).length > 0) sdOut["SerdeInfo"] = serdeOut;
|
|
33764
|
+
}
|
|
33765
|
+
if (Object.keys(sdOut).length > 0) built.StorageDescriptor = sdOut;
|
|
33766
|
+
}
|
|
33767
|
+
/**
|
|
33768
|
+
* The live table state the two merges need: its current `Parameters` (for
|
|
33769
|
+
* {@link preserveAwsManagedParameters}), its `StorageDescriptor` (for
|
|
33770
|
+
* {@link preserveAwsManagedStorageDescriptor}, issue #1479), plus the
|
|
33771
|
+
* `VersionId` that pins both.
|
|
33348
33772
|
*
|
|
33349
33773
|
* A missing table returns empty rather than throwing: `UpdateTable` is about
|
|
33350
33774
|
* to surface the real, more actionable error. Any OTHER failure is fatal by
|
|
@@ -33393,11 +33817,13 @@ var GlueProvider = class {
|
|
|
33393
33817
|
}));
|
|
33394
33818
|
return {
|
|
33395
33819
|
parameters: resp.Table?.Parameters,
|
|
33820
|
+
storageDescriptor: resp.Table?.StorageDescriptor,
|
|
33396
33821
|
versionId: resp.Table?.VersionId
|
|
33397
33822
|
};
|
|
33398
33823
|
} catch (error) {
|
|
33399
33824
|
if (error instanceof EntityNotFoundException) return {
|
|
33400
33825
|
parameters: void 0,
|
|
33826
|
+
storageDescriptor: void 0,
|
|
33401
33827
|
versionId: void 0
|
|
33402
33828
|
};
|
|
33403
33829
|
throw new ProvisioningError(preUpdateReadFailureMessage("Table", "glue:GetTable", logicalId, error), resourceType, logicalId, physicalId, error instanceof Error ? error : void 0);
|
|
@@ -34433,6 +34859,19 @@ function parameterKeySet(value) {
|
|
|
34433
34859
|
return new Set(Object.keys(value));
|
|
34434
34860
|
}
|
|
34435
34861
|
/**
|
|
34862
|
+
* Narrow an unknown template / state value to a plain object, or `undefined`.
|
|
34863
|
+
* A malformed side (string / array / null) yields `undefined` and therefore
|
|
34864
|
+
* an empty key set — the tolerant treatment `parameterKeySet` already gives
|
|
34865
|
+
* bags, extended to the StorageDescriptor merge's containers. (An unresolved
|
|
34866
|
+
* intrinsic is itself a plain object, so it passes through and contributes
|
|
34867
|
+
* its `Fn::*`/`Ref` key — harmless here, since providers only ever see
|
|
34868
|
+
* resolved values.)
|
|
34869
|
+
*/
|
|
34870
|
+
function asRecord(value) {
|
|
34871
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return;
|
|
34872
|
+
return value;
|
|
34873
|
+
}
|
|
34874
|
+
/**
|
|
34436
34875
|
* Message for a failed pre-update `Get*` read (issue #1461). Names the IAM
|
|
34437
34876
|
* action so a permission gap is one line away from fixed, and says what the
|
|
34438
34877
|
* read is FOR so the failure does not read as gratuitous.
|
|
@@ -66008,7 +66447,7 @@ function createMigrateCommand() {
|
|
|
66008
66447
|
*/
|
|
66009
66448
|
function buildProgram() {
|
|
66010
66449
|
const program = new Command();
|
|
66011
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.278.
|
|
66450
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.278.32");
|
|
66012
66451
|
program.hook("preAction", (_thisCommand, actionCommand) => {
|
|
66013
66452
|
const { profile } = actionCommand.optsWithGlobals();
|
|
66014
66453
|
if (profile !== void 0) process.env["AWS_PROFILE"] = profile;
|