@go-to-k/cdkd 0.280.10 → 0.280.12

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
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import { $ as applyRoleArnIfSet, A as red, At as AssetManifestLoader, B as isTerminationProtectionPropagationError, Bt as resolveStateBucketWithDefault, C as isStatefulRecreateTargetSync, Ct as validateAssetBucketName, D as cyan, Dn as withErrorHandling, Dt as getDockerCmd, E as bold, En as normalizeAwsError, Et as formatDockerLoginError, F as ProviderRegistry, Ft as getLegacyStateBucketName, G as normalizeAwsTagsToCfn, Gt as CFN_TEMPLATE_BODY_LIMIT, H as cfnRefValueFromPhysicalId, Ht as resolveUseCdkBootstrapAssets, I as findActionableSilentDrops, It as resolveApp, J as readConfigString, Jt as findLargeInlineResources, K as resolveExplicitPhysicalId, Kt as CFN_TEMPLATE_URL_LIMIT, L as CloudControlProvider, Lt as resolveAutoAssetStorage, M as IAMRoleProvider, Mt as Synthesizer, N as collectInlinePolicyNamesManagedBySiblings, Nt as synthesisStatusMessage, O as gray, Ot as runDockerForeground, P as clearOnUpdateRemoval, Pt as getDefaultStateBucketName, Q as requireConfigString, Qt as processStackMessages, R as slowCcOperationTimeoutMs, Rt as resolveCaptureObservedState, S as MULTI_REGION_RECREATE_BLOCKED_TYPES, St as parseBootstrapMarker, T as formatResourceLine, Tt as buildDockerImage, U as refStateLookupFromResource, Ut as stateBucketExistenceConfirmed, V as IntrinsicFunctionResolver, Vt as resolveStateBucketWithDefaultAndSource, W as WAFv2WebACLProvider, Wt as warnDeprecatedNoPrefixCliFlag, X as requireConfigArray, Xt as expectedOwnerParam, Y as replayWarn, Yt as uploadCfnTemplate, Z as requireConfigObject, Zt as AssemblyReader, _ as createPreDeleteFinalSnapshot, _n as ProvisioningError, _t as rewriteTemplateAssetReferences, a as DeploymentEventsStore, at as DagBuilder, b as unsupportedFinalSnapshotError, bn as StackHasActiveImportsError, bt as ensureAssetStorage, c as replayFailedOperations, ct as S3StateBackend, d as IMPLICIT_DELETE_DEPENDENCIES, dn as LocalMigrateError, dt as AssetPublisher, en as resolveBucketRegion, et as DiffCalculator, f as computeImplicitDeleteEdges, fn as LocalStartServiceError, ft as stringifyValue, g as ccRoutedFinalSnapshotError, gn as PartialFailureError, gt as loadPublishableAssetManifest, h as buildFinalSnapshotIdentifier, hn as NestedStackChildDirectDestroyError, ht as createAssetRedirectResolver, i as DeploymentEventsReader, in as setAwsClients, it as isThrottlingError, j as yellow, jt as getDockerImageBySourceHash, k as green, kt as runDockerStreaming, l as replayRollback, ln as DeployCancelledError, lt as rebuildClientForBucketRegion, m as PRE_DELETE_SNAPSHOT_TYPES, mn as MissingCdkCliError, mt as buildAssetRedirectMap, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as getAwsClients, nt as withRetry, o as planFailedOps, on as CdkdError, ot as TemplateParser, p as ATOMIC_FINAL_SNAPSHOT_TYPES, pt as WorkGraph, q as assertRegionMatch, qt as MIGRATE_TMP_PREFIX, r as DeployEngine, rn as resetAwsClients, rt as isRetryableTransientError, s as planRollback, st as LockManager, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as AwsClients, tt as describeTypeWithThrottleRetry, u as withResourceDeadline, un as LocalInvokeBuildError$1, ut as shouldRetainResource, v as isFinalSnapshotError, vn as ResourceTimeoutError, vt as AssetModeResolver, w as renderStatefulReason, wt as validateContainerRepoName, x as extractDeploymentEventError, xn as StackTerminationProtectionError, xt as getBootstrapMarkerKey, y as refusesFinalSnapshot, yn as ResourceUpdateNotSupportedError, yt as BOOTSTRAP_MARKER_PREFIX, z as disableInstanceApiTermination, zt as resolveSkipPrefix } from "./deploy-engine-BJATTy_n.js";
2
+ import { $ as applyRoleArnIfSet, A as red, At as AssetManifestLoader, B as isTerminationProtectionPropagationError, Bt as resolveStateBucketWithDefault, C as isStatefulRecreateTargetSync, Ct as validateAssetBucketName, D as cyan, Dn as withErrorHandling, Dt as getDockerCmd, E as bold, En as normalizeAwsError, Et as formatDockerLoginError, F as ProviderRegistry, Ft as getLegacyStateBucketName, G as normalizeAwsTagsToCfn, Gt as CFN_TEMPLATE_BODY_LIMIT, H as cfnRefValueFromPhysicalId, Ht as resolveUseCdkBootstrapAssets, I as findActionableSilentDrops, It as resolveApp, J as readConfigString, Jt as findLargeInlineResources, K as resolveExplicitPhysicalId, Kt as CFN_TEMPLATE_URL_LIMIT, L as CloudControlProvider, Lt as resolveAutoAssetStorage, M as IAMRoleProvider, Mt as Synthesizer, N as collectInlinePolicyNamesManagedBySiblings, Nt as synthesisStatusMessage, O as gray, Ot as runDockerForeground, P as clearOnUpdateRemoval, Pt as getDefaultStateBucketName, Q as requireConfigString, Qt as processStackMessages, R as slowCcOperationTimeoutMs, Rt as resolveCaptureObservedState, S as MULTI_REGION_RECREATE_BLOCKED_TYPES, St as parseBootstrapMarker, T as formatResourceLine, Tt as buildDockerImage, U as refStateLookupFromResource, Ut as stateBucketExistenceConfirmed, V as IntrinsicFunctionResolver, Vt as resolveStateBucketWithDefaultAndSource, W as WAFv2WebACLProvider, Wt as warnDeprecatedNoPrefixCliFlag, X as requireConfigArray, Xt as expectedOwnerParam, Y as replayWarn, Yt as uploadCfnTemplate, Z as requireConfigObject, Zt as AssemblyReader, _ as createPreDeleteFinalSnapshot, _n as ProvisioningError, _t as rewriteTemplateAssetReferences, a as DeploymentEventsStore, at as DagBuilder, b as unsupportedFinalSnapshotError, bn as StackHasActiveImportsError, bt as ensureAssetStorage, c as replayFailedOperations, ct as S3StateBackend, d as IMPLICIT_DELETE_DEPENDENCIES, dn as LocalMigrateError, dt as AssetPublisher, en as resolveBucketRegion, et as DiffCalculator, f as computeImplicitDeleteEdges, fn as LocalStartServiceError, ft as stringifyValue, g as ccRoutedFinalSnapshotError, gn as PartialFailureError, gt as loadPublishableAssetManifest, h as buildFinalSnapshotIdentifier, hn as NestedStackChildDirectDestroyError, ht as createAssetRedirectResolver, i as DeploymentEventsReader, in as setAwsClients, it as isThrottlingError, j as yellow, jt as getDockerImageBySourceHash, k as green, kt as runDockerStreaming, l as replayRollback, ln as DeployCancelledError, lt as rebuildClientForBucketRegion, m as PRE_DELETE_SNAPSHOT_TYPES, mn as MissingCdkCliError, mt as buildAssetRedirectMap, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as getAwsClients, nt as withRetry, o as planFailedOps, on as CdkdError, ot as TemplateParser, p as ATOMIC_FINAL_SNAPSHOT_TYPES, pt as WorkGraph, q as assertRegionMatch, qt as MIGRATE_TMP_PREFIX, r as DeployEngine, rn as resetAwsClients, rt as isRetryableTransientError, s as planRollback, st as LockManager, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as AwsClients, tt as describeTypeWithThrottleRetry, u as withResourceDeadline, un as LocalInvokeBuildError$1, ut as shouldRetainResource, v as isFinalSnapshotError, vn as ResourceTimeoutError, vt as AssetModeResolver, w as renderStatefulReason, wt as validateContainerRepoName, x as extractDeploymentEventError, xn as StackTerminationProtectionError, xt as getBootstrapMarkerKey, y as refusesFinalSnapshot, yn as ResourceUpdateNotSupportedError, yt as BOOTSTRAP_MARKER_PREFIX, z as disableInstanceApiTermination, zt as resolveSkipPrefix } from "./deploy-engine-CHhLFsDu.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-BVnACl8U.js";
4
+ import { t as ASGProvider } from "./asg-provider-BjmkUhRq.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";
@@ -31,7 +31,7 @@ import { basename, dirname, isAbsolute, join, resolve } from "node:path";
31
31
  import { execFile, spawn } from "node:child_process";
