cdk-factory 0.17.6__py3-none-any.whl → 0.20.2__py3-none-any.whl
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.
Potentially problematic release.
This version of cdk-factory might be problematic. Click here for more details.
- cdk_factory/configurations/deployment.py +12 -0
- cdk_factory/configurations/resources/acm.py +9 -2
- cdk_factory/configurations/resources/auto_scaling.py +7 -5
- cdk_factory/configurations/resources/ecs_cluster.py +5 -0
- cdk_factory/configurations/resources/ecs_service.py +24 -2
- cdk_factory/configurations/resources/lambda_edge.py +18 -4
- cdk_factory/configurations/resources/rds.py +1 -1
- cdk_factory/configurations/resources/route53.py +5 -0
- cdk_factory/configurations/resources/s3.py +9 -1
- cdk_factory/constructs/cloudfront/cloudfront_distribution_construct.py +1 -1
- cdk_factory/constructs/lambdas/policies/policy_docs.py +1 -1
- cdk_factory/interfaces/networked_stack_mixin.py +1 -1
- cdk_factory/interfaces/standardized_ssm_mixin.py +82 -10
- cdk_factory/stack_library/acm/acm_stack.py +5 -15
- cdk_factory/stack_library/api_gateway/api_gateway_stack.py +2 -2
- cdk_factory/stack_library/auto_scaling/{auto_scaling_stack_standardized.py → auto_scaling_stack.py} +213 -105
- cdk_factory/stack_library/cloudfront/cloudfront_stack.py +76 -22
- cdk_factory/stack_library/code_artifact/code_artifact_stack.py +3 -25
- cdk_factory/stack_library/cognito/cognito_stack.py +2 -2
- cdk_factory/stack_library/dynamodb/dynamodb_stack.py +2 -2
- cdk_factory/stack_library/ecs/__init__.py +2 -4
- cdk_factory/stack_library/ecs/{ecs_cluster_stack_standardized.py → ecs_cluster_stack.py} +52 -41
- cdk_factory/stack_library/ecs/ecs_service_stack.py +49 -26
- cdk_factory/stack_library/lambda_edge/EDGE_LOG_RETENTION_TODO.md +226 -0
- cdk_factory/stack_library/lambda_edge/LAMBDA_EDGE_LOG_RETENTION_BLOG.md +215 -0
- cdk_factory/stack_library/lambda_edge/lambda_edge_stack.py +241 -81
- cdk_factory/stack_library/load_balancer/load_balancer_stack.py +128 -177
- cdk_factory/stack_library/rds/rds_stack.py +65 -72
- cdk_factory/stack_library/route53/route53_stack.py +244 -38
- cdk_factory/stack_library/rum/rum_stack.py +3 -3
- cdk_factory/stack_library/security_group/security_group_full_stack.py +1 -31
- cdk_factory/stack_library/security_group/security_group_stack.py +1 -8
- cdk_factory/stack_library/simple_queue_service/sqs_stack.py +1 -34
- cdk_factory/stack_library/stack_base.py +5 -0
- cdk_factory/stack_library/vpc/{vpc_stack_standardized.py → vpc_stack.py} +6 -109
- cdk_factory/stack_library/websites/static_website_stack.py +7 -3
- cdk_factory/utilities/api_gateway_integration_utility.py +2 -2
- cdk_factory/utilities/environment_services.py +2 -2
- cdk_factory/version.py +1 -1
- {cdk_factory-0.17.6.dist-info → cdk_factory-0.20.2.dist-info}/METADATA +1 -1
- {cdk_factory-0.17.6.dist-info → cdk_factory-0.20.2.dist-info}/RECORD +44 -42
- {cdk_factory-0.17.6.dist-info → cdk_factory-0.20.2.dist-info}/WHEEL +0 -0
- {cdk_factory-0.17.6.dist-info → cdk_factory-0.20.2.dist-info}/entry_points.txt +0 -0
- {cdk_factory-0.17.6.dist-info → cdk_factory-0.20.2.dist-info}/licenses/LICENSE +0 -0
cdk_factory/stack_library/auto_scaling/{auto_scaling_stack_standardized.py → auto_scaling_stack.py}
RENAMED
|
@@ -9,11 +9,11 @@ from typing import Dict, Any, List, Optional
|
|
|
9
9
|
import aws_cdk as cdk
|
|
10
10
|
from aws_cdk import aws_ec2 as ec2
|
|
11
11
|
from aws_cdk import aws_autoscaling as autoscaling
|
|
12
|
-
from aws_cdk import aws_cloudwatch as cloudwatch
|
|
13
12
|
from aws_cdk import aws_iam as iam
|
|
14
|
-
from aws_cdk import aws_ssm as ssm
|
|
15
13
|
from aws_cdk import aws_ecs as ecs
|
|
16
|
-
from aws_cdk import Duration
|
|
14
|
+
from aws_cdk import Duration
|
|
15
|
+
|
|
16
|
+
from aws_cdk.aws_autoscaling import HealthChecks, AdditionalHealthCheckType
|
|
17
17
|
from aws_lambda_powertools import Logger
|
|
18
18
|
from constructs import Construct
|
|
19
19
|
|
|
@@ -34,10 +34,10 @@ logger = Logger(service="AutoScalingStackStandardized")
|
|
|
34
34
|
class AutoScalingStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
|
|
35
35
|
"""
|
|
36
36
|
Reusable stack for AWS Auto Scaling Groups with standardized SSM integration.
|
|
37
|
-
|
|
37
|
+
|
|
38
38
|
This version uses the StandardizedSsmMixin to provide consistent SSM parameter
|
|
39
39
|
handling across all CDK Factory modules.
|
|
40
|
-
|
|
40
|
+
|
|
41
41
|
Key Features:
|
|
42
42
|
- Standardized SSM import/export patterns
|
|
43
43
|
- Template variable resolution
|
|
@@ -49,10 +49,10 @@ class AutoScalingStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
|
|
|
49
49
|
def __init__(self, scope: Construct, id: str, **kwargs) -> None:
|
|
50
50
|
# Initialize parent classes properly
|
|
51
51
|
super().__init__(scope, id, **kwargs)
|
|
52
|
-
|
|
52
|
+
|
|
53
53
|
# Initialize VPC cache from mixin
|
|
54
54
|
self._initialize_vpc_cache()
|
|
55
|
-
|
|
55
|
+
|
|
56
56
|
# Initialize module attributes
|
|
57
57
|
self.asg_config = None
|
|
58
58
|
self.stack_config = None
|
|
@@ -92,17 +92,17 @@ class AutoScalingStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
|
|
|
92
92
|
asg_name = deployment.build_resource_name(self.asg_config.name)
|
|
93
93
|
|
|
94
94
|
# Setup standardized SSM integration
|
|
95
|
-
self.
|
|
95
|
+
self.setup_ssm_integration(
|
|
96
96
|
scope=self,
|
|
97
97
|
config=self.asg_config,
|
|
98
98
|
resource_type="auto_scaling",
|
|
99
99
|
resource_name=asg_name,
|
|
100
100
|
deployment=deployment,
|
|
101
|
-
workload=workload
|
|
101
|
+
workload=workload,
|
|
102
102
|
)
|
|
103
103
|
|
|
104
104
|
# Process SSM imports using standardized method
|
|
105
|
-
self.
|
|
105
|
+
self.process_ssm_imports()
|
|
106
106
|
|
|
107
107
|
# Get security groups using standardized approach
|
|
108
108
|
self.security_groups = self._get_security_groups()
|
|
@@ -127,7 +127,7 @@ class AutoScalingStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
|
|
|
127
127
|
|
|
128
128
|
# Add scaling policies
|
|
129
129
|
self._add_scaling_policies()
|
|
130
|
-
|
|
130
|
+
|
|
131
131
|
# Add update policy
|
|
132
132
|
self._add_update_policy()
|
|
133
133
|
|
|
@@ -143,12 +143,12 @@ class AutoScalingStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
|
|
|
143
143
|
def _get_security_groups(self) -> List[ec2.ISecurityGroup]:
|
|
144
144
|
"""
|
|
145
145
|
Get security groups for the Auto Scaling Group using standardized SSM imports.
|
|
146
|
-
|
|
146
|
+
|
|
147
147
|
Returns:
|
|
148
148
|
List of security group references
|
|
149
149
|
"""
|
|
150
150
|
security_groups = []
|
|
151
|
-
|
|
151
|
+
|
|
152
152
|
# Primary method: Use standardized SSM imports
|
|
153
153
|
ssm_imports = self._get_ssm_imports()
|
|
154
154
|
if "security_group_ids" in ssm_imports:
|
|
@@ -160,7 +160,9 @@ class AutoScalingStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
|
|
|
160
160
|
self, f"SecurityGroup-SSM-{idx}", sg_id
|
|
161
161
|
)
|
|
162
162
|
)
|
|
163
|
-
logger.info(
|
|
163
|
+
logger.info(
|
|
164
|
+
f"Added {len(imported_sg_ids)} security groups from SSM imports"
|
|
165
|
+
)
|
|
164
166
|
else:
|
|
165
167
|
security_groups.append(
|
|
166
168
|
ec2.SecurityGroup.from_security_group_id(
|
|
@@ -168,10 +170,12 @@ class AutoScalingStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
|
|
|
168
170
|
)
|
|
169
171
|
)
|
|
170
172
|
logger.info(f"Added security group from SSM imports")
|
|
171
|
-
|
|
173
|
+
|
|
172
174
|
# Fallback: Check for direct configuration (backward compatibility)
|
|
173
175
|
elif self.asg_config.security_group_ids:
|
|
174
|
-
logger.warning(
|
|
176
|
+
logger.warning(
|
|
177
|
+
"Using direct security group configuration - consider migrating to SSM imports"
|
|
178
|
+
)
|
|
175
179
|
for idx, sg_id in enumerate(self.asg_config.security_group_ids):
|
|
176
180
|
logger.info(f"Adding security group from direct config: {sg_id}")
|
|
177
181
|
# Handle comma-separated security group IDs
|
|
@@ -180,7 +184,9 @@ class AutoScalingStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
|
|
|
180
184
|
for block_idx, block in enumerate(blocks):
|
|
181
185
|
security_groups.append(
|
|
182
186
|
ec2.SecurityGroup.from_security_group_id(
|
|
183
|
-
self,
|
|
187
|
+
self,
|
|
188
|
+
f"SecurityGroup-Direct-{idx}-{block_idx}",
|
|
189
|
+
block.strip(),
|
|
184
190
|
)
|
|
185
191
|
)
|
|
186
192
|
else:
|
|
@@ -190,8 +196,10 @@ class AutoScalingStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
|
|
|
190
196
|
)
|
|
191
197
|
)
|
|
192
198
|
else:
|
|
193
|
-
logger.warning(
|
|
194
|
-
|
|
199
|
+
logger.warning(
|
|
200
|
+
"No security groups found from SSM imports or direct configuration"
|
|
201
|
+
)
|
|
202
|
+
|
|
195
203
|
return security_groups
|
|
196
204
|
|
|
197
205
|
def _get_vpc_id(self) -> str:
|
|
@@ -200,9 +208,7 @@ class AutoScalingStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
|
|
|
200
208
|
"""
|
|
201
209
|
# Use the centralized VPC resolution from VPCProviderMixin
|
|
202
210
|
vpc = self.resolve_vpc(
|
|
203
|
-
config=self.asg_config,
|
|
204
|
-
deployment=self.deployment,
|
|
205
|
-
workload=self.workload
|
|
211
|
+
config=self.asg_config, deployment=self.deployment, workload=self.workload
|
|
206
212
|
)
|
|
207
213
|
return vpc.vpc_id
|
|
208
214
|
|
|
@@ -211,21 +217,11 @@ class AutoScalingStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
|
|
|
211
217
|
Get subnet IDs using standardized SSM approach.
|
|
212
218
|
"""
|
|
213
219
|
# Primary method: Use standardized SSM imports
|
|
214
|
-
ssm_imports = self._get_ssm_imports()
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
elif hasattr(self, '_get_subnets_from_provider'):
|
|
220
|
-
return self._get_subnets_from_provider()
|
|
221
|
-
|
|
222
|
-
# Final fallback: Direct configuration
|
|
223
|
-
elif hasattr(self.asg_config, 'subnet_ids') and self.asg_config.subnet_ids:
|
|
224
|
-
return self.asg_config.subnet_ids
|
|
225
|
-
|
|
226
|
-
else:
|
|
227
|
-
logger.warning("No subnet IDs found, using default behavior")
|
|
228
|
-
return []
|
|
220
|
+
# ssm_imports = self._get_ssm_imports()
|
|
221
|
+
|
|
222
|
+
subnet_ids = self.get_subnet_ids(self.asg_config)
|
|
223
|
+
|
|
224
|
+
return subnet_ids
|
|
229
225
|
|
|
230
226
|
def _create_instance_role(self, asg_name: str) -> iam.Role:
|
|
231
227
|
"""Create IAM role for EC2 instances"""
|
|
@@ -248,13 +244,14 @@ class AutoScalingStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
|
|
|
248
244
|
def _create_user_data(self) -> ec2.UserData:
|
|
249
245
|
"""Create user data for EC2 instances"""
|
|
250
246
|
user_data = ec2.UserData.for_linux()
|
|
251
|
-
|
|
247
|
+
|
|
252
248
|
# Add basic setup commands
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
249
|
+
# this will break amazon linux 2023 which uses dnf instead of yum
|
|
250
|
+
# user_data.add_commands(
|
|
251
|
+
# "#!/bin/bash",
|
|
252
|
+
# "yum update -y",
|
|
253
|
+
# "yum install -y aws-cfn-bootstrap",
|
|
254
|
+
# )
|
|
258
255
|
|
|
259
256
|
# Add user data commands from configuration
|
|
260
257
|
if self.asg_config.user_data_commands:
|
|
@@ -264,11 +261,13 @@ class AutoScalingStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
|
|
|
264
261
|
for command in self.asg_config.user_data_commands:
|
|
265
262
|
processed_command = command
|
|
266
263
|
# Substitute SSM-imported values
|
|
267
|
-
if "
|
|
268
|
-
cluster_name = ssm_imports["
|
|
269
|
-
processed_command = command.replace(
|
|
264
|
+
if "cluster_name" in ssm_imports and "{{cluster_name}}" in command:
|
|
265
|
+
cluster_name = ssm_imports["cluster_name"]
|
|
266
|
+
processed_command = command.replace(
|
|
267
|
+
"{{cluster_name}}", cluster_name
|
|
268
|
+
)
|
|
270
269
|
processed_commands.append(processed_command)
|
|
271
|
-
|
|
270
|
+
|
|
272
271
|
user_data.add_commands(*processed_commands)
|
|
273
272
|
self.user_data_commands = processed_commands
|
|
274
273
|
|
|
@@ -276,11 +275,11 @@ class AutoScalingStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
|
|
|
276
275
|
if self.ecs_cluster:
|
|
277
276
|
# Use the SSM-imported cluster name if available, otherwise fallback to default format
|
|
278
277
|
ssm_imports = self._get_ssm_imports()
|
|
279
|
-
if "
|
|
280
|
-
cluster_name = ssm_imports["
|
|
278
|
+
if "cluster_name" in ssm_imports:
|
|
279
|
+
cluster_name = ssm_imports["cluster_name"]
|
|
281
280
|
ecs_commands = [
|
|
282
281
|
f"echo 'ECS_CLUSTER={cluster_name}' >> /etc/ecs/ecs.config",
|
|
283
|
-
"systemctl restart ecs"
|
|
282
|
+
"systemctl restart ecs",
|
|
284
283
|
]
|
|
285
284
|
else:
|
|
286
285
|
# Fallback to default naming pattern
|
|
@@ -288,11 +287,13 @@ class AutoScalingStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
|
|
|
288
287
|
"echo 'ECS_CLUSTER={}{}' >> /etc/ecs/ecs.config".format(
|
|
289
288
|
self.deployment.workload_name, self.deployment.environment
|
|
290
289
|
),
|
|
291
|
-
"systemctl restart ecs"
|
|
290
|
+
"systemctl restart ecs",
|
|
292
291
|
]
|
|
293
292
|
user_data.add_commands(*ecs_commands)
|
|
294
293
|
|
|
295
|
-
logger.info(
|
|
294
|
+
logger.info(
|
|
295
|
+
f"Created user data with {len(self.user_data_commands)} custom commands"
|
|
296
|
+
)
|
|
296
297
|
return user_data
|
|
297
298
|
|
|
298
299
|
def _get_or_create_vpc(self) -> ec2.Vpc:
|
|
@@ -300,14 +301,18 @@ class AutoScalingStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
|
|
|
300
301
|
if self._vpc is None:
|
|
301
302
|
vpc_id = self._get_vpc_id()
|
|
302
303
|
subnet_ids = self._get_subnet_ids()
|
|
303
|
-
|
|
304
|
+
|
|
304
305
|
# Create VPC and subnets from imported values
|
|
305
306
|
self._vpc = ec2.Vpc.from_vpc_attributes(
|
|
306
|
-
self,
|
|
307
|
+
self,
|
|
308
|
+
"ImportedVPC",
|
|
307
309
|
vpc_id=vpc_id,
|
|
308
|
-
availability_zones=[
|
|
310
|
+
availability_zones=[
|
|
311
|
+
"us-east-1a",
|
|
312
|
+
"us-east-1b",
|
|
313
|
+
], # Add required availability zones
|
|
309
314
|
)
|
|
310
|
-
|
|
315
|
+
|
|
311
316
|
# Create and store subnets if we have subnet IDs
|
|
312
317
|
self._subnets = []
|
|
313
318
|
if subnet_ids:
|
|
@@ -319,41 +324,40 @@ class AutoScalingStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
|
|
|
319
324
|
else:
|
|
320
325
|
# Use default subnets from VPC
|
|
321
326
|
self._subnets = self._vpc.public_subnets
|
|
322
|
-
|
|
327
|
+
|
|
323
328
|
return self._vpc
|
|
324
329
|
|
|
325
330
|
def _get_subnets(self) -> List[ec2.Subnet]:
|
|
326
331
|
"""Get the subnets from the shared VPC"""
|
|
327
|
-
return getattr(self,
|
|
332
|
+
return getattr(self, "_subnets", [])
|
|
328
333
|
|
|
329
334
|
def _create_ecs_cluster_if_needed(self) -> Optional[ecs.Cluster]:
|
|
330
335
|
"""Create ECS cluster if ECS configuration is detected"""
|
|
331
336
|
# Check if user data contains ECS configuration (use raw config since user_data_commands might not be set yet)
|
|
332
337
|
ecs_detected = False
|
|
333
338
|
if self.asg_config.user_data_commands:
|
|
334
|
-
ecs_detected = any(
|
|
335
|
-
|
|
339
|
+
ecs_detected = any(
|
|
340
|
+
"ECS_CLUSTER" in cmd for cmd in self.asg_config.user_data_commands
|
|
341
|
+
)
|
|
342
|
+
|
|
336
343
|
if ecs_detected:
|
|
337
344
|
ssm_imports = self._get_ssm_imports()
|
|
338
|
-
if "
|
|
339
|
-
cluster_name = ssm_imports["
|
|
340
|
-
|
|
345
|
+
if "cluster_name" in ssm_imports:
|
|
346
|
+
cluster_name = ssm_imports["cluster_name"]
|
|
347
|
+
|
|
341
348
|
# Use the shared VPC
|
|
342
349
|
vpc = self._get_or_create_vpc()
|
|
343
|
-
|
|
350
|
+
|
|
344
351
|
self.ecs_cluster = ecs.Cluster.from_cluster_attributes(
|
|
345
|
-
self,
|
|
346
|
-
"ImportedECSCluster",
|
|
347
|
-
cluster_name=cluster_name,
|
|
348
|
-
vpc=vpc
|
|
352
|
+
self, "ImportedECSCluster", cluster_name=cluster_name, vpc=vpc
|
|
349
353
|
)
|
|
350
354
|
logger.info(f"Connected to existing ECS cluster: {cluster_name}")
|
|
351
|
-
|
|
355
|
+
|
|
352
356
|
return self.ecs_cluster
|
|
353
357
|
|
|
354
358
|
def _create_launch_template(self, asg_name: str) -> ec2.LaunchTemplate:
|
|
355
359
|
"""Create launch template for Auto Scaling Group"""
|
|
356
|
-
|
|
360
|
+
|
|
357
361
|
# Use the configured AMI ID or fall back to appropriate lookup
|
|
358
362
|
if self.asg_config.ami_id:
|
|
359
363
|
# Use explicit AMI ID provided by user
|
|
@@ -367,14 +371,19 @@ class AutoScalingStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
|
|
|
367
371
|
elif self.asg_config.ami_type.upper() == "AMAZON-LINUX-2":
|
|
368
372
|
machine_image = ec2.MachineImage.latest_amazon_linux2()
|
|
369
373
|
elif self.asg_config.ami_type.upper() == "ECS_OPTIMIZED":
|
|
370
|
-
|
|
374
|
+
# Use ECS-optimized AMI from SSM parameter
|
|
375
|
+
from aws_cdk import aws_ssm as ssm
|
|
376
|
+
|
|
377
|
+
machine_image = ec2.MachineImage.from_ssm_parameter(
|
|
378
|
+
parameter_name="/aws/service/ecs/optimized-ami/amazon-linux-2023/recommended/image_id"
|
|
379
|
+
)
|
|
371
380
|
else:
|
|
372
381
|
# Default to latest Amazon Linux
|
|
373
382
|
machine_image = ec2.MachineImage.latest_amazon_linux2023()
|
|
374
383
|
else:
|
|
375
384
|
# Default fallback
|
|
376
385
|
machine_image = ec2.MachineImage.latest_amazon_linux2023()
|
|
377
|
-
|
|
386
|
+
|
|
378
387
|
launch_template = ec2.LaunchTemplate(
|
|
379
388
|
self,
|
|
380
389
|
f"{asg_name}-LaunchTemplate",
|
|
@@ -385,17 +394,27 @@ class AutoScalingStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
|
|
|
385
394
|
security_group=self.security_groups[0] if self.security_groups else None,
|
|
386
395
|
key_name=self.asg_config.key_name,
|
|
387
396
|
detailed_monitoring=self.asg_config.detailed_monitoring,
|
|
388
|
-
block_devices=
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
397
|
+
block_devices=(
|
|
398
|
+
[
|
|
399
|
+
ec2.BlockDevice(
|
|
400
|
+
device_name=block_device.get("device_name", "/dev/xvda"),
|
|
401
|
+
volume=ec2.BlockDeviceVolume.ebs(
|
|
402
|
+
volume_size=block_device.get("volume_size", 8),
|
|
403
|
+
volume_type=getattr(
|
|
404
|
+
ec2.EbsDeviceVolumeType,
|
|
405
|
+
block_device.get("volume_type", "GP3").upper(),
|
|
406
|
+
),
|
|
407
|
+
delete_on_termination=block_device.get(
|
|
408
|
+
"delete_on_termination", True
|
|
409
|
+
),
|
|
410
|
+
encrypted=block_device.get("encrypted", False),
|
|
411
|
+
),
|
|
396
412
|
)
|
|
397
|
-
|
|
398
|
-
|
|
413
|
+
for block_device in self.asg_config.block_devices
|
|
414
|
+
]
|
|
415
|
+
if self.asg_config.block_devices
|
|
416
|
+
else None
|
|
417
|
+
),
|
|
399
418
|
)
|
|
400
419
|
|
|
401
420
|
logger.info(f"Created launch template: {launch_template.launch_template_name}")
|
|
@@ -407,6 +426,22 @@ class AutoScalingStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
|
|
|
407
426
|
vpc = self._get_or_create_vpc()
|
|
408
427
|
subnets = self._get_subnets()
|
|
409
428
|
|
|
429
|
+
health_checks = (
|
|
430
|
+
# ELB + EC2 (EC2 is always included; ELB is “additional”)
|
|
431
|
+
HealthChecks.with_additional_checks(
|
|
432
|
+
additional_types=[AdditionalHealthCheckType.ELB],
|
|
433
|
+
grace_period=Duration.seconds(
|
|
434
|
+
self.asg_config.health_check_grace_period
|
|
435
|
+
),
|
|
436
|
+
)
|
|
437
|
+
if self.asg_config.health_check_type.upper() == "ELB"
|
|
438
|
+
# EC2-only
|
|
439
|
+
else HealthChecks.ec2(
|
|
440
|
+
grace_period=Duration.seconds(
|
|
441
|
+
self.asg_config.health_check_grace_period
|
|
442
|
+
),
|
|
443
|
+
)
|
|
444
|
+
)
|
|
410
445
|
auto_scaling_group = autoscaling.AutoScalingGroup(
|
|
411
446
|
self,
|
|
412
447
|
f"{asg_name}-ASG",
|
|
@@ -416,18 +451,18 @@ class AutoScalingStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
|
|
|
416
451
|
min_capacity=self.asg_config.min_capacity,
|
|
417
452
|
max_capacity=self.asg_config.max_capacity,
|
|
418
453
|
desired_capacity=self.asg_config.desired_capacity,
|
|
419
|
-
|
|
420
|
-
grace=cdk.Duration.seconds(self.asg_config.health_check_grace_period)
|
|
421
|
-
) if self.asg_config.health_check_type.upper() == "ELB" else autoscaling.HealthCheck.ec2(
|
|
422
|
-
grace=cdk.Duration.seconds(self.asg_config.health_check_grace_period)
|
|
423
|
-
),
|
|
454
|
+
health_checks=health_checks,
|
|
424
455
|
cooldown=cdk.Duration.seconds(self.asg_config.cooldown),
|
|
425
456
|
termination_policies=[
|
|
426
|
-
getattr(autoscaling.TerminationPolicy, policy.upper())
|
|
457
|
+
getattr(autoscaling.TerminationPolicy, policy.upper())
|
|
427
458
|
for policy in self.asg_config.termination_policies
|
|
428
459
|
],
|
|
429
460
|
)
|
|
430
461
|
|
|
462
|
+
# Add instance refresh if configured
|
|
463
|
+
if self.asg_config.instance_refresh:
|
|
464
|
+
self._configure_instance_refresh(auto_scaling_group)
|
|
465
|
+
|
|
431
466
|
# Attach target groups if configured
|
|
432
467
|
self._attach_target_groups(auto_scaling_group)
|
|
433
468
|
|
|
@@ -449,7 +484,7 @@ class AutoScalingStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
|
|
|
449
484
|
def _get_target_group_arns(self) -> List[str]:
|
|
450
485
|
"""Get target group ARNs using standardized SSM approach"""
|
|
451
486
|
target_group_arns = []
|
|
452
|
-
|
|
487
|
+
|
|
453
488
|
# Use standardized SSM imports
|
|
454
489
|
ssm_imports = self._get_ssm_imports()
|
|
455
490
|
if "target_group_arns" in ssm_imports:
|
|
@@ -458,11 +493,11 @@ class AutoScalingStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
|
|
|
458
493
|
target_group_arns.extend(imported_arns)
|
|
459
494
|
else:
|
|
460
495
|
target_group_arns.append(imported_arns)
|
|
461
|
-
|
|
496
|
+
|
|
462
497
|
# Fallback: Direct configuration
|
|
463
498
|
elif self.asg_config.target_group_arns:
|
|
464
499
|
target_group_arns.extend(self.asg_config.target_group_arns)
|
|
465
|
-
|
|
500
|
+
|
|
466
501
|
return target_group_arns
|
|
467
502
|
|
|
468
503
|
def _add_scaling_policies(self) -> None:
|
|
@@ -482,30 +517,40 @@ class AutoScalingStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
|
|
|
482
517
|
target_value=policy_config.get("target_cpu", 70),
|
|
483
518
|
predefined_metric_specification=autoscaling.CfnScalingPolicy.PredefinedMetricSpecificationProperty(
|
|
484
519
|
predefined_metric_type="ASGAverageCPUUtilization"
|
|
485
|
-
)
|
|
486
|
-
)
|
|
520
|
+
),
|
|
521
|
+
),
|
|
487
522
|
)
|
|
488
523
|
logger.info("Added CPU utilization scaling policy")
|
|
489
524
|
|
|
490
525
|
def _add_update_policy(self) -> None:
|
|
491
526
|
"""Add update policy to the Auto Scaling Group"""
|
|
492
527
|
update_policy = self.asg_config.update_policy
|
|
493
|
-
|
|
528
|
+
|
|
494
529
|
if not update_policy:
|
|
530
|
+
# No update policy configured, don't add one
|
|
495
531
|
return
|
|
496
|
-
|
|
532
|
+
|
|
497
533
|
# Get the underlying CloudFormation resource to add update policy
|
|
498
534
|
cfn_asg = self.auto_scaling_group.node.default_child
|
|
499
|
-
|
|
500
|
-
#
|
|
501
|
-
cfn_asg
|
|
535
|
+
|
|
536
|
+
# Get CDK's default policy first (if any)
|
|
537
|
+
default_policy = getattr(cfn_asg, "update_policy", {})
|
|
538
|
+
|
|
539
|
+
# Merge with defaults, then use the robust add_override method
|
|
540
|
+
merged_policy = {
|
|
541
|
+
**default_policy, # Preserve CDK defaults
|
|
502
542
|
"AutoScalingRollingUpdate": {
|
|
503
|
-
"MinInstancesInService": update_policy.get(
|
|
543
|
+
"MinInstancesInService": update_policy.get(
|
|
544
|
+
"min_instances_in_service", 1
|
|
545
|
+
),
|
|
504
546
|
"MaxBatchSize": update_policy.get("max_batch_size", 1),
|
|
505
|
-
"PauseTime": f"PT{update_policy.get('pause_time', 300)}S"
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
|
|
547
|
+
"PauseTime": f"PT{update_policy.get('pause_time', 300)}S",
|
|
548
|
+
},
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
# Use the robust CDK-documented approach
|
|
552
|
+
cfn_asg.add_override("UpdatePolicy", merged_policy)
|
|
553
|
+
|
|
509
554
|
logger.info("Added rolling update policy to Auto Scaling Group")
|
|
510
555
|
|
|
511
556
|
def _export_ssm_parameters(self) -> None:
|
|
@@ -521,10 +566,73 @@ class AutoScalingStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
|
|
|
521
566
|
}
|
|
522
567
|
|
|
523
568
|
# Export using standardized SSM mixin
|
|
524
|
-
exported_params = self.
|
|
525
|
-
|
|
569
|
+
exported_params = self.export_ssm_parameters(resource_values)
|
|
570
|
+
|
|
526
571
|
logger.info(f"Exported SSM parameters: {exported_params}")
|
|
527
572
|
|
|
573
|
+
def _configure_instance_refresh(self, asg: autoscaling.AutoScalingGroup) -> None:
|
|
574
|
+
"""Configure instance refresh for rolling updates"""
|
|
575
|
+
instance_refresh_config = self.asg_config.instance_refresh
|
|
576
|
+
|
|
577
|
+
if not instance_refresh_config.get("enabled", False):
|
|
578
|
+
return
|
|
579
|
+
|
|
580
|
+
logger.warning("Instance refresh is not supported in this version of the CDK")
|
|
581
|
+
return
|
|
582
|
+
|
|
583
|
+
# Get the CloudFormation ASG resource
|
|
584
|
+
cfn_asg = asg.node.default_child
|
|
585
|
+
|
|
586
|
+
# Configure instance refresh using CloudFormation UpdatePolicy
|
|
587
|
+
# UpdatePolicy is added at the resource level, not as a property
|
|
588
|
+
update_policy = {
|
|
589
|
+
"AutoScalingRollingUpdate": {
|
|
590
|
+
"PauseTime": "PT300S", # 5 minutes pause
|
|
591
|
+
"MinInstancesInService": "1",
|
|
592
|
+
"MaxBatchSize": "1",
|
|
593
|
+
"WaitOnResourceSignals": True,
|
|
594
|
+
"SuspendProcesses": [
|
|
595
|
+
"HealthCheck",
|
|
596
|
+
"ReplaceUnhealthy",
|
|
597
|
+
"AZRebalance",
|
|
598
|
+
"AlarmNotification",
|
|
599
|
+
"ScheduledActions",
|
|
600
|
+
],
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
# # Apply instance refresh using CloudFormation's cfn_options.update_policy
|
|
605
|
+
# cfn_asg.cfn_options.update_policy = cdk.CfnUpdatePolicy.from_rolling_update(
|
|
606
|
+
# pause_time=cdk.Duration.seconds(300),
|
|
607
|
+
# min_instances_in_service=1,
|
|
608
|
+
# max_batch_size=1,
|
|
609
|
+
# wait_on_resource_signals=True
|
|
610
|
+
# )
|
|
611
|
+
|
|
612
|
+
# Grab the L1 to attach UpdatePolicy.InstanceRefresh
|
|
613
|
+
cfn_asg: autoscaling.CfnAutoScalingGroup = asg.node.default_child
|
|
614
|
+
|
|
615
|
+
# cfn_asg.cfn_options.update_policy = CfnUpdatePolicy.from_auto_scaling_instance_refresh(
|
|
616
|
+
# # Triggers tell CFN *what* changes should start a refresh
|
|
617
|
+
# triggers=[CfnUpdatePolicy.InstanceRefreshTrigger.LAUNCH_TEMPLATE],
|
|
618
|
+
# preferences=CfnUpdatePolicy.InstanceRefreshPreferences(
|
|
619
|
+
# # warmup is like “grace” before counting a new instance healthy
|
|
620
|
+
# instance_warmup=Duration.minutes(5),
|
|
621
|
+
# # how aggressive the refresh is; 90 keeps capacity high
|
|
622
|
+
# min_healthy_percentage=90,
|
|
623
|
+
# # skip instances that already match the new LT (fast when only userdata/env tweaked)
|
|
624
|
+
# skip_matching=True,
|
|
625
|
+
# # optional: put instances in Standby first; default is rolling terminate/launch
|
|
626
|
+
# # standby_instances=CfnUpdatePolicy.StandbyInstances.TERMINATE,
|
|
627
|
+
# # checkpoint_percentages=[25, 50, 75], # optional: progressive checkpoints
|
|
628
|
+
# # checkpoint_delay=Duration.minutes(2), # optional delay at checkpoints
|
|
629
|
+
# ),
|
|
630
|
+
# )
|
|
631
|
+
logger.info(f"Configured instance refresh via CDK CfnUpdatePolicy")
|
|
632
|
+
|
|
633
|
+
# Note: This provides rolling update functionality similar to instance refresh
|
|
634
|
+
# For true instance refresh with preferences, we would need CDK v2.80+ or custom CloudFormation
|
|
635
|
+
|
|
528
636
|
|
|
529
637
|
# Backward compatibility alias
|
|
530
638
|
AutoScalingStackStandardized = AutoScalingStack
|