@go-to-k/cdkd 0.103.2 → 0.104.0
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/cli.js +481 -78
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -9,7 +9,7 @@ import { CreateTopicCommand, DeleteTopicCommand, GetSubscriptionAttributesComman
|
|
|
9
9
|
import { AddPermissionCommand, CreateEventSourceMappingCommand, CreateFunctionCommand, CreateFunctionUrlConfigCommand, DeleteEventSourceMappingCommand, DeleteFunctionCommand, DeleteFunctionUrlConfigCommand, DeleteLayerVersionCommand, GetEventSourceMappingCommand, GetFunctionCommand, GetFunctionUrlConfigCommand, GetLayerVersionByArnCommand, GetPolicyCommand, LambdaClient, ListFunctionsCommand, ListLayersCommand, ListTagsCommand, PublishLayerVersionCommand, RemovePermissionCommand, ResourceNotFoundException, TagResourceCommand as TagResourceCommand$1, UntagResourceCommand as UntagResourceCommand$1, UpdateEventSourceMappingCommand, UpdateFunctionCodeCommand, UpdateFunctionConfigurationCommand, UpdateFunctionUrlConfigCommand, waitUntilFunctionUpdatedV2 } from "@aws-sdk/client-lambda";
|
|
10
10
|
import { AssumeRoleCommand, GetCallerIdentityCommand, STSClient } from "@aws-sdk/client-sts";
|
|
11
11
|
import { AssociateRouteTableCommand, AttachInternetGatewayCommand, AuthorizeSecurityGroupEgressCommand, AuthorizeSecurityGroupIngressCommand, CreateInternetGatewayCommand, CreateNatGatewayCommand, CreateNetworkAclCommand, CreateNetworkAclEntryCommand, CreateRouteCommand, CreateRouteTableCommand, CreateSecurityGroupCommand, CreateSubnetCommand, CreateTagsCommand, CreateVpcCommand, DeleteInternetGatewayCommand, DeleteNatGatewayCommand, DeleteNetworkAclCommand, DeleteNetworkAclEntryCommand, DeleteNetworkInterfaceCommand, DeleteRouteCommand, DeleteRouteTableCommand, DeleteSecurityGroupCommand, DeleteSubnetCommand, DeleteTagsCommand, DeleteVpcCommand, DescribeAvailabilityZonesCommand, DescribeInstanceAttributeCommand, DescribeInstancesCommand, DescribeInternetGatewaysCommand, DescribeNatGatewaysCommand, DescribeNetworkAclsCommand, DescribeNetworkInterfacesCommand, DescribeRouteTablesCommand, DescribeSecurityGroupsCommand, DescribeSubnetsCommand, DescribeVolumesCommand, DescribeVpcAttributeCommand, DescribeVpcsCommand, DetachInternetGatewayCommand, DisassociateRouteTableCommand, EC2Client, ModifyInstanceAttributeCommand, ModifySubnetAttributeCommand, ModifyVpcAttributeCommand, ReplaceNetworkAclAssociationCommand, RevokeSecurityGroupEgressCommand, RevokeSecurityGroupIngressCommand, RunInstancesCommand, TerminateInstancesCommand, waitUntilInstanceRunning, waitUntilInstanceTerminated, waitUntilNatGatewayAvailable, waitUntilNatGatewayDeleted } from "@aws-sdk/client-ec2";
|
|
12
|
-
import { CreateTableCommand, DeleteTableCommand, DescribeTableCommand, DynamoDBClient, ListTablesCommand, ListTagsOfResourceCommand, ResourceNotFoundException as ResourceNotFoundException$1, TagResourceCommand as TagResourceCommand$2, UntagResourceCommand as UntagResourceCommand$2, UpdateTableCommand, UpdateTimeToLiveCommand } from "@aws-sdk/client-dynamodb";
|
|
12
|
+
import { CreateTableCommand, DeleteTableCommand, DescribeContinuousBackupsCommand, DescribeContributorInsightsCommand, DescribeKinesisStreamingDestinationCommand, DescribeTableCommand, DescribeTimeToLiveCommand, DynamoDBClient, ListTablesCommand, ListTagsOfResourceCommand, ResourceNotFoundException as ResourceNotFoundException$1, TagResourceCommand as TagResourceCommand$2, UntagResourceCommand as UntagResourceCommand$2, UpdateTableCommand, UpdateTimeToLiveCommand } from "@aws-sdk/client-dynamodb";
|
|
13
13
|
import { CloudFormationClient, CreateChangeSetCommand, DeleteChangeSetCommand, DeleteStackCommand, DescribeChangeSetCommand, DescribeStackEventsCommand, DescribeStackResourcesCommand, DescribeStacksCommand, DescribeTypeCommand, ExecuteChangeSetCommand, GetTemplateCommand, UpdateStackCommand, waitUntilChangeSetCreateComplete, waitUntilStackDeleteComplete, waitUntilStackImportComplete, waitUntilStackUpdateComplete } from "@aws-sdk/client-cloudformation";
|
|
14
14
|
import { APIGatewayClient, CreateAuthorizerCommand, CreateDeploymentCommand, CreateResourceCommand, CreateStageCommand, DeleteAuthorizerCommand, DeleteDeploymentCommand, DeleteMethodCommand, DeleteResourceCommand, DeleteStageCommand, GetAccountCommand, GetAuthorizerCommand, GetDeploymentCommand, GetMethodCommand, GetResourceCommand, GetStageCommand, NotFoundException as NotFoundException$1, PutIntegrationCommand, PutIntegrationResponseCommand, PutMethodCommand, PutMethodResponseCommand, TagResourceCommand as TagResourceCommand$3, UntagResourceCommand as UntagResourceCommand$3, UpdateAccountCommand, UpdateAuthorizerCommand, UpdateMethodCommand, UpdateStageCommand } from "@aws-sdk/client-api-gateway";
|
|
15
15
|
import { CreateEventBusCommand, DeleteEventBusCommand, DeleteRuleCommand, DescribeEventBusCommand, DescribeRuleCommand, EventBridgeClient, ListEventBusesCommand, ListRulesCommand, ListTagsForResourceCommand as ListTagsForResourceCommand$1, ListTargetsByRuleCommand, PutRuleCommand, PutTargetsCommand, RemoveTargetsCommand, ResourceNotFoundException as ResourceNotFoundException$2, TagResourceCommand as TagResourceCommand$4, UntagResourceCommand as UntagResourceCommand$4, UpdateEventBusCommand } from "@aws-sdk/client-eventbridge";
|
|
@@ -7864,20 +7864,36 @@ var DynamoDBTableProvider = class {
|
|
|
7864
7864
|
* is the 2019.11.21 generation, which uses the regular DynamoDB CRUD API
|
|
7865
7865
|
* (`CreateTableCommand` + `UpdateTableCommand` with `ReplicaUpdates`).
|
|
7866
7866
|
*
|
|
7867
|
-
*
|
|
7868
|
-
* - `update()`
|
|
7869
|
-
*
|
|
7870
|
-
*
|
|
7871
|
-
* -
|
|
7872
|
-
*
|
|
7873
|
-
*
|
|
7867
|
+
* In-place update support (post-PR #384 follow-up):
|
|
7868
|
+
* - `update()` covers every mutable surface — Tags, DeletionProtection,
|
|
7869
|
+
* TableClass, SSE, StreamSpec, OnDemand throughput, BillingMode flip,
|
|
7870
|
+
* Replica add / remove / modify, GSI add / remove / modify, TTL toggle.
|
|
7871
|
+
* - The serialization is load-bearing: AWS's `UpdateTable` accepts only
|
|
7872
|
+
* ONE of `{BillingMode, ReplicaUpdates, GlobalSecondaryIndexUpdates}`
|
|
7873
|
+
* per call, so each category is its own SDK round-trip with a wait-for
|
|
7874
|
+
* -ACTIVE in between. Immutable property changes (TableName, KeySchema,
|
|
7875
|
+
* AttributeDefinitions removal, LocalSecondaryIndexes) throw
|
|
7876
|
+
* `ProvisioningError` naming the offending field — the deploy engine's
|
|
7877
|
+
* diff classification should catch these as REPLACEMENT before ever
|
|
7878
|
+
* calling `update()`, but the guard is defense-in-depth.
|
|
7874
7879
|
* - Per-replica drift (`ContributorInsightsSpecification` /
|
|
7875
|
-
* `PointInTimeRecoverySpecification` / `KinesisStreamSpecification`)
|
|
7876
|
-
*
|
|
7880
|
+
* `PointInTimeRecoverySpecification` / `KinesisStreamSpecification`)
|
|
7881
|
+
* is surfaced for the LOCAL replica only; cross-region replicas
|
|
7882
|
+
* require per-region SDK clients which are out of scope for v1.
|
|
7877
7883
|
*/
|
|
7878
7884
|
var DynamoDBGlobalTableProvider = class {
|
|
7879
7885
|
dynamoDBClient;
|
|
7880
7886
|
logger = getLogger().child("DynamoDBGlobalTableProvider");
|
|
7887
|
+
/**
|
|
7888
|
+
* Caches `getAttribute(physicalId, attribute)` results for the lifetime
|
|
7889
|
+
* of this provider instance (one deploy run). Safe under the current
|
|
7890
|
+
* `update()` contract because `update()` cannot mid-deploy mutate
|
|
7891
|
+
* StreamArn / Arn / TableId — those are AWS-managed identifiers that
|
|
7892
|
+
* only change on REPLACEMENT (which destroys the provider instance).
|
|
7893
|
+
* If a future PR adds a stream toggle path that flips StreamArn on the
|
|
7894
|
+
* same physicalId, the cache must be invalidated on the matching
|
|
7895
|
+
* UpdateTable success.
|
|
7896
|
+
*/
|
|
7881
7897
|
attributeCache = /* @__PURE__ */ new Map();
|
|
7882
7898
|
handledProperties = new Map([["AWS::DynamoDB::GlobalTable", new Set([
|
|
7883
7899
|
"TableName",
|
|
@@ -7893,8 +7909,7 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
7893
7909
|
"TimeToLiveSpecification",
|
|
7894
7910
|
"WriteProvisionedThroughputSettings",
|
|
7895
7911
|
"WriteOnDemandThroughputSettings",
|
|
7896
|
-
"DeletionProtectionEnabled"
|
|
7897
|
-
"Tags"
|
|
7912
|
+
"DeletionProtectionEnabled"
|
|
7898
7913
|
])]]);
|
|
7899
7914
|
constructor() {
|
|
7900
7915
|
const awsClients = getAwsClients();
|
|
@@ -7935,15 +7950,7 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
7935
7950
|
AttributeDefinitions: attributeDefinitions,
|
|
7936
7951
|
BillingMode: billingMode
|
|
7937
7952
|
};
|
|
7938
|
-
if (billingMode === "PROVISIONED")
|
|
7939
|
-
const writeAutoScaling = properties["WriteProvisionedThroughputSettings"]?.["WriteCapacityAutoScalingSettings"];
|
|
7940
|
-
const writeCapacity = Number(writeAutoScaling?.["MinCapacity"] ?? 5);
|
|
7941
|
-
const readAutoScaling = (replicas.find((r) => r["Region"] === currentRegion)?.["ReadProvisionedThroughputSettings"])?.["ReadCapacityAutoScalingSettings"];
|
|
7942
|
-
createParams.ProvisionedThroughput = {
|
|
7943
|
-
ReadCapacityUnits: Number(readAutoScaling?.["MinCapacity"] ?? 5),
|
|
7944
|
-
WriteCapacityUnits: writeCapacity
|
|
7945
|
-
};
|
|
7946
|
-
}
|
|
7953
|
+
if (billingMode === "PROVISIONED") createParams.ProvisionedThroughput = derivePerCallProvisionedThroughput(properties, currentRegion);
|
|
7947
7954
|
const streamSpecInput = properties["StreamSpecification"];
|
|
7948
7955
|
const needsStream = replicas.some((r) => r["Region"] !== currentRegion) || replicas.length > 1;
|
|
7949
7956
|
if (streamSpecInput) createParams.StreamSpecification = {
|
|
@@ -7969,7 +7976,8 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
7969
7976
|
if (properties["TableClass"]) createParams.TableClass = properties["TableClass"];
|
|
7970
7977
|
const wodts = properties["WriteOnDemandThroughputSettings"];
|
|
7971
7978
|
if (wodts?.["MaxWriteRequestUnits"] !== void 0) createParams.OnDemandThroughput = { MaxWriteRequestUnits: Number(wodts["MaxWriteRequestUnits"]) };
|
|
7972
|
-
|
|
7979
|
+
const localReplicaTags = replicas.find((r) => r["Region"] === currentRegion)?.["Tags"];
|
|
7980
|
+
if (localReplicaTags && localReplicaTags.length > 0) createParams.Tags = localReplicaTags;
|
|
7973
7981
|
try {
|
|
7974
7982
|
await this.dynamoDBClient.send(new CreateTableCommand(createParams));
|
|
7975
7983
|
this.logger.debug(`CreateTable initiated for ${tableName}, waiting for ACTIVE`);
|
|
@@ -7978,11 +7986,11 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
7978
7986
|
throw new ProvisioningError(`Failed to create DynamoDB GlobalTable ${logicalId}: ${error instanceof Error ? error.message : String(error)}`, resourceType, logicalId, tableName, cause);
|
|
7979
7987
|
}
|
|
7980
7988
|
try {
|
|
7981
|
-
const tableInfo = await this.waitForTableActive(tableName);
|
|
7989
|
+
const tableInfo = await this.waitForTableActive(tableName, logicalId);
|
|
7982
7990
|
for (const replica of replicas) {
|
|
7983
7991
|
const region = replica["Region"];
|
|
7984
7992
|
if (!region || region === currentRegion) continue;
|
|
7985
|
-
await this.addReplica(tableName, replica, region);
|
|
7993
|
+
await this.addReplica(tableName, replica, region, logicalId);
|
|
7986
7994
|
}
|
|
7987
7995
|
if (properties["TimeToLiveSpecification"]) {
|
|
7988
7996
|
const ttl = properties["TimeToLiveSpecification"];
|
|
@@ -8009,6 +8017,21 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
8009
8017
|
} catch (wiringError) {
|
|
8010
8018
|
this.logger.warn(`Wiring failed after CreateTable for ${tableName}; attempting best-effort cleanup`);
|
|
8011
8019
|
try {
|
|
8020
|
+
const replicasForCleanup = (await this.dynamoDBClient.send(new DescribeTableCommand({ TableName: tableName }))).Table?.Replicas ?? [];
|
|
8021
|
+
for (const replica of replicasForCleanup) {
|
|
8022
|
+
const region = replica.RegionName;
|
|
8023
|
+
if (!region || region === currentRegion) continue;
|
|
8024
|
+
try {
|
|
8025
|
+
await this.dynamoDBClient.send(new UpdateTableCommand({
|
|
8026
|
+
TableName: tableName,
|
|
8027
|
+
ReplicaUpdates: [{ Delete: { RegionName: region } }]
|
|
8028
|
+
}));
|
|
8029
|
+
await this.waitForReplicaGone(tableName, region, logicalId);
|
|
8030
|
+
} catch (replicaCleanupErr) {
|
|
8031
|
+
const msg = replicaCleanupErr instanceof Error ? replicaCleanupErr.message : String(replicaCleanupErr);
|
|
8032
|
+
this.logger.warn(`Partial-create cleanup: failed to drop replica ${region} on ${tableName}: ${msg}. Run: aws dynamodb update-table --table-name ${tableName} --replica-updates 'Delete={RegionName=${region}}' --region ${currentRegion}`);
|
|
8033
|
+
}
|
|
8034
|
+
}
|
|
8012
8035
|
await this.dynamoDBClient.send(new DeleteTableCommand({ TableName: tableName }));
|
|
8013
8036
|
} catch (cleanupErr) {
|
|
8014
8037
|
const cleanupMsg = cleanupErr instanceof Error ? cleanupErr.message : String(cleanupErr);
|
|
@@ -8025,7 +8048,7 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
8025
8048
|
* Capped at 10 minutes per replica (AWS Replica provisioning typically
|
|
8026
8049
|
* takes 1–5 min).
|
|
8027
8050
|
*/
|
|
8028
|
-
async addReplica(tableName, replica, region) {
|
|
8051
|
+
async addReplica(tableName, replica, region, logicalId) {
|
|
8029
8052
|
const create = { RegionName: region };
|
|
8030
8053
|
if (replica["KMSMasterKeyId"]) create.KMSMasterKeyId = replica["KMSMasterKeyId"];
|
|
8031
8054
|
if (replica["GlobalSecondaryIndexes"]) create.GlobalSecondaryIndexes = replica["GlobalSecondaryIndexes"];
|
|
@@ -8035,19 +8058,239 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
8035
8058
|
TableName: tableName,
|
|
8036
8059
|
ReplicaUpdates: replicaUpdates
|
|
8037
8060
|
}));
|
|
8038
|
-
await this.waitForReplicaActive(tableName, region);
|
|
8061
|
+
await this.waitForReplicaActive(tableName, region, logicalId);
|
|
8062
|
+
}
|
|
8063
|
+
/**
|
|
8064
|
+
* Update a DynamoDB Global Table in place.
|
|
8065
|
+
*
|
|
8066
|
+
* AWS-side state-machine constraint: `UpdateTable` accepts only ONE of
|
|
8067
|
+
* `{BillingMode, ReplicaUpdates, GlobalSecondaryIndexUpdates}` per call,
|
|
8068
|
+
* so each category must serialize into its own SDK round-trip with a
|
|
8069
|
+
* `waitForTableActiveAfterUpdate` between every step. Order:
|
|
8070
|
+
* 1. Wait for current ACTIVE (defensive).
|
|
8071
|
+
* 2. Tags diff (TagResource / UntagResource — no wait needed).
|
|
8072
|
+
* 3. Non-conflicting flat fields (DeletionProtectionEnabled / TableClass
|
|
8073
|
+
* / SSESpecification / StreamSpecification / OnDemandThroughput)
|
|
8074
|
+
* in one combined `UpdateTableCommand`. Wait ACTIVE.
|
|
8075
|
+
* 4. BillingMode flip (separate UpdateTable). Wait ACTIVE.
|
|
8076
|
+
* 5. Replica diff (serial per Create / Update / Delete). Wait ACTIVE
|
|
8077
|
+
* after each.
|
|
8078
|
+
* 6. GSI diff (serial per Create / Update / Delete; new GSIs may need
|
|
8079
|
+
* additional AttributeDefinitions). Wait ACTIVE after each.
|
|
8080
|
+
* 7. TimeToLiveSpecification toggle.
|
|
8081
|
+
*
|
|
8082
|
+
* Immutable properties (TableName / KeySchema / AttributeDefinitions
|
|
8083
|
+
* removals / LocalSecondaryIndexes changes) throw `ProvisioningError`
|
|
8084
|
+
* naming the offending field — the deploy engine's diff classifier
|
|
8085
|
+
* should catch these as REPLACEMENT before ever calling `update()`,
|
|
8086
|
+
* but the guard is defense-in-depth.
|
|
8087
|
+
*/
|
|
8088
|
+
async update(logicalId, physicalId, resourceType, properties, previousProperties) {
|
|
8089
|
+
this.logger.debug(`Updating DynamoDB GlobalTable ${logicalId}: ${physicalId}`);
|
|
8090
|
+
if (properties["TableName"] !== void 0 && previousProperties["TableName"] !== void 0 && properties["TableName"] !== previousProperties["TableName"]) throw new ProvisioningError(`TableName is immutable on AWS::DynamoDB::GlobalTable; replacement required (deploy with --replace, or destroy + redeploy)`, resourceType, logicalId, physicalId);
|
|
8091
|
+
if (properties["KeySchema"] !== void 0 && previousProperties["KeySchema"] !== void 0 && !deepEqual$1(properties["KeySchema"], previousProperties["KeySchema"])) throw new ProvisioningError(`KeySchema is immutable on AWS::DynamoDB::GlobalTable; replacement required (deploy with --replace, or destroy + redeploy)`, resourceType, logicalId, physicalId);
|
|
8092
|
+
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);
|
|
8093
|
+
const oldAttrs = previousProperties["AttributeDefinitions"] ?? [];
|
|
8094
|
+
const newAttrs = properties["AttributeDefinitions"] ?? [];
|
|
8095
|
+
const removedAttrs = oldAttrs.filter((o) => !newAttrs.some((n) => n.AttributeName === o.AttributeName));
|
|
8096
|
+
if (removedAttrs.length > 0) throw new ProvisioningError(`AttributeDefinitions removals are immutable on AWS::DynamoDB::GlobalTable (offenders: ${removedAttrs.map((a) => a.AttributeName).join(", ")}); replacement required`, resourceType, logicalId, physicalId);
|
|
8097
|
+
const currentRegion = await this.dynamoDBClient.config.region() ?? "";
|
|
8098
|
+
try {
|
|
8099
|
+
await this.waitForTableActiveAfterUpdate(physicalId, logicalId);
|
|
8100
|
+
const tableArn = (await this.dynamoDBClient.send(new DescribeTableCommand({ TableName: physicalId }))).Table?.TableArn;
|
|
8101
|
+
const extractLocalTags = (props) => {
|
|
8102
|
+
return (props["Replicas"] ?? []).find((r) => r["Region"] === currentRegion)?.["Tags"];
|
|
8103
|
+
};
|
|
8104
|
+
if (tableArn) await this.applyTagDiff(tableArn, extractLocalTags(previousProperties), extractLocalTags(properties));
|
|
8105
|
+
const flatUpdate = { TableName: physicalId };
|
|
8106
|
+
let flatChanged = false;
|
|
8107
|
+
if (properties["DeletionProtectionEnabled"] !== previousProperties["DeletionProtectionEnabled"]) {
|
|
8108
|
+
flatUpdate.DeletionProtectionEnabled = Boolean(properties["DeletionProtectionEnabled"] ?? false);
|
|
8109
|
+
flatChanged = true;
|
|
8110
|
+
}
|
|
8111
|
+
if (properties["TableClass"] !== void 0 && properties["TableClass"] !== previousProperties["TableClass"]) {
|
|
8112
|
+
flatUpdate.TableClass = properties["TableClass"];
|
|
8113
|
+
flatChanged = true;
|
|
8114
|
+
}
|
|
8115
|
+
if (properties["SSESpecification"] !== void 0 && !deepEqual$1(properties["SSESpecification"], previousProperties["SSESpecification"])) {
|
|
8116
|
+
const sse = properties["SSESpecification"];
|
|
8117
|
+
flatUpdate.SSESpecification = {
|
|
8118
|
+
Enabled: sse["SSEEnabled"] !== void 0 ? Boolean(sse["SSEEnabled"]) : true,
|
|
8119
|
+
...sse["SSEType"] !== void 0 && { SSEType: sse["SSEType"] }
|
|
8120
|
+
};
|
|
8121
|
+
flatChanged = true;
|
|
8122
|
+
}
|
|
8123
|
+
if (properties["StreamSpecification"] !== void 0 && !deepEqual$1(properties["StreamSpecification"], previousProperties["StreamSpecification"])) {
|
|
8124
|
+
flatUpdate.StreamSpecification = {
|
|
8125
|
+
StreamEnabled: true,
|
|
8126
|
+
StreamViewType: properties["StreamSpecification"]["StreamViewType"]
|
|
8127
|
+
};
|
|
8128
|
+
flatChanged = true;
|
|
8129
|
+
}
|
|
8130
|
+
if (!deepEqual$1(properties["WriteOnDemandThroughputSettings"], previousProperties["WriteOnDemandThroughputSettings"])) {
|
|
8131
|
+
const wodts = properties["WriteOnDemandThroughputSettings"];
|
|
8132
|
+
if (wodts?.["MaxWriteRequestUnits"] !== void 0) {
|
|
8133
|
+
flatUpdate.OnDemandThroughput = { MaxWriteRequestUnits: Number(wodts["MaxWriteRequestUnits"]) };
|
|
8134
|
+
flatChanged = true;
|
|
8135
|
+
}
|
|
8136
|
+
}
|
|
8137
|
+
if (flatChanged) {
|
|
8138
|
+
await this.dynamoDBClient.send(new UpdateTableCommand(flatUpdate));
|
|
8139
|
+
await this.waitForTableActiveAfterUpdate(physicalId, logicalId);
|
|
8140
|
+
}
|
|
8141
|
+
const oldBilling = previousProperties["BillingMode"] ?? "PAY_PER_REQUEST";
|
|
8142
|
+
const newBilling = properties["BillingMode"] ?? "PAY_PER_REQUEST";
|
|
8143
|
+
if (oldBilling !== newBilling) {
|
|
8144
|
+
const billingUpdate = {
|
|
8145
|
+
TableName: physicalId,
|
|
8146
|
+
BillingMode: newBilling
|
|
8147
|
+
};
|
|
8148
|
+
if (newBilling === "PROVISIONED") billingUpdate.ProvisionedThroughput = derivePerCallProvisionedThroughput(properties, currentRegion);
|
|
8149
|
+
await this.dynamoDBClient.send(new UpdateTableCommand(billingUpdate));
|
|
8150
|
+
await this.waitForTableActiveAfterUpdate(physicalId, logicalId);
|
|
8151
|
+
}
|
|
8152
|
+
const replicaDiff = diffReplicas(previousProperties["Replicas"] ?? [], properties["Replicas"] ?? []);
|
|
8153
|
+
for (const replica of replicaDiff.removed) {
|
|
8154
|
+
const region = replica["Region"];
|
|
8155
|
+
if (!region || region === currentRegion) continue;
|
|
8156
|
+
await this.dynamoDBClient.send(new UpdateTableCommand({
|
|
8157
|
+
TableName: physicalId,
|
|
8158
|
+
ReplicaUpdates: [{ Delete: { RegionName: region } }]
|
|
8159
|
+
}));
|
|
8160
|
+
await this.waitForReplicaGone(physicalId, region, logicalId);
|
|
8161
|
+
}
|
|
8162
|
+
for (const replica of replicaDiff.added) {
|
|
8163
|
+
const region = replica["Region"];
|
|
8164
|
+
if (!region || region === currentRegion) continue;
|
|
8165
|
+
await this.addReplica(physicalId, replica, region, logicalId);
|
|
8166
|
+
}
|
|
8167
|
+
for (const replica of replicaDiff.modified) {
|
|
8168
|
+
const region = replica["Region"];
|
|
8169
|
+
if (!region || region === currentRegion) continue;
|
|
8170
|
+
const updateAction = { RegionName: region };
|
|
8171
|
+
if (replica["KMSMasterKeyId"] !== void 0) updateAction.KMSMasterKeyId = replica["KMSMasterKeyId"];
|
|
8172
|
+
if (replica["GlobalSecondaryIndexes"]) updateAction.GlobalSecondaryIndexes = replica["GlobalSecondaryIndexes"];
|
|
8173
|
+
if (replica["TableClassOverride"]) updateAction.TableClassOverride = replica["TableClassOverride"];
|
|
8174
|
+
if (!(updateAction.KMSMasterKeyId !== void 0 || updateAction.GlobalSecondaryIndexes !== void 0 || updateAction.TableClassOverride !== void 0)) {
|
|
8175
|
+
this.logger.debug(`Cross-region replica ${region} of ${physicalId}: only Tags-style changes detected; skipping UpdateReplica (AWS rejects empty Update actions). Per-region Tag propagation is deferred.`);
|
|
8176
|
+
continue;
|
|
8177
|
+
}
|
|
8178
|
+
await this.dynamoDBClient.send(new UpdateTableCommand({
|
|
8179
|
+
TableName: physicalId,
|
|
8180
|
+
ReplicaUpdates: [{ Update: updateAction }]
|
|
8181
|
+
}));
|
|
8182
|
+
await this.waitForReplicaActive(physicalId, region, logicalId);
|
|
8183
|
+
}
|
|
8184
|
+
const gsiDiff = diffGlobalSecondaryIndexes(previousProperties["GlobalSecondaryIndexes"] ?? [], properties["GlobalSecondaryIndexes"] ?? []);
|
|
8185
|
+
for (const gsi of gsiDiff.removed) {
|
|
8186
|
+
if (!gsi.IndexName) continue;
|
|
8187
|
+
const gsiUpdate = { Delete: { IndexName: gsi.IndexName } };
|
|
8188
|
+
await this.dynamoDBClient.send(new UpdateTableCommand({
|
|
8189
|
+
TableName: physicalId,
|
|
8190
|
+
GlobalSecondaryIndexUpdates: [gsiUpdate]
|
|
8191
|
+
}));
|
|
8192
|
+
await this.waitForTableActiveAfterUpdate(physicalId, logicalId);
|
|
8193
|
+
}
|
|
8194
|
+
for (const gsi of gsiDiff.added) {
|
|
8195
|
+
if (!gsi.IndexName || !gsi.KeySchema || !gsi.Projection) continue;
|
|
8196
|
+
const gsiUpdate = { Create: {
|
|
8197
|
+
IndexName: gsi.IndexName,
|
|
8198
|
+
KeySchema: gsi.KeySchema,
|
|
8199
|
+
Projection: gsi.Projection,
|
|
8200
|
+
...gsi.ProvisionedThroughput && { ProvisionedThroughput: gsi.ProvisionedThroughput },
|
|
8201
|
+
...gsi.OnDemandThroughput && { OnDemandThroughput: gsi.OnDemandThroughput }
|
|
8202
|
+
} };
|
|
8203
|
+
await this.dynamoDBClient.send(new UpdateTableCommand({
|
|
8204
|
+
TableName: physicalId,
|
|
8205
|
+
AttributeDefinitions: newAttrs,
|
|
8206
|
+
GlobalSecondaryIndexUpdates: [gsiUpdate]
|
|
8207
|
+
}));
|
|
8208
|
+
await this.waitForTableActiveAfterUpdate(physicalId, logicalId);
|
|
8209
|
+
}
|
|
8210
|
+
for (const gsi of gsiDiff.modified) {
|
|
8211
|
+
if (!gsi.IndexName) continue;
|
|
8212
|
+
const gsiUpdate = { Update: {
|
|
8213
|
+
IndexName: gsi.IndexName,
|
|
8214
|
+
...gsi.ProvisionedThroughput && { ProvisionedThroughput: gsi.ProvisionedThroughput },
|
|
8215
|
+
...gsi.OnDemandThroughput && { OnDemandThroughput: gsi.OnDemandThroughput }
|
|
8216
|
+
} };
|
|
8217
|
+
await this.dynamoDBClient.send(new UpdateTableCommand({
|
|
8218
|
+
TableName: physicalId,
|
|
8219
|
+
GlobalSecondaryIndexUpdates: [gsiUpdate]
|
|
8220
|
+
}));
|
|
8221
|
+
await this.waitForTableActiveAfterUpdate(physicalId, logicalId);
|
|
8222
|
+
}
|
|
8223
|
+
if (!deepEqual$1(properties["TimeToLiveSpecification"], previousProperties["TimeToLiveSpecification"])) {
|
|
8224
|
+
const ttl = properties["TimeToLiveSpecification"];
|
|
8225
|
+
if (ttl?.["AttributeName"]) await this.dynamoDBClient.send(new UpdateTimeToLiveCommand({
|
|
8226
|
+
TableName: physicalId,
|
|
8227
|
+
TimeToLiveSpecification: {
|
|
8228
|
+
Enabled: ttl["Enabled"] !== void 0 ? Boolean(ttl["Enabled"]) : true,
|
|
8229
|
+
AttributeName: ttl["AttributeName"]
|
|
8230
|
+
}
|
|
8231
|
+
}));
|
|
8232
|
+
else if (previousProperties["TimeToLiveSpecification"]) {
|
|
8233
|
+
const prevTtl = previousProperties["TimeToLiveSpecification"];
|
|
8234
|
+
if (prevTtl["AttributeName"]) await this.dynamoDBClient.send(new UpdateTimeToLiveCommand({
|
|
8235
|
+
TableName: physicalId,
|
|
8236
|
+
TimeToLiveSpecification: {
|
|
8237
|
+
Enabled: false,
|
|
8238
|
+
AttributeName: prevTtl["AttributeName"]
|
|
8239
|
+
}
|
|
8240
|
+
}));
|
|
8241
|
+
}
|
|
8242
|
+
}
|
|
8243
|
+
const finalDescribe = await this.dynamoDBClient.send(new DescribeTableCommand({ TableName: physicalId }));
|
|
8244
|
+
return {
|
|
8245
|
+
physicalId,
|
|
8246
|
+
wasReplaced: false,
|
|
8247
|
+
attributes: {
|
|
8248
|
+
Arn: finalDescribe.Table?.TableArn,
|
|
8249
|
+
TableId: finalDescribe.Table?.TableId,
|
|
8250
|
+
StreamArn: finalDescribe.Table?.LatestStreamArn,
|
|
8251
|
+
TableName: physicalId
|
|
8252
|
+
}
|
|
8253
|
+
};
|
|
8254
|
+
} catch (error) {
|
|
8255
|
+
if (error instanceof ProvisioningError) throw error;
|
|
8256
|
+
const cause = error instanceof Error ? error : void 0;
|
|
8257
|
+
throw new ProvisioningError(`Failed to update DynamoDB GlobalTable ${logicalId}: ${error instanceof Error ? error.message : String(error)}`, resourceType, logicalId, physicalId, cause);
|
|
8258
|
+
}
|
|
8039
8259
|
}
|
|
8040
8260
|
/**
|
|
8041
|
-
*
|
|
8042
|
-
*
|
|
8043
|
-
*
|
|
8044
|
-
* add/remove, BillingMode flip, and throughput rewrites each have
|
|
8045
|
-
* distinct UpdateTable shapes and ordering rules. `cdkd drift --revert`
|
|
8046
|
-
* surfaces this as `ResourceUpdateNotSupportedError` (exit code 2);
|
|
8047
|
-
* the user falls back to `cdkd deploy --replace` or destroy + redeploy.
|
|
8261
|
+
* Apply a diff between old and new CFn-shape Tags arrays via DynamoDB's
|
|
8262
|
+
* `TagResource` / `UntagResource` APIs. Both take the table ARN as
|
|
8263
|
+
* `ResourceArn`.
|
|
8048
8264
|
*/
|
|
8049
|
-
async
|
|
8050
|
-
|
|
8265
|
+
async applyTagDiff(tableArn, oldTagsRaw, newTagsRaw) {
|
|
8266
|
+
const toMap = (tags) => {
|
|
8267
|
+
const m = /* @__PURE__ */ new Map();
|
|
8268
|
+
for (const t of tags ?? []) if (t.Key !== void 0 && t.Value !== void 0) m.set(t.Key, t.Value);
|
|
8269
|
+
return m;
|
|
8270
|
+
};
|
|
8271
|
+
const oldMap = toMap(oldTagsRaw);
|
|
8272
|
+
const newMap = toMap(newTagsRaw);
|
|
8273
|
+
const tagsToAdd = [];
|
|
8274
|
+
for (const [k, v] of newMap) if (oldMap.get(k) !== v) tagsToAdd.push({
|
|
8275
|
+
Key: k,
|
|
8276
|
+
Value: v
|
|
8277
|
+
});
|
|
8278
|
+
const tagsToRemove = [];
|
|
8279
|
+
for (const k of oldMap.keys()) if (!newMap.has(k)) tagsToRemove.push(k);
|
|
8280
|
+
if (tagsToRemove.length > 0) {
|
|
8281
|
+
await this.dynamoDBClient.send(new UntagResourceCommand$2({
|
|
8282
|
+
ResourceArn: tableArn,
|
|
8283
|
+
TagKeys: tagsToRemove
|
|
8284
|
+
}));
|
|
8285
|
+
this.logger.debug(`Removed ${tagsToRemove.length} tag(s) from DynamoDB GlobalTable ${tableArn}`);
|
|
8286
|
+
}
|
|
8287
|
+
if (tagsToAdd.length > 0) {
|
|
8288
|
+
await this.dynamoDBClient.send(new TagResourceCommand$2({
|
|
8289
|
+
ResourceArn: tableArn,
|
|
8290
|
+
Tags: tagsToAdd
|
|
8291
|
+
}));
|
|
8292
|
+
this.logger.debug(`Added/updated ${tagsToAdd.length} tag(s) on DynamoDB GlobalTable ${tableArn}`);
|
|
8293
|
+
}
|
|
8051
8294
|
}
|
|
8052
8295
|
/**
|
|
8053
8296
|
* Delete a DynamoDB Global Table.
|
|
@@ -8075,7 +8318,7 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
8075
8318
|
}));
|
|
8076
8319
|
this.logger.debug(`Disabled DeletionProtectionEnabled on ${logicalId}, waiting for ACTIVE`);
|
|
8077
8320
|
try {
|
|
8078
|
-
await this.waitForTableActiveAfterUpdate(physicalId);
|
|
8321
|
+
await this.waitForTableActiveAfterUpdate(physicalId, logicalId);
|
|
8079
8322
|
} catch (waitErr) {
|
|
8080
8323
|
this.logger.debug(`Could not wait for table ${physicalId} ACTIVE after protection flip: ${waitErr instanceof Error ? waitErr.message : String(waitErr)}`);
|
|
8081
8324
|
}
|
|
@@ -8099,7 +8342,7 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
8099
8342
|
TableName: physicalId,
|
|
8100
8343
|
ReplicaUpdates: [{ Delete: { RegionName: region } }]
|
|
8101
8344
|
}));
|
|
8102
|
-
await this.waitForReplicaGone(physicalId, region);
|
|
8345
|
+
await this.waitForReplicaGone(physicalId, region, logicalId);
|
|
8103
8346
|
} catch (replicaErr) {
|
|
8104
8347
|
if (!(replicaErr instanceof ResourceNotFoundException$1)) throw replicaErr;
|
|
8105
8348
|
}
|
|
@@ -8112,7 +8355,7 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
8112
8355
|
}
|
|
8113
8356
|
try {
|
|
8114
8357
|
await this.dynamoDBClient.send(new DeleteTableCommand({ TableName: physicalId }));
|
|
8115
|
-
await this.waitForTableGone(physicalId);
|
|
8358
|
+
await this.waitForTableGone(physicalId, logicalId);
|
|
8116
8359
|
this.logger.debug(`Successfully deleted DynamoDB GlobalTable ${logicalId}`);
|
|
8117
8360
|
} catch (error) {
|
|
8118
8361
|
if (error instanceof ResourceNotFoundException$1) {
|
|
@@ -8158,22 +8401,29 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
8158
8401
|
/**
|
|
8159
8402
|
* Read the AWS-current DynamoDB GlobalTable configuration in CFn-property shape.
|
|
8160
8403
|
*
|
|
8161
|
-
* Reverse-maps `DescribeTable` + `ListTagsOfResource`
|
|
8162
|
-
* `
|
|
8404
|
+
* Reverse-maps `DescribeTable` + `ListTagsOfResource` + `DescribeTimeToLive`
|
|
8405
|
+
* + per-replica `DescribeContributorInsights` /
|
|
8406
|
+
* `DescribeContinuousBackups` / `DescribeKinesisStreamingDestination`
|
|
8407
|
+
* into the `AWS::DynamoDB::GlobalTable` property set.
|
|
8163
8408
|
*
|
|
8164
8409
|
* Type-discriminator gating (memory rule
|
|
8165
8410
|
* `feedback_always_emit_check_type_discriminator.md`):
|
|
8166
|
-
* - `ProvisionedThroughput`-bearing fields are only surfaced when
|
|
8167
|
-
* `BillingMode === 'PROVISIONED'`. Emitting placeholders on
|
|
8168
|
-
* PAY_PER_REQUEST tables (or vice versa) would fire false drift on
|
|
8169
|
-
* every clean run.
|
|
8170
8411
|
* - StreamSpecification / SSESpecification follow the existing
|
|
8171
8412
|
* DynamoDB::Table provider's Class 1 guard: only surfaced when AWS
|
|
8172
8413
|
* reports the feature actually enabled.
|
|
8173
|
-
*
|
|
8174
|
-
*
|
|
8175
|
-
*
|
|
8176
|
-
*
|
|
8414
|
+
* - `ProvisionedThroughput`-bearing fields are declared in
|
|
8415
|
+
* `getDriftUnknownPaths` and intentionally not emitted in v1 — the
|
|
8416
|
+
* reverse-mapping from AWS's `ProvisionedThroughput` shape into
|
|
8417
|
+
* CFn's `WriteProvisionedThroughputSettings` /
|
|
8418
|
+
* `ReadProvisionedThroughputSettings` wrappers (which carry
|
|
8419
|
+
* `WriteCapacityAutoScalingSettings` etc.) needs more work to round
|
|
8420
|
+
* -trip cleanly.
|
|
8421
|
+
*
|
|
8422
|
+
* Per-replica sub-specifications (`ContributorInsightsSpecification` /
|
|
8423
|
+
* `PointInTimeRecoverySpecification` / `KinesisStreamSpecification`)
|
|
8424
|
+
* are surfaced only for the LOCAL replica. Cross-region replicas
|
|
8425
|
+
* require per-region SDK clients (`new DynamoDBClient({region})`),
|
|
8426
|
+
* deferred to a follow-up PR.
|
|
8177
8427
|
*/
|
|
8178
8428
|
async readCurrentState(physicalId, _logicalId, _resourceType) {
|
|
8179
8429
|
try {
|
|
@@ -8196,19 +8446,38 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
8196
8446
|
if (table.SSEDescription.SSEType !== void 0) sse["SSEType"] = table.SSEDescription.SSEType;
|
|
8197
8447
|
result["SSESpecification"] = sse;
|
|
8198
8448
|
}
|
|
8199
|
-
result["Replicas"] = (table.Replicas ?? []).map((r) => ({
|
|
8200
|
-
Region: r.RegionName,
|
|
8201
|
-
...r.KMSMasterKeyId !== void 0 && { KMSMasterKeyId: r.KMSMasterKeyId }
|
|
8202
|
-
}));
|
|
8203
8449
|
if (table.TableClassSummary?.TableClass) result["TableClass"] = table.TableClassSummary.TableClass;
|
|
8204
8450
|
if (table.DeletionProtectionEnabled !== void 0) result["DeletionProtectionEnabled"] = table.DeletionProtectionEnabled;
|
|
8205
|
-
|
|
8206
|
-
|
|
8207
|
-
|
|
8208
|
-
|
|
8209
|
-
|
|
8451
|
+
const currentRegion = await this.dynamoDBClient.config.region() ?? "";
|
|
8452
|
+
const tableNameForSubs = table.TableName ?? physicalId;
|
|
8453
|
+
result["Replicas"] = await Promise.all((table.Replicas ?? []).map(async (r) => {
|
|
8454
|
+
const entry = { Region: r.RegionName };
|
|
8455
|
+
if (r.KMSMasterKeyId !== void 0) entry["KMSMasterKeyId"] = r.KMSMasterKeyId;
|
|
8456
|
+
if (r.RegionName && r.RegionName === currentRegion) {
|
|
8457
|
+
const subs = await this.readLocalReplicaSubSpecs(tableNameForSubs);
|
|
8458
|
+
Object.assign(entry, subs);
|
|
8459
|
+
if (table.TableArn) try {
|
|
8460
|
+
entry["Tags"] = normalizeAwsTagsToCfn((await this.dynamoDBClient.send(new ListTagsOfResourceCommand({ ResourceArn: table.TableArn }))).Tags);
|
|
8461
|
+
} catch (tagErr) {
|
|
8462
|
+
if (tagErr instanceof ResourceNotFoundException$1) throw tagErr;
|
|
8463
|
+
this.logger.warn(`Could not fetch tags for DynamoDB GlobalTable ${tableNameForSubs}: ${tagErr instanceof Error ? tagErr.message : String(tagErr)}`);
|
|
8464
|
+
entry["Tags"] = [];
|
|
8465
|
+
}
|
|
8466
|
+
else entry["Tags"] = [];
|
|
8467
|
+
}
|
|
8468
|
+
return entry;
|
|
8469
|
+
}));
|
|
8470
|
+
try {
|
|
8471
|
+
const ttlDesc = (await this.dynamoDBClient.send(new DescribeTimeToLiveCommand({ TableName: tableNameForSubs }))).TimeToLiveDescription;
|
|
8472
|
+
const ttlStatus = ttlDesc?.TimeToLiveStatus;
|
|
8473
|
+
if (ttlStatus === "ENABLED" && ttlDesc?.AttributeName) result["TimeToLiveSpecification"] = {
|
|
8474
|
+
AttributeName: ttlDesc.AttributeName,
|
|
8475
|
+
Enabled: true
|
|
8476
|
+
};
|
|
8477
|
+
else if (ttlStatus === "DISABLED") {}
|
|
8478
|
+
} catch (ttlErr) {
|
|
8479
|
+
this.logger.debug(`Could not read TimeToLive for ${tableNameForSubs}: ${ttlErr instanceof Error ? ttlErr.message : String(ttlErr)}`);
|
|
8210
8480
|
}
|
|
8211
|
-
else result["Tags"] = [];
|
|
8212
8481
|
return result;
|
|
8213
8482
|
} catch (err) {
|
|
8214
8483
|
if (err instanceof ResourceNotFoundException$1) return void 0;
|
|
@@ -8216,25 +8485,64 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
8216
8485
|
}
|
|
8217
8486
|
}
|
|
8218
8487
|
/**
|
|
8488
|
+
* Read per-replica sub-specifications for the LOCAL replica:
|
|
8489
|
+
* - `ContributorInsightsSpecification` via `DescribeContributorInsights`
|
|
8490
|
+
* (table-level; GSI overrides are NOT surfaced in v1 — they would
|
|
8491
|
+
* require one call per GSI and a different CFn nesting under the
|
|
8492
|
+
* `Replicas[].GlobalSecondaryIndexes[]` shape).
|
|
8493
|
+
* - `PointInTimeRecoverySpecification` via `DescribeContinuousBackups`.
|
|
8494
|
+
* - `KinesisStreamSpecification` via
|
|
8495
|
+
* `DescribeKinesisStreamingDestination` (filtered to the local
|
|
8496
|
+
* region's destination when AWS reports more than one).
|
|
8497
|
+
*
|
|
8498
|
+
* Each call is best-effort: errors omit the offending key rather than
|
|
8499
|
+
* fail the whole drift read.
|
|
8500
|
+
*
|
|
8501
|
+
* Cross-region replicas would need per-region SDK clients (the calls
|
|
8502
|
+
* are region-scoped to the replica) — deferred to a follow-up PR.
|
|
8503
|
+
*/
|
|
8504
|
+
async readLocalReplicaSubSpecs(tableName) {
|
|
8505
|
+
const out = {};
|
|
8506
|
+
try {
|
|
8507
|
+
const ci = await this.dynamoDBClient.send(new DescribeContributorInsightsCommand({ TableName: tableName }));
|
|
8508
|
+
if (ci.ContributorInsightsStatus) out["ContributorInsightsSpecification"] = { Enabled: ci.ContributorInsightsStatus === "ENABLED" };
|
|
8509
|
+
} catch (err) {
|
|
8510
|
+
this.logger.debug(`Could not read ContributorInsights for ${tableName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
8511
|
+
}
|
|
8512
|
+
try {
|
|
8513
|
+
const pitrStatus = (await this.dynamoDBClient.send(new DescribeContinuousBackupsCommand({ TableName: tableName }))).ContinuousBackupsDescription?.PointInTimeRecoveryDescription?.PointInTimeRecoveryStatus;
|
|
8514
|
+
if (pitrStatus) out["PointInTimeRecoverySpecification"] = { PointInTimeRecoveryEnabled: pitrStatus === "ENABLED" };
|
|
8515
|
+
} catch (err) {
|
|
8516
|
+
this.logger.debug(`Could not read PointInTimeRecovery for ${tableName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
8517
|
+
}
|
|
8518
|
+
try {
|
|
8519
|
+
const active = ((await this.dynamoDBClient.send(new DescribeKinesisStreamingDestinationCommand({ TableName: tableName }))).KinesisDataStreamDestinations ?? []).find((d) => d.DestinationStatus === "ACTIVE" || d.DestinationStatus === "ENABLING");
|
|
8520
|
+
if (active?.StreamArn) {
|
|
8521
|
+
const ksOut = { StreamArn: active.StreamArn };
|
|
8522
|
+
if (active.ApproximateCreationDateTimePrecision !== void 0) ksOut["ApproximateCreationDateTimePrecision"] = active.ApproximateCreationDateTimePrecision;
|
|
8523
|
+
out["KinesisStreamSpecification"] = ksOut;
|
|
8524
|
+
}
|
|
8525
|
+
} catch (err) {
|
|
8526
|
+
this.logger.debug(`Could not read KinesisStreamingDestination for ${tableName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
8527
|
+
}
|
|
8528
|
+
return out;
|
|
8529
|
+
}
|
|
8530
|
+
/**
|
|
8219
8531
|
* State property paths cdkd's GlobalTable readCurrentState cannot (yet)
|
|
8220
8532
|
* reverse-map. The drift comparator skips these so a templated value
|
|
8221
8533
|
* doesn't fire guaranteed false drift on every clean run.
|
|
8222
8534
|
*
|
|
8223
|
-
* - `TimeToLiveSpecification`: cdkd's create() applies it via
|
|
8224
|
-
* UpdateTimeToLive, but the reverse-mapping needs a separate
|
|
8225
|
-
* DescribeTimeToLive call (not yet implemented).
|
|
8226
8535
|
* - `WriteProvisionedThroughputSettings` /
|
|
8227
8536
|
* `WriteOnDemandThroughputSettings`: CFn's shapes wrap
|
|
8228
8537
|
* auto-scaling / on-demand max-RU settings whose reverse-mapping
|
|
8229
8538
|
* from `DescribeTable.ProvisionedThroughput` / `OnDemandThroughput`
|
|
8230
8539
|
* is non-trivial and would fire false drift in v1.
|
|
8540
|
+
*
|
|
8541
|
+
* `TimeToLiveSpecification` is reverse-mapped via `DescribeTimeToLive`
|
|
8542
|
+
* in `readCurrentState` (no longer in this list).
|
|
8231
8543
|
*/
|
|
8232
8544
|
getDriftUnknownPaths(_resourceType) {
|
|
8233
|
-
return [
|
|
8234
|
-
"TimeToLiveSpecification",
|
|
8235
|
-
"WriteProvisionedThroughputSettings",
|
|
8236
|
-
"WriteOnDemandThroughputSettings"
|
|
8237
|
-
];
|
|
8545
|
+
return ["WriteProvisionedThroughputSettings", "WriteOnDemandThroughputSettings"];
|
|
8238
8546
|
}
|
|
8239
8547
|
/**
|
|
8240
8548
|
* Adopt an existing DynamoDB GlobalTable into cdkd state.
|
|
@@ -8278,7 +8586,7 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
8278
8586
|
} while (exclusiveStartTableName);
|
|
8279
8587
|
return null;
|
|
8280
8588
|
}
|
|
8281
|
-
async waitForTableActive(tableName, maxAttempts = 120) {
|
|
8589
|
+
async waitForTableActive(tableName, logicalId, maxAttempts = 120) {
|
|
8282
8590
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
8283
8591
|
const response = await this.dynamoDBClient.send(new DescribeTableCommand({ TableName: tableName }));
|
|
8284
8592
|
const status = response.Table?.TableStatus;
|
|
@@ -8288,10 +8596,10 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
8288
8596
|
tableId: response.Table?.TableId,
|
|
8289
8597
|
streamArn: response.Table?.LatestStreamArn
|
|
8290
8598
|
};
|
|
8291
|
-
if (status !== "CREATING" && status !== "UPDATING") throw new
|
|
8599
|
+
if (status !== "CREATING" && status !== "UPDATING") throw new ProvisioningError(`Unexpected table status while waiting for ACTIVE on ${tableName}: ${status}`, "AWS::DynamoDB::GlobalTable", logicalId, tableName);
|
|
8292
8600
|
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
8293
8601
|
}
|
|
8294
|
-
throw new
|
|
8602
|
+
throw new ProvisioningError(`Table ${tableName} did not reach ACTIVE within ${maxAttempts}s`, "AWS::DynamoDB::GlobalTable", logicalId, tableName);
|
|
8295
8603
|
}
|
|
8296
8604
|
/**
|
|
8297
8605
|
* Wait for the table to reach ACTIVE after an UpdateTable call. Unlike
|
|
@@ -8299,32 +8607,32 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
8299
8607
|
* table may already be ACTIVE on the no-op path (already-disabled
|
|
8300
8608
|
* protection) or transition through UPDATING.
|
|
8301
8609
|
*/
|
|
8302
|
-
async waitForTableActiveAfterUpdate(tableName, maxAttempts =
|
|
8610
|
+
async waitForTableActiveAfterUpdate(tableName, logicalId, maxAttempts = 600) {
|
|
8303
8611
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
8304
8612
|
if ((await this.dynamoDBClient.send(new DescribeTableCommand({ TableName: tableName }))).Table?.TableStatus === "ACTIVE") return;
|
|
8305
8613
|
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
8306
8614
|
}
|
|
8307
|
-
throw new
|
|
8615
|
+
throw new ProvisioningError(`Table ${tableName} did not reach ACTIVE within ${maxAttempts}s after UpdateTable`, "AWS::DynamoDB::GlobalTable", logicalId, tableName);
|
|
8308
8616
|
}
|
|
8309
8617
|
/**
|
|
8310
8618
|
* Wait until a specific replica's `ReplicaStatus` flips to ACTIVE.
|
|
8311
8619
|
* Replica provisioning typically takes 1–5 min; cap at 10 min.
|
|
8312
8620
|
*/
|
|
8313
|
-
async waitForReplicaActive(tableName, region, maxAttempts = 600) {
|
|
8621
|
+
async waitForReplicaActive(tableName, region, logicalId, maxAttempts = 600) {
|
|
8314
8622
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
8315
8623
|
const replica = (await this.dynamoDBClient.send(new DescribeTableCommand({ TableName: tableName }))).Table?.Replicas?.find((r) => r.RegionName === region);
|
|
8316
8624
|
if (replica?.ReplicaStatus === "ACTIVE") return;
|
|
8317
8625
|
this.logger.debug(`Replica ${region} status: ${replica?.ReplicaStatus} (attempt ${attempt}/${maxAttempts})`);
|
|
8318
8626
|
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
8319
8627
|
}
|
|
8320
|
-
throw new
|
|
8628
|
+
throw new ProvisioningError(`Replica ${region} for table ${tableName} did not reach ACTIVE within ${maxAttempts}s`, "AWS::DynamoDB::GlobalTable", logicalId, tableName);
|
|
8321
8629
|
}
|
|
8322
8630
|
/**
|
|
8323
8631
|
* Wait until a specific replica disappears from `Replicas[]` after a
|
|
8324
8632
|
* Delete replica update. Replica deletion typically takes 1–5 min;
|
|
8325
8633
|
* cap at 10 min.
|
|
8326
8634
|
*/
|
|
8327
|
-
async waitForReplicaGone(tableName, region, maxAttempts = 600) {
|
|
8635
|
+
async waitForReplicaGone(tableName, region, logicalId, maxAttempts = 600) {
|
|
8328
8636
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) try {
|
|
8329
8637
|
if (!(await this.dynamoDBClient.send(new DescribeTableCommand({ TableName: tableName }))).Table?.Replicas?.find((r) => r.RegionName === region)) return;
|
|
8330
8638
|
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
@@ -8332,7 +8640,7 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
8332
8640
|
if (err instanceof ResourceNotFoundException$1) return;
|
|
8333
8641
|
throw err;
|
|
8334
8642
|
}
|
|
8335
|
-
throw new
|
|
8643
|
+
throw new ProvisioningError(`Replica ${region} for table ${tableName} did not disappear within ${maxAttempts}s`, "AWS::DynamoDB::GlobalTable", logicalId, tableName);
|
|
8336
8644
|
}
|
|
8337
8645
|
/**
|
|
8338
8646
|
* Wait for `DescribeTable` to return `ResourceNotFoundException`,
|
|
@@ -8345,7 +8653,7 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
8345
8653
|
* Typical small-table delete completes in 5–30s; cap at 10 min for
|
|
8346
8654
|
* worst-case large-table / replica-cascade scenarios.
|
|
8347
8655
|
*/
|
|
8348
|
-
async waitForTableGone(tableName, maxAttempts = 600) {
|
|
8656
|
+
async waitForTableGone(tableName, logicalId, maxAttempts = 600) {
|
|
8349
8657
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) try {
|
|
8350
8658
|
await this.dynamoDBClient.send(new DescribeTableCommand({ TableName: tableName }));
|
|
8351
8659
|
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
@@ -8353,9 +8661,104 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
8353
8661
|
if (err instanceof ResourceNotFoundException$1) return;
|
|
8354
8662
|
throw err;
|
|
8355
8663
|
}
|
|
8356
|
-
throw new
|
|
8664
|
+
throw new ProvisioningError(`Table ${tableName} did not disappear within ${maxAttempts}s`, "AWS::DynamoDB::GlobalTable", logicalId, tableName);
|
|
8357
8665
|
}
|
|
8358
8666
|
};
|
|
8667
|
+
/**
|
|
8668
|
+
* Diff CFn `Replicas[]` arrays. Keyed by `Region`. Returns adds, removes,
|
|
8669
|
+
* and modifies (entries whose other keys — KMSMasterKeyId,
|
|
8670
|
+
* GlobalSecondaryIndexes, TableClassOverride — differ from the old shape).
|
|
8671
|
+
*/
|
|
8672
|
+
/**
|
|
8673
|
+
* Derive the per-call `ProvisionedThroughput` shape required by
|
|
8674
|
+
* `CreateTableCommand` / `UpdateTableCommand` when BillingMode flips to
|
|
8675
|
+
* PROVISIONED. Shared between create() and the BillingMode-flip path in
|
|
8676
|
+
* update() so a user template's non-default read/write capacity is
|
|
8677
|
+
* preserved consistently across both code paths.
|
|
8678
|
+
*
|
|
8679
|
+
* Source of truth (CFn `AWS::DynamoDB::GlobalTable` shape):
|
|
8680
|
+
* - WriteCapacityUnits → `properties.WriteProvisionedThroughputSettings`
|
|
8681
|
+
* (top-level on the table). Literal `WriteCapacityUnits` wins over
|
|
8682
|
+
* auto-scaling `MinCapacity`; both default to 5 if absent.
|
|
8683
|
+
* - ReadCapacityUnits → `Replicas[?Region==<region>].ReadProvisionedThroughputSettings`
|
|
8684
|
+
* (per-replica, the deploy region's setting). Same literal-vs-auto-
|
|
8685
|
+
* scaling-vs-default-5 precedence.
|
|
8686
|
+
*/
|
|
8687
|
+
function derivePerCallProvisionedThroughput(properties, region) {
|
|
8688
|
+
const wps = properties["WriteProvisionedThroughputSettings"];
|
|
8689
|
+
const writeAutoScaling = wps?.["WriteCapacityAutoScalingSettings"];
|
|
8690
|
+
const writeCapacity = Number(wps?.["WriteCapacityUnits"] ?? writeAutoScaling?.["MinCapacity"] ?? 5);
|
|
8691
|
+
const localReadSettings = (properties["Replicas"] ?? []).find((r) => r["Region"] === region)?.["ReadProvisionedThroughputSettings"];
|
|
8692
|
+
const readAutoScaling = localReadSettings?.["ReadCapacityAutoScalingSettings"];
|
|
8693
|
+
return {
|
|
8694
|
+
ReadCapacityUnits: Number(localReadSettings?.["ReadCapacityUnits"] ?? readAutoScaling?.["MinCapacity"] ?? 5),
|
|
8695
|
+
WriteCapacityUnits: writeCapacity
|
|
8696
|
+
};
|
|
8697
|
+
}
|
|
8698
|
+
function diffReplicas(oldReplicas, newReplicas) {
|
|
8699
|
+
const oldByRegion = /* @__PURE__ */ new Map();
|
|
8700
|
+
const newByRegion = /* @__PURE__ */ new Map();
|
|
8701
|
+
for (const r of oldReplicas) {
|
|
8702
|
+
const region = r["Region"];
|
|
8703
|
+
if (region) oldByRegion.set(region, r);
|
|
8704
|
+
}
|
|
8705
|
+
for (const r of newReplicas) {
|
|
8706
|
+
const region = r["Region"];
|
|
8707
|
+
if (region) newByRegion.set(region, r);
|
|
8708
|
+
}
|
|
8709
|
+
const added = [];
|
|
8710
|
+
const removed = [];
|
|
8711
|
+
const modified = [];
|
|
8712
|
+
for (const [region, replica] of newByRegion) if (!oldByRegion.has(region)) added.push(replica);
|
|
8713
|
+
else if (!deepEqual$1(oldByRegion.get(region), replica)) modified.push(replica);
|
|
8714
|
+
for (const [region, replica] of oldByRegion) if (!newByRegion.has(region)) removed.push(replica);
|
|
8715
|
+
return {
|
|
8716
|
+
added,
|
|
8717
|
+
removed,
|
|
8718
|
+
modified
|
|
8719
|
+
};
|
|
8720
|
+
}
|
|
8721
|
+
/**
|
|
8722
|
+
* Diff CFn `GlobalSecondaryIndexes[]` arrays. Keyed by `IndexName`.
|
|
8723
|
+
* Modified = same IndexName but other fields (ProvisionedThroughput /
|
|
8724
|
+
* OnDemandThroughput) differ. KeySchema / Projection changes count as
|
|
8725
|
+
* "modified" too — AWS rejects those via UpdateGSI, but the diff caller
|
|
8726
|
+
* surfaces the AWS-side error rather than this helper second-guessing.
|
|
8727
|
+
*/
|
|
8728
|
+
function diffGlobalSecondaryIndexes(oldGsi, newGsi) {
|
|
8729
|
+
const oldByName = /* @__PURE__ */ new Map();
|
|
8730
|
+
const newByName = /* @__PURE__ */ new Map();
|
|
8731
|
+
for (const g of oldGsi) if (g.IndexName) oldByName.set(g.IndexName, g);
|
|
8732
|
+
for (const g of newGsi) if (g.IndexName) newByName.set(g.IndexName, g);
|
|
8733
|
+
const added = [];
|
|
8734
|
+
const removed = [];
|
|
8735
|
+
const modified = [];
|
|
8736
|
+
for (const [name, gsi] of newByName) if (!oldByName.has(name)) added.push(gsi);
|
|
8737
|
+
else if (!deepEqual$1(oldByName.get(name), gsi)) modified.push(gsi);
|
|
8738
|
+
for (const [name, gsi] of oldByName) if (!newByName.has(name)) removed.push(gsi);
|
|
8739
|
+
return {
|
|
8740
|
+
added,
|
|
8741
|
+
removed,
|
|
8742
|
+
modified
|
|
8743
|
+
};
|
|
8744
|
+
}
|
|
8745
|
+
/**
|
|
8746
|
+
* Structural equality via JSON.stringify. Both inputs are CFn-shape
|
|
8747
|
+
* POJOs (no functions, no symbols, no cycles), so JSON round-trip is
|
|
8748
|
+
* sufficient and free of the property-order pitfalls of deeper
|
|
8749
|
+
* comparators. Object property order from `Object.keys` is insertion
|
|
8750
|
+
* order in modern engines; AWS-SDK shapes are constructed by the SDK
|
|
8751
|
+
* in stable order, so this is safe in practice.
|
|
8752
|
+
*/
|
|
8753
|
+
function deepEqual$1(a, b) {
|
|
8754
|
+
if (a === b) return true;
|
|
8755
|
+
if (a === void 0 || b === void 0) return false;
|
|
8756
|
+
try {
|
|
8757
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
8758
|
+
} catch {
|
|
8759
|
+
return false;
|
|
8760
|
+
}
|
|
8761
|
+
}
|
|
8359
8762
|
|
|
8360
8763
|
//#endregion
|
|
8361
8764
|
//#region src/provisioning/providers/logs-loggroup-provider.ts
|
|
@@ -43625,7 +44028,7 @@ function reorderArgs(argv) {
|
|
|
43625
44028
|
*/
|
|
43626
44029
|
async function main() {
|
|
43627
44030
|
const program = new Command();
|
|
43628
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.
|
|
44031
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.104.0");
|
|
43629
44032
|
program.addCommand(createBootstrapCommand());
|
|
43630
44033
|
program.addCommand(createSynthCommand());
|
|
43631
44034
|
program.addCommand(createListCommand());
|