32
32
  import { tmpdir } from "node:os";
33
33
  import { AssociateVPCWithHostedZoneCommand, ChangeResourceRecordSetsCommand, ChangeTagsForResourceCommand, CreateHostedZoneCommand, CreateQueryLoggingConfigCommand, DeleteHostedZoneCommand, DeleteQueryLoggingConfigCommand, DisassociateVPCFromHostedZoneCommand, GetHostedZoneCommand, ListHostedZonesByNameCommand, ListQueryLoggingConfigsCommand, ListResourceRecordSetsCommand, ListTagsForResourceCommand as ListTagsForResourceCommand$8, Route53Client, UpdateHostedZoneCommentCommand, UpdateHostedZoneFeaturesCommand } from "@aws-sdk/client-route-53";
34
- import { AddTagsCommand, CreateListenerCommand, CreateLoadBalancerCommand, CreateTargetGroupCommand, DeleteListenerCommand, DeleteLoadBalancerCommand, DeleteTargetGroupCommand, DescribeListenerAttributesCommand, DescribeListenersCommand, DescribeLoadBalancerAttributesCommand, DescribeLoadBalancersCommand, DescribeTagsCommand, DescribeTargetGroupsCommand, ElasticLoadBalancingV2Client, ModifyListenerAttributesCommand, ModifyListenerCommand, ModifyLoadBalancerAttributesCommand, ModifyTargetGroupCommand, RemoveTagsCommand, SetIpAddressTypeCommand, SetSecurityGroupsCommand, SetSubnetsCommand, waitUntilLoadBalancerAvailable } from "@aws-sdk/client-elastic-load-balancing-v2";
34
+ import { AddTagsCommand, CreateListenerCommand, CreateLoadBalancerCommand, CreateTargetGroupCommand, DeleteListenerCommand, DeleteLoadBalancerCommand, DeleteTargetGroupCommand, DeregisterTargetsCommand, DescribeCapacityReservationCommand, DescribeListenerAttributesCommand, DescribeListenersCommand, DescribeLoadBalancerAttributesCommand, DescribeLoadBalancersCommand, DescribeTagsCommand, DescribeTargetGroupAttributesCommand, DescribeTargetGroupsCommand, ElasticLoadBalancingV2Client, ModifyCapacityReservationCommand, ModifyIpPoolsCommand, ModifyListenerAttributesCommand, ModifyListenerCommand, ModifyLoadBalancerAttributesCommand, ModifyTargetGroupAttributesCommand, ModifyTargetGroupCommand, RegisterTargetsCommand, RemoveTagsCommand, SetIpAddressTypeCommand, SetSecurityGroupsCommand, SetSubnetsCommand, waitUntilLoadBalancerAvailable } from "@aws-sdk/client-elastic-load-balancing-v2";
35
35
  import { CreateAliasCommand, CreateKeyCommand, DeleteAliasCommand, DescribeKeyCommand, DisableKeyCommand, DisableKeyRotationCommand, EnableKeyCommand, EnableKeyRotationCommand, GetKeyPolicyCommand, GetKeyRotationStatusCommand, KMSClient, ListAliasesCommand, ListResourceTagsCommand, NotFoundException as NotFoundException$2, PutKeyPolicyCommand, ScheduleKeyDeletionCommand, TagResourceCommand as TagResourceCommand$11, UntagResourceCommand as UntagResourceCommand$11, UpdateAliasCommand, UpdateKeyDescriptionCommand } from "@aws-sdk/client-kms";
36
36
  import { join as join$1 } from "path";
37
37
  import { BatchDeleteImageCommand, CreateRepositoryCommand, DeleteLifecyclePolicyCommand, DeleteRepositoryCommand, DeleteRepositoryPolicyCommand, DescribeImagesCommand as DescribeImagesCommand$1, DescribeRepositoriesCommand, ECRClient, GetAuthorizationTokenCommand, GetLifecyclePolicyCommand, LifecyclePolicyNotFoundException, ListTagsForResourceCommand as ListTagsForResourceCommand$9, PutImageScanningConfigurationCommand, PutImageTagMutabilityCommand, PutLifecyclePolicyCommand, RepositoryNotEmptyException, RepositoryNotFoundException, SetRepositoryPolicyCommand, TagResourceCommand as TagResourceCommand$12, UntagResourceCommand as UntagResourceCommand$12 } from "@aws-sdk/client-ecr";
@@ -22003,7 +22003,17 @@ var ApiGatewayV2Provider = class {
22003
22003
  "PayloadFormatVersion",
22004
22004
  "TimeoutInMillis",
22005
22005
  "RequestParameters",
22006
- "Description"
22006
+ "Description",
22007
+ "ConnectionId",
22008
+ "ConnectionType",
22009
+ "ContentHandlingStrategy",
22010
+ "CredentialsArn",
22011
+ "IntegrationSubtype",
22012
+ "PassthroughBehavior",
22013
+ "RequestTemplates",
22014
+ "ResponseParameters",
22015
+ "TemplateSelectionExpression",
22016
+ "TlsConfig"
22007
22017
  ])],
22008
22018
  ["AWS::ApiGatewayV2::Route", /* @__PURE__ */ new Set([
22009
22019
  "ApiId",
@@ -22229,7 +22239,17 @@ var ApiGatewayV2Provider = class {
22229
22239
  PayloadFormatVersion: properties["PayloadFormatVersion"],
22230
22240
  TimeoutInMillis: properties["TimeoutInMillis"],
22231
22241
  RequestParameters: properties["RequestParameters"],
22232
- Description: properties["Description"]
22242
+ Description: properties["Description"],
22243
+ ConnectionId: properties["ConnectionId"],
22244
+ ConnectionType: properties["ConnectionType"],
22245
+ ContentHandlingStrategy: properties["ContentHandlingStrategy"],
22246
+ CredentialsArn: properties["CredentialsArn"],
22247
+ IntegrationSubtype: properties["IntegrationSubtype"],
22248
+ PassthroughBehavior: properties["PassthroughBehavior"],
22249
+ RequestTemplates: properties["RequestTemplates"],
22250
+ ResponseParameters: this.toSdkResponseParameters(properties["ResponseParameters"]),
22251
+ TemplateSelectionExpression: properties["TemplateSelectionExpression"],
22252
+ TlsConfig: properties["TlsConfig"]
22233
22253
  }))).IntegrationId;
22234
22254
  this.logger.debug(`Successfully created API Gateway V2 Integration ${logicalId}: ${integrationId}`);
