@go-to-k/cdkd 0.219.2 → 0.219.3

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,6 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import { a as setAwsClients, i as resetAwsClients, r as getAwsClients, t as AwsClients } from "./aws-clients-DWUnLza1.js";
3
- import { $ as CFN_TEMPLATE_URL_LIMIT, A as DagBuilder, B as getDockerCmd, C as CloudControlProvider, D as IntrinsicFunctionResolver, Dt as withErrorHandling, E as isTerminationProtectionPropagationError, Et as normalizeAwsError, F as AssetPublisher, Ft as generateResourceName, G as getLegacyStateBucketName, H as runDockerStreaming, I as stringifyValue, It as generateResourceNameWithFallback, J as resolveSkipPrefix, K as resolveApp, L as WorkGraph, Lt as withSkipPrefix, M as LockManager, Mt as getLiveRenderer, N as S3StateBackend, Nt as PATTERN_B_NAME_PROPERTIES, O as applyRoleArnIfSet, P as shouldRetainResource, Pt as PATTERN_B_RESOURCE_TYPES, Q as CFN_TEMPLATE_BODY_LIMIT, R as buildDockerImage, Rt as withStackName, S as findActionableSilentDrops, T as disableInstanceApiTermination, U as Synthesizer, V as runDockerForeground, W as getDefaultStateBucketName, X as resolveStateBucketWithDefaultAndSource, Y as resolveStateBucketWithDefault, Z as warnDeprecatedNoPrefixCliFlag, _ as CDK_PATH_TAG, _t as ProvisioningError, a as withRetry, at as resolveBucketRegion, b as resolveExplicitPhysicalId, bt as StackHasActiveImportsError, c as formatResourceLine, d as gray, dt as LocalMigrateError, et as MIGRATE_TMP_PREFIX, f as green, ft as LocalStartServiceError, g as collectInlinePolicyNamesManagedBySiblings, gt as PartialFailureError, h as IAMRoleProvider, ht as NestedStackChildDirectDestroyError, i as withResourceDeadline, j as TemplateParser, jt as runStackBuffered, k as DiffCalculator, kt as getLogger, l as bold, m as yellow, mt as MissingCdkCliError, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nt as uploadCfnTemplate, o as isRetryableTransientError, p as red, q as resolveCaptureObservedState, r as DeployEngine, rt as AssemblyReader, s as IMPLICIT_DELETE_DEPENDENCIES, st as CdkdError, t as DEFAULT_RESOURCE_TIMEOUT_MS, tt as findLargeInlineResources, u as cyan, ut as LocalInvokeBuildError$1, v as matchesCdkPath, vt as ResourceTimeoutError, w as assertRegionMatch, x as ProviderRegistry, xt as StackTerminationProtectionError, y as normalizeAwsTagsToCfn, yt as ResourceUpdateNotSupportedError, z as formatDockerLoginError } from "./deploy-engine-B6CuzOHi.js";
2
+ import { A as withErrorHandling, B as generateResourceNameWithFallback, C as StackHasActiveImportsError, F as getLiveRenderer, H as withStackName, I as PATTERN_B_NAME_PROPERTIES, L as PATTERN_B_RESOURCE_TYPES, M as getLogger, P as runStackBuffered, S as ResourceUpdateNotSupportedError, V as withSkipPrefix, _ as MissingCdkCliError, a as assertRegionMatch, b as ProvisioningError, f as LocalInvokeBuildError$1, i as resolveExplicitPhysicalId, k as normalizeAwsError, l as CdkdError, m as LocalStartServiceError, n as matchesCdkPath, o as disableInstanceApiTermination, p as LocalMigrateError, r as normalizeAwsTagsToCfn, s as isTerminationProtectionPropagationError, t as CDK_PATH_TAG, v as NestedStackChildDirectDestroyError, w as StackTerminationProtectionError, x as ResourceTimeoutError, y as PartialFailureError, z as generateResourceName } from "./import-helpers-wLipXr5g.js";
3
+ import { a as setAwsClients, i as resetAwsClients, r as getAwsClients, t as AwsClients } from "./aws-clients-pjPwZz1r.js";
4
+ import { A as WorkGraph, B as resolveCaptureObservedState, C as DagBuilder, D as shouldRetainResource, E as S3StateBackend, F as runDockerStreaming, G as CFN_TEMPLATE_BODY_LIMIT, H as resolveStateBucketWithDefault, I as Synthesizer, J as findLargeInlineResources, K as CFN_TEMPLATE_URL_LIMIT, L as getDefaultStateBucketName, M as formatDockerLoginError, N as getDockerCmd, O as AssetPublisher, P as runDockerForeground, Q as resolveBucketRegion, R as getLegacyStateBucketName, S as DiffCalculator, T as LockManager, U as resolveStateBucketWithDefaultAndSource, V as resolveSkipPrefix, W as warnDeprecatedNoPrefixCliFlag, X as AssemblyReader, Y as uploadCfnTemplate, _ as ProviderRegistry, a as withRetry, b as IntrinsicFunctionResolver, c as formatResourceLine, d as gray, f as green, g as collectInlinePolicyNamesManagedBySiblings, h as IAMRoleProvider, i as withResourceDeadline, j as buildDockerImage, k as stringifyValue, l as bold, m as yellow, n as DEFAULT_RESOURCE_WARN_AFTER_MS, o as isRetryableTransientError, p as red, q as MIGRATE_TMP_PREFIX, r as DeployEngine, s as IMPLICIT_DELETE_DEPENDENCIES, t as DEFAULT_RESOURCE_TIMEOUT_MS, u as cyan, v as findActionableSilentDrops, w as TemplateParser, x as applyRoleArnIfSet, y as CloudControlProvider, z as resolveApp } from "./deploy-engine-Drw_e42s.js";
5
+ import { t as ASGProvider } from "./asg-provider-B_hrCxRx.js";
4
6
  import { AsyncLocalStorage } from "node:async_hooks";
5
7
  import { randomBytes, randomUUID } from "node:crypto";
