@go-to-k/cdkd 0.105.0 → 0.107.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 CHANGED
@@ -30,12 +30,12 @@ import { CreateAliasCommand, CreateKeyCommand, DeleteAliasCommand, DescribeKeyCo
30
30
  import { promisify } from "node:util";
31
31
  import { CreateRepositoryCommand, DeleteLifecyclePolicyCommand, DeleteRepositoryCommand, DeleteRepositoryPolicyCommand, DescribeRepositoriesCommand, ECRClient, GetAuthorizationTokenCommand, GetLifecyclePolicyCommand, LifecyclePolicyNotFoundException, ListTagsForResourceCommand as ListTagsForResourceCommand$7, PutImageScanningConfigurationCommand, PutImageTagMutabilityCommand, PutLifecyclePolicyCommand, RepositoryNotFoundException, SetRepositoryPolicyCommand, TagResourceCommand as TagResourceCommand$9 } from "@aws-sdk/client-ecr";
32
32
  import graphlib from "graphlib";
33
- import { AddTagsToResourceCommand as AddTagsToResourceCommand$1, CreateDBClusterCommand, CreateDBInstanceCommand, CreateDBSubnetGroupCommand, DBProxyNotFoundFault, DBProxyTargetGroupNotFoundFault, DBProxyTargetNotFoundFault, DeleteDBClusterCommand, DeleteDBInstanceCommand, DeleteDBSubnetGroupCommand, DeregisterDBProxyTargetsCommand, DescribeDBClustersCommand, DescribeDBInstancesCommand, DescribeDBProxyTargetGroupsCommand, DescribeDBSubnetGroupsCommand, ListTagsForResourceCommand as ListTagsForResourceCommand$8, ModifyDBClusterCommand, ModifyDBInstanceCommand, ModifyDBProxyTargetGroupCommand, ModifyDBSubnetGroupCommand, RDSClient, RegisterDBProxyTargetsCommand, RemoveTagsFromResourceCommand as RemoveTagsFromResourceCommand$1 } from "@aws-sdk/client-rds";
33
+ import { AddTagsToResourceCommand as AddTagsToResourceCommand$1, CreateDBClusterCommand, CreateDBInstanceCommand, CreateDBProxyCommand, CreateDBSubnetGroupCommand, DBProxyNotFoundFault, DBProxyTargetGroupNotFoundFault, DBProxyTargetNotFoundFault, DeleteDBClusterCommand, DeleteDBInstanceCommand, DeleteDBProxyCommand, DeleteDBSubnetGroupCommand, DeregisterDBProxyTargetsCommand, DescribeDBClustersCommand, DescribeDBInstancesCommand, DescribeDBProxiesCommand, DescribeDBProxyTargetGroupsCommand, DescribeDBSubnetGroupsCommand, ListTagsForResourceCommand as ListTagsForResourceCommand$8, ModifyDBClusterCommand, ModifyDBInstanceCommand, ModifyDBProxyCommand, ModifyDBProxyTargetGroupCommand, ModifyDBSubnetGroupCommand, RDSClient, RegisterDBProxyTargetsCommand, RemoveTagsFromResourceCommand as RemoveTagsFromResourceCommand$1 } from "@aws-sdk/client-rds";
34
34
  import { Command, Option } from "commander";
35
35
  import { writeFileSync as writeFileSync$1 } from "fs";
36
36
  import { join as join$1 } from "path";
37
37
  import * as zlib from "node:zlib";
38
- import { ApplicationAutoScalingClient, DescribeScalingPoliciesCommand } from "@aws-sdk/client-application-auto-scaling";
38
+ import { ApplicationAutoScalingClient, DescribeScalableTargetsCommand, DescribeScalingPoliciesCommand } from "@aws-sdk/client-application-auto-scaling";
39
39
  import { ApiGatewayV2Client, CreateApiCommand, CreateAuthorizerCommand as CreateAuthorizerCommand$1, CreateIntegrationCommand, CreateRouteCommand as CreateRouteCommand$1, CreateStageCommand as CreateStageCommand$1, DeleteApiCommand, DeleteAuthorizerCommand as DeleteAuthorizerCommand$1, DeleteIntegrationCommand, DeleteRouteCommand as DeleteRouteCommand$1, DeleteStageCommand as DeleteStageCommand$1, GetApiCommand, GetApisCommand, GetAuthorizerCommand as GetAuthorizerCommand$1, GetIntegrationCommand, GetRouteCommand, GetStageCommand as GetStageCommand$1, NotFoundException as NotFoundException$3, UpdateApiCommand, UpdateAuthorizerCommand as UpdateAuthorizerCommand$1, UpdateIntegrationCommand, UpdateRouteCommand, UpdateStageCommand as UpdateStageCommand$1 } from "@aws-sdk/client-apigatewayv2";
40
40
  import { CreateStateMachineCommand, DeleteStateMachineCommand, DescribeStateMachineCommand, ListStateMachinesCommand, ListTagsForResourceCommand as ListTagsForResourceCommand$9, SFNClient, StateMachineDoesNotExist, TagResourceCommand as TagResourceCommand$10, UntagResourceCommand as UntagResourceCommand$9, UpdateStateMachineCommand } from "@aws-sdk/client-sfn";
41
41
  import { CreateClusterCommand, CreateServiceCommand, DeleteClusterCommand, DeleteServiceCommand, DeregisterTaskDefinitionCommand, DescribeClustersCommand, DescribeServicesCommand, DescribeTaskDefinitionCommand, ECSClient, ListClustersCommand, ListServicesCommand, ListTagsForResourceCommand as ListTagsForResourceCommand$10, PutClusterCapacityProvidersCommand, RegisterTaskDefinitionCommand, TagResourceCommand as TagResourceCommand$11, UntagResourceCommand as UntagResourceCommand$10, UpdateClusterCommand, UpdateServiceCommand } from "@aws-sdk/client-ecs";
@@ -7899,6 +7899,18 @@ var DynamoDBGlobalTableProvider = class {
7899
7899
  */
7900
7900
  regionalClientCache = /* @__PURE__ */ new Map();
7901
7901
  /**
7902
+ * Caches per-region `ApplicationAutoScalingClient` instances for
7903
+ * per-replica `ReadCapacityAutoScalingSettings` reverse-mapping in
7904
+ * `readCurrentState`. Same lifetime / shape as `regionalClientCache`
7905
+ * — one per region for the duration of the provider instance.
7906
+ *
7907
+ * Issue #395: per-replica read capacity autoscaling lives in the
7908
+ * replica's region (each replica has its own scaling target +
7909
+ * policy registered against `application-autoscaling` in that
7910
+ * region), so we cannot reuse the local-region client.
7911
+ */
7912
+ regionalAutoScalingClientCache = /* @__PURE__ */ new Map();
7913
+ /**
7902
7914
  * Caches `getAttribute(physicalId, attribute)` results for the lifetime
7903
7915
  * of this provider instance (one deploy run). Safe under the current
7904
7916
  * `update()` contract because `update()` cannot mid-deploy mutate
@@ -7954,6 +7966,24 @@ var DynamoDBGlobalTableProvider = class {
7954
7966
  return client;
7955
7967
  }
7956
7968
  /**
7969
+ * Return an `ApplicationAutoScalingClient` pinned to the given region,
7970
+ * caching per region for the lifetime of this provider instance. Mirrors
7971
+ * `getRegionalClient` but for the application-autoscaling service.
7972
+ *
7973
+ * Used by `readAutoScalingSettings` (Issue #395) to recover per-replica
7974
+ * `ReadCapacityAutoScalingSettings` shapes — each cross-region replica's
7975
+ * scaling target + policy are registered with the autoscaling control
7976
+ * plane in the replica's region, so cross-region drift reads need a
7977
+ * region-scoped client.
7978
+ */
7979
+ getRegionalAutoScalingClient(region) {
7980
+ const cached = this.regionalAutoScalingClientCache.get(region);
7981
+ if (cached) return cached;
7982
+ const client = new ApplicationAutoScalingClient({ region });
7983
+ this.regionalAutoScalingClientCache.set(region, client);
7984
+ return client;
7985
+ }
7986
+ /**
7957
7987
  * Construct the regional table ARN for a cross-region replica of a
7958
7988
  * GlobalTable. AWS replicates the same `TableName` across every
7959
7989
  * replica region, with each replica's ARN differing only in the
@@ -8493,13 +8523,17 @@ var DynamoDBGlobalTableProvider = class {
8493
8523
  * - StreamSpecification / SSESpecification follow the existing
8494
8524
  * DynamoDB::Table provider's Class 1 guard: only surfaced when AWS
8495
8525
  * reports the feature actually enabled.
8496
- * - `ProvisionedThroughput`-bearing fields are declared in
8497
- * `getDriftUnknownPaths` and intentionally not emitted in v1 — the
8498
- * reverse-mapping from AWS's `ProvisionedThroughput` shape into
8499
- * CFn's `WriteProvisionedThroughputSettings` /
8500
- * `ReadProvisionedThroughputSettings` wrappers (which carry
8501
- * `WriteCapacityAutoScalingSettings` etc.) needs more work to round
8502
- * -trip cleanly.
8526
+ * - `WriteProvisionedThroughputSettings` reverse-maps both shapes:
8527
+ * flat `{WriteCapacityUnits}` (no autoscaling) and full
8528
+ * `{WriteCapacityAutoScalingSettings}` (Issue #395; recovered from
8529
+ * application-autoscaling's `DescribeScalableTargets` +
8530
+ * `DescribeScalingPolicies` for the
8531
+ * `dynamodb:table:WriteCapacityUnits` dimension).
8532
+ * - Per-replica `ReadProvisionedThroughputSettings` reverse-maps the
8533
+ * `{ReadCapacityAutoScalingSettings}` shape only — there is no
8534
+ * per-replica flat read capacity in `DescribeTable`'s response
8535
+ * (the local `ProvisionedThroughput` block is table-level, not
8536
+ * replica-level), so non-autoscaled replicas omit the key.
8503
8537
  *
8504
8538
  * Per-replica sub-specifications (`ContributorInsightsSpecification` /
8505
8539
  * `PointInTimeRecoverySpecification` / `KinesisStreamSpecification`)
@@ -8555,15 +8589,23 @@ var DynamoDBGlobalTableProvider = class {
8555
8589
  }
8556
8590
  }
8557
8591
  else entry["Tags"] = [];
8592
+ if (billingMode === "PROVISIONED") {
8593
+ const replicaAutoScalingClient = isLocal ? void 0 : this.getRegionalAutoScalingClient(regionLabel);
8594
+ const readAutoScaling = await this.readAutoScalingSettings(tableNameForSubs, "dynamodb:table:ReadCapacityUnits", replicaAutoScalingClient);
8595
+ if (readAutoScaling) entry["ReadProvisionedThroughputSettings"] = { ReadCapacityAutoScalingSettings: readAutoScaling };
8596
+ }
8558
8597
  return entry;
8559
8598
  }));
8560
8599
  if (table.OnDemandThroughput?.MaxWriteRequestUnits !== void 0) result["WriteOnDemandThroughputSettings"] = { MaxWriteRequestUnits: table.OnDemandThroughput.MaxWriteRequestUnits };
8561
8600
  else result["WriteOnDemandThroughputSettings"] = {};
8562
8601
  if (billingMode === "PROVISIONED") {
8563
- const writeCapacity = table.ProvisionedThroughput?.WriteCapacityUnits;
8564
- if (writeCapacity !== void 0) if (!await this.hasWriteAutoScalingPolicy(tableNameForSubs)) result["WriteProvisionedThroughputSettings"] = { WriteCapacityUnits: writeCapacity };
8565
- else result["WriteProvisionedThroughputSettings"] = {};
8566
- else result["WriteProvisionedThroughputSettings"] = {};
8602
+ const autoScaling = await this.readAutoScalingSettings(tableNameForSubs, "dynamodb:table:WriteCapacityUnits");
8603
+ if (autoScaling) result["WriteProvisionedThroughputSettings"] = { WriteCapacityAutoScalingSettings: autoScaling };
8604
+ else {
8605
+ const writeCapacity = table.ProvisionedThroughput?.WriteCapacityUnits;
8606
+ if (writeCapacity !== void 0) result["WriteProvisionedThroughputSettings"] = { WriteCapacityUnits: writeCapacity };
8607
+ else result["WriteProvisionedThroughputSettings"] = {};
8608
+ }
8567
8609
  } else result["WriteProvisionedThroughputSettings"] = {};
8568
8610
  try {
8569
8611
  const ttlDesc = (await this.dynamoDBClient.send(new DescribeTimeToLiveCommand({ TableName: tableNameForSubs }))).TimeToLiveDescription;
@@ -8629,29 +8671,90 @@ var DynamoDBGlobalTableProvider = class {
8629
8671
  return out;
8630
8672
  }
8631
8673
  /**
8632
- * Detect whether application-autoscaling has a scaling policy on this
8633
- * table's WriteCapacityUnits dimension. Used to decide whether the
8634
- * `WriteProvisionedThroughputSettings.WriteCapacityUnits` flat-value
8635
- * surface in `readCurrentState` is safe to emit (auto-scaling
8636
- * dynamically mutates capacity, so a flat snapshot would fire false
8637
- * drift on every scale event — when an autoscaling policy is in play
8638
- * we omit the flat surface so a follow-up PR can reverse-map the
8639
- * full `WriteCapacityAutoScalingSettings` shape).
8640
- *
8641
- * Best-effort: errors return `false` (no autoscaling detected) so a
8642
- * permissions gap on `application-autoscaling:DescribeScalingPolicies`
8643
- * does not abort drift reads. Logged at debug.
8644
- */
8645
- async hasWriteAutoScalingPolicy(tableName) {
8646
- try {
8647
- return ((await new ApplicationAutoScalingClient({ region: await this.dynamoDBClient.config.region() ?? "" }).send(new DescribeScalingPoliciesCommand({
8674
+ * Reverse-map application-autoscaling state for the given DynamoDB table
8675
+ * dimension into the CFn `*CapacityAutoScalingSettings` shape (Issue
8676
+ * #395). Used for BOTH the table-level write dimension
8677
+ * (`dynamodb:table:WriteCapacityUnits`) and the per-replica read
8678
+ * dimension (`dynamodb:table:ReadCapacityUnits`); the CFn shape is
8679
+ * symmetric.
8680
+ *
8681
+ * Returns shape (CFn-canonical PascalCase keys, verified via `cdk synth`
8682
+ * on a real `TableV2` with `Capacity.autoscaled(...)` on 2026-05-16 —
8683
+ * see PR notes for the probe transcript):
8684
+ *
8685
+ * ```
8686
+ * {
8687
+ * MinCapacity: number;
8688
+ * MaxCapacity: number;
8689
+ * TargetTrackingScalingPolicyConfiguration: {
8690
+ * TargetValue: number;
8691
+ * DisableScaleIn?: boolean;
8692
+ * ScaleInCooldown?: number;
8693
+ * ScaleOutCooldown?: number;
8694
+ * };
8695
+ * }
8696
+ * ```
8697
+ *
8698
+ * `SeedCapacity` is intentionally NOT round-tripped — it is a CFn
8699
+ * create-only field with no corresponding application-autoscaling
8700
+ * surface (AWS does not retain the initial seed value once the
8701
+ * scaling target is registered).
8702
+ *
8703
+ * Resolution order:
8704
+ * 1. `DescribeScalableTargets` → recover `MinCapacity` / `MaxCapacity`
8705
+ * for the matching `ScalableDimension`. Returns `null` when no
8706
+ * target is registered (= no autoscaling in play for this
8707
+ * dimension — caller falls back to the flat capacity surface).
8708
+ * 2. `DescribeScalingPolicies` → find the `TargetTrackingScaling`
8709
+ * policy (filter out `StepScaling` / `PredictiveScaling` — CFn's
8710
+ * shape only carries the target-tracking variant). Returns `null`
8711
+ * when no `TargetTrackingScaling` policy is present (a scaling
8712
+ * target without a target-tracking policy is not cdkd-managed
8713
+ * drift territory; surface the flat capacity instead).
8714
+ *
8715
+ * Best-effort: any error (permissions gap, throttle, network) returns
8716
+ * `null` and the caller falls back to the flat-capacity branch. Logged
8717
+ * at debug so a real permissions misconfiguration is still visible
8718
+ * under `--verbose`.
8719
+ *
8720
+ * @param tableName DynamoDB table name (the `physicalId`).
8721
+ * @param scalableDimension `'dynamodb:table:WriteCapacityUnits'` or
8722
+ * `'dynamodb:table:ReadCapacityUnits'`.
8723
+ * @param client Pre-built region-scoped autoscaling client. Defaults to
8724
+ * a fresh client in the local region — pass an explicit client when
8725
+ * reading from a cross-region replica.
8726
+ */
8727
+ async readAutoScalingSettings(tableName, scalableDimension, client) {
8728
+ try {
8729
+ const asClient = client ?? new ApplicationAutoScalingClient({ region: await this.dynamoDBClient.config.region() ?? "" });
8730
+ const targets = (await asClient.send(new DescribeScalableTargetsCommand({
8731
+ ServiceNamespace: "dynamodb",
8732
+ ResourceIds: [`table/${tableName}`],
8733
+ ScalableDimension: scalableDimension
8734
+ }))).ScalableTargets ?? [];
8735
+ if (targets.length === 0) return null;
8736
+ const target = targets[0];
8737
+ if (target.MinCapacity === void 0 || target.MaxCapacity === void 0) return null;
8738
+ const targetTracking = ((await asClient.send(new DescribeScalingPoliciesCommand({
8648
8739
  ServiceNamespace: "dynamodb",
8649
8740
  ResourceId: `table/${tableName}`,
8650
- ScalableDimension: "dynamodb:table:WriteCapacityUnits"
8651
- }))).ScalingPolicies ?? []).length > 0;
8741
+ ScalableDimension: scalableDimension
8742
+ }))).ScalingPolicies ?? []).find((p) => p.PolicyType === "TargetTrackingScaling");
8743
+ if (!targetTracking) return null;
8744
+ const cfg = targetTracking.TargetTrackingScalingPolicyConfiguration;
8745
+ if (!cfg || cfg.TargetValue === void 0) return null;
8746
+ const tttConfig = { TargetValue: cfg.TargetValue };
8747
+ if (cfg.DisableScaleIn !== void 0) tttConfig["DisableScaleIn"] = cfg.DisableScaleIn;
8748
+ if (cfg.ScaleInCooldown !== void 0) tttConfig["ScaleInCooldown"] = cfg.ScaleInCooldown;
8749
+ if (cfg.ScaleOutCooldown !== void 0) tttConfig["ScaleOutCooldown"] = cfg.ScaleOutCooldown;
8750
+ return {
8751
+ MinCapacity: target.MinCapacity,
8752
+ MaxCapacity: target.MaxCapacity,
8753
+ TargetTrackingScalingPolicyConfiguration: tttConfig
8754
+ };
8652
8755
  } catch (err) {
8653
- this.logger.debug(`Could not query application-autoscaling for ${tableName}: ${err instanceof Error ? err.message : String(err)}`);
8654
- return false;
8756
+ this.logger.debug(`Could not read application-autoscaling settings for ${tableName} (${scalableDimension}): ${err instanceof Error ? err.message : String(err)}`);
8757
+ return null;
8655
8758
  }
8656
8759
  }
8657
8760
  /**
@@ -8659,13 +8762,17 @@ var DynamoDBGlobalTableProvider = class {
8659
8762
  * reverse-map. The drift comparator skips these so a templated value
8660
8763
  * doesn't fire guaranteed false drift on every clean run.
8661
8764
  *
8662
- * Issue #389 emptied this list:
8663
- * - `WriteProvisionedThroughputSettings` (flat-WriteCapacityUnits
8664
- * subset) is now reverse-mapped from `Table.ProvisionedThroughput`
8665
- * when BillingMode is PROVISIONED AND application-autoscaling has
8666
- * no policy on the WriteCapacityUnits dimension. Auto-scaling
8667
- * cases emit an empty `{}` placeholder so the comparator does NOT
8668
- * fire false drift on the literal `WriteCapacityUnits` subtree.
8765
+ * Issue #389 + #395 emptied this list:
8766
+ * - `WriteProvisionedThroughputSettings` reverse-maps both the flat
8767
+ * `{WriteCapacityUnits}` shape (no autoscaling) AND the full
8768
+ * `{WriteCapacityAutoScalingSettings}` shape (autoscaling-managed;
8769
+ * recovered from `DescribeScalableTargets` +
8770
+ * `DescribeScalingPolicies`). PAY_PER_REQUEST tables emit `{}`.
8771
+ * - Per-replica `ReadProvisionedThroughputSettings.ReadCapacityAutoScalingSettings`
8772
+ * reverse-maps from a region-scoped application-autoscaling client
8773
+ * for the `dynamodb:table:ReadCapacityUnits` dimension. Non-
8774
+ * autoscaled replicas omit the key (no per-replica flat read
8775
+ * capacity available in `DescribeTable`).
8669
8776
  * - `WriteOnDemandThroughputSettings` is reverse-mapped from
8670
8777
  * `Table.OnDemandThroughput.MaxWriteRequestUnits`. Always-emit
8671
8778
  * `{}` placeholder when AWS reports no override.
@@ -18007,7 +18114,7 @@ var RDSProvider = class {
18007
18114
  /**
18008
18115
  * Wait for a DBCluster to become available
18009
18116
  */
18010
- async waitForClusterAvailable(dbClusterIdentifier, maxWaitMs = 6e5) {
18117
+ async waitForClusterAvailable(dbClusterIdentifier, maxWaitMs = 18e5) {
18011
18118
  const startTime = Date.now();
18012
18119
  let delay = 5e3;
18013
18120
  while (Date.now() - startTime < maxWaitMs) {
@@ -18022,7 +18129,7 @@ var RDSProvider = class {
18022
18129
  /**
18023
18130
  * Wait for a DBCluster to be deleted
18024
18131
  */
18025
- async waitForClusterDeleted(dbClusterIdentifier, maxWaitMs = 6e5) {
18132
+ async waitForClusterDeleted(dbClusterIdentifier, maxWaitMs = 18e5) {
18026
18133
  const startTime = Date.now();
18027
18134
  let delay = 5e3;
18028
18135
  while (Date.now() - startTime < maxWaitMs) {
@@ -18043,7 +18150,7 @@ var RDSProvider = class {
18043
18150
  /**
18044
18151
  * Wait for a DBInstance to become available
18045
18152
  */
18046
- async waitForInstanceAvailable(dbInstanceIdentifier, maxWaitMs = 6e5) {
18153
+ async waitForInstanceAvailable(dbInstanceIdentifier, maxWaitMs = 18e5) {
18047
18154
  const startTime = Date.now();
18048
18155
  let delay = 1e4;
18049
18156
  while (Date.now() - startTime < maxWaitMs) {
@@ -18058,7 +18165,7 @@ var RDSProvider = class {
18058
18165
  /**
18059
18166
  * Wait for a DBInstance to be deleted
18060
18167
  */
18061
- async waitForInstanceDeleted(dbInstanceIdentifier, maxWaitMs = 6e5) {
18168
+ async waitForInstanceDeleted(dbInstanceIdentifier, maxWaitMs = 18e5) {
18062
18169
  const startTime = Date.now();
18063
18170
  let delay = 1e4;
18064
18171
  while (Date.now() - startTime < maxWaitMs) {
@@ -18286,6 +18393,355 @@ var RDSProvider = class {
18286
18393
  }
18287
18394
  };
18288
18395
 
18396
+ //#endregion
18397
+ //#region src/provisioning/providers/rds-dbproxy-provider.ts
18398
+ const POLL_INTERVAL_MS = 5e3;
18399
+ const POLL_TIMEOUT_MS = 900 * 1e3;
18400
+ /**
18401
+ * AWS RDS DBProxy Provider
18402
+ *
18403
+ * Implements resource provisioning for `AWS::RDS::DBProxy`.
18404
+ *
18405
+ * **Why a dedicated SDK provider** (per `feedback_dedicated_provider_over_special_case.md`):
18406
+ * keeps the AWS::RDS::DBProxy family (DBProxy + DBProxyTargetGroup) on one
18407
+ * codebase. Pre-PR DBProxy went through CC API, which worked for create
18408
+ * and delete but the sibling DBProxyTargetGroup type was broken — moving
18409
+ * the parent type to a dedicated provider ensures the whole family is
18410
+ * consistent and lets us evolve both surfaces together (e.g. shared
18411
+ * `DescribeDBProxies` calls in future enrichment, common error handling).
18412
+ *
18413
+ * **Lifecycle**:
18414
+ * - `create`: `CreateDBProxyCommand`, then poll `DescribeDBProxies` until
18415
+ * `DBProxyStatus === 'available'`. Returns physicalId=DBProxyName plus
18416
+ * `Endpoint` / `DBProxyArn` / `VpcId` in attributes.
18417
+ * - `update`: `ModifyDBProxyCommand` for the mutable fields (Auth /
18418
+ * DebugLogging / IdleClientTimeout / RequireTLS / RoleArn /
18419
+ * SecurityGroups). Tags handled via separate `AddTagsToResource` /
18420
+ * `RemoveTagsFromResource` diff. EngineFamily and VpcSubnetIds are
18421
+ * immutable on AWS; a diff in those surfaces as `ResourceReplacement`
18422
+ * from the deploy engine, not handled here.
18423
+ * - `delete`: `DeleteDBProxyCommand`, then poll for `DBProxyNotFoundFault`
18424
+ * to confirm full removal (AWS keeps the proxy in `deleting` state for
18425
+ * ~30-60s after the delete returns). `DBProxyNotFoundFault` at any
18426
+ * point is treated as idempotent success (region-match-gated).
18427
+ * - `getAttribute`: `DescribeDBProxies` for `Endpoint` / `DBProxyArn` /
18428
+ * `VpcId`, cached per `(physicalId, attribute)`.
18429
+ * - `import`: explicit `--resource` override OR auto-lookup via
18430
+ * `DescribeDBProxies` + `ListTagsForResource` matching `aws:cdk:path`.
18431
+ *
18432
+ * **physicalId** = DBProxyName (matches CFn `primaryIdentifier`).
18433
+ */
18434
+ var RDSDBProxyProvider = class {
18435
+ rdsClient;
18436
+ providerRegion = process.env["AWS_REGION"];
18437
+ logger = getLogger().child("RDSDBProxyProvider");
18438
+ attributeCache = /* @__PURE__ */ new Map();
18439
+ handledProperties = new Map([["AWS::RDS::DBProxy", new Set([
18440
+ "DBProxyName",
18441
+ "EngineFamily",
18442
+ "Auth",
18443
+ "RoleArn",
18444
+ "VpcSubnetIds",
18445
+ "VpcSecurityGroupIds",
18446
+ "RequireTLS",
18447
+ "IdleClientTimeout",
18448
+ "DebugLogging",
18449
+ "Tags"
18450
+ ])]]);
18451
+ getClient() {
18452
+ if (!this.rdsClient) this.rdsClient = new RDSClient(this.providerRegion ? { region: this.providerRegion } : {});
18453
+ return this.rdsClient;
18454
+ }
18455
+ async create(logicalId, resourceType, properties) {
18456
+ const dbProxyName = properties["DBProxyName"] ?? generateResourceName(logicalId, { maxLength: 64 });
18457
+ const engineFamily = properties["EngineFamily"];
18458
+ if (!engineFamily) throw new ProvisioningError(`EngineFamily is required for AWS::RDS::DBProxy ${logicalId}`, resourceType, logicalId);
18459
+ const auth = properties["Auth"];
18460
+ if (!auth || auth.length === 0) throw new ProvisioningError(`Auth (at least one entry) is required for AWS::RDS::DBProxy ${logicalId}`, resourceType, logicalId);
18461
+ const roleArn = properties["RoleArn"];
18462
+ if (!roleArn) throw new ProvisioningError(`RoleArn is required for AWS::RDS::DBProxy ${logicalId}`, resourceType, logicalId);
18463
+ const vpcSubnetIds = properties["VpcSubnetIds"];
18464
+ if (!vpcSubnetIds || vpcSubnetIds.length === 0) throw new ProvisioningError(`VpcSubnetIds (at least one) is required for AWS::RDS::DBProxy ${logicalId}`, resourceType, logicalId);
18465
+ const client = this.getClient();
18466
+ this.logger.debug(`Creating DBProxy ${dbProxyName} (${engineFamily})`);
18467
+ try {
18468
+ await client.send(new CreateDBProxyCommand({
18469
+ DBProxyName: dbProxyName,
18470
+ EngineFamily: engineFamily,
18471
+ Auth: auth,
18472
+ RoleArn: roleArn,
18473
+ VpcSubnetIds: vpcSubnetIds,
18474
+ VpcSecurityGroupIds: properties["VpcSecurityGroupIds"],
18475
+ RequireTLS: properties["RequireTLS"],
18476
+ IdleClientTimeout: properties["IdleClientTimeout"],
18477
+ DebugLogging: properties["DebugLogging"],
18478
+ Tags: this.toAwsTags(properties["Tags"])
18479
+ }));
18480
+ } catch (error) {
18481
+ throw this.wrapError(error, "CREATE", resourceType, logicalId, void 0);
18482
+ }
18483
+ let endpoint;
18484
+ let dbProxyArn;
18485
+ let vpcId;
18486
+ const deadline = Date.now() + POLL_TIMEOUT_MS;
18487
+ let status;
18488
+ while (Date.now() < deadline) {
18489
+ try {
18490
+ const proxy = (await client.send(new DescribeDBProxiesCommand({ DBProxyName: dbProxyName }))).DBProxies?.[0];
18491
+ status = proxy?.Status;
18492
+ if (status === "available") {
18493
+ endpoint = proxy?.Endpoint;
18494
+ dbProxyArn = proxy?.DBProxyArn;
18495
+ vpcId = proxy?.VpcId;
18496
+ break;
18497
+ }
18498
+ if (status === "incompatible-network" || status === "insufficient-resource-limits") throw new ProvisioningError(`DBProxy ${dbProxyName} entered terminal failure state: ${status}`, resourceType, logicalId, dbProxyName);
18499
+ } catch (error) {
18500
+ if (error instanceof DBProxyNotFoundFault) {} else if (error instanceof ProvisioningError) throw error;
18501
+ else throw this.wrapError(error, "CREATE (poll)", resourceType, logicalId, dbProxyName);
18502
+ }
18503
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
18504
+ }
18505
+ if (!endpoint || !dbProxyArn) throw new ProvisioningError(`Timed out waiting for DBProxy ${dbProxyName} to become available (last status: ${status ?? "unknown"})`, resourceType, logicalId, dbProxyName);
18506
+ return {
18507
+ physicalId: dbProxyName,
18508
+ attributes: {
18509
+ DBProxyArn: dbProxyArn,
18510
+ Endpoint: endpoint,
18511
+ VpcId: vpcId ?? ""
18512
+ }
18513
+ };
18514
+ }
18515
+ async update(logicalId, physicalId, resourceType, properties, previousProperties) {
18516
+ const client = this.getClient();
18517
+ const input = { DBProxyName: physicalId };
18518
+ let hasModify = false;
18519
+ for (const key of [
18520
+ "Auth",
18521
+ "RequireTLS",
18522
+ "IdleClientTimeout",
18523
+ "DebugLogging",
18524
+ "RoleArn",
18525
+ "VpcSecurityGroupIds"
18526
+ ]) if (JSON.stringify(properties[key]) !== JSON.stringify(previousProperties[key])) {
18527
+ const sdkKey = key === "VpcSecurityGroupIds" ? "SecurityGroups" : key;
18528
+ input[sdkKey] = properties[key];
18529
+ hasModify = true;
18530
+ }
18531
+ if (hasModify) {
18532
+ this.logger.debug(`Updating DBProxy ${physicalId}: ${Object.keys(input).filter((k) => k !== "DBProxyName").join(", ")}`);
18533
+ try {
18534
+ await client.send(new ModifyDBProxyCommand(input));
18535
+ } catch (error) {
18536
+ throw this.wrapError(error, "UPDATE", resourceType, logicalId, physicalId);
18537
+ }
18538
+ }
18539
+ await this.applyTagDiff(physicalId, previousProperties["Tags"], properties["Tags"], resourceType, logicalId);
18540
+ this.invalidateAttributeCache(physicalId);
18541
+ return {
18542
+ physicalId,
18543
+ wasReplaced: false
18544
+ };
18545
+ }
18546
+ async delete(logicalId, physicalId, resourceType, _properties, context) {
18547
+ const client = this.getClient();
18548
+ this.logger.debug(`Deleting DBProxy ${physicalId}`);
18549
+ try {
18550
+ await client.send(new DeleteDBProxyCommand({ DBProxyName: physicalId }));
18551
+ } catch (error) {
18552
+ if (error instanceof DBProxyNotFoundFault) {
18553
+ assertRegionMatch(await client.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
18554
+ this.logger.debug(`DBProxy ${physicalId} already gone, treating as success`);
18555
+ return;
18556
+ }
18557
+ throw this.wrapError(error, "DELETE", resourceType, logicalId, physicalId);
18558
+ }
18559
+ const deadline = Date.now() + POLL_TIMEOUT_MS;
18560
+ while (Date.now() < deadline) {
18561
+ try {
18562
+ await client.send(new DescribeDBProxiesCommand({ DBProxyName: physicalId }));
18563
+ } catch (error) {
18564
+ if (error instanceof DBProxyNotFoundFault) {
18565
+ this.logger.debug(`DBProxy ${physicalId} fully deleted`);
18566
+ return;
18567
+ }
18568
+ throw this.wrapError(error, "DELETE (poll)", resourceType, logicalId, physicalId);
18569
+ }
18570
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
18571
+ }
18572
+ throw new ProvisioningError(`Timed out waiting for DBProxy ${physicalId} to fully delete`, resourceType, logicalId, physicalId);
18573
+ }
18574
+ async getAttribute(physicalId, _resourceType, attributeName) {
18575
+ const cacheKey = `${physicalId}:${attributeName}`;
18576
+ const cached = this.attributeCache.get(cacheKey);
18577
+ if (cached !== void 0) return cached;
18578
+ if (attributeName !== "Endpoint" && attributeName !== "DBProxyArn" && attributeName !== "VpcId") {
18579
+ this.logger.warn(`Unknown attribute ${attributeName} for AWS::RDS::DBProxy, returning undefined`);
18580
+ return;
18581
+ }
18582
+ try {
18583
+ const proxy = (await this.getClient().send(new DescribeDBProxiesCommand({ DBProxyName: physicalId }))).DBProxies?.[0];
18584
+ if (!proxy) return void 0;
18585
+ const value = {
18586
+ Endpoint: proxy.Endpoint,
18587
+ DBProxyArn: proxy.DBProxyArn,
18588
+ VpcId: proxy.VpcId
18589
+ }[attributeName];
18590
+ if (value !== void 0) this.attributeCache.set(cacheKey, value);
18591
+ return value;
18592
+ } catch (error) {
18593
+ if (error instanceof DBProxyNotFoundFault) return void 0;
18594
+ throw error;
18595
+ }
18596
+ }
18597
+ async import(input) {
18598
+ const explicit = resolveExplicitPhysicalId(input, "DBProxyName");
18599
+ if (explicit) return this.buildImportResult(explicit);
18600
+ const client = this.getClient();
18601
+ let marker;
18602
+ do {
18603
+ const describe = await client.send(new DescribeDBProxiesCommand({
18604
+ Marker: marker,
18605
+ MaxRecords: 100
18606
+ }));
18607
+ for (const proxy of describe.DBProxies ?? []) {
18608
+ if (!proxy.DBProxyArn) continue;
18609
+ try {
18610
+ if (matchesCdkPath((await client.send(new ListTagsForResourceCommand$8({ ResourceName: proxy.DBProxyArn }))).TagList ?? [], input.cdkPath)) return this.buildImportResult(proxy.DBProxyName ?? "");
18611
+ } catch (error) {
18612
+ this.logger.debug(`ListTagsForResource failed for ${proxy.DBProxyName}: ${error instanceof Error ? error.message : String(error)}`);
18613
+ }
18614
+ }
18615
+ marker = describe.Marker;
18616
+ } while (marker);
18617
+ return null;
18618
+ }
18619
+ /**
18620
+ * Reads the AWS-current configuration. Drift comparator uses this as the
18621
+ * authoritative snapshot for resources written under schema v3+.
18622
+ */
18623
+ async readCurrentState(physicalId) {
18624
+ const client = this.getClient();
18625
+ let proxy;
18626
+ try {
18627
+ proxy = (await client.send(new DescribeDBProxiesCommand({ DBProxyName: physicalId }))).DBProxies?.[0];
18628
+ if (!proxy) return void 0;
18629
+ } catch (error) {
18630
+ if (error instanceof DBProxyNotFoundFault) return void 0;
18631
+ throw error;
18632
+ }
18633
+ const p = proxy;
18634
+ const result = {
18635
+ DBProxyName: p.DBProxyName,
18636
+ EngineFamily: p.EngineFamily,
18637
+ RoleArn: p.RoleArn,
18638
+ VpcSubnetIds: p.VpcSubnetIds ?? [],
18639
+ VpcSecurityGroupIds: p.VpcSecurityGroupIds ?? [],
18640
+ RequireTLS: p.RequireTLS ?? false,
18641
+ IdleClientTimeout: p.IdleClientTimeout,
18642
+ DebugLogging: p.DebugLogging ?? false,
18643
+ Auth: (p.Auth ?? []).map((a) => ({
18644
+ Description: a.Description,
18645
+ UserName: a.UserName,
18646
+ AuthScheme: a.AuthScheme,
18647
+ SecretArn: a.SecretArn,
18648
+ IAMAuth: a.IAMAuth,
18649
+ ClientPasswordAuthType: a.ClientPasswordAuthType
18650
+ }))
18651
+ };
18652
+ if (p.DBProxyArn) try {
18653
+ result["Tags"] = normalizeAwsTagsToCfn((await client.send(new ListTagsForResourceCommand$8({ ResourceName: p.DBProxyArn }))).TagList ?? []);
18654
+ } catch (error) {
18655
+ this.logger.debug(`ListTagsForResource failed for ${physicalId}: ${error instanceof Error ? error.message : String(error)}`);
18656
+ result["Tags"] = [];
18657
+ }
18658
+ else result["Tags"] = [];
18659
+ return result;
18660
+ }
18661
+ async applyTagDiff(physicalId, oldTags, newTags, resourceType, logicalId) {
18662
+ const client = this.getClient();
18663
+ const arnCacheKey = `${physicalId}:DBProxyArn`;
18664
+ let arn = this.attributeCache.get(arnCacheKey);
18665
+ if (!arn) try {
18666
+ arn = (await client.send(new DescribeDBProxiesCommand({ DBProxyName: physicalId }))).DBProxies?.[0]?.DBProxyArn;
18667
+ if (arn) this.attributeCache.set(arnCacheKey, arn);
18668
+ } catch (error) {
18669
+ this.logger.debug(`Skipping tag diff for ${physicalId} (no ARN): ${error instanceof Error ? error.message : String(error)}`);
18670
+ return;
18671
+ }
18672
+ if (!arn) return;
18673
+ const oldMap = this.toTagMap(oldTags);
18674
+ const newMap = this.toTagMap(newTags);
18675
+ const toRemove = [];
18676
+ const toAdd = [];
18677
+ for (const k of oldMap.keys()) if (!newMap.has(k)) toRemove.push(k);
18678
+ for (const [k, v] of newMap.entries()) if (oldMap.get(k) !== v) toAdd.push({
18679
+ Key: k,
18680
+ Value: v
18681
+ });
18682
+ if (toRemove.length > 0) try {
18683
+ await client.send(new RemoveTagsFromResourceCommand$1({
18684
+ ResourceName: arn,
18685
+ TagKeys: toRemove
18686
+ }));
18687
+ } catch (error) {
18688
+ throw this.wrapError(error, "UPDATE (remove tags)", resourceType, logicalId, physicalId);
18689
+ }
18690
+ if (toAdd.length > 0) try {
18691
+ await client.send(new AddTagsToResourceCommand$1({
18692
+ ResourceName: arn,
18693
+ Tags: toAdd
18694
+ }));
18695
+ } catch (error) {
18696
+ throw this.wrapError(error, "UPDATE (add tags)", resourceType, logicalId, physicalId);
18697
+ }
18698
+ }
18699
+ toTagMap(tags) {
18700
+ const map = /* @__PURE__ */ new Map();
18701
+ if (Array.isArray(tags)) {
18702
+ for (const entry of tags) if (entry?.Key !== void 0) map.set(entry.Key, entry.Value ?? "");
18703
+ }
18704
+ return map;
18705
+ }
18706
+ toAwsTags(tags) {
18707
+ if (!Array.isArray(tags) || tags.length === 0) return void 0;
18708
+ return tags.filter((t) => t.Key !== void 0).map((t) => ({
18709
+ Key: t.Key,
18710
+ Value: t.Value ?? ""
18711
+ }));
18712
+ }
18713
+ async buildImportResult(physicalId) {
18714
+ try {
18715
+ const proxy = (await this.getClient().send(new DescribeDBProxiesCommand({ DBProxyName: physicalId }))).DBProxies?.[0];
18716
+ return {
18717
+ physicalId,
18718
+ attributes: {
18719
+ DBProxyArn: proxy?.DBProxyArn ?? "",
18720
+ Endpoint: proxy?.Endpoint ?? "",
18721
+ VpcId: proxy?.VpcId ?? ""
18722
+ }
18723
+ };
18724
+ } catch {
18725
+ return {
18726
+ physicalId,
18727
+ attributes: {
18728
+ DBProxyArn: "",
18729
+ Endpoint: "",
18730
+ VpcId: ""
18731
+ }
18732
+ };
18733
+ }
18734
+ }
18735
+ invalidateAttributeCache(physicalId) {
18736
+ for (const key of this.attributeCache.keys()) if (key.startsWith(`${physicalId}:`)) this.attributeCache.delete(key);
18737
+ }
18738
+ wrapError(error, op, resourceType, logicalId, physicalId) {
18739
+ const message = error instanceof Error ? error.message : String(error);
18740
+ const cause = error instanceof Error ? error : void 0;
18741
+ return new ProvisioningError(`${op} failed for ${logicalId}: ${message}`, resourceType, logicalId, physicalId, cause);
18742
+ }
18743
+ };
18744
+
18289
18745
  //#endregion
18290
18746
  //#region src/provisioning/providers/rds-dbproxy-targetgroup-provider.ts
18291
18747
  /**
@@ -18393,8 +18849,71 @@ var RDSDBProxyTargetGroupProvider = class {
18393
18849
  }
18394
18850
  };
18395
18851
  }
18396
- async update(logicalId, _physicalId, resourceType, _oldProperties, _newProperties) {
18397
- throw new ResourceUpdateNotSupportedError(resourceType, logicalId, "destroy + redeploy; in-place updates of registered targets / connection pool config are not yet supported");
18852
+ /**
18853
+ * In-place update support: target add/remove (DBClusterIdentifiers /
18854
+ * DBInstanceIdentifiers diff) via `RegisterDBProxyTargets` /
18855
+ * `DeregisterDBProxyTargets`, and ConnectionPoolConfigurationInfo
18856
+ * rewrite via `ModifyDBProxyTargetGroup`. DBProxyName + TargetGroupName
18857
+ * are part of the resource identity — a diff in either surfaces as
18858
+ * replacement upstream (not handled here).
18859
+ */
18860
+ async update(logicalId, physicalId, resourceType, properties, previousProperties) {
18861
+ const dbProxyName = properties["DBProxyName"];
18862
+ if (!dbProxyName) throw new ProvisioningError(`DBProxyName is required for AWS::RDS::DBProxyTargetGroup ${logicalId} update`, resourceType, logicalId, physicalId);
18863
+ const targetGroupName = properties["TargetGroupName"] ?? "default";
18864
+ const client = this.getClient();
18865
+ const oldPool = previousProperties["ConnectionPoolConfigurationInfo"];
18866
+ const newPool = properties["ConnectionPoolConfigurationInfo"];
18867
+ if (JSON.stringify(oldPool) !== JSON.stringify(newPool)) {
18868
+ this.logger.debug(`Updating connection pool config for ${dbProxyName}/${targetGroupName}`);
18869
+ try {
18870
+ await client.send(new ModifyDBProxyTargetGroupCommand({
18871
+ DBProxyName: dbProxyName,
18872
+ TargetGroupName: targetGroupName,
18873
+ ConnectionPoolConfig: newPool ?? {}
18874
+ }));
18875
+ } catch (error) {
18876
+ throw this.wrapError(error, "UPDATE (pool config)", resourceType, logicalId, physicalId);
18877
+ }
18878
+ }
18879
+ const oldClusters = new Set(previousProperties["DBClusterIdentifiers"] ?? []);
18880
+ const newClusters = new Set(properties["DBClusterIdentifiers"] ?? []);
18881
+ const oldInstances = new Set(previousProperties["DBInstanceIdentifiers"] ?? []);
18882
+ const newInstances = new Set(properties["DBInstanceIdentifiers"] ?? []);
18883
+ const clustersToRemove = [...oldClusters].filter((c) => !newClusters.has(c));
18884
+ const clustersToAdd = [...newClusters].filter((c) => !oldClusters.has(c));
18885
+ const instancesToRemove = [...oldInstances].filter((i) => !newInstances.has(i));
18886
+ const instancesToAdd = [...newInstances].filter((i) => !oldInstances.has(i));
18887
+ if (clustersToRemove.length > 0 || instancesToRemove.length > 0) {
18888
+ this.logger.debug(`Deregistering targets from ${dbProxyName}/${targetGroupName}: clusters=[${clustersToRemove.join(",")}], instances=[${instancesToRemove.join(",")}]`);
18889
+ try {
18890
+ await client.send(new DeregisterDBProxyTargetsCommand({
18891
+ DBProxyName: dbProxyName,
18892
+ TargetGroupName: targetGroupName,
18893
+ DBClusterIdentifiers: clustersToRemove.length > 0 ? clustersToRemove : void 0,
18894
+ DBInstanceIdentifiers: instancesToRemove.length > 0 ? instancesToRemove : void 0
18895
+ }));
18896
+ } catch (error) {
18897
+ if (!(error instanceof DBProxyTargetNotFoundFault)) throw this.wrapError(error, "UPDATE (deregister)", resourceType, logicalId, physicalId);
18898
+ }
18899
+ }
18900
+ if (clustersToAdd.length > 0 || instancesToAdd.length > 0) {
18901
+ this.logger.debug(`Registering targets to ${dbProxyName}/${targetGroupName}: clusters=[${clustersToAdd.join(",")}], instances=[${instancesToAdd.join(",")}]`);
18902
+ try {
18903
+ await client.send(new RegisterDBProxyTargetsCommand({
18904
+ DBProxyName: dbProxyName,
18905
+ TargetGroupName: targetGroupName,
18906
+ DBClusterIdentifiers: clustersToAdd.length > 0 ? clustersToAdd : void 0,
18907
+ DBInstanceIdentifiers: instancesToAdd.length > 0 ? instancesToAdd : void 0
18908
+ }));
18909
+ } catch (error) {
18910
+ throw this.wrapError(error, "UPDATE (register)", resourceType, logicalId, physicalId);
18911
+ }
18912
+ }
18913
+ return {
18914
+ physicalId,
18915
+ wasReplaced: false
18916
+ };
18398
18917
  }
18399
18918
  async delete(logicalId, physicalId, resourceType, properties, context) {
18400
18919
  const props = properties ?? {};
@@ -29183,6 +29702,7 @@ function registerAllProviders(registry) {
29183
29702
  registry.register("AWS::RDS::DBSubnetGroup", rdsProvider);
29184
29703
  registry.register("AWS::RDS::DBCluster", rdsProvider);
29185
29704
  registry.register("AWS::RDS::DBInstance", rdsProvider);
29705
+ registry.register("AWS::RDS::DBProxy", new RDSDBProxyProvider());
29186
29706
  registry.register("AWS::RDS::DBProxyTargetGroup", new RDSDBProxyTargetGroupProvider());
29187
29707
  const docdbProvider = new DocDBProvider();
29188
29708
  registry.register("AWS::DocDB::DBSubnetGroup", docdbProvider);
@@ -44163,7 +44683,7 @@ function reorderArgs(argv) {
44163
44683
  */
44164
44684
  async function main() {
44165
44685
  const program = new Command();
44166
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.105.0");
44686
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.107.0");
44167
44687
  program.addCommand(createBootstrapCommand());
44168
44688
  program.addCommand(createSynthCommand());
44169
44689
  program.addCommand(createListCommand());