22235
22255
  return {
@@ -22448,6 +22468,16 @@ var ApiGatewayV2Provider = class {
22448
22468
  if (resp.TimeoutInMillis !== void 0) result["TimeoutInMillis"] = resp.TimeoutInMillis;
22449
22469
  if (resp.RequestParameters !== void 0) result["RequestParameters"] = resp.RequestParameters;
22450
22470
  if (resp.Description !== void 0) result["Description"] = resp.Description;
22471
+ if (resp.ConnectionId !== void 0) result["ConnectionId"] = resp.ConnectionId;
22472
+ if (resp.ConnectionType !== void 0) result["ConnectionType"] = resp.ConnectionType;
22473
+ if (resp.ContentHandlingStrategy !== void 0) result["ContentHandlingStrategy"] = resp.ContentHandlingStrategy;
22474
+ if (resp.CredentialsArn !== void 0) result["CredentialsArn"] = resp.CredentialsArn;
22475
+ if (resp.IntegrationSubtype !== void 0) result["IntegrationSubtype"] = resp.IntegrationSubtype;
22476
+ if (resp.PassthroughBehavior !== void 0) result["PassthroughBehavior"] = resp.PassthroughBehavior;
22477
+ if (resp.RequestTemplates !== void 0) result["RequestTemplates"] = resp.RequestTemplates;
22478
+ if (resp.ResponseParameters !== void 0) result["ResponseParameters"] = this.toCfnResponseParameters(resp.ResponseParameters);
22479
+ if (resp.TemplateSelectionExpression !== void 0) result["TemplateSelectionExpression"] = resp.TemplateSelectionExpression;
22480
+ if (resp.TlsConfig !== void 0) result["TlsConfig"] = resp.TlsConfig;
22451
22481
  return result;
22452
22482
  } catch (err) {
22453
22483
  if (err instanceof NotFoundException$3) return void 0;
@@ -22730,7 +22760,11 @@ var ApiGatewayV2Provider = class {
22730
22760
  * `UpdateIntegration` keys on `(ApiId, IntegrationId)`. Mutable
22731
22761
  * fields cdkd manages: `IntegrationType` / `IntegrationUri` /
22732
22762
  * `IntegrationMethod` / `PayloadFormatVersion` / `TimeoutInMillis` /
22733
- * `RequestParameters` / `Description`.
22763
+ * `RequestParameters` / `Description`, plus the issue #609 backfill
22764
+ * (`ConnectionId` / `ConnectionType` / `ContentHandlingStrategy` /
22765
+ * `CredentialsArn` / `IntegrationSubtype` / `PassthroughBehavior` /
22766
+ * `RequestTemplates` / `ResponseParameters` / `TemplateSelectionExpression`
22767
+ * / `TlsConfig`).
22734
22768
  */
22735
22769
  async updateIntegration(logicalId, physicalId, resourceType, properties, previousProperties) {
22736
22770
  const apiId = properties["ApiId"] ?? previousProperties["ApiId"];
@@ -22775,6 +22809,50 @@ var ApiGatewayV2Provider = class {
22775
22809
  changed = true;
22776
22810
  }
22777
22811
  }
22812
+ if (properties["ConnectionId"] != null && properties["ConnectionId"] !== previousProperties["ConnectionId"]) {
22813
+ input.ConnectionId = properties["ConnectionId"];
22814
+ changed = true;
22815
+ }
22816
+ if (properties["ConnectionType"] != null && properties["ConnectionType"] !== previousProperties["ConnectionType"]) {
22817
+ input.ConnectionType = properties["ConnectionType"];
22818
+ changed = true;
22819
+ }
22820
+ if (properties["ContentHandlingStrategy"] != null && properties["ContentHandlingStrategy"] !== previousProperties["ContentHandlingStrategy"]) {
22821
+ input.ContentHandlingStrategy = properties["ContentHandlingStrategy"];
22822
+ changed = true;
22823
+ }
22824
+ if (properties["CredentialsArn"] != null && properties["CredentialsArn"] !== previousProperties["CredentialsArn"]) {
22825
+ input.CredentialsArn = properties["CredentialsArn"];
22826
+ changed = true;
22827
+ }
22828
+ if (properties["IntegrationSubtype"] != null && properties["IntegrationSubtype"] !== previousProperties["IntegrationSubtype"]) {
22829
+ input.IntegrationSubtype = properties["IntegrationSubtype"];
22830
+ changed = true;
22831
+ }
22832
+ if (input.IntegrationSubtype === void 0) {
22833
+ const liveSubtype = properties["IntegrationSubtype"] ?? previousProperties["IntegrationSubtype"];
22834
+ if (liveSubtype !== void 0) input.IntegrationSubtype = liveSubtype;
22835
+ }
22836
+ if (properties["PassthroughBehavior"] != null && properties["PassthroughBehavior"] !== previousProperties["PassthroughBehavior"]) {
22837
+ input.PassthroughBehavior = properties["PassthroughBehavior"];
22838
+ changed = true;
22839
+ }
22840
+ if (properties["RequestTemplates"] != null && !this.deepEqual(properties["RequestTemplates"], previousProperties["RequestTemplates"])) {
22841
+ input.RequestTemplates = properties["RequestTemplates"];
22842
+ changed = true;
22843
+ }
22844
+ if (properties["ResponseParameters"] != null && !this.deepEqual(properties["ResponseParameters"], previousProperties["ResponseParameters"])) {
22845
+ input.ResponseParameters = this.toSdkResponseParameters(properties["ResponseParameters"]);
22846
+ changed = true;
22847
+ }
22848
+ if (properties["TemplateSelectionExpression"] != null && properties["TemplateSelectionExpression"] !== previousProperties["TemplateSelectionExpression"]) {
22849
+ input.TemplateSelectionExpression = properties["TemplateSelectionExpression"];
22850
+ changed = true;
22851
+ }
22852
+ if (properties["TlsConfig"] != null && !this.deepEqual(properties["TlsConfig"], previousProperties["TlsConfig"])) {
22853
+ input.TlsConfig = properties["TlsConfig"];
22854
+ changed = true;
22855
+ }
22778
22856
  if (!changed) {
22779
22857
  this.logger.debug(`No mutable Integration fields changed for ${logicalId}; skipping UpdateIntegration`);
22780
22858
  return {
@@ -23125,6 +23203,84 @@ var ApiGatewayV2Provider = class {
23125
23203
  return merged;
23126
23204
  }
23127
23205
  /**
23206
+ * `ResponseParameters` is the one `AWS::ApiGatewayV2::Integration` property
23207
+ * whose CFn shape does NOT match the SDK's, so forwarding it verbatim is not
23208
+ * an option (issue #609). CloudFormation models it as
23209
+ * `{ "<statusCode>": { ResponseParameters: [{ Destination, Source }, ...] } }`
23210
+ * while `CreateIntegration` / `UpdateIntegration` take the flattened
23211
+ * `{ "<statusCode>": { "<Destination>": "<Source>" } }`. The AWS SDK v3
23212
+ * serializer drops members it cannot model, so the un-converted CFn shape
23213
+ * would deliver an EMPTY map per status code — the silent-drop class one
23214
+ * level down.
23215
+ *
23216
+ * Anything that is not the expected object / array shape (an unresolved
23217
+ * intrinsic, a malformed block) passes through untouched so AWS surfaces the
23218
+ * real validation error instead of cdkd inventing one.
23219
+ *
23220
+ * `Source` is COERCED from a number / boolean rather than skipped. CFn types
23221
+ * `ResponseParameters` as free-form `object` and coerces scalars, cdkd does
23222
+ * not (the `coerceNumber` class in `.claude/rules/providers.md`), so an
23223
+ * unquoted `Source: 403` under the canonical `overwrite:statuscode`
23224
+ * destination is a perfectly legal template whose value arrives as a NUMBER.
23225
+ * Skipping it would re-introduce the silent drop this method exists to close.
23226
+ * A `Destination` is never coerced — it becomes an SDK map KEY, and a
23227
+ * non-string there is a malformed template, not a scalar shorthand.
23228
+ */
23229
+ toSdkResponseParameters(value) {
23230
+ if (value == null) return void 0;
23231
+ if (typeof value !== "object" || Array.isArray(value)) return value;
23232
+ const out = {};
23233
+ for (const [statusCode, block] of Object.entries(value)) {
23234
+ if (block == null || typeof block !== "object" || Array.isArray(block)) {
23235
+ out[statusCode] = block;
23236
+ continue;
23237
+ }
23238
+ const entries = block["ResponseParameters"];
23239
+ if (!Array.isArray(entries)) {
23240
+ out[statusCode] = block;
23241
+ continue;
23242
+ }
23243
+ const mapped = {};
23244
+ for (const entry of entries) {
23245
+ const destination = entry != null && typeof entry === "object" && !Array.isArray(entry) ? entry["Destination"] : void 0;
23246
+ const source = entry != null && typeof entry === "object" && !Array.isArray(entry) ? entry["Source"] : void 0;
23247
+ const coerced = typeof source === "string" ? source : typeof source === "number" || typeof source === "boolean" ? String(source) : void 0;
23248
+ if (typeof destination !== "string" || coerced === void 0) {
23249
+ this.logger.warn(`AWS::ApiGatewayV2::Integration ResponseParameters['${statusCode}'] has an entry cdkd cannot deliver (Destination must be a string and Source a string / number / boolean; got ${JSON.stringify(destination)} / ${JSON.stringify(source)}); skipping that entry.`);
23250
+ continue;
23251
+ }
23252
+ mapped[destination] = coerced;
23253
+ }
23254
+ out[statusCode] = mapped;
23255
+ }
23256
+ return out;
23257
+ }
23258
+ /**
23259
+ * Inverse of {@link toSdkResponseParameters}, for `readCurrentState`. Without
23260
+ * it the AWS-current side would carry the flat SDK map while the cdkd
23261
+ * baseline carries the CFn list-of-pairs, and every `cdkd drift` run would
23262
+ * report permanent phantom drift on a resource nobody touched.
23263
+ *
23264
+ * The pairs are sorted by `Destination` because this turns an UNORDERED SDK
23265
+ * map into an ARRAY, and `calculateResourceDrift` compares arrays
23266
+ * POSITIONALLY — AWS does not guarantee map readback order, so an unsorted
23267
+ * rebuild would trade the shape-mismatch phantom drift for an ordering one
23268
+ * (and `--revert` would re-push an identical value forever). Same reasoning
23269
+ * as `src/analyzer/drift-normalize.ts`, applied at the source instead.
23270
+ */
23271
+ toCfnResponseParameters(value) {
23272
+ if (value == null) return void 0;
23273
+ const out = {};
23274
+ for (const [statusCode, flat] of Object.entries(value)) {
23275
+ if (flat == null || typeof flat !== "object" || Array.isArray(flat)) continue;
23276
+ out[statusCode] = { ResponseParameters: Object.entries(flat).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([destination, source]) => ({
23277
+ Destination: destination,
23278
+ Source: source
23279
+ })) };
23280
+ }
23281
+ return out;
23282
+ }
23283
+ /**
23128
23284
  * Convert CloudFormation Tags (Array<{Key, Value}>) to SDK Tags (Record<string, string>).
23129
23285
  */
23130
23286
  cfnTagsToRecord(tags) {
@@ -27352,6 +27508,36 @@ var ECSProvider = class {
27352
27508
  //#endregion
27353
27509
  //#region src/provisioning/providers/elbv2-provider.ts
27354
27510
  /**
27511
+ * Test seam for the capacity-reservation stabilize poll (mirrors
27512
+ * `finalSnapshotDelays` in ../final-snapshot.ts). Unit tests stub `sleep`
27513
+ * so the poll runs without wall-clock delay.
27514
+ */
27515
+ const capacityReservationDelays = { sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)) };
27516
+ /**
27517
+ * Documented AWS defaults for TargetGroup attributes, used to reset a
27518
+ * removed `TargetGroupAttributes` entry. Unlike ModifyLoadBalancerAttributes
27519
+ * / ModifyListenerAttributes, ModifyTargetGroupAttributes REJECTS an empty
27520
+ * `Value` ("A target group attribute value must be specified" — live-verified
27521
+ * 2026-08-11), so clearing an override requires sending the documented
27522
+ * default explicitly. Keys whose default is target-type-dependent or
27523
+ * undocumented are deliberately absent — a removal of those warns and
27524
+ * retains the live value instead of guessing.
27525
+ * Source: ELBv2 API reference, TargetGroupAttribute key table.
27526
+ */
27527
+ const TARGET_GROUP_ATTRIBUTE_DEFAULTS = {
27528
+ "deregistration_delay.timeout_seconds": "300",
27529
+ "deregistration_delay.connection_termination.enabled": "false",
27530
+ "stickiness.enabled": "false",
27531
+ "stickiness.lb_cookie.duration_seconds": "86400",
27532
+ "stickiness.app_cookie.duration_seconds": "86400",
27533
+ "load_balancing.algorithm.type": "round_robin",
27534
+ "load_balancing.algorithm.anomaly_mitigation": "off",
27535
+ "load_balancing.cross_zone.enabled": "use_load_balancer_configuration",
27536
+ "slow_start.duration_seconds": "0",
27537
+ "lambda.multi_value_headers.enabled": "false",
27538
+ "proxy_protocol_v2.enabled": "false"
27539
+ };
27540
+ /**
27355
27541
  * AWS ELBv2 Provider
27356
27542
  *
27357
27543
  * Implements resource provisioning for ELBv2 resources:
@@ -27384,7 +27570,12 @@ var ELBv2Provider = class {
27384
27570
  "Type",
27385
27571
  "IpAddressType",
27386
27572
  "LoadBalancerAttributes",
27387
- "Tags"
27573
+ "Tags",
27574
+ "EnablePrefixForIpv6SourceNat",
27575
+ "EnforceSecurityGroupInboundRulesOnPrivateLinkTraffic",
27576
+ "Ipv4IpamPoolId",
27577
+ "MinimumLoadBalancerCapacity",
27578
+ "EnableCapacityReservationProvisionStabilize"
27388
27579
  ])],
27389
27580
  ["AWS::ElasticLoadBalancingV2::TargetGroup", /* @__PURE__ */ new Set([
27390
27581
  "Protocol",
@@ -27402,7 +27593,11 @@ var ELBv2Provider = class {
27402
27593
  "UnhealthyThresholdCount",
27403
27594
  "Matcher",
27404
27595
  "Name",
27405
- "Tags"
27596
+ "Tags",
27597
+ "IpAddressType",
27598
+ "TargetControlPort",
27599
+ "TargetGroupAttributes",
27600
+ "Targets"
27406
27601
  ])],
27407
27602
  ["AWS::ElasticLoadBalancingV2::Listener", /* @__PURE__ */ new Set([
27408
27603
  "LoadBalancerArn",
@@ -27458,6 +27653,7 @@ var ELBv2Provider = class {
27458
27653
  try {
27459
27654
  const tags = this.extractTags(properties);
27460
27655
  const lbName = generateResourceNameWithFallback(properties["Name"], logicalId, { maxLength: 32 });
27656
+ const ipv4IpamPoolId = properties["Ipv4IpamPoolId"];
27461
27657
  const lb = (await this.getClient().send(new CreateLoadBalancerCommand({
27462
27658
  Name: lbName,
27463
27659
  Subnets: properties["Subnets"],
@@ -27466,6 +27662,8 @@ var ELBv2Provider = class {
27466
27662
  Scheme: properties["Scheme"],
27467
27663
  Type: properties["Type"],
27468
27664
  IpAddressType: properties["IpAddressType"],
27665
+ EnablePrefixForIpv6SourceNat: properties["EnablePrefixForIpv6SourceNat"],
27666
+ ...ipv4IpamPoolId !== void 0 && { IpamPools: { Ipv4IpamPoolId: ipv4IpamPoolId } },
27469
27667
  ...tags.length > 0 && { Tags: tags }
27470
27668
  }))).LoadBalancers?.[0];
27471
27669
  if (!lb || !lb.LoadBalancerArn) throw new Error("CreateLoadBalancer did not return LoadBalancer ARN");
@@ -27482,17 +27680,32 @@ var ELBv2Provider = class {
27482
27680
  }, { LoadBalancerArns: [lbArn] });
27483
27681
  this.logger.debug(`LoadBalancer ${logicalId} is active`);
27484
27682
  } else this.logger.debug(`LoadBalancer ${logicalId} created (skipping active-state wait per --no-wait)`);
27485
- const lbAttributes = properties["LoadBalancerAttributes"];
27486
- if (lbAttributes && lbAttributes.length > 0) {
27683
+ const lbAttributes = this.normalizeAttributes(properties["LoadBalancerAttributes"]);
27684
+ if (lbAttributes.length > 0) {
27487
27685
  await this.getClient().send(new ModifyLoadBalancerAttributesCommand({
27488
27686
  LoadBalancerArn: lbArn,
27489
- Attributes: lbAttributes.map((attr) => ({
27490
- Key: attr.Key,
27491
- Value: attr.Value
27492
- }))
27687
+ Attributes: lbAttributes
27493
27688
  }));
27494
27689
  this.logger.debug(`Applied ${lbAttributes.length} LoadBalancer attributes for ${logicalId}`);
27495
27690
  }
27691
+ const enforcePrivateLink = properties["EnforceSecurityGroupInboundRulesOnPrivateLinkTraffic"];
27692
+ if (enforcePrivateLink !== void 0) {
27693
+ await this.getClient().send(new SetSecurityGroupsCommand({
27694
+ LoadBalancerArn: lbArn,
27695
+ SecurityGroups: properties["SecurityGroups"] ?? [],
27696
+ EnforceSecurityGroupInboundRulesOnPrivateLinkTraffic: enforcePrivateLink
27697
+ }));
27698
+ this.logger.debug(`Applied EnforceSecurityGroupInboundRulesOnPrivateLinkTraffic=${enforcePrivateLink} for ${logicalId}`);
27699
+ }
27700
+ const minCapacity = properties["MinimumLoadBalancerCapacity"];
27701
+ if (minCapacity?.CapacityUnits !== void 0) {
27702
+ await this.getClient().send(new ModifyCapacityReservationCommand({
27703
+ LoadBalancerArn: lbArn,
27704
+ MinimumLoadBalancerCapacity: { CapacityUnits: Number(minCapacity.CapacityUnits) }
27705
+ }));
27706
+ this.logger.debug(`Requested capacity reservation of ${minCapacity.CapacityUnits} LCU for ${logicalId}`);
27707
+ if (isTruthyCfnBoolean(properties["EnableCapacityReservationProvisionStabilize"]) && process.env["CDKD_NO_WAIT"] !== "true") await this.waitForCapacityReservationProvisioned(lbArn, logicalId);
27708
+ }
27496
27709
  } catch (innerError) {
27497
27710
  try {
27498
27711
  await this.getClient().send(new DeleteLoadBalancerCommand({ LoadBalancerArn: lbArn }));
@@ -27524,7 +27737,12 @@ var ELBv2Provider = class {
27524
27737
  "SubnetMappings",
27525
27738
  "SecurityGroups",
27526
27739
  "IpAddressType",
27527
- "Tags"
27740
+ "Tags",
27741
+ "EnablePrefixForIpv6SourceNat",
27742
+ "EnforceSecurityGroupInboundRulesOnPrivateLinkTraffic",
27743
+ "Ipv4IpamPoolId",
27744
+ "MinimumLoadBalancerCapacity",
27745
+ "EnableCapacityReservationProvisionStabilize"
27528
27746
  ]);
27529
27747
  const stripHandled = (p) => {
27530
27748
  const out = {};
@@ -27532,19 +27750,7 @@ var ELBv2Provider = class {
27532
27750
  return out;
27533
27751
  };
27534
27752
  if (JSON.stringify(stripHandled(properties)) !== JSON.stringify(stripHandled(previousProperties))) throw new ResourceUpdateNotSupportedError("AWS::ElasticLoadBalancingV2::LoadBalancer", logicalId, "ELBv2 LoadBalancer Name / Type / Scheme are immutable on AWS — none of the ELBv2 Modify* / Set* APIs accept these fields; they are fixed at creation. cdkd handles LoadBalancerAttributes / Subnets / SubnetMappings / SecurityGroups / IpAddressType / Tags in-place; for Name / Type / Scheme re-deploy with cdkd deploy --replace, or destroy + redeploy the stack.");
27535
- const newAttrs = properties["LoadBalancerAttributes"] ?? [];
27536
- const oldAttrs = previousProperties["LoadBalancerAttributes"] ?? [];
27537
- const newAttrMap = new Map(newAttrs.map((a) => [a.Key, a.Value]));
27538
- const oldAttrMap = new Map(oldAttrs.map((a) => [a.Key, a.Value]));
27539
- const submittedAttrs = [];
27540
- for (const [k, v] of newAttrMap) if (oldAttrMap.get(k) !== v) submittedAttrs.push({
27541
- Key: k,
27542
- Value: v
27543
- });
27544
- for (const [k] of oldAttrMap) if (!newAttrMap.has(k)) submittedAttrs.push({
27545
- Key: k,
27546
- Value: ""
27547
- });
27753
+ const submittedAttrs = this.diffAttributes(this.normalizeAttributes(properties["LoadBalancerAttributes"]), this.normalizeAttributes(previousProperties["LoadBalancerAttributes"]));
27548
27754
  if (submittedAttrs.length > 0) {
27549
27755
  await this.getClient().send(new ModifyLoadBalancerAttributesCommand({
27550
27756
  LoadBalancerArn: physicalId,
@@ -27556,21 +27762,28 @@ var ELBv2Provider = class {
27556
27762
  const oldSubnets = previousProperties["Subnets"];
27557
27763
  const newMappings = properties["SubnetMappings"];
27558
27764
  const oldMappings = previousProperties["SubnetMappings"];
27765
+ const newIpv6SourceNat = properties["EnablePrefixForIpv6SourceNat"];
27766
+ const oldIpv6SourceNat = previousProperties["EnablePrefixForIpv6SourceNat"];
27559
27767
  const subnetsChanged = JSON.stringify(newSubnets) !== JSON.stringify(oldSubnets);
27560
27768
  const mappingsChanged = JSON.stringify(newMappings) !== JSON.stringify(oldMappings);
27561
- if (subnetsChanged || mappingsChanged) {
27769
+ if (subnetsChanged || mappingsChanged || newIpv6SourceNat !== void 0 && newIpv6SourceNat !== oldIpv6SourceNat) {
27562
27770
  await this.getClient().send(new SetSubnetsCommand({
27563
27771
  LoadBalancerArn: physicalId,
27564
- ...newMappings && newMappings.length > 0 ? { SubnetMappings: newMappings } : { Subnets: newSubnets }
27772
+ ...newMappings && newMappings.length > 0 ? { SubnetMappings: newMappings } : { Subnets: newSubnets },
27773
+ ...newIpv6SourceNat !== void 0 && { EnablePrefixForIpv6SourceNat: newIpv6SourceNat }
27565
27774
  }));
27566
27775
  this.logger.debug(`Updated Subnets / SubnetMappings for ${logicalId}`);
27567
27776
  }
27568
27777
  const newSGs = properties["SecurityGroups"];
27569
27778
  const oldSGs = previousProperties["SecurityGroups"];
27570
- if (JSON.stringify(newSGs) !== JSON.stringify(oldSGs)) {
27779
+ const newEnforce = properties["EnforceSecurityGroupInboundRulesOnPrivateLinkTraffic"];
27780
+ const oldEnforce = previousProperties["EnforceSecurityGroupInboundRulesOnPrivateLinkTraffic"];
27781
+ const enforceChanged = newEnforce !== void 0 && newEnforce !== oldEnforce;
27782
+ if (JSON.stringify(newSGs) !== JSON.stringify(oldSGs) || enforceChanged) {
27571
27783
  await this.getClient().send(new SetSecurityGroupsCommand({
27572
27784
  LoadBalancerArn: physicalId,
27573
- SecurityGroups: newSGs ?? []
27785
+ SecurityGroups: newSGs ?? [],
27786
+ ...newEnforce !== void 0 && { EnforceSecurityGroupInboundRulesOnPrivateLinkTraffic: newEnforce }
27574
27787
  }));
27575
27788
  this.logger.debug(`Updated SecurityGroups for ${logicalId}`);
27576
27789
  }
@@ -27583,12 +27796,64 @@ var ELBv2Provider = class {
27583
27796
  }));
27584
27797
  this.logger.debug(`Updated IpAddressType for ${logicalId}`);
27585
27798
  }
27799
+ const newIpamPool = properties["Ipv4IpamPoolId"];
27800
+ if (newIpamPool !== previousProperties["Ipv4IpamPoolId"]) {
27801
+ await this.getClient().send(new ModifyIpPoolsCommand({
27802
+ LoadBalancerArn: physicalId,
27803
+ ...newIpamPool !== void 0 ? { IpamPools: { Ipv4IpamPoolId: newIpamPool } } : { RemoveIpamPools: ["ipv4"] }
27804
+ }));
27805
+ this.logger.debug(newIpamPool !== void 0 ? `Updated Ipv4IpamPoolId for ${logicalId}` : `Removed IPAM pool association for ${logicalId}`);
27806
+ }
27807
+ const newCapacity = properties["MinimumLoadBalancerCapacity"];
27808
+ const oldCapacity = previousProperties["MinimumLoadBalancerCapacity"];
27809
+ const newCapacityUnits = newCapacity?.CapacityUnits !== void 0 ? Number(newCapacity.CapacityUnits) : void 0;
27810
+ if (newCapacityUnits !== (oldCapacity?.CapacityUnits !== void 0 ? Number(oldCapacity.CapacityUnits) : void 0)) {
27811
+ await this.getClient().send(new ModifyCapacityReservationCommand({
27812
+ LoadBalancerArn: physicalId,
27813
+ ...newCapacityUnits !== void 0 ? { MinimumLoadBalancerCapacity: { CapacityUnits: newCapacityUnits } } : { ResetCapacityReservation: true }
27814
+ }));
27815
+ this.logger.debug(newCapacityUnits !== void 0 ? `Requested capacity reservation of ${newCapacityUnits} LCU for ${logicalId}` : `Reset capacity reservation for ${logicalId}`);
27816
+ if (newCapacityUnits !== void 0 && isTruthyCfnBoolean(properties["EnableCapacityReservationProvisionStabilize"]) && process.env["CDKD_NO_WAIT"] !== "true") await this.waitForCapacityReservationProvisioned(physicalId, logicalId);
27817
+ }
27586
27818
  await this.applyTagDiff(physicalId, previousProperties["Tags"], properties["Tags"]);
27587
27819
  return {
27588
27820
  physicalId,
27589
27821
  wasReplaced: false
27590
27822
  };
27591
27823
  }
27824
+ /**
27825
+ * Poll DescribeCapacityReservation until every zonal state reports
27826
+ * `provisioned` — the semantics of the CFn-only
27827
+ * `EnableCapacityReservationProvisionStabilize` flag (no SDK member; it
27828
+ * opts the deploy into waiting for the reservation instead of returning
27829
+ * while zones are still `pending`). Bounded at ~10 min / 15s interval;
27830
+ * a timeout WARNS and continues (the reservation keeps provisioning
27831
+ * asynchronously and the ModifyCapacityReservation was accepted), while a
27832
+ * `failed` zonal state throws — that reservation will never provision.
27833
+ */
27834
+ async waitForCapacityReservationProvisioned(lbArn, logicalId) {
27835
+ const maxAttempts = 40;
27836
+ const intervalMs = 15e3;
27837
+ this.logger.debug(`Waiting for capacity reservation of ${logicalId} to provision...`);
27838
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
27839
+ let zones;
27840
+ try {
27841
+ zones = (await this.getClient().send(new DescribeCapacityReservationCommand({ LoadBalancerArn: lbArn }))).CapacityReservationState ?? [];
27842
+ } catch (probeError) {
27843
+ this.logger.debug(`DescribeCapacityReservation for ${logicalId} failed transiently (attempt ${attempt + 1}/${maxAttempts}): ${probeError instanceof Error ? probeError.message : String(probeError)}`);
27844
+ await capacityReservationDelays.sleep(intervalMs);
27845
+ continue;
27846
+ }
27847
+ const failed = zones.find((z) => z.State?.Code === "failed");
27848
+ if (failed) throw new Error(`Capacity reservation for ${logicalId} failed in ${failed.AvailabilityZone ?? "an availability zone"}: ${failed.State?.Reason ?? "no reason reported"}`);
27849
+ if (zones.length === 0 || zones.every((z) => z.State?.Code === "provisioned")) {
27850
+ this.logger.debug(`Capacity reservation for ${logicalId} is provisioned`);
27851
+ return;
27852
+ }
27853
+ await capacityReservationDelays.sleep(intervalMs);
27854
+ }
27855
+ this.logger.warn(`Capacity reservation for ${logicalId} did not reach 'provisioned' within ${maxAttempts * intervalMs / 6e4} minutes; continuing (provisioning completes asynchronously)`);
27856
+ }
27592
27857
  async deleteLoadBalancer(logicalId, physicalId, resourceType, context) {
27593
27858
  this.logger.debug(`Deleting LoadBalancer ${logicalId}: ${physicalId}`);
27594
27859
  if (context?.removeProtection === true) try {
@@ -27637,16 +27902,45 @@ var ELBv2Provider = class {
27637
27902
  HealthCheckTimeoutSeconds: properties["HealthCheckTimeoutSeconds"] !== void 0 ? Number(properties["HealthCheckTimeoutSeconds"]) : void 0,
27638
27903
  HealthyThresholdCount: properties["HealthyThresholdCount"] !== void 0 ? Number(properties["HealthyThresholdCount"]) : void 0,
27639
27904
  UnhealthyThresholdCount: properties["UnhealthyThresholdCount"] !== void 0 ? Number(properties["UnhealthyThresholdCount"]) : void 0,
27905
+ IpAddressType: properties["IpAddressType"],
27906
+ TargetControlPort: properties["TargetControlPort"] !== void 0 ? Number(properties["TargetControlPort"]) : void 0,
27640
27907
  ...matcher && { Matcher: matcher },
27641
27908
  ...tags.length > 0 && { Tags: tags }
27642
27909
  }))).TargetGroups?.[0];
27643
27910
  if (!tg || !tg.TargetGroupArn) throw new Error("CreateTargetGroup did not return TargetGroup ARN");
27644
- this.logger.debug(`Successfully created TargetGroup ${logicalId}: ${tg.TargetGroupArn}`);
27911
+ const tgArn = tg.TargetGroupArn;
27912
+ this.logger.debug(`Successfully created TargetGroup ${logicalId}: ${tgArn}`);
27913
+ try {
27914
+ const tgAttributes = this.normalizeAttributes(properties["TargetGroupAttributes"]);
27915
+ if (tgAttributes.length > 0) {
27916
+ await this.getClient().send(new ModifyTargetGroupAttributesCommand({
27917
+ TargetGroupArn: tgArn,
27918
+ Attributes: tgAttributes
27919
+ }));
27920
+ this.logger.debug(`Applied ${tgAttributes.length} TargetGroup attribute(s) for ${logicalId}`);
27921
+ }
27922
+ const targets = this.convertTargets(properties["Targets"]);
27923
+ if (targets.length > 0) {
27924
+ await this.getClient().send(new RegisterTargetsCommand({
27925
+ TargetGroupArn: tgArn,
27926
+ Targets: targets
27927
+ }));
27928
+ this.logger.debug(`Registered ${targets.length} target(s) for ${logicalId}`);
27929
+ }
27930
+ } catch (innerError) {
27931
+ try {
27932
+ await this.getClient().send(new DeleteTargetGroupCommand({ TargetGroupArn: tgArn }));
27933
+ this.logger.debug(`Cleaned up partially-created TargetGroup ${logicalId} (${tgArn}) after wiring failure`);
27934
+ } catch (cleanupError) {
27935
+ this.logger.warn(`Failed to clean up partially-created TargetGroup ${logicalId} (${tgArn}): ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}. Manual deletion may be required before the next deploy: aws elbv2 delete-target-group --target-group-arn ${tgArn}`);
27936
+ }
27937
+ throw innerError;
27938
+ }
27645
27939
  return {
27646
- physicalId: tg.TargetGroupArn,
27940
+ physicalId: tgArn,
27647
27941
  attributes: {
27648
- TargetGroupArn: tg.TargetGroupArn,
27649
- TargetGroupFullName: tg.TargetGroupArn?.split(":").pop()?.replace("targetgroup/", ""),
27942
+ TargetGroupArn: tgArn,
27943
+ TargetGroupFullName: tgArn.split(":").pop()?.replace("targetgroup/", ""),
27650
27944
  TargetGroupName: tg.TargetGroupName
27651
27945
  }
27652
27946
  };
@@ -27657,6 +27951,13 @@ var ELBv2Provider = class {
27657
27951
  }
27658
27952
  async updateTargetGroup(logicalId, physicalId, resourceType, properties, previousProperties) {
27659
27953
  this.logger.debug(`Updating TargetGroup ${logicalId}: ${physicalId}`);
27954
+ const immutableChanged = (key, coerce) => {
27955
+ const next = properties[key];
27956
+ const prev = previousProperties[key];
27957
+ if (coerce) return (next !== void 0 ? Number(next) : void 0) !== (prev !== void 0 ? Number(prev) : void 0);
27958
+ return JSON.stringify(next) !== JSON.stringify(prev);
27959
+ };
27960
+ for (const [immutableKey, coerce] of [["IpAddressType", false], ["TargetControlPort", true]]) if (immutableChanged(immutableKey, coerce)) throw new ResourceUpdateNotSupportedError(resourceType, logicalId, `ELBv2 TargetGroup ${immutableKey} is immutable on AWS — no Modify* API accepts it; it is fixed at creation. Re-deploy with cdkd deploy --replace, or destroy + redeploy the stack.`);
27660
27961
  try {
27661
27962
  const rawMatcher = properties["Matcher"];
27662
27963
  const matcher = rawMatcher && (rawMatcher.HttpCode !== void 0 || rawMatcher.GrpcCode !== void 0) ? rawMatcher : void 0;
@@ -27673,6 +27974,43 @@ var ELBv2Provider = class {
27673
27974
  UnhealthyThresholdCount: properties["UnhealthyThresholdCount"] !== void 0 ? Number(properties["UnhealthyThresholdCount"]) : void 0,
27674
27975
  ...matcher && { Matcher: matcher }
27675
27976
  }));
27977
+ const tgAttrDiff = this.diffAttributes(this.normalizeAttributes(properties["TargetGroupAttributes"]), this.normalizeAttributes(previousProperties["TargetGroupAttributes"]), (key) => {
27978
+ const fallback = TARGET_GROUP_ATTRIBUTE_DEFAULTS[key];
27979
+ if (fallback === void 0) this.logger.warn(`TargetGroup attribute ${key} was removed from the template but has no documented default cdkd can reset it to — the live value is retained. Set the attribute explicitly to change it.`);
27980
+ return fallback;
27981
+ });
27982
+ if (tgAttrDiff.length > 0) {
27983
+ await this.getClient().send(new ModifyTargetGroupAttributesCommand({
27984
+ TargetGroupArn: physicalId,
27985
+ Attributes: tgAttrDiff
27986
+ }));
27987
+ this.logger.debug(`Applied ${tgAttrDiff.length} TargetGroupAttributes change(s) for ${logicalId}`);
27988
+ }
27989
+ const newTargets = this.convertTargets(properties["Targets"]);
27990
+ const oldTargets = this.convertTargets(previousProperties["Targets"]);
27991
+ const targetKey = (t) => JSON.stringify([
27992
+ t.Id,
27993
+ t.Port ?? null,
27994
+ t.AvailabilityZone ?? null
27995
+ ]);
27996
+ const oldTargetKeys = new Set(oldTargets.map(targetKey));
27997
+ const newTargetKeys = new Set(newTargets.map(targetKey));
27998
+ const targetsToRegister = newTargets.filter((t) => !oldTargetKeys.has(targetKey(t)));
27999
+ const targetsToDeregister = oldTargets.filter((t) => !newTargetKeys.has(targetKey(t)));
28000
+ if (targetsToRegister.length > 0) {
28001
+ await this.getClient().send(new RegisterTargetsCommand({
28002
+ TargetGroupArn: physicalId,
28003
+ Targets: targetsToRegister
28004
+ }));
28005
+ this.logger.debug(`Registered ${targetsToRegister.length} target(s) for ${logicalId}`);
28006
+ }
28007
+ if (targetsToDeregister.length > 0) {
28008
+ await this.getClient().send(new DeregisterTargetsCommand({
28009
+ TargetGroupArn: physicalId,
28010
+ Targets: targetsToDeregister
28011
+ }));
28012
+ this.logger.debug(`Deregistered ${targetsToDeregister.length} target(s) for ${logicalId}`);
28013
+ }
27676
28014
  const tg = (await this.getClient().send(new DescribeTargetGroupsCommand({ TargetGroupArns: [physicalId] }))).TargetGroups?.[0];
27677
28015
  await this.applyTagDiff(physicalId, previousProperties["Tags"], properties["Tags"]);
27678
28016
  this.logger.debug(`Successfully updated TargetGroup ${logicalId}`);
@@ -27771,19 +28109,7 @@ var ELBv2Provider = class {
27771
28109
  ...alpnPolicy && alpnPolicy.length > 0 && { AlpnPolicy: alpnPolicy },
27772
28110
  ...mutualAuth !== void 0 && { MutualAuthentication: mutualAuth }
27773
28111
  }));
27774
- const newAttrs = this.normalizeAttributes(properties["ListenerAttributes"]);
27775
- const oldAttrs = this.normalizeAttributes(previousProperties["ListenerAttributes"]);
27776
- const newAttrMap = new Map(newAttrs.map((a) => [a.Key, a.Value]));
27777
- const oldAttrMap = new Map(oldAttrs.map((a) => [a.Key, a.Value]));
27778
- const submittedAttrs = [];
27779
- for (const [k, v] of newAttrMap) if (oldAttrMap.get(k) !== v) submittedAttrs.push({
27780
- Key: k,
27781
- Value: v
27782
- });
27783
- for (const [k] of oldAttrMap) if (!newAttrMap.has(k)) submittedAttrs.push({
27784
- Key: k,
27785
- Value: ""
27786
- });
28112
+ const submittedAttrs = this.diffAttributes(this.normalizeAttributes(properties["ListenerAttributes"]), this.normalizeAttributes(previousProperties["ListenerAttributes"]));
27787
28113
  if (submittedAttrs.length > 0) {
27788
28114
  await withRetry(() => this.getClient().send(new ModifyListenerAttributesCommand({
27789
28115
  ListenerArn: physicalId,
@@ -27859,6 +28185,58 @@ var ELBv2Provider = class {
27859
28185
  return out;
27860
28186
  }
27861
28187
  /**
28188
+ * Key-diff two normalized `{Key, Value}` attribute lists into the payload
28189
+ * for a Modify*Attributes call: changed values from `newAttrs` win, and
28190
+ * keys present only in `oldAttrs` are pushed back through `removalValue` —
28191
+ * by default the empty string, which ModifyLoadBalancerAttributes /
28192
+ * ModifyListenerAttributes accept as "clear the override". A resolver
28193
+ * returning `undefined` SKIPS the entry (the resolver owns any warn).
28194
+ * An empty return means nothing changed and the call should be skipped.
28195
+ * Shared by the LoadBalancer / TargetGroup / Listener update paths.
28196
+ */
28197
+ diffAttributes(newAttrs, oldAttrs, removalValue = () => "") {
28198
+ const newAttrMap = new Map(newAttrs.map((a) => [a.Key, a.Value]));
28199
+ const oldAttrMap = new Map(oldAttrs.map((a) => [a.Key, a.Value]));
28200
+ const submitted = [];
28201
+ for (const [k, v] of newAttrMap) if (oldAttrMap.get(k) !== v) submitted.push({
28202
+ Key: k,
28203
+ Value: v
28204
+ });
28205
+ for (const [k] of oldAttrMap) if (!newAttrMap.has(k)) {
28206
+ const value = removalValue(k);
28207
+ if (value !== void 0) submitted.push({
28208
+ Key: k,
28209
+ Value: value
28210
+ });
28211
+ }
28212
+ return submitted;
28213
+ }
28214
+ /**
28215
+ * Normalize a CFn `Targets` value (an array of `{ Id, Port?,
28216
+ * AvailabilityZone? }` objects) into the SDK's `TargetDescription[]` shape.
28217
+ * Entries missing an `Id` are dropped (Id is the only required member);
28218
+ * `Port` is numeric-coerced (CFn templates may carry it as a string).
28219
+ * Returns `[]` for an absent / non-array value so callers can branch on
28220
+ * `.length`.
28221
+ */
28222
+ convertTargets(raw) {
28223
+ if (!Array.isArray(raw)) return [];
28224
+ const out = [];
28225
+ for (const entry of raw) {
28226
+ if (entry === null || typeof entry !== "object" || typeof entry.Id !== "string" || entry.Id.length === 0) {
28227
+ this.logger.warn(`Dropping malformed TargetGroup Targets entry (missing string Id): ${JSON.stringify(entry)}`);
28228
+ continue;
28229
+ }
28230
+ const e = entry;
28231
+ out.push({
28232
+ Id: e.Id,
28233
+ ...e.Port !== void 0 && { Port: Number(e.Port) },
28234
+ ...e.AvailabilityZone !== void 0 && { AvailabilityZone: e.AvailabilityZone }
28235
+ });
28236
+ }
28237
+ return out;
28238
+ }
28239
+ /**
27862
28240
  * Apply a diff between old and new CFn-shape Tags arrays via ELBv2's
27863
28241
  * `AddTags` / `RemoveTags` APIs. Both accept `ResourceArns: [arn]`
27864
28242
  * (single ARN), `Tags: [{Key, Value}]` for AddTags, and
@@ -27956,6 +28334,15 @@ var ELBv2Provider = class {
27956
28334
  if (lb.Scheme !== void 0) result["Scheme"] = lb.Scheme;
27957
28335
  if (lb.Type !== void 0) result["Type"] = lb.Type;
27958
28336
  if (lb.IpAddressType !== void 0) result["IpAddressType"] = lb.IpAddressType;
28337
+ if (lb.EnablePrefixForIpv6SourceNat !== void 0) result["EnablePrefixForIpv6SourceNat"] = lb.EnablePrefixForIpv6SourceNat;
28338
+ if (lb.EnforceSecurityGroupInboundRulesOnPrivateLinkTraffic !== void 0) result["EnforceSecurityGroupInboundRulesOnPrivateLinkTraffic"] = lb.EnforceSecurityGroupInboundRulesOnPrivateLinkTraffic;
28339
+ if (lb.IpamPools?.Ipv4IpamPoolId !== void 0) result["Ipv4IpamPoolId"] = lb.IpamPools.Ipv4IpamPoolId;
28340
+ try {
28341
+ const units = (await this.getClient().send(new DescribeCapacityReservationCommand({ LoadBalancerArn: physicalId }))).MinimumLoadBalancerCapacity?.CapacityUnits;
28342
+ if (units !== void 0 && units > 0) result["MinimumLoadBalancerCapacity"] = { CapacityUnits: units };
28343
+ } catch (err) {
28344
+ if (this.isNotFoundError(err)) return void 0;
28345
+ }
27959
28346
  try {
27960
28347
  result["LoadBalancerAttributes"] = ((await this.getClient().send(new DescribeLoadBalancerAttributesCommand({ LoadBalancerArn: physicalId }))).Attributes ?? []).filter((a) => typeof a.Key === "string" && typeof a.Value === "string").map((a) => ({
27961
28348
  Key: a.Key,
@@ -27991,13 +28378,30 @@ var ELBv2Provider = class {
27991
28378
  if (tg.HealthCheckTimeoutSeconds !== void 0) result["HealthCheckTimeoutSeconds"] = tg.HealthCheckTimeoutSeconds;
27992
28379
  if (tg.HealthyThresholdCount !== void 0) result["HealthyThresholdCount"] = tg.HealthyThresholdCount;
27993
28380
  if (tg.UnhealthyThresholdCount !== void 0) result["UnhealthyThresholdCount"] = tg.UnhealthyThresholdCount;
28381
+ if (tg.IpAddressType !== void 0) result["IpAddressType"] = tg.IpAddressType;
28382
+ if (tg.TargetControlPort !== void 0) result["TargetControlPort"] = tg.TargetControlPort;
27994
28383
  const matcher = {};
27995
28384
  if (tg.Matcher?.HttpCode !== void 0) matcher["HttpCode"] = tg.Matcher.HttpCode;
27996
28385
  if (tg.Matcher?.GrpcCode !== void 0) matcher["GrpcCode"] = tg.Matcher.GrpcCode;
27997
28386
  result["Matcher"] = matcher;
28387
+ try {
28388
+ result["TargetGroupAttributes"] = ((await this.getClient().send(new DescribeTargetGroupAttributesCommand({ TargetGroupArn: physicalId }))).Attributes ?? []).filter((a) => typeof a.Key === "string" && typeof a.Value === "string").map((a) => ({
28389
+ Key: a.Key,
28390
+ Value: a.Value
28391
+ })).sort((a, b) => a.Key.localeCompare(b.Key));
28392
+ } catch (err) {
28393
+ if (this.isNotFoundError(err)) return void 0;
28394
+ }
27998
28395
  await this.attachTags(result, physicalId);
27999
28396
  return result;
28000
28397
  }
28398
+ getDriftUnknownPaths(resourceType) {
28399
+ switch (resourceType) {
28400
+ case "AWS::ElasticLoadBalancingV2::LoadBalancer": return ["EnableCapacityReservationProvisionStabilize"];
28401
+ case "AWS::ElasticLoadBalancingV2::TargetGroup": return ["Targets"];
28402
+ default: return [];
28403
+ }
28404
+ }
28001
28405
  async readListener(physicalId) {
28002
28406
  let listener;
28003
28407
  try {
@@ -68757,7 +69161,7 @@ function createMigrateCommand() {
68757
69161
  */
68758
69162
  function buildProgram() {
68759
69163
  const program = new Command();
68760
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.280.10");
69164
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.280.12");
68761
69165
  program.hook("preAction", (_thisCommand, actionCommand) => {
68762
69166
  const { profile } = actionCommand.optsWithGlobals();
68763
69167
  if (profile !== void 0) process.env["AWS_PROFILE"] = profile;