6
8
  import { CopyObjectCommand, CreateBucketCommand, DeleteBucketAnalyticsConfigurationCommand, DeleteBucketCommand, DeleteBucketCorsCommand, DeleteBucketIntelligentTieringConfigurationCommand, DeleteBucketInventoryConfigurationCommand, DeleteBucketLifecycleCommand, DeleteBucketMetricsConfigurationCommand, DeleteBucketPolicyCommand, DeleteBucketReplicationCommand, DeleteBucketTaggingCommand, DeleteBucketWebsiteCommand, DeleteObjectsCommand, GetBucketAccelerateConfigurationCommand, GetBucketCorsCommand, GetBucketEncryptionCommand, GetBucketLifecycleConfigurationCommand, GetBucketLocationCommand, GetBucketLoggingCommand, GetBucketNotificationConfigurationCommand, GetBucketPolicyCommand, GetBucketReplicationCommand, GetBucketTaggingCommand, GetBucketVersioningCommand, GetBucketWebsiteCommand, GetObjectCommand, GetObjectLockConfigurationCommand, GetPublicAccessBlockCommand, HeadBucketCommand, ListBucketAnalyticsConfigurationsCommand, ListBucketIntelligentTieringConfigurationsCommand, ListBucketInventoryConfigurationsCommand, ListBucketMetricsConfigurationsCommand, ListBucketsCommand, ListDirectoryBucketsCommand, 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";
@@ -59,7 +61,6 @@ import { AddTagsCommand as AddTagsCommand$1, CloudTrailClient, CreateTrailComman
59
61
  import { BatchGetProjectsCommand, CodeBuildClient, CreateProjectCommand, DeleteProjectCommand, ListProjectsCommand, ResourceNotFoundException as ResourceNotFoundException$10, UpdateProjectCommand } from "@aws-sdk/client-codebuild";
60
62
  import { CreateVectorBucketCommand, DeleteIndexCommand, DeleteVectorBucketCommand, GetVectorBucketCommand, ListIndexesCommand, ListTagsForResourceCommand as ListTagsForResourceCommand$18, ListVectorBucketsCommand, S3VectorsClient } from "@aws-sdk/client-s3vectors";
61
63
  import { CreateNamespaceCommand, CreateTableBucketCommand, CreateTableCommand as CreateTableCommand$2, DeleteNamespaceCommand as DeleteNamespaceCommand$1, DeleteTableBucketCommand, DeleteTableCommand as DeleteTableCommand$2, GetTableBucketCommand, GetTableCommand as GetTableCommand$1, ListNamespacesCommand as ListNamespacesCommand$1, ListTableBucketsCommand, ListTablesCommand as ListTablesCommand$1, ListTagsForResourceCommand as ListTagsForResourceCommand$19, NotFoundException as NotFoundException$5, S3TablesClient, TagResourceCommand as TagResourceCommand$16, UntagResourceCommand as UntagResourceCommand$15 } from "@aws-sdk/client-s3tables";
62
- import { AttachLoadBalancerTargetGroupsCommand, AttachLoadBalancersCommand, AttachTrafficSourcesCommand, AutoScalingClient, CreateAutoScalingGroupCommand, CreateOrUpdateTagsCommand, DeleteAutoScalingGroupCommand, DeleteLifecycleHookCommand, DeleteNotificationConfigurationCommand, DeleteTagsCommand as DeleteTagsCommand$1, DescribeAutoScalingGroupsCommand, DescribeLifecycleHooksCommand, DescribeNotificationConfigurationsCommand, DescribeTrafficSourcesCommand, DetachLoadBalancerTargetGroupsCommand, DetachLoadBalancersCommand, DetachTrafficSourcesCommand, DisableMetricsCollectionCommand, EnableMetricsCollectionCommand, PutLifecycleHookCommand, PutNotificationConfigurationCommand, UpdateAutoScalingGroupCommand } from "@aws-sdk/client-auto-scaling";
63
64
  import { Document, Pair, Scalar, YAMLMap, YAMLSeq, parse as parse$1, stringify } from "yaml";
64
65
  import { createLocalStartAgentCoreCommand, createLocalStartCloudFrontCommand, createLocalStateProvider, getEmbedConfig, isCfnFlagPresent, listTargets, rejectExplicitCfnStackWithMultipleStacks, resolveCfnFallbackRegion, setEmbedConfig, substituteAgainstState, substituteAgainstStateAsync, substituteEnvVarsFromState, substituteEnvVarsFromStateAsync } from "cdk-local";
65
66
  import { A2A_CONTAINER_PORT, A2A_PATH, AGENTCORE_A2A_PROTOCOL, AGENTCORE_AGUI_PROTOCOL, AGENTCORE_MCP_PROTOCOL, ConnectionRegistry, EcsTaskResolutionError, HOST_GATEWAY_MIN_VERSION, LocalInvokeBuildError, MCP_CONTAINER_PORT, MCP_PATH, a2aInvokeOnce, addAlbSpecificOptions, addCommonEcsServiceOptions, addStartServiceSpecificOptions, albStrategy, architectureToPlatform, attachAuthorizers, attachStageContext, availableApiIdentifiers, bufferToBody, buildAgentCoreCodeImage, buildCognitoJwksUrl, buildConnectEvent, buildContainerImage, buildCorsConfigByApiId, buildCorsConfigFromCloudFrontChain, buildDisconnectEvent, buildJwksUrlFromIssuer, buildMessageEvent, buildMgmtEndpointEnvUrl, buildStageMap, classifySourceChange, createAuthorizerCache, createFileWatcher, createFileWatcher as createFileWatcher$1, createJwksCache, createWatchPredicates, defaultCredentialsLoader, derivePseudoParametersFromRegion, discoverRoutes, discoverWebSocketApis, downloadAndExtractS3Bundle, filterRoutesByApiIdentifier, groupRoutesByServer, handleConnectionsRequest, invokeAgentCore, invokeAgentCoreWs, materializeLayerFromArn, mcpInvokeOnce, parseConnectionsPath, parseSelectionExpressionPath, pickAgentCoreCandidateStack, pickAgentCoreCandidateStack as pickAgentCoreCandidateStack$1, probeHostGatewaySupport, readMtlsMaterialsFromDisk, resolveAgentCoreTarget, resolveEnvVars, resolveHostGatewayExtraHosts, resolveRuntimeCodeMountPath, resolveRuntimeFileExtension, resolveRuntimeImage, resolveSingleTarget, resolveWatchConfig, runEcsServiceEmulator, signAgentCoreInvocation, startApiServer, substituteImagePlaceholders, tryResolveImageFnJoin, verifyJwtViaDiscovery, waitForAgentCorePing } from "cdk-local/internal";
@@ -33664,788 +33665,6 @@ var ECRProvider = class {
33664
33665
  }
33665
33666
  };
33666
33667
 
33667
- //#endregion
33668
- //#region src/provisioning/providers/asg-provider.ts
33669
- /**
33670
- * AWS Auto Scaling Provider
33671
- *
33672
- * Implements resource provisioning for `AWS::AutoScaling::AutoScalingGroup`.
33673
- *
33674
- * WHY a dedicated SDK provider (instead of CC API fallback):
33675
- * 1. Owns the `--remove-protection` flip-off: ASG protection has three
33676
- * levels (`none` / `prevent-force-deletion` / `prevent-all-deletion`)
33677
- * and the destroy path needs to (a) clear it via `UpdateAutoScalingGroup
33678
- * ({DeletionProtection: 'none'})` before the actual delete and (b) set
33679
- * `ForceDelete: true` on `DeleteAutoScalingGroup` so AWS terminates any
33680
- * running instances as part of the delete (matches the user's "I know
33681
- * what I'm doing" intent).
33682
- * 2. Faster than CC API for the common case — direct Create/Update calls
33683
- * with no eventual-consistency polling beyond what `DescribeAutoScaling
33684
- * Groups` already provides.
33685
- *
33686
- * Update has narrower coverage than create: AWS does not support modifying
33687
- * `AutoScalingGroupName` (immutable) — that diff still surfaces
33688
- * `ResourceUpdateNotSupportedError` so the caller can `cdkd deploy
33689
- * --replace`. The mutable fields handled in-place via
33690
- * `UpdateAutoScalingGroup` include MinSize / MaxSize / DesiredCapacity /
33691
- * VPCZoneIdentifier / HealthCheckType / HealthCheckGracePeriod /
33692
- * DefaultCooldown / Cooldown / NewInstancesProtectedFromScaleIn /
33693
- * MaxInstanceLifetime / TerminationPolicies / CapacityRebalance /
33694
- * ServiceLinkedRoleARN / Context / DesiredCapacityType /
33695
- * DefaultInstanceWarmup / AvailabilityZones / AvailabilityZoneDistribution
33696
- * / AvailabilityZoneImpairmentPolicy / SkipZonalShiftValidation /
33697
- * CapacityReservationSpecification / InstanceMaintenancePolicy /
33698
- * DeletionProtection / MixedInstancesPolicy / LaunchTemplate.
33699
- *
33700
- * Sub-shape diffs are applied via dedicated AWS APIs before the main
33701
- * `UpdateAutoScalingGroup` call:
33702
- * - `Tags` → `CreateOrUpdateTags` / `DeleteTags` (#475)
33703
- * - `LoadBalancerNames` → `AttachLoadBalancers` /
33704
- * `DetachLoadBalancers` (#476)
33705
- * - `TargetGroupARNs` → `AttachLoadBalancerTargetGroups` /
33706
- * `DetachLoadBalancerTargetGroups` (#476)
33707
- * - `MetricsCollection` → `EnableMetricsCollection` /
33708
- * `DisableMetricsCollection`
33709
- * - `LifecycleHookSpecificationList` → per-entry `PutLifecycleHook` /
33710
- * `DeleteLifecycleHook`
33711
- * - `TrafficSources` → `AttachTrafficSources` /
33712
- * `DetachTrafficSources`
33713
- * - `NotificationConfigurations` → per-topic
33714
- * `PutNotificationConfiguration` /
33715
- * `DeleteNotificationConfiguration`
33716
- *
33717
- * Each helper is a no-op when the before/after JSON is identical.
33718
- */
33719
- var ASGProvider = class ASGProvider {
33720
- asgClient;
33721
- ec2Client;
33722
- providerRegion = process.env["AWS_REGION"];
33723
- logger = getLogger().child("ASGProvider");
33724
- handledProperties = new Map([["AWS::AutoScaling::AutoScalingGroup", new Set([
33725
- "AutoScalingGroupName",
33726
- "LaunchTemplate",
33727
- "MinSize",
33728
- "MaxSize",
33729
- "DesiredCapacity",
33730
- "VPCZoneIdentifier",
33731
- "AvailabilityZones",
33732
- "HealthCheckType",
33733
- "HealthCheckGracePeriod",
33734
- "Cooldown",
33735
- "DefaultCooldown",
33736
- "Tags",
33737
- "TerminationPolicies",
33738
- "NewInstancesProtectedFromScaleIn",
33739
- "CapacityRebalance",
33740
- "ServiceLinkedRoleARN",
33741
- "MaxInstanceLifetime",
33742
- "LoadBalancerNames",
33743
- "TargetGroupARNs",
33744
- "MetricsCollection",
33745
- "LifecycleHookSpecificationList",
33746
- "MixedInstancesPolicy",
33747
- "Context",
33748
- "DesiredCapacityType",
33749
- "DefaultInstanceWarmup",
33750
- "TrafficSources",
33751
- "NotificationConfigurations",
33752
- "AvailabilityZoneDistribution",
33753
- "AvailabilityZoneImpairmentPolicy",
33754
- "SkipZonalShiftValidation",
33755
- "CapacityReservationSpecification",
33756
- "InstanceMaintenancePolicy",
33757
- "DeletionProtection"
33758
- ])]]);
33759
- unhandledByDesign = new Map([["AWS::AutoScaling::AutoScalingGroup", new Map([["LaunchConfigurationName", "AWS Launch Configurations end-of-life 2024-10; use LaunchTemplate instead"], ["NotificationConfiguration", "Legacy singular form; use NotificationConfigurations (plural) which cdkd already wires"]])]]);
33760
- getClient() {
33761
- if (!this.asgClient) this.asgClient = new AutoScalingClient(this.providerRegion ? { region: this.providerRegion } : {});
33762
- return this.asgClient;
33763
- }
33764
- getEc2Client() {
33765
- if (!this.ec2Client) this.ec2Client = new EC2Client(this.providerRegion ? { region: this.providerRegion } : {});
33766
- return this.ec2Client;
33767
- }
33768
- async create(logicalId, resourceType, properties) {
33769
- if (resourceType !== "AWS::AutoScaling::AutoScalingGroup") throw new ProvisioningError(`Unsupported resource type: ${resourceType}`, resourceType, logicalId);
33770
- const groupName = properties["AutoScalingGroupName"] || generateResourceName(logicalId, { maxLength: 255 });
33771
- this.logger.debug(`Creating AutoScalingGroup ${logicalId}: ${groupName}`);
33772
- try {
33773
- const launchTemplate = this.buildLaunchTemplate(properties);
33774
- const tags = this.buildTags(groupName, properties);
33775
- const vpcZoneIdentifier = this.joinVpcZoneIdentifier(properties["VPCZoneIdentifier"]);
33776
- const minSize = properties["MinSize"] != null ? Number(properties["MinSize"]) : 0;
33777
- const maxSize = properties["MaxSize"] != null ? Number(properties["MaxSize"]) : minSize;
33778
- await this.getClient().send(new CreateAutoScalingGroupCommand({
33779
- AutoScalingGroupName: groupName,
33780
- MinSize: minSize,
33781
- MaxSize: maxSize,
33782
- ...properties["DesiredCapacity"] != null && { DesiredCapacity: Number(properties["DesiredCapacity"]) },
33783
- ...launchTemplate && { LaunchTemplate: launchTemplate },
33784
- ...properties["MixedInstancesPolicy"] !== void 0 && { MixedInstancesPolicy: properties["MixedInstancesPolicy"] },
33785
- ...vpcZoneIdentifier !== void 0 && { VPCZoneIdentifier: vpcZoneIdentifier },
33786
- ...properties["AvailabilityZones"] !== void 0 && { AvailabilityZones: properties["AvailabilityZones"] },
33787
- ...properties["HealthCheckType"] !== void 0 && { HealthCheckType: properties["HealthCheckType"] },
33788
- ...properties["HealthCheckGracePeriod"] != null && { HealthCheckGracePeriod: Number(properties["HealthCheckGracePeriod"]) },
33789
- ...properties["Cooldown"] != null && { DefaultCooldown: Number(properties["Cooldown"]) },
33790
- ...properties["DefaultCooldown"] != null && { DefaultCooldown: Number(properties["DefaultCooldown"]) },
33791
- ...properties["TerminationPolicies"] !== void 0 && { TerminationPolicies: properties["TerminationPolicies"] },
33792
- ...properties["NewInstancesProtectedFromScaleIn"] !== void 0 && { NewInstancesProtectedFromScaleIn: properties["NewInstancesProtectedFromScaleIn"] },
33793
- ...properties["CapacityRebalance"] !== void 0 && { CapacityRebalance: properties["CapacityRebalance"] },
33794
- ...properties["ServiceLinkedRoleARN"] !== void 0 && { ServiceLinkedRoleARN: properties["ServiceLinkedRoleARN"] },
33795
- ...properties["MaxInstanceLifetime"] != null && { MaxInstanceLifetime: Number(properties["MaxInstanceLifetime"]) },
33796
- ...properties["LoadBalancerNames"] !== void 0 && { LoadBalancerNames: properties["LoadBalancerNames"] },
33797
- ...properties["TargetGroupARNs"] !== void 0 && { TargetGroupARNs: properties["TargetGroupARNs"] },
33798
- ...properties["Context"] !== void 0 && { Context: properties["Context"] },
33799
- ...properties["DesiredCapacityType"] !== void 0 && { DesiredCapacityType: properties["DesiredCapacityType"] },
33800
- ...properties["DefaultInstanceWarmup"] != null && { DefaultInstanceWarmup: Number(properties["DefaultInstanceWarmup"]) },
33801
- ...properties["LifecycleHookSpecificationList"] !== void 0 && { LifecycleHookSpecificationList: properties["LifecycleHookSpecificationList"] },
33802
- ...properties["TrafficSources"] !== void 0 && { TrafficSources: properties["TrafficSources"] },
33803
- ...properties["AvailabilityZoneDistribution"] !== void 0 && { AvailabilityZoneDistribution: properties["AvailabilityZoneDistribution"] },
33804
- ...properties["AvailabilityZoneImpairmentPolicy"] !== void 0 && { AvailabilityZoneImpairmentPolicy: properties["AvailabilityZoneImpairmentPolicy"] },
33805
- ...properties["SkipZonalShiftValidation"] !== void 0 && { SkipZonalShiftValidation: properties["SkipZonalShiftValidation"] },
33806
- ...properties["CapacityReservationSpecification"] !== void 0 && { CapacityReservationSpecification: properties["CapacityReservationSpecification"] },
33807
- ...properties["InstanceMaintenancePolicy"] !== void 0 && { InstanceMaintenancePolicy: properties["InstanceMaintenancePolicy"] },
33808
- ...properties["DeletionProtection"] !== void 0 && { DeletionProtection: properties["DeletionProtection"] },
33809
- ...tags.length > 0 && { Tags: tags }
33810
- }));
33811
- this.logger.debug(`Successfully created AutoScalingGroup ${logicalId}: ${groupName}`);
33812
- const arn = await this.fetchArn(groupName);
33813
- const attributes = {};
33814
- if (arn) attributes["Arn"] = arn;
33815
- if (launchTemplate?.LaunchTemplateId) attributes["LaunchTemplateID"] = launchTemplate.LaunchTemplateId;
33816
- return {
33817
- physicalId: groupName,
33818
- attributes
33819
- };
33820
- } catch (error) {
33821
- const cause = error instanceof Error ? error : void 0;
33822
- throw new ProvisioningError(`Failed to create AutoScalingGroup ${logicalId}: ${error instanceof Error ? error.message : String(error)}`, resourceType, logicalId, groupName, cause);
33823
- }
33824
- }
33825
- async update(logicalId, physicalId, resourceType, properties, previousProperties) {
33826
- if (resourceType !== "AWS::AutoScaling::AutoScalingGroup") throw new ProvisioningError(`Unsupported resource type: ${resourceType}`, resourceType, logicalId, physicalId);
33827
- this.logger.debug(`Updating AutoScalingGroup ${logicalId}: ${physicalId}`);
33828
- const stringEq = (a, b) => JSON.stringify(a) === JSON.stringify(b);
33829
- if (!stringEq(properties["AutoScalingGroupName"], previousProperties["AutoScalingGroupName"])) throw new ResourceUpdateNotSupportedError(resourceType, logicalId, "AutoScalingGroupName is immutable on AWS — UpdateAutoScalingGroup does not accept a new name; the name is fixed at creation. Use cdkd deploy --replace to replace the group.");
33830
- try {
33831
- await this.applyTagsDiff(physicalId, properties["Tags"], previousProperties["Tags"]);
33832
- await this.applyLoadBalancerNamesDiff(physicalId, properties["LoadBalancerNames"], previousProperties["LoadBalancerNames"]);
33833
- await this.applyTargetGroupArnsDiff(physicalId, properties["TargetGroupARNs"], previousProperties["TargetGroupARNs"]);
33834
- await this.applyMetricsCollectionDiff(physicalId, properties["MetricsCollection"], previousProperties["MetricsCollection"]);
33835
- await this.applyLifecycleHooksDiff(physicalId, properties["LifecycleHookSpecificationList"], previousProperties["LifecycleHookSpecificationList"]);
33836
- await this.applyTrafficSourcesDiff(physicalId, properties["TrafficSources"], previousProperties["TrafficSources"]);
33837
- await this.applyNotificationConfigurationsDiff(physicalId, properties["NotificationConfigurations"], previousProperties["NotificationConfigurations"]);
33838
- const launchTemplate = this.buildLaunchTemplate(properties);
33839
- const vpcZoneIdentifier = this.joinVpcZoneIdentifier(properties["VPCZoneIdentifier"]);
33840
- await this.getClient().send(new UpdateAutoScalingGroupCommand({
33841
- AutoScalingGroupName: physicalId,
33842
- ...properties["MinSize"] != null && { MinSize: Number(properties["MinSize"]) },
33843
- ...properties["MaxSize"] != null && { MaxSize: Number(properties["MaxSize"]) },
33844
- ...properties["DesiredCapacity"] != null && { DesiredCapacity: Number(properties["DesiredCapacity"]) },
33845
- ...launchTemplate && { LaunchTemplate: launchTemplate },
33846
- ...properties["MixedInstancesPolicy"] !== void 0 && { MixedInstancesPolicy: properties["MixedInstancesPolicy"] },
33847
- ...vpcZoneIdentifier !== void 0 && { VPCZoneIdentifier: vpcZoneIdentifier },
33848
- ...properties["AvailabilityZones"] !== void 0 && { AvailabilityZones: properties["AvailabilityZones"] },
33849
- ...properties["HealthCheckType"] !== void 0 && { HealthCheckType: properties["HealthCheckType"] },
33850
- ...properties["HealthCheckGracePeriod"] != null && { HealthCheckGracePeriod: Number(properties["HealthCheckGracePeriod"]) },
33851
- ...properties["Cooldown"] != null && { DefaultCooldown: Number(properties["Cooldown"]) },
33852
- ...properties["DefaultCooldown"] != null && { DefaultCooldown: Number(properties["DefaultCooldown"]) },
33853
- ...properties["TerminationPolicies"] !== void 0 && { TerminationPolicies: properties["TerminationPolicies"] },
33854
- ...properties["NewInstancesProtectedFromScaleIn"] !== void 0 && { NewInstancesProtectedFromScaleIn: properties["NewInstancesProtectedFromScaleIn"] },
33855
- ...properties["CapacityRebalance"] !== void 0 && { CapacityRebalance: properties["CapacityRebalance"] },
33856
- ...properties["ServiceLinkedRoleARN"] !== void 0 && { ServiceLinkedRoleARN: properties["ServiceLinkedRoleARN"] },
33857
- ...properties["MaxInstanceLifetime"] != null && { MaxInstanceLifetime: Number(properties["MaxInstanceLifetime"]) },
33858
- ...properties["Context"] !== void 0 && { Context: properties["Context"] },
33859
- ...properties["DesiredCapacityType"] !== void 0 && { DesiredCapacityType: properties["DesiredCapacityType"] },
33860
- ...properties["DefaultInstanceWarmup"] != null && { DefaultInstanceWarmup: Number(properties["DefaultInstanceWarmup"]) },
33861
- ...properties["AvailabilityZoneDistribution"] !== void 0 && { AvailabilityZoneDistribution: properties["AvailabilityZoneDistribution"] },
33862
- ...properties["AvailabilityZoneImpairmentPolicy"] !== void 0 && { AvailabilityZoneImpairmentPolicy: properties["AvailabilityZoneImpairmentPolicy"] },
33863
- ...properties["SkipZonalShiftValidation"] !== void 0 && { SkipZonalShiftValidation: properties["SkipZonalShiftValidation"] },
33864
- ...properties["CapacityReservationSpecification"] !== void 0 && { CapacityReservationSpecification: properties["CapacityReservationSpecification"] },
33865
- ...properties["InstanceMaintenancePolicy"] !== void 0 && { InstanceMaintenancePolicy: properties["InstanceMaintenancePolicy"] },
33866
- ...properties["DeletionProtection"] !== void 0 && { DeletionProtection: properties["DeletionProtection"] }
33867
- }));
33868
- this.logger.debug(`Successfully updated AutoScalingGroup ${logicalId}`);
33869
- const arn = await this.fetchArn(physicalId);
33870
- const attributes = {};
33871
- if (arn) attributes["Arn"] = arn;
33872
- if (launchTemplate?.LaunchTemplateId) attributes["LaunchTemplateID"] = launchTemplate.LaunchTemplateId;
33873
- return {
33874
- physicalId,
33875
- wasReplaced: false,
33876
- attributes
33877
- };
33878
- } catch (error) {
33879
- if (error instanceof ResourceUpdateNotSupportedError) throw error;
33880
- const cause = error instanceof Error ? error : void 0;
33881
- throw new ProvisioningError(`Failed to update AutoScalingGroup ${logicalId}: ${error instanceof Error ? error.message : String(error)}`, resourceType, logicalId, physicalId, cause);
33882
- }
33883
- }
33884
- async delete(logicalId, physicalId, resourceType, _properties, context) {
33885
- this.logger.debug(`Deleting AutoScalingGroup ${logicalId}: ${physicalId}`);
33886
- if (context?.removeProtection === true) {
33887
- try {
33888
- await this.getClient().send(new UpdateAutoScalingGroupCommand({
33889
- AutoScalingGroupName: physicalId,
33890
- DeletionProtection: "none"
33891
- }));
33892
- this.logger.debug(`Disabled DeletionProtection on AutoScalingGroup ${logicalId} before delete`);
33893
- } catch (flipError) {
33894
- this.logger.debug(`Could not disable DeletionProtection on ${physicalId}: ${flipError instanceof Error ? flipError.message : String(flipError)}`);
33895
- }
33896
- await this.removeInstanceTerminationProtection(physicalId, logicalId);
33897
- }
33898
- try {
33899
- await this.getClient().send(new DeleteAutoScalingGroupCommand({
33900
- AutoScalingGroupName: physicalId,
33901
- ForceDelete: context?.removeProtection === true
33902
- }));
33903
- this.logger.debug(`Successfully initiated deletion of AutoScalingGroup ${logicalId}`);
33904
- await this.waitForGroupDeleted(physicalId);
33905
- } catch (error) {
33906
- if (this.isNotFoundError(error)) {
33907
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
33908
- this.logger.debug(`AutoScalingGroup ${physicalId} does not exist, skipping deletion`);
33909
- return;
33910
- }
33911
- const cause = error instanceof Error ? error : void 0;
33912
- throw new ProvisioningError(`Failed to delete AutoScalingGroup ${logicalId}: ${error instanceof Error ? error.message : String(error)}`, resourceType, logicalId, physicalId, cause);
33913
- }
33914
- }
33915
- async getAttribute(physicalId, _resourceType, attributeName) {
33916
- const group = await this.describeGroup(physicalId);
33917
- if (!group) throw new ProvisioningError(`AutoScalingGroup ${physicalId} not found while resolving attribute ${attributeName}`, "AWS::AutoScaling::AutoScalingGroup", physicalId, physicalId);
33918
- switch (attributeName) {
33919
- case "Arn":
33920
- case "AutoScalingGroupARN": return group.AutoScalingGroupARN ?? "";
33921
- case "LaunchConfigurationName": return group.LaunchConfigurationName ?? "";
33922
- case "LaunchTemplateID":
33923
- case "LaunchTemplateId": return group.LaunchTemplate?.LaunchTemplateId ?? "";
33924
- default: return "";
33925
- }
33926
- }
33927
- /**
33928
- * Read the AWS-current AutoScalingGroup configuration in CFn-property shape.
33929
- *
33930
- * Surfaces the user-controllable subset of `DescribeAutoScalingGroups`,
33931
- * with always-emit placeholders on user-controllable top-level keys per
33932
- * the cdkd PR #145 always-emit convention so that v3 `observedProperties`
33933
- * baseline catches console-side ADDs to fields a clean deploy did not
33934
- * template (e.g. a console-set `DeletionProtection: 'prevent-force-deletion'`
33935
- * on a group originally created without it).
33936
- *
33937
- * Sub-shapes (LifecycleHookSpecificationList / TrafficSources /
33938
- * NotificationConfigurations) are surfaced via three parallel Describe
33939
- * calls fired alongside the primary `DescribeAutoScalingGroups`. Each is
33940
- * best-effort: a per-call failure (e.g. permissions gap on
33941
- * `autoscaling:DescribeLifecycleHooks`) is logged at debug and the
33942
- * matching key falls back to its always-emit `[]` placeholder rather
33943
- * than aborting the whole drift read.
33944
- *
33945
- * `MetricsCollection` is reverse-mapped from `EnabledMetrics` (already
33946
- * present on the primary `DescribeAutoScalingGroups` response, so no
33947
- * extra call is needed).
33948
- *
33949
- * Returns `undefined` when the group is gone.
33950
- */
33951
- async readCurrentState(physicalId, _logicalId, _resourceType) {
33952
- const groupPromise = (async () => {
33953
- try {
33954
- return await this.describeGroup(physicalId);
33955
- } catch (err) {
33956
- if (this.isNotFoundError(err)) return void 0;
33957
- throw err;
33958
- }
33959
- })();
33960
- const lifecycleHooksPromise = this.getClient().send(new DescribeLifecycleHooksCommand({ AutoScalingGroupName: physicalId })).then((r) => r.LifecycleHooks ?? []).catch((err) => {
33961
- this.logger.debug(`DescribeLifecycleHooks(${physicalId}) failed: ${err instanceof Error ? err.message : String(err)}`);
33962
- return [];
33963
- });
33964
- const trafficSourcesPromise = this.getClient().send(new DescribeTrafficSourcesCommand({ AutoScalingGroupName: physicalId })).then((r) => r.TrafficSources ?? []).catch((err) => {
33965
- this.logger.debug(`DescribeTrafficSources(${physicalId}) failed: ${err instanceof Error ? err.message : String(err)}`);
33966
- return [];
33967
- });
33968
- const notificationsPromise = this.getClient().send(new DescribeNotificationConfigurationsCommand({ AutoScalingGroupNames: [physicalId] })).then((r) => r.NotificationConfigurations ?? []).catch((err) => {
33969
- this.logger.debug(`DescribeNotificationConfigurations(${physicalId}) failed: ${err instanceof Error ? err.message : String(err)}`);
33970
- return [];
33971
- });
33972
- const [group, lifecycleHooks, trafficSources, notifications] = await Promise.all([
33973
- groupPromise,
33974
- lifecycleHooksPromise,
33975
- trafficSourcesPromise,
33976
- notificationsPromise
33977
- ]);
33978
- if (!group) return void 0;
33979
- const result = {};
33980
- if (group.AutoScalingGroupName !== void 0) result["AutoScalingGroupName"] = group.AutoScalingGroupName;
33981
- if (group.LaunchTemplate) {
33982
- const lt = {};
33983
- if (group.LaunchTemplate.LaunchTemplateId !== void 0) lt["LaunchTemplateId"] = group.LaunchTemplate.LaunchTemplateId;
33984
- if (group.LaunchTemplate.LaunchTemplateName !== void 0) lt["LaunchTemplateName"] = group.LaunchTemplate.LaunchTemplateName;
33985
- if (group.LaunchTemplate.Version !== void 0) lt["Version"] = group.LaunchTemplate.Version;
33986
- result["LaunchTemplate"] = lt;
33987
- }
33988
- result["MinSize"] = group.MinSize ?? 0;
33989
- result["MaxSize"] = group.MaxSize ?? 0;
33990
- if (group.DesiredCapacity !== void 0) result["DesiredCapacity"] = group.DesiredCapacity;
33991
- if (group.VPCZoneIdentifier !== void 0 && group.VPCZoneIdentifier !== "") result["VPCZoneIdentifier"] = group.VPCZoneIdentifier.split(",").map((s) => s.trim());
33992
- else result["VPCZoneIdentifier"] = [];
33993
- result["AvailabilityZones"] = group.AvailabilityZones ?? [];
33994
- if (group.HealthCheckType !== void 0) result["HealthCheckType"] = group.HealthCheckType;
33995
- if (group.HealthCheckGracePeriod !== void 0) result["HealthCheckGracePeriod"] = group.HealthCheckGracePeriod;
33996
- if (group.DefaultCooldown !== void 0) result["Cooldown"] = group.DefaultCooldown;
33997
- result["NewInstancesProtectedFromScaleIn"] = group.NewInstancesProtectedFromScaleIn ?? false;
33998
- result["TerminationPolicies"] = group.TerminationPolicies ?? [];
33999
- result["CapacityRebalance"] = group.CapacityRebalance ?? false;
34000
- if (group.ServiceLinkedRoleARN !== void 0) result["ServiceLinkedRoleARN"] = group.ServiceLinkedRoleARN;
34001
- if (group.MaxInstanceLifetime !== void 0) result["MaxInstanceLifetime"] = group.MaxInstanceLifetime;
34002
- result["LoadBalancerNames"] = group.LoadBalancerNames ?? [];
34003
- result["TargetGroupARNs"] = group.TargetGroupARNs ?? [];
34004
- if (group.Context !== void 0) result["Context"] = group.Context;
34005
- if (group.DesiredCapacityType !== void 0) result["DesiredCapacityType"] = group.DesiredCapacityType;
34006
- if (group.DefaultInstanceWarmup !== void 0) result["DefaultInstanceWarmup"] = group.DefaultInstanceWarmup;
34007
- if (group.MixedInstancesPolicy !== void 0) result["MixedInstancesPolicy"] = group.MixedInstancesPolicy;
34008
- if (group.AvailabilityZoneDistribution !== void 0) result["AvailabilityZoneDistribution"] = group.AvailabilityZoneDistribution;
34009
- if (group.AvailabilityZoneImpairmentPolicy !== void 0) result["AvailabilityZoneImpairmentPolicy"] = group.AvailabilityZoneImpairmentPolicy;
34010
- if (group.CapacityReservationSpecification !== void 0) result["CapacityReservationSpecification"] = group.CapacityReservationSpecification;
34011
- if (group.InstanceMaintenancePolicy !== void 0) result["InstanceMaintenancePolicy"] = group.InstanceMaintenancePolicy;
34012
- if (group.DeletionProtection !== void 0) result["DeletionProtection"] = group.DeletionProtection;
34013
- else result["DeletionProtection"] = "none";
34014
- result["Tags"] = normalizeAwsTagsToCfn(group.Tags);
34015
- result["MetricsCollection"] = mapEnabledMetricsToCfn(group.EnabledMetrics);
34016
- result["LifecycleHookSpecificationList"] = mapLifecycleHooksToCfn(lifecycleHooks);
34017
- result["TrafficSources"] = mapTrafficSourcesToCfn(trafficSources.filter((t) => {
34018
- if (t.Identifier === void 0) return false;
34019
- if (t.Type === "elbv2" || t.Type === "elb") return false;
34020
- return true;
34021
- }));
34022
- result["NotificationConfigurations"] = mapNotificationsToCfn(notifications);
34023
- return result;
34024
- }
34025
- buildLaunchTemplate(properties) {
34026
- const lt = properties["LaunchTemplate"];
34027
- if (!lt) return void 0;
34028
- const out = {};
34029
- if (lt.LaunchTemplateId !== void 0) {
34030
- out.LaunchTemplateId = lt.LaunchTemplateId;
34031
- if (lt.LaunchTemplateName !== void 0) this.logger.debug(`buildLaunchTemplate: both LaunchTemplateId (${lt.LaunchTemplateId}) and LaunchTemplateName (${lt.LaunchTemplateName}) templated; dropping Name (#551)`);
34032
- } else if (lt.LaunchTemplateName !== void 0) out.LaunchTemplateName = lt.LaunchTemplateName;
34033
- if (lt.Version !== void 0) out.Version = String(lt.Version);
34034
- if (out.LaunchTemplateId === void 0 && out.LaunchTemplateName === void 0) return;
34035
- return out;
34036
- }
34037
- /**
34038
- * CFn `Tags` is `[{Key, Value, PropagateAtLaunch?}]`. AWS expects each
34039
- * tag to also carry `ResourceId: <groupName>` and `ResourceType:
34040
- * 'auto-scaling-group'`. We tack those on at create time so the SDK
34041
- * input shape matches without forcing the user to template them.
34042
- */
34043
- buildTags(groupName, properties) {
34044
- const raw = properties["Tags"];
34045
- if (!raw) return [];
34046
- return raw.filter((t) => t.Key !== void 0).map((t) => ({
34047
- ResourceId: groupName,
34048
- ResourceType: "auto-scaling-group",
34049
- Key: t.Key,
34050
- Value: t.Value ?? "",
34051
- PropagateAtLaunch: t.PropagateAtLaunch ?? false
34052
- }));
34053
- }
34054
- /**
34055
- * CFn `VPCZoneIdentifier` is a list of subnet ids; the AWS SDK input
34056
- * field is a comma-joined string.
34057
- */
34058
- joinVpcZoneIdentifier(value) {
34059
- if (value === void 0 || value === null) return void 0;
34060
- if (Array.isArray(value)) {
34061
- const cleaned = value.map((v) => String(v).trim()).filter((v) => v.length > 0);
34062
- if (cleaned.length === 0) return void 0;
34063
- return cleaned.join(",");
34064
- }
34065
- if (typeof value === "string") return value;
34066
- }
34067
- async describeGroup(groupName) {
34068
- return (await this.getClient().send(new DescribeAutoScalingGroupsCommand({ AutoScalingGroupNames: [groupName] }))).AutoScalingGroups?.[0];
34069
- }
34070
- /**
34071
- * Flip EC2-level termination protection (`DisableApiTermination`) off on
34072
- * every instance currently launched by the group, so the subsequent
34073
- * `DeleteAutoScalingGroup(ForceDelete: true)` can actually terminate them
34074
- * instead of orphaning the protected instances (issue #796). Best-effort:
34075
- * a Describe failure or a per-instance flip failure is logged at debug and
34076
- * does not block the delete (the modify WRITE lags the terminate READ, so
34077
- * the shared helper swallows propagation errors the same way the EC2 path
34078
- * does — the orphan, if any, surfaces as a leftover instance the caller
34079
- * can clean up rather than a hard delete failure).
34080
- */
34081
- async removeInstanceTerminationProtection(groupName, logicalId) {
34082
- let instanceIds;
34083
- try {
34084
- instanceIds = ((await this.describeGroup(groupName))?.Instances ?? []).map((i) => i.InstanceId).filter((id) => typeof id === "string" && id.length > 0);
34085
- } catch (describeError) {
34086
- this.logger.debug(`Could not enumerate instances of AutoScalingGroup ${logicalId} for termination-protection removal: ${describeError instanceof Error ? describeError.message : String(describeError)}`);
34087
- return;
34088
- }
34089
- if (instanceIds.length === 0) return;
34090
- this.logger.debug(`Disabling EC2 termination protection on ${instanceIds.length} instance(s) of AutoScalingGroup ${logicalId} before force delete`);
34091
- for (const instanceId of instanceIds) await disableInstanceApiTermination(this.getEc2Client(), instanceId, this.logger);
34092
- }
34093
- async fetchArn(groupName) {
34094
- try {
34095
- return (await this.describeGroup(groupName))?.AutoScalingGroupARN;
34096
- } catch (err) {
34097
- this.logger.debug(`DescribeAutoScalingGroups(${groupName}) failed: ${err instanceof Error ? err.message : String(err)}`);
34098
- return;
34099
- }
34100
- }
34101
- isNotFoundError(error) {
34102
- if (!(error instanceof Error)) return false;
34103
- const name = error.name ?? "";
34104
- const message = error.message.toLowerCase();
34105
- return name === "ValidationError" && (message.includes("autoscalinggroup name not found") || message.includes("not found") || message.includes("does not exist"));
34106
- }
34107
- async waitForGroupDeleted(groupName, maxWaitMs = 9e5) {
34108
- const startTime = Date.now();
34109
- let delay = 5e3;
34110
- while (Date.now() - startTime < maxWaitMs) {
34111
- try {
34112
- if (!await this.describeGroup(groupName)) return;
34113
- } catch (error) {
34114
- if (this.isNotFoundError(error)) return;
34115
- throw error;
34116
- }
34117
- await this.sleep(delay);
34118
- delay = Math.min(delay * 2, 3e4);
34119
- }
34120
- throw new Error(`Timed out waiting for AutoScalingGroup ${groupName} to be deleted (15 minute cap)`);
34121
- }
34122
- sleep(ms) {
34123
- return new Promise((resolve) => setTimeout(resolve, ms));
34124
- }
34125
- /**
34126
- * Diff and apply changes to the ASG's `Tags` property via the
34127
- * `CreateOrUpdateTags` / `DeleteTags` AWS APIs (#475). CFn Tags shape is
34128
- * `[{Key, Value, PropagateAtLaunch}]`; AWS Tag input adds `ResourceId`
34129
- * (= the ASG name) and `ResourceType: 'auto-scaling-group'`.
34130
- *
34131
- * Diff semantics:
34132
- * - Removed keys → `DeleteTags`.
34133
- * - Added keys → `CreateOrUpdateTags`.
34134
- * - Modified value or `PropagateAtLaunch` flag → `CreateOrUpdateTags`
34135
- * (the AWS API upserts by `(ResourceId, ResourceType, Key)` tuple, so
34136
- * a single upsert call replaces the old value).
34137
- *
34138
- * No-op when before/after JSON is identical.
34139
- */
34140
- async applyTagsDiff(physicalId, next, prev) {
34141
- if (JSON.stringify(next ?? []) === JSON.stringify(prev ?? [])) return;
34142
- const nextEntries = Array.isArray(next) ? next : [];
34143
- const prevEntries = Array.isArray(prev) ? prev : [];
34144
- const nextByKey = /* @__PURE__ */ new Map();
34145
- for (const t of nextEntries) if (t.Key) nextByKey.set(t.Key, t);
34146
- const prevByKey = /* @__PURE__ */ new Map();
34147
- for (const t of prevEntries) if (t.Key) prevByKey.set(t.Key, t);
34148
- const toDelete = [];
34149
- for (const [key, tag] of prevByKey) if (!nextByKey.has(key)) toDelete.push(tag);
34150
- if (toDelete.length > 0) await this.getClient().send(new DeleteTagsCommand$1({ Tags: toDelete.map((t) => ({
34151
- ResourceId: physicalId,
34152
- ResourceType: "auto-scaling-group",
34153
- Key: t.Key
34154
- })) }));
34155
- const toUpsert = [];
34156
- for (const [key, tag] of nextByKey) {
34157
- const before = prevByKey.get(key);
34158
- if (JSON.stringify(before) === JSON.stringify(tag)) continue;
34159
- toUpsert.push(tag);
34160
- }
34161
- if (toUpsert.length > 0) await this.getClient().send(new CreateOrUpdateTagsCommand({ Tags: toUpsert.map((t) => ({
34162
- ResourceId: physicalId,
34163
- ResourceType: "auto-scaling-group",
34164
- Key: t.Key,
34165
- ...t.Value !== void 0 && { Value: t.Value },
34166
- ...t.PropagateAtLaunch !== void 0 && { PropagateAtLaunch: t.PropagateAtLaunch }
34167
- })) }));
34168
- }
34169
- /**
34170
- * Diff `LoadBalancerNames` (Classic Load Balancers) and issue
34171
- * `AttachLoadBalancers` / `DetachLoadBalancers` for the delta (#476).
34172
- * Names are opaque strings; AWS allows N attached LBs per ASG so this
34173
- * helper batches every add into one Attach call and every remove into
34174
- * one Detach call. No-op when before/after JSON is identical.
34175
- */
34176
- async applyLoadBalancerNamesDiff(physicalId, next, prev) {
34177
- if (JSON.stringify(next ?? []) === JSON.stringify(prev ?? [])) return;
34178
- const nextNames = (Array.isArray(next) ? next : []).filter((n) => typeof n === "string");
34179
- const prevNames = (Array.isArray(prev) ? prev : []).filter((n) => typeof n === "string");
34180
- const nextSet = new Set(nextNames);
34181
- const prevSet = new Set(prevNames);
34182
- const toAttach = nextNames.filter((n) => !prevSet.has(n));
34183
- const toDetach = prevNames.filter((n) => !nextSet.has(n));
34184
- if (toDetach.length > 0) await this.getClient().send(new DetachLoadBalancersCommand({
34185
- AutoScalingGroupName: physicalId,
34186
- LoadBalancerNames: toDetach
34187
- }));
34188
- if (toAttach.length > 0) await this.getClient().send(new AttachLoadBalancersCommand({
34189
- AutoScalingGroupName: physicalId,
34190
- LoadBalancerNames: toAttach
34191
- }));
34192
- }
34193
- /**
34194
- * Diff `TargetGroupARNs` (ALB / NLB target groups) and issue
34195
- * `AttachLoadBalancerTargetGroups` /
34196
- * `DetachLoadBalancerTargetGroups` for the delta (#476). Target-group
34197
- * ARNs are opaque strings; same per-call batching pattern as
34198
- * `applyLoadBalancerNamesDiff`. No-op when before/after JSON is
34199
- * identical.
34200
- */
34201
- async applyTargetGroupArnsDiff(physicalId, next, prev) {
34202
- if (JSON.stringify(next ?? []) === JSON.stringify(prev ?? [])) return;
34203
- const nextArns = (Array.isArray(next) ? next : []).filter((a) => typeof a === "string");
34204
- const prevArns = (Array.isArray(prev) ? prev : []).filter((a) => typeof a === "string");
34205
- const nextSet = new Set(nextArns);
34206
- const prevSet = new Set(prevArns);
34207
- const toAttach = nextArns.filter((a) => !prevSet.has(a));
34208
- const toDetach = prevArns.filter((a) => !nextSet.has(a));
34209
- if (toDetach.length > 0) await this.getClient().send(new DetachLoadBalancerTargetGroupsCommand({
34210
- AutoScalingGroupName: physicalId,
34211
- TargetGroupARNs: toDetach
34212
- }));
34213
- if (toAttach.length > 0) await this.getClient().send(new AttachLoadBalancerTargetGroupsCommand({
34214
- AutoScalingGroupName: physicalId,
34215
- TargetGroupARNs: toAttach
34216
- }));
34217
- if (toDetach.length > 0 || toAttach.length > 0) await this.waitForTargetGroupArnsConvergence(physicalId, new Set(nextArns));
34218
- }
34219
- static TG_CONVERGENCE_TIMEOUT_MS = 3e4;
34220
- static TG_CONVERGENCE_POLL_INTERVAL_MS = 1e3;
34221
- async waitForTargetGroupArnsConvergence(physicalId, expected) {
34222
- const deadlineMs = Date.now() + ASGProvider.TG_CONVERGENCE_TIMEOUT_MS;
34223
- let lastObserved = /* @__PURE__ */ new Set();
34224
- while (Date.now() < deadlineMs) {
34225
- let resp;
34226
- try {
34227
- resp = await this.getClient().send(new DescribeAutoScalingGroupsCommand({ AutoScalingGroupNames: [physicalId] }));
34228
- } catch (err) {
34229
- this.logger.debug(`applyTargetGroupArnsDiff convergence poll: transient error, retrying — ${err instanceof Error ? err.message : String(err)}`);
34230
- await new Promise((r) => setTimeout(r, ASGProvider.TG_CONVERGENCE_POLL_INTERVAL_MS));
34231
- continue;
34232
- }
34233
- lastObserved = new Set(resp.AutoScalingGroups?.[0]?.TargetGroupARNs ?? []);
34234
- if (lastObserved.size === expected.size && [...expected].every((a) => lastObserved.has(a))) return;
34235
- await new Promise((r) => setTimeout(r, ASGProvider.TG_CONVERGENCE_POLL_INTERVAL_MS));
34236
- }
34237
- const expectedSorted = [...expected].sort();
34238
- const observedSorted = [...lastObserved].sort();
34239
- this.logger.warn(`applyTargetGroupArnsDiff: TG set did not converge within ${ASGProvider.TG_CONVERGENCE_TIMEOUT_MS}ms for ASG ${physicalId}. expected=${JSON.stringify(expectedSorted)} observed=${JSON.stringify(observedSorted)}`);
34240
- }
34241
- async applyMetricsCollectionDiff(physicalId, next, prev) {
34242
- if (JSON.stringify(next ?? []) === JSON.stringify(prev ?? [])) return;
34243
- const nextEntries = Array.isArray(next) ? next : [];
34244
- const prevEntries = Array.isArray(prev) ? prev : [];
34245
- const prevByGranularity = /* @__PURE__ */ new Map();
34246
- for (const e of prevEntries) if (e.Granularity) prevByGranularity.set(e.Granularity, e.Metrics);
34247
- const nextByGranularity = /* @__PURE__ */ new Map();
34248
- for (const e of nextEntries) if (e.Granularity) nextByGranularity.set(e.Granularity, e.Metrics);
34249
- for (const [granularity, metrics] of prevByGranularity) if (!nextByGranularity.has(granularity)) await this.getClient().send(new DisableMetricsCollectionCommand({
34250
- AutoScalingGroupName: physicalId,
34251
- ...metrics && metrics.length > 0 ? { Metrics: metrics } : {}
34252
- }));
34253
- for (const [granularity, metrics] of nextByGranularity) {
34254
- const before = prevByGranularity.get(granularity);
34255
- if (JSON.stringify(before ?? null) === JSON.stringify(metrics ?? null)) continue;
34256
- if (before && before.length > 0) {
34257
- const removed = metrics ? before.filter((m) => !metrics.includes(m)) : [];
34258
- if (removed.length > 0) await this.getClient().send(new DisableMetricsCollectionCommand({
34259
- AutoScalingGroupName: physicalId,
34260
- Metrics: removed
34261
- }));
34262
- }
34263
- await this.getClient().send(new EnableMetricsCollectionCommand({
34264
- AutoScalingGroupName: physicalId,
34265
- Granularity: granularity,
34266
- ...metrics && metrics.length > 0 ? { Metrics: metrics } : {}
34267
- }));
34268
- }
34269
- }
34270
- async applyLifecycleHooksDiff(physicalId, next, prev) {
34271
- if (JSON.stringify(next ?? []) === JSON.stringify(prev ?? [])) return;
34272
- const nextEntries = Array.isArray(next) ? next : [];
34273
- const prevEntries = Array.isArray(prev) ? prev : [];
34274
- const nextNames = new Set(nextEntries.map((e) => e.LifecycleHookName).filter((n) => !!n));
34275
- for (const e of prevEntries) if (e.LifecycleHookName && !nextNames.has(e.LifecycleHookName)) await this.getClient().send(new DeleteLifecycleHookCommand({
34276
- AutoScalingGroupName: physicalId,
34277
- LifecycleHookName: e.LifecycleHookName
34278
- }));
34279
- const prevByName = /* @__PURE__ */ new Map();
34280
- for (const e of prevEntries) if (e.LifecycleHookName) prevByName.set(e.LifecycleHookName, e);
34281
- for (const e of nextEntries) {
34282
- if (!e.LifecycleHookName) continue;
34283
- const prevHook = prevByName.get(e.LifecycleHookName);
34284
- if (JSON.stringify(prevHook) === JSON.stringify(e)) continue;
34285
- await this.getClient().send(new PutLifecycleHookCommand({
34286
- AutoScalingGroupName: physicalId,
34287
- LifecycleHookName: e.LifecycleHookName,
34288
- ...e.LifecycleTransition !== void 0 && { LifecycleTransition: e.LifecycleTransition },
34289
- ...e.RoleARN !== void 0 && { RoleARN: e.RoleARN },
34290
- ...e.NotificationTargetARN !== void 0 && { NotificationTargetARN: e.NotificationTargetARN },
34291
- ...e.NotificationMetadata !== void 0 && { NotificationMetadata: e.NotificationMetadata },
34292
- ...e.HeartbeatTimeout !== void 0 && { HeartbeatTimeout: e.HeartbeatTimeout },
34293
- ...e.DefaultResult !== void 0 && { DefaultResult: e.DefaultResult }
34294
- }));
34295
- }
34296
- }
34297
- async applyTrafficSourcesDiff(physicalId, next, prev) {
34298
- if (JSON.stringify(next ?? []) === JSON.stringify(prev ?? [])) return;
34299
- const nextEntries = Array.isArray(next) ? next : [];
34300
- const prevEntries = Array.isArray(prev) ? prev : [];
34301
- const nextIds = new Set(nextEntries.map((e) => e.Identifier).filter((i) => !!i));
34302
- const prevIds = new Set(prevEntries.map((e) => e.Identifier).filter((i) => !!i));
34303
- const toDetach = prevEntries.filter((e) => e.Identifier && !nextIds.has(e.Identifier));
34304
- const toAttach = nextEntries.filter((e) => e.Identifier && !prevIds.has(e.Identifier));
34305
- if (toDetach.length > 0) await this.getClient().send(new DetachTrafficSourcesCommand({
34306
- AutoScalingGroupName: physicalId,
34307
- TrafficSources: toDetach.map((e) => ({
34308
- Identifier: e.Identifier,
34309
- ...e.Type !== void 0 && { Type: e.Type }
34310
- }))
34311
- }));
34312
- if (toAttach.length > 0) await this.getClient().send(new AttachTrafficSourcesCommand({
34313
- AutoScalingGroupName: physicalId,
34314
- TrafficSources: toAttach.map((e) => ({
34315
- Identifier: e.Identifier,
34316
- ...e.Type !== void 0 && { Type: e.Type }
34317
- }))
34318
- }));
34319
- }
34320
- async applyNotificationConfigurationsDiff(physicalId, next, prev) {
34321
- if (JSON.stringify(next ?? []) === JSON.stringify(prev ?? [])) return;
34322
- const nextEntries = Array.isArray(next) ? next : [];
34323
- const prevEntries = Array.isArray(prev) ? prev : [];
34324
- const nextByTopic = /* @__PURE__ */ new Map();
34325
- for (const e of nextEntries) if (e.TopicARN) nextByTopic.set(e.TopicARN, e.NotificationTypes);
34326
- const prevByTopic = /* @__PURE__ */ new Map();
34327
- for (const e of prevEntries) if (e.TopicARN) prevByTopic.set(e.TopicARN, e.NotificationTypes);
34328
- for (const topic of prevByTopic.keys()) if (!nextByTopic.has(topic)) await this.getClient().send(new DeleteNotificationConfigurationCommand({
34329
- AutoScalingGroupName: physicalId,
34330
- TopicARN: topic
34331
- }));
34332
- for (const [topic, types] of nextByTopic) {
34333
- const before = prevByTopic.get(topic);
34334
- if (JSON.stringify(before ?? null) === JSON.stringify(types ?? null)) continue;
34335
- await this.getClient().send(new PutNotificationConfigurationCommand({
34336
- AutoScalingGroupName: physicalId,
34337
- TopicARN: topic,
34338
- NotificationTypes: types ?? []
34339
- }));
34340
- }
34341
- }
34342
- };
34343
- /**
34344
- * Reverse-map AWS `EnabledMetrics: [{Metric, Granularity}]` (flat list,
34345
- * one row per enabled metric) back to the CFn array shape
34346
- * `[{Granularity, Metrics?[]}]`. Metrics with the same Granularity are
34347
- * grouped together; the resulting Metrics list is sorted alphabetically
34348
- * for stable positional compare in the drift comparator.
34349
- *
34350
- * Always returns a placeholder `[]` per the cdkd PR #145 always-emit
34351
- * convention so a console-side EnableMetricsCollection on a previously-
34352
- * empty group surfaces as drift on the v3 `observedProperties` baseline.
34353
- */
34354
- function mapEnabledMetricsToCfn(enabledMetrics) {
34355
- if (!enabledMetrics || enabledMetrics.length === 0) return [];
34356
- const byGranularity = /* @__PURE__ */ new Map();
34357
- for (const e of enabledMetrics) {
34358
- const g = e.Granularity;
34359
- if (!g) continue;
34360
- let set = byGranularity.get(g);
34361
- if (!set) {
34362
- set = /* @__PURE__ */ new Set();
34363
- byGranularity.set(g, set);
34364
- }
34365
- if (e.Metric) set.add(e.Metric);
34366
- }
34367
- const result = [];
34368
- for (const granularity of Array.from(byGranularity.keys()).sort()) {
34369
- const metrics = Array.from(byGranularity.get(granularity) ?? []).sort();
34370
- result.push(metrics.length > 0 ? {
34371
- Granularity: granularity,
34372
- Metrics: metrics
34373
- } : { Granularity: granularity });
34374
- }
34375
- return result;
34376
- }
34377
- /**
34378
- * Reverse-map AWS `DescribeLifecycleHooks` response to the CFn
34379
- * `LifecycleHookSpecificationList` shape. Each hook is surfaced under the
34380
- * exact CFn property name. AWS-side fields cdkd state never carried
34381
- * (`AutoScalingGroupName` — duplicated on every hook by AWS,
34382
- * `GlobalTimeout` — AWS-derived) are filtered out. Sorted by
34383
- * LifecycleHookName for stable positional compare.
34384
- */
34385
- function mapLifecycleHooksToCfn(hooks) {
34386
- if (!hooks || hooks.length === 0) return [];
34387
- const result = [];
34388
- for (const h of hooks) {
34389
- if (!h.LifecycleHookName) continue;
34390
- const entry = { LifecycleHookName: h.LifecycleHookName };
34391
- if (h.LifecycleTransition !== void 0) entry["LifecycleTransition"] = h.LifecycleTransition;
34392
- if (h.RoleARN !== void 0) entry["RoleARN"] = h.RoleARN;
34393
- if (h.NotificationTargetARN !== void 0) entry["NotificationTargetARN"] = h.NotificationTargetARN;
34394
- if (h.NotificationMetadata !== void 0) entry["NotificationMetadata"] = h.NotificationMetadata;
34395
- if (h.HeartbeatTimeout !== void 0) entry["HeartbeatTimeout"] = h.HeartbeatTimeout;
34396
- if (h.DefaultResult !== void 0) entry["DefaultResult"] = h.DefaultResult;
34397
- result.push(entry);
34398
- }
34399
- result.sort((a, b) => String(a["LifecycleHookName"]).localeCompare(String(b["LifecycleHookName"])));
34400
- return result;
34401
- }
34402
- /**
34403
- * Reverse-map AWS `DescribeTrafficSources` response to the CFn
34404
- * `TrafficSources` shape `[{Identifier, Type?}]`. AWS-side runtime fields
34405
- * (`State`, the deprecated `TrafficSource` alias) are filtered out.
34406
- * Sorted by Identifier for stable positional compare.
34407
- */
34408
- function mapTrafficSourcesToCfn(trafficSources) {
34409
- if (!trafficSources || trafficSources.length === 0) return [];
34410
- const result = [];
34411
- for (const t of trafficSources) {
34412
- if (!t.Identifier) continue;
34413
- const entry = { Identifier: t.Identifier };
34414
- if (t.Type !== void 0) entry["Type"] = t.Type;
34415
- result.push(entry);
34416
- }
34417
- result.sort((a, b) => String(a["Identifier"]).localeCompare(String(b["Identifier"])));
34418
- return result;
34419
- }
34420
- /**
34421
- * Reverse-map AWS `DescribeNotificationConfigurations` (a flat list, one
34422
- * row per `(topicArn, notificationType)`) into the CFn shape
34423
- * `[{TopicARN, NotificationTypes[]}]`. NotificationTypes are grouped per
34424
- * TopicARN and sorted alphabetically for stable positional compare.
34425
- */
34426
- function mapNotificationsToCfn(configurations) {
34427
- if (!configurations || configurations.length === 0) return [];
34428
- const byTopic = /* @__PURE__ */ new Map();
34429
- for (const c of configurations) {
34430
- if (!c.TopicARN) continue;
34431
- let set = byTopic.get(c.TopicARN);
34432
- if (!set) {
34433
- set = /* @__PURE__ */ new Set();
34434
- byTopic.set(c.TopicARN, set);
34435
- }
34436
- if (c.NotificationType) set.add(c.NotificationType);
34437
- }
34438
- const result = [];
34439
- for (const topic of Array.from(byTopic.keys()).sort()) {
34440
- const types = Array.from(byTopic.get(topic) ?? []).sort();
34441
- result.push({
34442
- TopicARN: topic,
34443
- NotificationTypes: types
34444
- });
34445
- }
34446
- return result;
34447
- }
34448
-
34449
33668
  //#endregion
34450
33669
  //#region src/cli/commands/destroy-runner.ts
34451
33670
  /**
@@ -53553,7 +52772,7 @@ function reorderArgs(argv) {
53553
52772
  async function main() {
53554
52773
  installPipeCloseHandler();
53555
52774
  const program = new Command();
53556
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.219.2");
52775
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.219.3");
53557
52776
  program.addCommand(createBootstrapCommand());
53558
52777
  program.addCommand(createSynthCommand());
53559
52778
  program.addCommand(createListCommand());