@go-to-k/cdkd 0.278.35 → 0.278.37
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-8XxBHtzK.js → asg-provider-BTeSlton.js} +2 -2
- package/dist/{asg-provider-8XxBHtzK.js.map → asg-provider-BTeSlton.js.map} +1 -1
- package/dist/cli.js +341 -60
- package/dist/cli.js.map +1 -1
- package/dist/{deploy-engine-CWZlo2Il.js → deploy-engine-bEhHOGVR.js} +6 -6
- package/dist/{deploy-engine-CWZlo2Il.js.map → deploy-engine-bEhHOGVR.js.map} +1 -1
- package/dist/index.js +1 -1
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { $ as describeTypeWithThrottleRetry, $t as AwsClients, A as red, At as Synthesizer, B as isTerminationProtectionPropagationError, Bt as resolveUseCdkBootstrapAssets, C as isStatefulRecreateTargetSync, Ct as buildDockerImage, D as cyan, Dt as runDockerStreaming, E as bold, Et as runDockerForeground, F as ProviderRegistry, Ft as resolveAutoAssetStorage, G as normalizeAwsTagsToCfn, Gt as MIGRATE_TMP_PREFIX, H as cfnRefValueFromPhysicalId, Ht as warnDeprecatedNoPrefixCliFlag, I as findActionableSilentDrops, It as resolveCaptureObservedState, J as readConfigString, Jt as expectedOwnerParam, K as resolveExplicitPhysicalId, Kt as findLargeInlineResources, L as CloudControlProvider, Lt as resolveSkipPrefix, M as IAMRoleProvider, Mt as getDefaultStateBucketName, N as collectInlinePolicyNamesManagedBySiblings, Nt as getLegacyStateBucketName, O as gray, Ot as AssetManifestLoader, P as clearOnUpdateRemoval, Pt as resolveApp, Q as DiffCalculator, Qt as resolveBucketRegion, R as slowCcOperationTimeoutMs, Rt as resolveStateBucketWithDefault, S as MULTI_REGION_RECREATE_BLOCKED_TYPES, St as validateContainerRepoName, T as formatResourceLine, Tn as withErrorHandling, Tt as getDockerCmd, U as refStateLookupFromResource, Ut as CFN_TEMPLATE_BODY_LIMIT, V as IntrinsicFunctionResolver, Vt as stateBucketExistenceConfirmed, W as WAFv2WebACLProvider, Wt as CFN_TEMPLATE_URL_LIMIT, X as requireConfigString, Xt as processStackMessages, Y as requireConfigArray, Yt as AssemblyReader, Z as applyRoleArnIfSet, _ as createPreDeleteFinalSnapshot, _n as ResourceUpdateNotSupportedError, _t as BOOTSTRAP_MARKER_PREFIX, a as DeploymentEventsStore, at as LockManager, b as unsupportedFinalSnapshotError, bt as parseBootstrapMarker, c as replayFailedOperations, cn as LocalInvokeBuildError$1, ct as shouldRetainResource, d as IMPLICIT_DELETE_DEPENDENCIES, dt as WorkGraph, en as getAwsClients, et as withRetry, f as computeImplicitDeleteEdges, fn as MissingCdkCliError, ft as buildAssetRedirectMap, g as ccRoutedFinalSnapshotError, gn as ResourceTimeoutError, gt as AssetModeResolver, h as buildFinalSnapshotIdentifier, hn as ProvisioningError, ht as rewriteTemplateAssetReferences, i as DeploymentEventsReader, in as CdkdError, it as TemplateParser, j as yellow, jt as synthesisStatusMessage, k as green, kt as getDockerImageBySourceHash, l as replayRollback, ln as LocalMigrateError, lt as AssetPublisher, m as PRE_DELETE_SNAPSHOT_TYPES, mn as PartialFailureError, mt as loadPublishableAssetManifest, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as setAwsClients, nt as isThrottlingError, o as planFailedOps, ot as S3StateBackend, p as ATOMIC_FINAL_SNAPSHOT_TYPES, pn as NestedStackChildDirectDestroyError, pt as createAssetRedirectResolver, q as assertRegionMatch, qt as uploadCfnTemplate, r as DeployEngine, rt as DagBuilder, s as planRollback, sn as DeployCancelledError, st as rebuildClientForBucketRegion, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as resetAwsClients, tt as isRetryableTransientError, u as withResourceDeadline, un as LocalStartServiceError, ut as stringifyValue, v as isFinalSnapshotError, vn as StackHasActiveImportsError, vt as ensureAssetStorage, w as renderStatefulReason, wn as normalizeAwsError, wt as formatDockerLoginError, x as extractDeploymentEventError, xt as validateAssetBucketName, y as refusesFinalSnapshot, yn as StackTerminationProtectionError, yt as getBootstrapMarkerKey, z as disableInstanceApiTermination, zt as resolveStateBucketWithDefaultAndSource } from "./deploy-engine-
|
|
2
|
+
import { $ as describeTypeWithThrottleRetry, $t as AwsClients, A as red, At as Synthesizer, B as isTerminationProtectionPropagationError, Bt as resolveUseCdkBootstrapAssets, C as isStatefulRecreateTargetSync, Ct as buildDockerImage, D as cyan, Dt as runDockerStreaming, E as bold, Et as runDockerForeground, F as ProviderRegistry, Ft as resolveAutoAssetStorage, G as normalizeAwsTagsToCfn, Gt as MIGRATE_TMP_PREFIX, H as cfnRefValueFromPhysicalId, Ht as warnDeprecatedNoPrefixCliFlag, I as findActionableSilentDrops, It as resolveCaptureObservedState, J as readConfigString, Jt as expectedOwnerParam, K as resolveExplicitPhysicalId, Kt as findLargeInlineResources, L as CloudControlProvider, Lt as resolveSkipPrefix, M as IAMRoleProvider, Mt as getDefaultStateBucketName, N as collectInlinePolicyNamesManagedBySiblings, Nt as getLegacyStateBucketName, O as gray, Ot as AssetManifestLoader, P as clearOnUpdateRemoval, Pt as resolveApp, Q as DiffCalculator, Qt as resolveBucketRegion, R as slowCcOperationTimeoutMs, Rt as resolveStateBucketWithDefault, S as MULTI_REGION_RECREATE_BLOCKED_TYPES, St as validateContainerRepoName, T as formatResourceLine, Tn as withErrorHandling, Tt as getDockerCmd, U as refStateLookupFromResource, Ut as CFN_TEMPLATE_BODY_LIMIT, V as IntrinsicFunctionResolver, Vt as stateBucketExistenceConfirmed, W as WAFv2WebACLProvider, Wt as CFN_TEMPLATE_URL_LIMIT, X as requireConfigString, Xt as processStackMessages, Y as requireConfigArray, Yt as AssemblyReader, Z as applyRoleArnIfSet, _ as createPreDeleteFinalSnapshot, _n as ResourceUpdateNotSupportedError, _t as BOOTSTRAP_MARKER_PREFIX, a as DeploymentEventsStore, at as LockManager, b as unsupportedFinalSnapshotError, bt as parseBootstrapMarker, c as replayFailedOperations, cn as LocalInvokeBuildError$1, ct as shouldRetainResource, d as IMPLICIT_DELETE_DEPENDENCIES, dt as WorkGraph, en as getAwsClients, et as withRetry, f as computeImplicitDeleteEdges, fn as MissingCdkCliError, ft as buildAssetRedirectMap, g as ccRoutedFinalSnapshotError, gn as ResourceTimeoutError, gt as AssetModeResolver, h as buildFinalSnapshotIdentifier, hn as ProvisioningError, ht as rewriteTemplateAssetReferences, i as DeploymentEventsReader, in as CdkdError, it as TemplateParser, j as yellow, jt as synthesisStatusMessage, k as green, kt as getDockerImageBySourceHash, l as replayRollback, ln as LocalMigrateError, lt as AssetPublisher, m as PRE_DELETE_SNAPSHOT_TYPES, mn as PartialFailureError, mt as loadPublishableAssetManifest, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as setAwsClients, nt as isThrottlingError, o as planFailedOps, ot as S3StateBackend, p as ATOMIC_FINAL_SNAPSHOT_TYPES, pn as NestedStackChildDirectDestroyError, pt as createAssetRedirectResolver, q as assertRegionMatch, qt as uploadCfnTemplate, r as DeployEngine, rt as DagBuilder, s as planRollback, sn as DeployCancelledError, st as rebuildClientForBucketRegion, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as resetAwsClients, tt as isRetryableTransientError, u as withResourceDeadline, un as LocalStartServiceError, ut as stringifyValue, v as isFinalSnapshotError, vn as StackHasActiveImportsError, vt as ensureAssetStorage, w as renderStatefulReason, wn as normalizeAwsError, wt as formatDockerLoginError, x as extractDeploymentEventError, xt as validateAssetBucketName, y as refusesFinalSnapshot, yn as StackTerminationProtectionError, yt as getBootstrapMarkerKey, z as disableInstanceApiTermination, zt as resolveStateBucketWithDefaultAndSource } from "./deploy-engine-bEhHOGVR.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-BTeSlton.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";
|
|
@@ -66,7 +66,8 @@ import { CodeCommitClient, CreateCommitCommand, CreateRepositoryCommand as Creat
|
|
|
66
66
|
import AdmZip from "adm-zip";
|
|
67
67
|
import { CreateLifecyclePolicyCommand, DLMClient, DeleteLifecyclePolicyCommand as DeleteLifecyclePolicyCommand$1, GetLifecyclePolicyCommand as GetLifecyclePolicyCommand$1, ResourceNotFoundException as ResourceNotFoundException$14, TagResourceCommand as TagResourceCommand$21, UntagResourceCommand as UntagResourceCommand$21, UpdateLifecyclePolicyCommand } from "@aws-sdk/client-dlm";
|
|
68
68
|
import { CreateVectorBucketCommand, DeleteIndexCommand, DeleteVectorBucketCommand, GetVectorBucketCommand, ListIndexesCommand, ListTagsForResourceCommand as ListTagsForResourceCommand$17, S3VectorsClient, TagResourceCommand as TagResourceCommand$22, UntagResourceCommand as UntagResourceCommand$22 } from "@aws-sdk/client-s3vectors";
|
|
69
|
-
import {
|
|
69
|
+
import { ListTagsForResourceCommand as ListTagsForResourceCommand$18, S3ControlClient, TagResourceCommand as TagResourceCommand$23, UntagResourceCommand as UntagResourceCommand$23 } from "@aws-sdk/client-s3-control";
|
|
70
|
+
import { CreateNamespaceCommand, CreateTableBucketCommand, CreateTableCommand as CreateTableCommand$2, DeleteNamespaceCommand as DeleteNamespaceCommand$1, DeleteTableBucketCommand, DeleteTableCommand as DeleteTableCommand$2, GetTableBucketCommand, GetTableCommand as GetTableCommand$1, ListNamespacesCommand as ListNamespacesCommand$1, ListTableBucketsCommand, ListTablesCommand, ListTagsForResourceCommand as ListTagsForResourceCommand$19, NotFoundException as NotFoundException$6, S3TablesClient, TagResourceCommand as TagResourceCommand$24, UntagResourceCommand as UntagResourceCommand$24 } from "@aws-sdk/client-s3tables";
|
|
70
71
|
import { Document, Pair, Scalar, YAMLMap, YAMLSeq, parse as parse$1, stringify } from "yaml";
|
|
71
72
|
import { createLocalStartAgentCoreCommand, createLocalStartCloudFrontCommand, createLocalStateProvider, getEmbedConfig, isCfnFlagPresent, listTargets, rejectExplicitCfnStackWithMultipleStacks, resolveCfnFallbackRegion, setEmbedConfig, substituteAgainstState, substituteAgainstStateAsync, substituteEnvVarsFromState, substituteEnvVarsFromStateAsync } from "cdk-local";
|
|
72
73
|
import { A2A_CONTAINER_PORT, A2A_PATH, AGENTCORE_A2A_PROTOCOL, AGENTCORE_AGUI_PROTOCOL, AGENTCORE_MCP_PROTOCOL, ConnectionRegistry, EcsTaskResolutionError, HOST_GATEWAY_MIN_VERSION, LocalInvokeBuildError, MCP_CONTAINER_PORT, MCP_PATH, a2aInvokeOnce, addAlbSpecificOptions, addCommonEcsServiceOptions, addStartServiceSpecificOptions, albStrategy, architectureToPlatform, attachAuthorizers, attachStageContext, availableApiIdentifiers, bufferToBody, buildAgentCoreCodeImage, buildCognitoJwksUrl, buildConnectEvent, buildContainerImage, buildCorsConfigByApiId, buildCorsConfigFromCloudFrontChain, buildDisconnectEvent, buildJwksUrlFromIssuer, buildMessageEvent, buildMgmtEndpointEnvUrl, buildStageMap, classifySourceChange, createAuthorizerCache, createFileWatcher, createFileWatcher as createFileWatcher$1, createJwksCache, createWatchPredicates, defaultCredentialsLoader, derivePseudoParametersFromRegion, discoverRoutes, discoverWebSocketApis, downloadAndExtractS3Bundle, filterRoutesByApiIdentifier, groupRoutesByServer, handleConnectionsRequest, invokeAgentCore, invokeAgentCoreWs, materializeLayerFromArn, mcpInvokeOnce, parseConnectionsPath, parseSelectionExpressionPath, pickAgentCoreCandidateStack, pickAgentCoreCandidateStack as pickAgentCoreCandidateStack$1, probeHostGatewaySupport, readMtlsMaterialsFromDisk, resolveAgentCoreTarget, resolveEnvVars, resolveHostGatewayExtraHosts, resolveRuntimeCodeMountPath, resolveRuntimeFileExtension, resolveRuntimeImage, resolveSingleTarget, resolveWatchConfig, runEcsServiceEmulator, signAgentCoreInvocation, startApiServer, substituteImagePlaceholders, tryResolveImageFnJoin, verifyJwtViaDiscovery, waitForAgentCorePing } from "cdk-local/internal";
|
|
@@ -12314,7 +12315,8 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
12314
12315
|
AttributeDefinitions: attributeDefinitions,
|
|
12315
12316
|
BillingMode: billingMode
|
|
12316
12317
|
};
|
|
12317
|
-
|
|
12318
|
+
const diagnostics = [];
|
|
12319
|
+
if (billingMode === "PROVISIONED") createParams.ProvisionedThroughput = derivePerCallProvisionedThroughput(properties, currentRegion, "min", diagnostics);
|
|
12318
12320
|
const streamSpecInput = properties["StreamSpecification"];
|
|
12319
12321
|
const needsStream = replicas.some((r) => r["Region"] !== currentRegion) || replicas.length > 1;
|
|
12320
12322
|
if (streamSpecInput != null) {
|
|
@@ -12335,7 +12337,6 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
12335
12337
|
StreamViewType: "NEW_AND_OLD_IMAGES"
|
|
12336
12338
|
};
|
|
12337
12339
|
}
|
|
12338
|
-
const diagnostics = [];
|
|
12339
12340
|
let sdkIndexes;
|
|
12340
12341
|
try {
|
|
12341
12342
|
sdkIndexes = toSdkGlobalSecondaryIndexes(properties, currentRegion, billingMode, "min", diagnostics);
|
|
@@ -12551,7 +12552,10 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
12551
12552
|
const onDemandCeilingLive = oldBilling === "PAY_PER_REQUEST" && newBilling === "PAY_PER_REQUEST";
|
|
12552
12553
|
const diagnostics = [];
|
|
12553
12554
|
const desiredCeilings = collectTableOnDemandCeilings(properties, currentRegion, diagnostics);
|
|
12554
|
-
const
|
|
12555
|
+
const flippingToProvisioned = oldBilling !== newBilling && newBilling === "PROVISIONED";
|
|
12556
|
+
if (flippingToProvisioned) derivePerCallProvisionedThroughput(properties, currentRegion, "seed", diagnostics);
|
|
12557
|
+
const desiredSdkIndexes = toSdkGlobalSecondaryIndexes(properties, currentRegion, newBilling, "min", flippingToProvisioned ? void 0 : diagnostics);
|
|
12558
|
+
if (flippingToProvisioned) toSdkGlobalSecondaryIndexes(properties, currentRegion, newBilling, "seed", diagnostics);
|
|
12555
12559
|
for (const replica of properties["Replicas"] ?? []) {
|
|
12556
12560
|
const replicaRegion = asRecord$1(replica)?.["Region"];
|
|
12557
12561
|
if (typeof replicaRegion !== "string" || replicaRegion === currentRegion) continue;
|
|
@@ -13231,9 +13235,11 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
13231
13235
|
const cause = regionErr instanceof Error ? regionErr : void 0;
|
|
13232
13236
|
throw new ProvisioningError(`Could not resolve client region for DynamoDB GlobalTable ${logicalId} delete — would risk dropping the local replica`, resourceType, logicalId, physicalId, cause);
|
|
13233
13237
|
}
|
|
13238
|
+
let preDeleteIndexes;
|
|
13234
13239
|
try {
|
|
13235
13240
|
const describe = await this.dynamoDBClient.send(new DescribeTableCommand({ TableName: physicalId }));
|
|
13236
13241
|
const replicas = describe.Table?.Replicas ?? [];
|
|
13242
|
+
preDeleteIndexes = describe.Table?.GlobalSecondaryIndexes;
|
|
13237
13243
|
const tableIndexNames = (describe.Table?.GlobalSecondaryIndexes ?? []).map((gsi) => gsi.IndexName).filter((name) => typeof name === "string");
|
|
13238
13244
|
for (const replica of replicas) {
|
|
13239
13245
|
const region = replica.RegionName;
|
|
@@ -13264,6 +13270,10 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
13264
13270
|
}
|
|
13265
13271
|
}
|
|
13266
13272
|
try {
|
|
13273
|
+
if (hasTransitionalIndex(preDeleteIndexes)) {
|
|
13274
|
+
this.logger.debug(`Waiting for indexes on ${physicalId} to settle before DeleteTable (issue #1521)`);
|
|
13275
|
+
await this.waitForIndexesActive(physicalId, logicalId);
|
|
13276
|
+
}
|
|
13267
13277
|
await this.dynamoDBClient.send(new DeleteTableCommand({ TableName: physicalId }));
|
|
13268
13278
|
await this.waitForTableGone(physicalId, logicalId);
|
|
13269
13279
|
this.logger.debug(`Successfully deleted DynamoDB GlobalTable ${logicalId}`);
|
|
@@ -13678,34 +13688,78 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
13678
13688
|
* `waitForTableActive`, this tolerates any non-terminal status — the
|
|
13679
13689
|
* table may already be ACTIVE on the no-op path (already-disabled
|
|
13680
13690
|
* protection) or transition through UPDATING.
|
|
13691
|
+
*
|
|
13692
|
+
* **A table is ACTIVE while one of its INDEXES is still transitioning**, and
|
|
13693
|
+
* the next `UpdateTable` in the serialized sequence is then rejected with
|
|
13694
|
+
* `Attempt to change a resource which is still in use: Index is being
|
|
13695
|
+
* updated` (issue #1521). That is not hypothetical: it broke the
|
|
13696
|
+
* `PAY_PER_REQUEST -> PROVISIONED` flip that also drops a GSI — AWS applies
|
|
13697
|
+
* the flip to every index, and cdkd issued the index DELETE while the index
|
|
13698
|
+
* was still absorbing it. Reproduced on pristine `main`, three runs out of
|
|
13699
|
+
* four on 2026-08-10, and the failed deploy's own `cdkd destroy` then
|
|
13700
|
+
* ORPHANED the table for the same reason.
|
|
13701
|
+
*
|
|
13702
|
+
* So the wait has TWO conditions read off the SAME response (an extra
|
|
13703
|
+
* `DescribeTable` per wait would double this path's API traffic for
|
|
13704
|
+
* something the response already carries) — but they keep DIFFERENT failure
|
|
13705
|
+
* semantics, and conflating them is a real regression rather than a
|
|
13706
|
+
* simplification. A table that never reaches ACTIVE is a hard error: nothing
|
|
13707
|
+
* can proceed. A still-transitioning INDEX is best-effort: `IndexStatus`
|
|
13708
|
+
* stays `CREATING` for the whole BACKFILL of a newly added GSI, which on a
|
|
13709
|
+
* populated table routinely outlives any cap — so throwing there would fail
|
|
13710
|
+
* a deploy whose resources are all correct, and would do it on an unrelated
|
|
13711
|
+
* tag-only edit that merely ran while an earlier index was still building.
|
|
13712
|
+
* Past the cap the index half warns and proceeds, leaving AWS's own
|
|
13713
|
+
* rejection as the backstop, which is the behavior it had before #1521 for
|
|
13714
|
+
* every case except the one the wait exists to fix.
|
|
13681
13715
|
*/
|
|
13682
13716
|
async waitForTableActiveAfterUpdate(tableName, logicalId, maxAttempts = 600) {
|
|
13717
|
+
let tableReachedActive = false;
|
|
13683
13718
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
13684
|
-
|
|
13719
|
+
const response = await this.dynamoDBClient.send(new DescribeTableCommand({ TableName: tableName }));
|
|
13720
|
+
if (response.Table?.TableStatus === "ACTIVE") {
|
|
13721
|
+
tableReachedActive = true;
|
|
13722
|
+
if (!hasTransitionalIndex(response.Table?.GlobalSecondaryIndexes)) return;
|
|
13723
|
+
}
|
|
13685
13724
|
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
13686
13725
|
}
|
|
13726
|
+
if (tableReachedActive) {
|
|
13727
|
+
this.logger.warn(`Indexes on ${tableName} (${logicalId}) were still transitioning after ${maxAttempts}s — a large index backfill can outlive this wait. Proceeding; AWS rejects the next call if it is still too early.`);
|
|
13728
|
+
return;
|
|
13729
|
+
}
|
|
13687
13730
|
throw new ProvisioningError(`Table ${tableName} did not reach ACTIVE within ${maxAttempts}s after UpdateTable`, "AWS::DynamoDB::GlobalTable", logicalId, tableName);
|
|
13688
13731
|
}
|
|
13689
13732
|
/**
|
|
13690
13733
|
* Wait until every GSI reports `IndexStatus: ACTIVE` (Issue #1419).
|
|
13691
13734
|
*
|
|
13692
|
-
* `
|
|
13693
|
-
*
|
|
13694
|
-
* is
|
|
13695
|
-
* resource is not ready, so registering right after the GSI-add loop
|
|
13735
|
+
* A GSI added by `UpdateTable` leaves the TABLE ACTIVE while the index
|
|
13736
|
+
* itself is still `CREATING` — application-autoscaling rejects a target
|
|
13737
|
+
* whose resource is not ready, so registering right after the GSI-add loop
|
|
13696
13738
|
* would skip the very index the deploy just introduced.
|
|
13697
13739
|
*
|
|
13740
|
+
* Since issue #1521 the same distinction is enforced INSIDE
|
|
13741
|
+
* {@link waitForTableActiveAfterUpdate} (which now reads both conditions off
|
|
13742
|
+
* its own response), so this helper is no longer the only place that knows
|
|
13743
|
+
* it. It remains the explicit wait for the two callers that have no
|
|
13744
|
+
* table-status wait to piggyback on: the autoscaling registration above and
|
|
13745
|
+
* the pre-`DeleteTable` gate, which AWS rejects while an index is
|
|
13746
|
+
* transitioning. An index that is `DELETING` still reports, so the shared
|
|
13747
|
+
* {@link hasTransitionalIndex} predicate covers `CREATING` / `UPDATING` /
|
|
13748
|
+
* `DELETING` alike.
|
|
13749
|
+
*
|
|
13698
13750
|
* **Best-effort by design**: on timeout it warns and returns rather than
|
|
13699
13751
|
* throwing. A missed registration self-heals on the next deploy (the
|
|
13700
13752
|
* reconcile re-asserts anything not already present), whereas throwing
|
|
13701
13753
|
* here would fail a deploy whose actual resources are all correct. Index
|
|
13702
13754
|
* backfill on a large table can take a long time, hence the 15-minute cap.
|
|
13755
|
+
* The caller that follows it with a mutating call keeps AWS's own error as
|
|
13756
|
+
* the backstop for the case where even that cap is not enough.
|
|
13703
13757
|
*/
|
|
13704
13758
|
async waitForIndexesActive(tableName, logicalId) {
|
|
13705
13759
|
const maxAttempts = 900;
|
|
13706
13760
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
13707
13761
|
try {
|
|
13708
|
-
if (((await this.dynamoDBClient.send(new DescribeTableCommand({ TableName: tableName }))).Table?.GlobalSecondaryIndexes ?? [])
|
|
13762
|
+
if (!hasTransitionalIndex((await this.dynamoDBClient.send(new DescribeTableCommand({ TableName: tableName }))).Table?.GlobalSecondaryIndexes ?? [])) return;
|
|
13709
13763
|
} catch (err) {
|
|
13710
13764
|
this.logger.debug(`DescribeTable while waiting for indexes on ${tableName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
13711
13765
|
return;
|
|
@@ -13809,10 +13863,17 @@ function extractLocalDeletionProtection(props, region) {
|
|
|
13809
13863
|
* literal `ReadCapacityUnits`, which is taken before its auto-scaling
|
|
13810
13864
|
* members; 5 if absent.
|
|
13811
13865
|
*/
|
|
13812
|
-
function derivePerCallProvisionedThroughput(properties, region, source = "min") {
|
|
13866
|
+
function derivePerCallProvisionedThroughput(properties, region, source = "min", diagnostics) {
|
|
13867
|
+
const rawReplicas = properties["Replicas"];
|
|
13868
|
+
const rawRead = (Array.isArray(rawReplicas) ? rawReplicas : []).find((r) => asRecord$1(r)?.["Region"] === region)?.["ReadProvisionedThroughputSettings"];
|
|
13869
|
+
const rawWrite = properties["WriteProvisionedThroughputSettings"];
|
|
13870
|
+
const read = deriveReadCapacityUnits(asRecord$1(rawRead), source);
|
|
13871
|
+
const write = deriveWriteCapacityUnits(asRecord$1(rawWrite), source);
|
|
13872
|
+
if (read === void 0) reportUnresolvedProvisionedCapacity(rawRead, "Read", source, { appliedUnits: DEFAULT_CAPACITY_UNITS }, diagnostics, { blockName: "Replicas[local].ReadProvisionedThroughputSettings" });
|
|
13873
|
+
if (write === void 0) reportUnresolvedProvisionedCapacity(rawWrite, "Write", source, { appliedUnits: DEFAULT_CAPACITY_UNITS }, diagnostics, { blockName: "WriteProvisionedThroughputSettings" });
|
|
13813
13874
|
return {
|
|
13814
|
-
ReadCapacityUnits:
|
|
13815
|
-
WriteCapacityUnits:
|
|
13875
|
+
ReadCapacityUnits: read ?? DEFAULT_CAPACITY_UNITS,
|
|
13876
|
+
WriteCapacityUnits: write ?? DEFAULT_CAPACITY_UNITS
|
|
13816
13877
|
};
|
|
13817
13878
|
}
|
|
13818
13879
|
/**
|
|
@@ -13967,6 +14028,26 @@ function asRecord$1(value) {
|
|
|
13967
14028
|
* read-back comparison must therefore expect ABSENCE, not this value.
|
|
13968
14029
|
*/
|
|
13969
14030
|
const ON_DEMAND_LIMIT_RESET = -1;
|
|
14031
|
+
/**
|
|
14032
|
+
* The GSI states during which AWS refuses another `UpdateTable` or a
|
|
14033
|
+
* `DeleteTable` on the table (issue #1521) — a table is ACTIVE while one of
|
|
14034
|
+
* its indexes is still transitioning, which is what made the serialized
|
|
14035
|
+
* update sequence race and what left a table ORPHANED when the follow-up
|
|
14036
|
+
* destroy hit the same rule.
|
|
14037
|
+
*
|
|
14038
|
+
* `DELETING` is included because a dropped index keeps reporting until it is
|
|
14039
|
+
* actually gone. The test is on the TRANSITIONAL values rather than on
|
|
14040
|
+
* `!== 'ACTIVE'`: an absent or unrecognized status must not park the caller
|
|
14041
|
+
* for its whole cap on a table that is fine.
|
|
14042
|
+
*/
|
|
14043
|
+
const INDEX_TRANSITIONAL_STATUSES = /* @__PURE__ */ new Set([
|
|
14044
|
+
"CREATING",
|
|
14045
|
+
"UPDATING",
|
|
14046
|
+
"DELETING"
|
|
14047
|
+
]);
|
|
14048
|
+
function hasTransitionalIndex(indexes) {
|
|
14049
|
+
return (indexes ?? []).some((gsi) => INDEX_TRANSITIONAL_STATUSES.has(gsi.IndexStatus ?? ""));
|
|
14050
|
+
}
|
|
13970
14051
|
/** Coerce a CFn numeric (CFn is stringly-typed) to a finite number. */
|
|
13971
14052
|
function toFiniteNumber(value) {
|
|
13972
14053
|
if (value === void 0 || value === null || value === "") return void 0;
|
|
@@ -14057,6 +14138,94 @@ function reportUnresolvedRawMember(rawValue, diagnostics, context) {
|
|
|
14057
14138
|
});
|
|
14058
14139
|
}
|
|
14059
14140
|
/**
|
|
14141
|
+
* Which member of a `*ProvisionedThroughputSettings` block to blame when the
|
|
14142
|
+
* derivation came back `undefined` (issue #1511).
|
|
14143
|
+
*
|
|
14144
|
+
* The derivation reports only "no number", so the member is re-derived here in
|
|
14145
|
+
* the SAME order {@link deriveWriteCapacityUnits} / {@link deriveReadCapacityUnits}
|
|
14146
|
+
* / {@link pickAutoScalingCapacity} consult members — the literal first, then
|
|
14147
|
+
* the auto-scaling block's `source`-selected member. Reaching this function at
|
|
14148
|
+
* all means every one of them failed, so the first DECLARED one is the value
|
|
14149
|
+
* the user was trying to set.
|
|
14150
|
+
*
|
|
14151
|
+
* `relPath` is relative to the block, `''` meaning the block ITSELF is the
|
|
14152
|
+
* unusable value (a string / array / bare unresolved intrinsic, or an object
|
|
14153
|
+
* carrying no capacity member at all).
|
|
14154
|
+
*
|
|
14155
|
+
* Returns `undefined` for an ABSENT block: defaulting there is the documented
|
|
14156
|
+
* pre-existing behavior for a template that never asked for a capacity, and
|
|
14157
|
+
* warning about it would shout on every deploy of a table that is fine.
|
|
14158
|
+
*/
|
|
14159
|
+
function blameUnresolvedProvisionedMember(rawSettings, side, source) {
|
|
14160
|
+
if (rawSettings === void 0) return void 0;
|
|
14161
|
+
const settings = asRecord$1(rawSettings);
|
|
14162
|
+
if (!settings || isUnresolvedIntrinsicBlock(settings)) return {
|
|
14163
|
+
relPath: "",
|
|
14164
|
+
value: rawSettings
|
|
14165
|
+
};
|
|
14166
|
+
const literal = settings[`${side}CapacityUnits`];
|
|
14167
|
+
if (literal !== void 0) return {
|
|
14168
|
+
relPath: `${side}CapacityUnits`,
|
|
14169
|
+
value: literal
|
|
14170
|
+
};
|
|
14171
|
+
const autoKey = `${side}CapacityAutoScalingSettings`;
|
|
14172
|
+
const rawAutoScaling = settings[autoKey];
|
|
14173
|
+
if (rawAutoScaling === void 0) return {
|
|
14174
|
+
relPath: "",
|
|
14175
|
+
value: rawSettings
|
|
14176
|
+
};
|
|
14177
|
+
const autoScaling = asRecord$1(rawAutoScaling);
|
|
14178
|
+
if (!autoScaling || isUnresolvedIntrinsicBlock(autoScaling)) return {
|
|
14179
|
+
relPath: autoKey,
|
|
14180
|
+
value: rawAutoScaling
|
|
14181
|
+
};
|
|
14182
|
+
const order = source === "seed" ? ["SeedCapacity", "MinCapacity"] : ["MinCapacity", "SeedCapacity"];
|
|
14183
|
+
for (const member of order) {
|
|
14184
|
+
const value = autoScaling[member];
|
|
14185
|
+
if (value !== void 0) return {
|
|
14186
|
+
relPath: `${autoKey}.${member}`,
|
|
14187
|
+
value
|
|
14188
|
+
};
|
|
14189
|
+
}
|
|
14190
|
+
return {
|
|
14191
|
+
relPath: autoKey,
|
|
14192
|
+
value: rawAutoScaling
|
|
14193
|
+
};
|
|
14194
|
+
}
|
|
14195
|
+
/**
|
|
14196
|
+
* Report a DECLARED provisioned capacity the template could not resolve — the
|
|
14197
|
+
* PROVISIONED twin of {@link reportUnresolvedRawMember} (issue #1511).
|
|
14198
|
+
*
|
|
14199
|
+
* The two sides need different wording because they have different outcomes,
|
|
14200
|
+
* which is exactly why #1503 deferred this half. An unreadable ON-DEMAND
|
|
14201
|
+
* ceiling is SUPPRESSED (nothing is sent, the live ceiling stands), so its
|
|
14202
|
+
* diagnostic says "nothing was sent". A provisioned table cannot suppress —
|
|
14203
|
+
* AWS requires `ProvisionedThroughput` on the table and on every GSI of it —
|
|
14204
|
+
* so the derivation falls through to {@link DEFAULT_CAPACITY_UNITS} and a
|
|
14205
|
+
* table the template explicitly sized is deployed at 5/5. That is a throttling
|
|
14206
|
+
* symptom days later rather than an error, which is what makes naming it here
|
|
14207
|
+
* worth a warning at all. The one provisioned site that CAN suppress is a
|
|
14208
|
+
* replica's `ProvisionedThroughputOverride` (absent = inherit the source
|
|
14209
|
+
* table), hence the `suppressed` outcome arm.
|
|
14210
|
+
*
|
|
14211
|
+
* Caller decides WHEN: only when the derivation returned `undefined` AND no
|
|
14212
|
+
* explicit SDK-shaped member is standing in for it, so the warning always
|
|
14213
|
+
* describes what actually reached AWS.
|
|
14214
|
+
*/
|
|
14215
|
+
function reportUnresolvedProvisionedCapacity(rawSettings, side, source, outcome, diagnostics, context) {
|
|
14216
|
+
if (!diagnostics) return;
|
|
14217
|
+
const blame = blameUnresolvedProvisionedMember(rawSettings, side, source);
|
|
14218
|
+
if (!blame) return;
|
|
14219
|
+
const path = blame.relPath ? `${context.blockName}.${blame.relPath}` : context.blockName;
|
|
14220
|
+
const consequence = "appliedUnits" in outcome ? `so cdkd sent ${outcome.appliedUnits} capacity units instead (its default — AWS requires a concrete provisioned capacity here, so there was nothing to suppress)` : outcome.suppressed;
|
|
14221
|
+
diagnostics.push({
|
|
14222
|
+
kind: "unresolved-member",
|
|
14223
|
+
...context.indexName !== void 0 && { indexName: context.indexName },
|
|
14224
|
+
member: blame.relPath ? blame.relPath.slice(blame.relPath.lastIndexOf(".") + 1) : context.blockName.slice(context.blockName.lastIndexOf(".") + 1),
|
|
14225
|
+
message: `${path} is declared but did not resolve to a number (${JSON.stringify(blame.value)?.slice(0, 80)}), ${consequence}. This is usually an unresolved intrinsic; resolve it to a literal to apply the capacity you declared.`
|
|
14226
|
+
});
|
|
14227
|
+
}
|
|
14228
|
+
/**
|
|
14060
14229
|
* Derive a single WRITE capacity number from a CFn
|
|
14061
14230
|
* `WriteProvisionedThroughputSettings` block.
|
|
14062
14231
|
*
|
|
@@ -14187,14 +14356,32 @@ function toSdkGlobalSecondaryIndexes(properties, region, billingMode, source = "
|
|
|
14187
14356
|
member: "OnDemandThroughput",
|
|
14188
14357
|
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."
|
|
14189
14358
|
});
|
|
14190
|
-
if (billingMode === "PROVISIONED")
|
|
14191
|
-
|
|
14192
|
-
|
|
14193
|
-
|
|
14194
|
-
|
|
14195
|
-
|
|
14196
|
-
|
|
14197
|
-
|
|
14359
|
+
if (billingMode === "PROVISIONED") {
|
|
14360
|
+
const rawLocalRead = localEntry?.["ReadProvisionedThroughputSettings"];
|
|
14361
|
+
const rawGsiRead = gsi["ReadProvisionedThroughputSettings"];
|
|
14362
|
+
const rawWrite = gsi["WriteProvisionedThroughputSettings"];
|
|
14363
|
+
const derivedRead = deriveReadCapacityUnits(asRecord$1(rawLocalRead), source) ?? deriveReadCapacityUnits(asRecord$1(rawGsiRead), source);
|
|
14364
|
+
const derivedWrite = deriveWriteCapacityUnits(asRecord$1(rawWrite), source);
|
|
14365
|
+
const explicitCovers = (member) => explicitProvisioned !== void 0 && toFiniteNumber(explicitProvisioned[member]) !== void 0;
|
|
14366
|
+
if (derivedRead === void 0 && !explicitCovers("ReadCapacityUnits")) {
|
|
14367
|
+
const fromReplica = rawLocalRead !== void 0;
|
|
14368
|
+
reportUnresolvedProvisionedCapacity(fromReplica ? rawLocalRead : rawGsiRead, "Read", source, { appliedUnits: DEFAULT_CAPACITY_UNITS }, diagnostics, {
|
|
14369
|
+
...typeof indexName === "string" && { indexName },
|
|
14370
|
+
blockName: fromReplica ? "Replicas[local].GlobalSecondaryIndexes[].ReadProvisionedThroughputSettings" : "ReadProvisionedThroughputSettings"
|
|
14371
|
+
});
|
|
14372
|
+
}
|
|
14373
|
+
if (derivedWrite === void 0 && !explicitCovers("WriteCapacityUnits")) reportUnresolvedProvisionedCapacity(rawWrite, "Write", source, { appliedUnits: DEFAULT_CAPACITY_UNITS }, diagnostics, {
|
|
14374
|
+
...typeof indexName === "string" && { indexName },
|
|
14375
|
+
blockName: "WriteProvisionedThroughputSettings"
|
|
14376
|
+
});
|
|
14377
|
+
sdk.ProvisionedThroughput = mergeExplicitThroughputBlock(explicitProvisioned, {
|
|
14378
|
+
ReadCapacityUnits: derivedRead ?? DEFAULT_CAPACITY_UNITS,
|
|
14379
|
+
WriteCapacityUnits: derivedWrite ?? DEFAULT_CAPACITY_UNITS
|
|
14380
|
+
}, ["ReadCapacityUnits", "WriteCapacityUnits"], diagnostics, {
|
|
14381
|
+
...typeof indexName === "string" && { indexName },
|
|
14382
|
+
blockName: "ProvisionedThroughput"
|
|
14383
|
+
});
|
|
14384
|
+
} else {
|
|
14198
14385
|
const rawWrite = asRecord$1(gsi["WriteOnDemandThroughputSettings"])?.["MaxWriteRequestUnits"];
|
|
14199
14386
|
const rawReadLocal = asRecord$1(localEntry?.["ReadOnDemandThroughputSettings"])?.["MaxReadRequestUnits"];
|
|
14200
14387
|
const rawReadGsi = asRecord$1(gsi["ReadOnDemandThroughputSettings"])?.["MaxReadRequestUnits"];
|
|
@@ -14363,7 +14550,12 @@ function toSdkReplicaThroughputOverrides(cfn, billingMode, diagnostics, context)
|
|
|
14363
14550
|
message: "an explicit OnDemandThroughputOverride was dropped because the table's BillingMode is PROVISIONED; AWS rejects on-demand ceilings on a provisioned table."
|
|
14364
14551
|
});
|
|
14365
14552
|
if (billingMode === "PROVISIONED") {
|
|
14366
|
-
const
|
|
14553
|
+
const rawRead = cfn["ReadProvisionedThroughputSettings"];
|
|
14554
|
+
const readCapacity = deriveReadCapacityUnits(asRecord$1(rawRead));
|
|
14555
|
+
if (readCapacity === void 0 && toFiniteNumber(explicitProvisioned?.["ReadCapacityUnits"]) === void 0) reportUnresolvedProvisionedCapacity(rawRead, "Read", "min", { suppressed: "so no ProvisionedThroughputOverride was sent and the replica keeps the source table's read capacity" }, diagnostics, {
|
|
14556
|
+
...context,
|
|
14557
|
+
blockName: "ReadProvisionedThroughputSettings"
|
|
14558
|
+
});
|
|
14367
14559
|
const merged = mergeExplicitThroughputBlock(explicitProvisioned, readCapacity !== void 0 ? { ReadCapacityUnits: readCapacity } : void 0, ["ReadCapacityUnits"], diagnostics, {
|
|
14368
14560
|
...context,
|
|
14369
14561
|
blockName: "ProvisionedThroughputOverride"
|
|
@@ -43910,9 +44102,11 @@ var S3DirectoryBucketProvider = class {
|
|
|
43910
44102
|
handledProperties = /* @__PURE__ */ new Map([["AWS::S3Express::DirectoryBucket", /* @__PURE__ */ new Set([
|
|
43911
44103
|
"DataRedundancy",
|
|
43912
44104
|
"LocationName",
|
|
43913
|
-
"BucketName"
|
|
44105
|
+
"BucketName",
|
|
44106
|
+
"Tags"
|
|
43914
44107
|
])]]);
|
|
43915
44108
|
ec2Client;
|
|
44109
|
+
s3ControlClient;
|
|
43916
44110
|
providerRegion = process.env["AWS_REGION"];
|
|
43917
44111
|
constructor() {
|
|
43918
44112
|
const awsClients = getAwsClients();
|
|
@@ -43924,6 +44118,17 @@ var S3DirectoryBucketProvider = class {
|
|
|
43924
44118
|
return this.ec2Client;
|
|
43925
44119
|
}
|
|
43926
44120
|
/**
|
|
44121
|
+
* Directory-bucket tags live on the S3 control plane: `PutBucketTagging`
|
|
44122
|
+
* is documented "not supported for directory buckets", and the tag CRUD
|
|
44123
|
+
* after create is S3 Control's `TagResource` / `UntagResource` /
|
|
44124
|
+
* `ListTagsForResource` (requiring the `s3express:TagResource` /
|
|
44125
|
+
* `s3express:UntagResource` / `s3express:ListTagsForResource` permissions).
|
|
44126
|
+
*/
|
|
44127
|
+
getS3ControlClient() {
|
|
44128
|
+
if (!this.s3ControlClient) this.s3ControlClient = new S3ControlClient(this.providerRegion ? { region: this.providerRegion } : {});
|
|
44129
|
+
return this.s3ControlClient;
|
|
44130
|
+
}
|
|
44131
|
+
/**
|
|
43927
44132
|
* Convert AZ name (us-east-1a) to AZ ID (use1-az1) via EC2 DescribeAvailabilityZones
|
|
43928
44133
|
*/
|
|
43929
44134
|
async getAzId(azName) {
|
|
@@ -43954,7 +44159,16 @@ var S3DirectoryBucketProvider = class {
|
|
|
43954
44159
|
* Build attributes for a directory bucket
|
|
43955
44160
|
*/
|
|
43956
44161
|
async buildAttributes(bucketName) {
|
|
43957
|
-
return { Arn:
|
|
44162
|
+
return { Arn: await this.buildBucketArn(bucketName) };
|
|
44163
|
+
}
|
|
44164
|
+
/**
|
|
44165
|
+
* Directory bucket ARN: `arn:aws:s3express:{region}:{account}:bucket/{name}`.
|
|
44166
|
+
* Also the `ResourceArn` for the S3 Control tag operations. Callers that
|
|
44167
|
+
* already resolved the account (for the S3 Control `AccountId` parameter)
|
|
44168
|
+
* pass it in so the STS lookup runs once per operation.
|
|
44169
|
+
*/
|
|
44170
|
+
async buildBucketArn(bucketName, accountId) {
|
|
44171
|
+
return `arn:aws:s3express:${await this.getRegion()}:${accountId ?? await this.getAccountId()}:bucket/${bucketName}`;
|
|
43958
44172
|
}
|
|
43959
44173
|
/**
|
|
43960
44174
|
* Create an S3 Express Directory Bucket
|
|
@@ -43969,6 +44183,8 @@ var S3DirectoryBucketProvider = class {
|
|
|
43969
44183
|
maxLength: 64,
|
|
43970
44184
|
lowercase: true
|
|
43971
44185
|
})}--${azId}--x-s3`;
|
|
44186
|
+
const tags = properties["Tags"];
|
|
44187
|
+
const includeTags = tags != null && !(Array.isArray(tags) && tags.length === 0);
|
|
43972
44188
|
try {
|
|
43973
44189
|
await this.s3Client.send(new CreateBucketCommand({
|
|
43974
44190
|
Bucket: bucketName,
|
|
@@ -43980,7 +44196,8 @@ var S3DirectoryBucketProvider = class {
|
|
|
43980
44196
|
Location: {
|
|
43981
44197
|
Name: azId,
|
|
43982
44198
|
Type: "AvailabilityZone"
|
|
43983
|
-
}
|
|
44199
|
+
},
|
|
44200
|
+
...includeTags ? { Tags: tags } : {}
|
|
43984
44201
|
}
|
|
43985
44202
|
}));
|
|
43986
44203
|
this.logger.debug(`Created S3 Express Directory Bucket: ${bucketName}`);
|
|
@@ -43997,28 +44214,60 @@ var S3DirectoryBucketProvider = class {
|
|
|
43997
44214
|
/**
|
|
43998
44215
|
* Update an S3 Express Directory Bucket
|
|
43999
44216
|
*
|
|
44000
|
-
*
|
|
44217
|
+
* `BucketName` / `DataRedundancy` / `LocationName` are create-only (the
|
|
44218
|
+
* registry-schema fallback classifies changes to them as replacement), so
|
|
44219
|
+
* the only in-place mutable property is `Tags` — applied via S3 Control's
|
|
44220
|
+
* `TagResource` / `UntagResource` (the directory-bucket tag CRUD; the
|
|
44221
|
+
* bucket-level `PutBucketTagging` is not supported for directory buckets).
|
|
44222
|
+
* `TagResource` is additive-only, so a tag dropped from the template
|
|
44223
|
+
* (partial removal) — or the entire `Tags` property removed (full removal,
|
|
44224
|
+
* `newTags === undefined`) — would survive on AWS unless the removed keys
|
|
44225
|
+
* are explicitly `UntagResource`d.
|
|
44001
44226
|
*/
|
|
44002
|
-
update(logicalId, physicalId,
|
|
44003
|
-
|
|
44004
|
-
|
|
44005
|
-
|
|
44006
|
-
|
|
44007
|
-
|
|
44227
|
+
async update(logicalId, physicalId, resourceType, properties, previousProperties) {
|
|
44228
|
+
try {
|
|
44229
|
+
const newTags = properties["Tags"];
|
|
44230
|
+
const oldTags = previousProperties["Tags"];
|
|
44231
|
+
const malformedNewTags = newTags != null && !Array.isArray(newTags);
|
|
44232
|
+
const newKeys = new Set((Array.isArray(newTags) ? newTags : []).map((t) => t.Key).filter((k) => !!k));
|
|
44233
|
+
const removedKeys = malformedNewTags ? [] : (Array.isArray(oldTags) ? oldTags : []).map((t) => t.Key).filter((k) => !!k && !newKeys.has(k));
|
|
44234
|
+
const tagsToApply = malformedNewTags || Array.isArray(newTags) && newTags.length > 0 ? newTags : void 0;
|
|
44235
|
+
if (JSON.stringify(newTags) !== JSON.stringify(oldTags) && (removedKeys.length > 0 || tagsToApply !== void 0)) {
|
|
44236
|
+
const accountId = await this.getAccountId();
|
|
44237
|
+
const resourceArn = await this.buildBucketArn(physicalId, accountId);
|
|
44238
|
+
if (removedKeys.length > 0) await this.getS3ControlClient().send(new UntagResourceCommand$23({
|
|
44239
|
+
AccountId: accountId,
|
|
44240
|
+
ResourceArn: resourceArn,
|
|
44241
|
+
TagKeys: removedKeys
|
|
44242
|
+
}));
|
|
44243
|
+
if (tagsToApply) await this.getS3ControlClient().send(new TagResourceCommand$23({
|
|
44244
|
+
AccountId: accountId,
|
|
44245
|
+
ResourceArn: resourceArn,
|
|
44246
|
+
Tags: tagsToApply
|
|
44247
|
+
}));
|
|
44248
|
+
this.logger.debug(`Updated tags for S3 Express Directory Bucket ${physicalId}`);
|
|
44249
|
+
} else this.logger.debug(`Update for S3 Express Directory Bucket ${logicalId} is a no-op (no tag changes; other properties are immutable)`);
|
|
44250
|
+
return {
|
|
44251
|
+
physicalId,
|
|
44252
|
+
wasReplaced: false
|
|
44253
|
+
};
|
|
44254
|
+
} catch (error) {
|
|
44255
|
+
const cause = error instanceof Error ? error : void 0;
|
|
44256
|
+
throw new ProvisioningError(`Failed to update S3 Express Directory Bucket ${logicalId}: ${error instanceof Error ? error.message : String(error)}`, resourceType, logicalId, physicalId, cause);
|
|
44257
|
+
}
|
|
44008
44258
|
}
|
|
44009
44259
|
/**
|
|
44010
44260
|
* Delete an S3 Express Directory Bucket
|
|
44011
44261
|
*
|
|
44012
44262
|
* CloudFormation-parity data guard (issue #1344, sibling of #1340): a
|
|
44013
44263
|
* non-empty directory bucket is only auto-emptied when the user opted in —
|
|
44014
|
-
* the `aws-cdk:auto-delete-objects` tag on the bucket (
|
|
44015
|
-
*
|
|
44016
|
-
*
|
|
44017
|
-
*
|
|
44018
|
-
*
|
|
44019
|
-
*
|
|
44020
|
-
*
|
|
44021
|
-
* bucket you tried to delete is not empty", 409).
|
|
44264
|
+
* the `aws-cdk:auto-delete-objects` tag on the bucket (a first-class
|
|
44265
|
+
* template `Tags` entry now that `Tags` is a handled property, issue #609)
|
|
44266
|
+
* or the deploy engine's `--force-stateful-recreation` consent on a
|
|
44267
|
+
* replacement delete (`DeleteContext.forceDataDelete`). Otherwise the
|
|
44268
|
+
* not-empty error surfaces exactly like CloudFormation's DELETE_FAILED
|
|
44269
|
+
* (live-A/B-verified 2026-08-03: CFn fails a non-empty DirectoryBucket
|
|
44270
|
+
* delete with "The bucket you tried to delete is not empty", 409).
|
|
44022
44271
|
*
|
|
44023
44272
|
* Directory buckets do not support versioning, so the opted-in auto-empty
|
|
44024
44273
|
* only needs to delete current objects.
|
|
@@ -44027,15 +44276,8 @@ var S3DirectoryBucketProvider = class {
|
|
|
44027
44276
|
this.logger.debug(`Deleting S3 Express Directory Bucket ${logicalId}: ${physicalId}`);
|
|
44028
44277
|
const allowAutoEmpty = context?.forceDataDelete === true || hasCdkAutoDeleteTag(properties, "aws-cdk:auto-delete-objects");
|
|
44029
44278
|
try {
|
|
44030
|
-
|
|
44031
|
-
this.logger.info(`Emptying directory bucket ${physicalId} before deletion (auto-delete opt-in present)`);
|
|
44032
|
-
await this.emptyBucket(physicalId);
|
|
44033
|
-
}
|
|
44034
|
-
await this.s3Client.send(new DeleteBucketCommand({ Bucket: physicalId }));
|
|
44035
|
-
this.logger.debug(`Successfully deleted S3 Express Directory Bucket ${logicalId}`);
|
|
44279
|
+
await this.deleteBucketWithEmptyRetry(logicalId, physicalId, allowAutoEmpty);
|
|
44036
44280
|
} catch (error) {
|
|
44037
|
-
const notEmptyMsg = error instanceof Error ? error.message : String(error);
|
|
44038
|
-
if (!allowAutoEmpty && (notEmptyMsg.includes("not empty") || notEmptyMsg.includes("BucketNotEmpty"))) throw new ProvisioningError(`Failed to delete S3 Express Directory Bucket ${logicalId}: bucket ${physicalId} is not empty. Matching CloudFormation, cdkd does not delete a non-empty directory bucket without an explicit opt-in. Delete all objects first (e.g. aws s3 rm s3://${physicalId} --recursive) and destroy again.`, resourceType, logicalId, physicalId, error instanceof Error ? error : void 0);
|
|
44039
44281
|
if (error instanceof Error && (error.name === "NoSuchBucket" || error.name === "BucketNotFound")) {
|
|
44040
44282
|
assertRegionMatch(await this.s3Client.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
44041
44283
|
this.logger.debug(`Bucket ${physicalId} does not exist, skipping deletion`);
|
|
@@ -44046,6 +44288,35 @@ var S3DirectoryBucketProvider = class {
|
|
|
44046
44288
|
}
|
|
44047
44289
|
}
|
|
44048
44290
|
/**
|
|
44291
|
+
* Delete the bucket, absorbing the write race on an opted-in auto-empty:
|
|
44292
|
+
* an object written between the empty pass and the delete makes AWS reject
|
|
44293
|
+
* the delete as not-empty, so re-empty and retry (bounded) — the same
|
|
44294
|
+
* bounded loop as the standard-bucket sibling, ported per issue #609's
|
|
44295
|
+
* "Tags becomes a handled property" note (the tag opt-in is mainstream
|
|
44296
|
+
* now, so the ~zero-opt-in rationale for skipping the loop in PR #1347 no
|
|
44297
|
+
* longer holds). Without the opt-in, the first not-empty error surfaces
|
|
44298
|
+
* as the CloudFormation-parity guard (issue #1344).
|
|
44299
|
+
*/
|
|
44300
|
+
async deleteBucketWithEmptyRetry(logicalId, bucketName, allowAutoEmpty) {
|
|
44301
|
+
const maxAttempts = 3;
|
|
44302
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) try {
|
|
44303
|
+
await this.s3Client.send(new DeleteBucketCommand({ Bucket: bucketName }));
|
|
44304
|
+
this.logger.debug(`Successfully deleted S3 Express Directory Bucket ${logicalId}`);
|
|
44305
|
+
return;
|
|
44306
|
+
} catch (error) {
|
|
44307
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
44308
|
+
if (msg.includes("not empty") || msg.includes("BucketNotEmpty")) {
|
|
44309
|
+
if (!allowAutoEmpty) throw new Error(`bucket ${bucketName} is not empty. Matching CloudFormation, cdkd does not delete a non-empty directory bucket without an explicit opt-in. Delete all objects first (e.g. aws s3 rm s3://${bucketName} --recursive) and destroy again.`);
|
|
44310
|
+
this.logger.info(`Directory bucket ${bucketName} not empty (attempt ${attempt}/${maxAttempts}), emptying (auto-delete opt-in present)...`);
|
|
44311
|
+
await this.emptyBucket(bucketName);
|
|
44312
|
+
continue;
|
|
44313
|
+
}
|
|
44314
|
+
throw error;
|
|
44315
|
+
}
|
|
44316
|
+
await this.s3Client.send(new DeleteBucketCommand({ Bucket: bucketName }));
|
|
44317
|
+
this.logger.debug(`Successfully deleted S3 Express Directory Bucket ${logicalId}`);
|
|
44318
|
+
}
|
|
44319
|
+
/**
|
|
44049
44320
|
* Empty all objects from a directory bucket.
|
|
44050
44321
|
* Lists and deletes objects in batches of 1000.
|
|
44051
44322
|
*/
|
|
@@ -44076,10 +44347,11 @@ var S3DirectoryBucketProvider = class {
|
|
|
44076
44347
|
* CFn-property shape.
|
|
44077
44348
|
*
|
|
44078
44349
|
* Issues `HeadBucket` to verify existence and surfaces `BucketName`
|
|
44079
|
-
* (the physicalId)
|
|
44080
|
-
*
|
|
44081
|
-
*
|
|
44082
|
-
*
|
|
44350
|
+
* (the physicalId) plus `Tags` (via S3 Control `ListTagsForResource`,
|
|
44351
|
+
* the directory-bucket tag read). `DataRedundancy` and `LocationName`
|
|
44352
|
+
* are not exposed by any cheap S3 Express API — the only way to inspect
|
|
44353
|
+
* them is by parsing the bucket name's `--<az-id>--x-s3` suffix, which is
|
|
44354
|
+
* best effort. We surface them when they are recoverable from the bucket
|
|
44083
44355
|
* name to give the comparator a chance to detect mismatches.
|
|
44084
44356
|
*
|
|
44085
44357
|
* `--<az-id>--x-s3` suffix parsing: directory bucket names follow the
|
|
@@ -44105,8 +44377,17 @@ var S3DirectoryBucketProvider = class {
|
|
|
44105
44377
|
}
|
|
44106
44378
|
const result = { BucketName: physicalId };
|
|
44107
44379
|
if (physicalId.endsWith("--x-s3")) result["DataRedundancy"] = "SingleAvailabilityZone";
|
|
44380
|
+
result["Tags"] = await this.readTags(physicalId);
|
|
44108
44381
|
return result;
|
|
44109
44382
|
}
|
|
44383
|
+
async readTags(bucketName) {
|
|
44384
|
+
const accountId = await this.getAccountId();
|
|
44385
|
+
const resourceArn = await this.buildBucketArn(bucketName, accountId);
|
|
44386
|
+
return normalizeAwsTagsToCfn((await this.getS3ControlClient().send(new ListTagsForResourceCommand$18({
|
|
44387
|
+
AccountId: accountId,
|
|
44388
|
+
ResourceArn: resourceArn
|
|
44389
|
+
}))).Tags);
|
|
44390
|
+
}
|
|
44110
44391
|
/**
|
|
44111
44392
|
* Adopt an existing S3 Express Directory Bucket into cdkd state.
|
|
44112
44393
|
*
|
|
@@ -44592,7 +44873,7 @@ var S3TablesProvider = class {
|
|
|
44592
44873
|
*/
|
|
44593
44874
|
async readTagsBestEffort(resourceArn) {
|
|
44594
44875
|
try {
|
|
44595
|
-
const tags = (await this.getClient().send(new ListTagsForResourceCommand$
|
|
44876
|
+
const tags = (await this.getClient().send(new ListTagsForResourceCommand$19({ resourceArn }))).tags ?? {};
|
|
44596
44877
|
const out = [];
|
|
44597
44878
|
for (const [Key, Value] of Object.entries(tags)) out.push({
|
|
44598
44879
|
Key,
|
|
@@ -44646,7 +44927,7 @@ var S3TablesProvider = class {
|
|
|
44646
44927
|
const resourceArn = await this.lookupTableArn(tableBucketARN, namespace, name);
|
|
44647
44928
|
if (!resourceArn) throw new ProvisioningError(`applyTableTagsDiff: GetTable returned no tableARN for ${physicalId} — table is gone or state is out-of-sync. Refusing to silently drop the tag update (run 'cdkd state orphan ${logicalId}' to clean up if the table was deleted out-of-band).`, resourceType, logicalId, physicalId);
|
|
44648
44929
|
if (removedKeys.length > 0) try {
|
|
44649
|
-
await this.getClient().send(new UntagResourceCommand$
|
|
44930
|
+
await this.getClient().send(new UntagResourceCommand$24({
|
|
44650
44931
|
resourceArn,
|
|
44651
44932
|
tagKeys: removedKeys
|
|
44652
44933
|
}));
|
|
@@ -44655,7 +44936,7 @@ var S3TablesProvider = class {
|
|
|
44655
44936
|
throw new ProvisioningError(`applyTableTagsDiff: UntagResource failed for ${resourceArn} (keys: ${removedKeys.join(", ")}): ${err instanceof Error ? err.message : String(err)}. State has NOT been updated so the next deploy will retry the tag-diff.`, resourceType, logicalId, physicalId, cause);
|
|
44656
44937
|
}
|
|
44657
44938
|
if (Object.keys(upserts).length > 0) try {
|
|
44658
|
-
await this.getClient().send(new TagResourceCommand$
|
|
44939
|
+
await this.getClient().send(new TagResourceCommand$24({
|
|
44659
44940
|
resourceArn,
|
|
44660
44941
|
tags: upserts
|
|
44661
44942
|
}));
|
|
@@ -44687,7 +44968,7 @@ var S3TablesProvider = class {
|
|
|
44687
44968
|
if (removedKeys.length === 0 && Object.keys(upserts).length === 0) return;
|
|
44688
44969
|
const resourceArn = physicalId;
|
|
44689
44970
|
if (removedKeys.length > 0) try {
|
|
44690
|
-
await this.getClient().send(new UntagResourceCommand$
|
|
44971
|
+
await this.getClient().send(new UntagResourceCommand$24({
|
|
44691
44972
|
resourceArn,
|
|
44692
44973
|
tagKeys: removedKeys
|
|
44693
44974
|
}));
|
|
@@ -44696,7 +44977,7 @@ var S3TablesProvider = class {
|
|
|
44696
44977
|
throw new ProvisioningError(`applyTableBucketTagsDiff: UntagResource failed for ${resourceArn} (keys: ${removedKeys.join(", ")}): ${err instanceof Error ? err.message : String(err)}. State has NOT been updated so the next deploy will retry the tag-diff.`, resourceType, logicalId, physicalId, cause);
|
|
44697
44978
|
}
|
|
44698
44979
|
if (Object.keys(upserts).length > 0) try {
|
|
44699
|
-
await this.getClient().send(new TagResourceCommand$
|
|
44980
|
+
await this.getClient().send(new TagResourceCommand$24({
|
|
44700
44981
|
resourceArn,
|
|
44701
44982
|
tags: upserts
|
|
44702
44983
|
}));
|
|
@@ -66616,7 +66897,7 @@ function createMigrateCommand() {
|
|
|
66616
66897
|
*/
|
|
66617
66898
|
function buildProgram() {
|
|
66618
66899
|
const program = new Command();
|
|
66619
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.278.
|
|
66900
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.278.37");
|
|
66620
66901
|
program.hook("preAction", (_thisCommand, actionCommand) => {
|
|
66621
66902
|
const { profile } = actionCommand.optsWithGlobals();
|
|
66622
66903
|
if (profile !== void 0) process.env["AWS_PROFILE"] = profile;
|