cdk-factory 0.19.19__py3-none-any.whl → 0.20.5__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.
Files changed (20) hide show
  1. cdk_factory/stack_library/auto_scaling/auto_scaling_stack.py +122 -85
  2. cdk_factory/stack_library/ecs/ecs_cluster_stack.py +2 -2
  3. cdk_factory/stack_library/ecs/ecs_service_stack.py +2 -0
  4. cdk_factory/stack_library/lambda_edge/functions/README.md +0 -0
  5. cdk_factory/stack_library/lambda_edge/functions/log_retention_manager/__init__.py +33 -0
  6. cdk_factory/stack_library/lambda_edge/functions/log_retention_manager/app.py +30 -0
  7. cdk_factory/stack_library/lambda_edge/functions/log_retention_manager/edge_log_retention.py +85 -0
  8. cdk_factory/stack_library/lambda_edge/functions/log_retention_manager/requirements.txt +2 -0
  9. cdk_factory/stack_library/lambda_edge/functions/log_retention_manager/test.py +22 -0
  10. cdk_factory/stack_library/lambda_edge/lambda_edge_log_retention_stack.py +0 -0
  11. cdk_factory/stack_library/lambda_edge/lambda_edge_stack.py +94 -8
  12. cdk_factory/stack_library/load_balancer/load_balancer_stack.py +4 -0
  13. cdk_factory/stack_library/route53/route53_stack.py +97 -133
  14. cdk_factory/version.py +1 -1
  15. {cdk_factory-0.19.19.dist-info → cdk_factory-0.20.5.dist-info}/METADATA +1 -1
  16. {cdk_factory-0.19.19.dist-info → cdk_factory-0.20.5.dist-info}/RECORD +19 -13
  17. cdk_factory/stack_library/lambda_edge/EDGE_LOG_RETENTION_TODO.md +0 -226
  18. {cdk_factory-0.19.19.dist-info → cdk_factory-0.20.5.dist-info}/WHEEL +0 -0
  19. {cdk_factory-0.19.19.dist-info → cdk_factory-0.20.5.dist-info}/entry_points.txt +0 -0
  20. {cdk_factory-0.19.19.dist-info → cdk_factory-0.20.5.dist-info}/licenses/LICENSE +0 -0
@@ -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, Stack, CfnUpdatePolicy
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
@@ -98,7 +98,7 @@ class AutoScalingStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
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
@@ -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(f"Added {len(imported_sg_ids)} security groups from SSM imports")
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("Using direct security group configuration - consider migrating to SSM imports")
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, f"SecurityGroup-Direct-{idx}-{block_idx}", block.strip()
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("No security groups found from SSM imports or direct configuration")
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
 
@@ -214,9 +220,8 @@ class AutoScalingStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
214
220
  # ssm_imports = self._get_ssm_imports()
215
221
 
216
222
  subnet_ids = self.get_subnet_ids(self.asg_config)
217
-
223
+
218
224
  return subnet_ids
219
-
220
225
 
221
226
  def _create_instance_role(self, asg_name: str) -> iam.Role:
222
227
  """Create IAM role for EC2 instances"""
@@ -239,7 +244,7 @@ class AutoScalingStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
239
244
  def _create_user_data(self) -> ec2.UserData:
240
245
  """Create user data for EC2 instances"""
241
246
  user_data = ec2.UserData.for_linux()
242
-
247
+
243
248
  # Add basic setup commands
244
249
  # this will break amazon linux 2023 which uses dnf instead of yum
245
250
  # user_data.add_commands(
@@ -258,9 +263,11 @@ class AutoScalingStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
258
263
  # Substitute SSM-imported values
259
264
  if "cluster_name" in ssm_imports and "{{cluster_name}}" in command:
260
265
  cluster_name = ssm_imports["cluster_name"]
261
- processed_command = command.replace("{{cluster_name}}", cluster_name)
266
+ processed_command = command.replace(
267
+ "{{cluster_name}}", cluster_name
268
+ )
262
269
  processed_commands.append(processed_command)
263
-
270
+
264
271
  user_data.add_commands(*processed_commands)
265
272
  self.user_data_commands = processed_commands
266
273
 
@@ -272,7 +279,7 @@ class AutoScalingStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
272
279
  cluster_name = ssm_imports["cluster_name"]
273
280
  ecs_commands = [
274
281
  f"echo 'ECS_CLUSTER={cluster_name}' >> /etc/ecs/ecs.config",
275
- "systemctl restart ecs"
282
+ "systemctl restart ecs",
276
283
  ]
277
284
  else:
278
285
  # Fallback to default naming pattern
@@ -280,11 +287,13 @@ class AutoScalingStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
280
287
  "echo 'ECS_CLUSTER={}{}' >> /etc/ecs/ecs.config".format(
281
288
  self.deployment.workload_name, self.deployment.environment
282
289
  ),
283
- "systemctl restart ecs"
290
+ "systemctl restart ecs",
284
291
  ]
285
292
  user_data.add_commands(*ecs_commands)
286
293
 
287
- logger.info(f"Created user data with {len(self.user_data_commands)} custom commands")
294
+ logger.info(
295
+ f"Created user data with {len(self.user_data_commands)} custom commands"
296
+ )
288
297
  return user_data
289
298
 
290
299
  def _get_or_create_vpc(self) -> ec2.Vpc:
@@ -292,14 +301,18 @@ class AutoScalingStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
292
301
  if self._vpc is None:
293
302
  vpc_id = self._get_vpc_id()
294
303
  subnet_ids = self._get_subnet_ids()
295
-
304
+
296
305
  # Create VPC and subnets from imported values
297
306
  self._vpc = ec2.Vpc.from_vpc_attributes(
298
- self, "ImportedVPC",
307
+ self,
308
+ "ImportedVPC",
299
309
  vpc_id=vpc_id,
300
- availability_zones=["us-east-1a", "us-east-1b"] # Add required availability zones
310
+ availability_zones=[
311
+ "us-east-1a",
312
+ "us-east-1b",
313
+ ], # Add required availability zones
301
314
  )
302
-
315
+
303
316
  # Create and store subnets if we have subnet IDs
304
317
  self._subnets = []
305
318
  if subnet_ids:
@@ -311,41 +324,40 @@ class AutoScalingStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
311
324
  else:
312
325
  # Use default subnets from VPC
313
326
  self._subnets = self._vpc.public_subnets
314
-
327
+
315
328
  return self._vpc
316
329
 
317
330
  def _get_subnets(self) -> List[ec2.Subnet]:
318
331
  """Get the subnets from the shared VPC"""
319
- return getattr(self, '_subnets', [])
332
+ return getattr(self, "_subnets", [])
320
333
 
321
334
  def _create_ecs_cluster_if_needed(self) -> Optional[ecs.Cluster]:
322
335
  """Create ECS cluster if ECS configuration is detected"""
323
336
  # Check if user data contains ECS configuration (use raw config since user_data_commands might not be set yet)
324
337
  ecs_detected = False
325
338
  if self.asg_config.user_data_commands:
326
- ecs_detected = any("ECS_CLUSTER" in cmd for cmd in self.asg_config.user_data_commands)
327
-
339
+ ecs_detected = any(
340
+ "ECS_CLUSTER" in cmd for cmd in self.asg_config.user_data_commands
341
+ )
342
+
328
343
  if ecs_detected:
329
344
  ssm_imports = self._get_ssm_imports()
330
345
  if "cluster_name" in ssm_imports:
331
346
  cluster_name = ssm_imports["cluster_name"]
332
-
347
+
333
348
  # Use the shared VPC
334
349
  vpc = self._get_or_create_vpc()
335
-
350
+
336
351
  self.ecs_cluster = ecs.Cluster.from_cluster_attributes(
337
- self,
338
- "ImportedECSCluster",
339
- cluster_name=cluster_name,
340
- vpc=vpc
352
+ self, "ImportedECSCluster", cluster_name=cluster_name, vpc=vpc
341
353
  )
342
354
  logger.info(f"Connected to existing ECS cluster: {cluster_name}")
343
-
355
+
344
356
  return self.ecs_cluster
345
357
 
346
358
  def _create_launch_template(self, asg_name: str) -> ec2.LaunchTemplate:
347
359
  """Create launch template for Auto Scaling Group"""
348
-
360
+
349
361
  # Use the configured AMI ID or fall back to appropriate lookup
350
362
  if self.asg_config.ami_id:
351
363
  # Use explicit AMI ID provided by user
@@ -361,6 +373,7 @@ class AutoScalingStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
361
373
  elif self.asg_config.ami_type.upper() == "ECS_OPTIMIZED":
362
374
  # Use ECS-optimized AMI from SSM parameter
363
375
  from aws_cdk import aws_ssm as ssm
376
+
364
377
  machine_image = ec2.MachineImage.from_ssm_parameter(
365
378
  parameter_name="/aws/service/ecs/optimized-ami/amazon-linux-2023/recommended/image_id"
366
379
  )
@@ -370,7 +383,7 @@ class AutoScalingStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
370
383
  else:
371
384
  # Default fallback
372
385
  machine_image = ec2.MachineImage.latest_amazon_linux2023()
373
-
386
+
374
387
  launch_template = ec2.LaunchTemplate(
375
388
  self,
376
389
  f"{asg_name}-LaunchTemplate",
@@ -381,17 +394,27 @@ class AutoScalingStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
381
394
  security_group=self.security_groups[0] if self.security_groups else None,
382
395
  key_name=self.asg_config.key_name,
383
396
  detailed_monitoring=self.asg_config.detailed_monitoring,
384
- block_devices=[
385
- ec2.BlockDevice(
386
- device_name=block_device.get("device_name", "/dev/xvda"),
387
- volume=ec2.BlockDeviceVolume.ebs(
388
- volume_size=block_device.get("volume_size", 8),
389
- volume_type=getattr(ec2.EbsDeviceVolumeType, block_device.get("volume_type", "GP3").upper()),
390
- delete_on_termination=block_device.get("delete_on_termination", True),
391
- encrypted=block_device.get("encrypted", False),
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
+ ),
392
412
  )
393
- ) for block_device in self.asg_config.block_devices
394
- ] if self.asg_config.block_devices else None,
413
+ for block_device in self.asg_config.block_devices
414
+ ]
415
+ if self.asg_config.block_devices
416
+ else None
417
+ ),
395
418
  )
396
419
 
397
420
  logger.info(f"Created launch template: {launch_template.launch_template_name}")
@@ -403,6 +426,22 @@ class AutoScalingStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
403
426
  vpc = self._get_or_create_vpc()
404
427
  subnets = self._get_subnets()
405
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
+ )
406
445
  auto_scaling_group = autoscaling.AutoScalingGroup(
407
446
  self,
408
447
  f"{asg_name}-ASG",
@@ -412,14 +451,10 @@ class AutoScalingStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
412
451
  min_capacity=self.asg_config.min_capacity,
413
452
  max_capacity=self.asg_config.max_capacity,
414
453
  desired_capacity=self.asg_config.desired_capacity,
415
- health_check=autoscaling.HealthCheck.elb(
416
- grace=cdk.Duration.seconds(self.asg_config.health_check_grace_period)
417
- ) if self.asg_config.health_check_type.upper() == "ELB" else autoscaling.HealthCheck.ec2(
418
- grace=cdk.Duration.seconds(self.asg_config.health_check_grace_period)
419
- ),
454
+ health_checks=health_checks,
420
455
  cooldown=cdk.Duration.seconds(self.asg_config.cooldown),
421
456
  termination_policies=[
422
- getattr(autoscaling.TerminationPolicy, policy.upper())
457
+ getattr(autoscaling.TerminationPolicy, policy.upper())
423
458
  for policy in self.asg_config.termination_policies
424
459
  ],
425
460
  )
@@ -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,38 +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:
495
530
  # No update policy configured, don't add one
496
531
  return
497
-
532
+
498
533
  # Get the underlying CloudFormation resource to add update policy
499
534
  cfn_asg = self.auto_scaling_group.node.default_child
500
-
535
+
501
536
  # Get CDK's default policy first (if any)
502
- default_policy = getattr(cfn_asg, 'update_policy', {})
503
-
537
+ default_policy = getattr(cfn_asg, "update_policy", {})
538
+
504
539
  # Merge with defaults, then use the robust add_override method
505
540
  merged_policy = {
506
541
  **default_policy, # Preserve CDK defaults
507
542
  "AutoScalingRollingUpdate": {
508
- "MinInstancesInService": update_policy.get("min_instances_in_service", 1),
543
+ "MinInstancesInService": update_policy.get(
544
+ "min_instances_in_service", 1
545
+ ),
509
546
  "MaxBatchSize": update_policy.get("max_batch_size", 1),
510
- "PauseTime": f"PT{update_policy.get('pause_time', 300)}S"
511
- }
547
+ "PauseTime": f"PT{update_policy.get('pause_time', 300)}S",
548
+ },
512
549
  }
513
-
550
+
514
551
  # Use the robust CDK-documented approach
515
552
  cfn_asg.add_override("UpdatePolicy", merged_policy)
516
-
553
+
517
554
  logger.info("Added rolling update policy to Auto Scaling Group")
518
555
 
519
556
  def _export_ssm_parameters(self) -> None:
@@ -530,22 +567,22 @@ class AutoScalingStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
530
567
 
531
568
  # Export using standardized SSM mixin
532
569
  exported_params = self.export_ssm_parameters(resource_values)
533
-
570
+
534
571
  logger.info(f"Exported SSM parameters: {exported_params}")
535
572
 
536
573
  def _configure_instance_refresh(self, asg: autoscaling.AutoScalingGroup) -> None:
537
574
  """Configure instance refresh for rolling updates"""
538
575
  instance_refresh_config = self.asg_config.instance_refresh
539
-
576
+
540
577
  if not instance_refresh_config.get("enabled", False):
541
578
  return
542
579
 
543
580
  logger.warning("Instance refresh is not supported in this version of the CDK")
544
581
  return
545
-
582
+
546
583
  # Get the CloudFormation ASG resource
547
584
  cfn_asg = asg.node.default_child
548
-
585
+
549
586
  # Configure instance refresh using CloudFormation UpdatePolicy
550
587
  # UpdatePolicy is added at the resource level, not as a property
551
588
  update_policy = {
@@ -559,11 +596,11 @@ class AutoScalingStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
559
596
  "ReplaceUnhealthy",
560
597
  "AZRebalance",
561
598
  "AlarmNotification",
562
- "ScheduledActions"
563
- ]
599
+ "ScheduledActions",
600
+ ],
564
601
  }
565
602
  }
566
-
603
+
567
604
  # # Apply instance refresh using CloudFormation's cfn_options.update_policy
568
605
  # cfn_asg.cfn_options.update_policy = cdk.CfnUpdatePolicy.from_rolling_update(
569
606
  # pause_time=cdk.Duration.seconds(300),
@@ -571,7 +608,7 @@ class AutoScalingStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
571
608
  # max_batch_size=1,
572
609
  # wait_on_resource_signals=True
573
610
  # )
574
-
611
+
575
612
  # Grab the L1 to attach UpdatePolicy.InstanceRefresh
576
613
  cfn_asg: autoscaling.CfnAutoScalingGroup = asg.node.default_child
577
614
 
@@ -592,7 +629,7 @@ class AutoScalingStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
592
629
  # ),
593
630
  # )
594
631
  logger.info(f"Configured instance refresh via CDK CfnUpdatePolicy")
595
-
632
+
596
633
  # Note: This provides rolling update functionality similar to instance refresh
597
634
  # For true instance refresh with preferences, we would need CDK v2.80+ or custom CloudFormation
598
635
 
@@ -136,7 +136,7 @@ class EcsClusterStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
136
136
 
137
137
  # Add container insights if enabled
138
138
  if self.ecs_config.container_insights:
139
- cluster_settings.append({"name": "containerInsights", "value": "enabled"})
139
+ cluster_settings.append({"name": "containerInsightsV2", "value": "enabled"})
140
140
 
141
141
  # Add custom cluster settings
142
142
  if self.ecs_config.cluster_settings:
@@ -151,7 +151,7 @@ class EcsClusterStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
151
151
  "ECSCluster",
152
152
  cluster_name=self.ecs_config.name,
153
153
  vpc=self.vpc,
154
- container_insights=self.ecs_config.container_insights,
154
+ container_insights_v2=ecs.ContainerInsights.ENABLED if self.ecs_config.container_insights else ecs.ContainerInsights.DISABLED,
155
155
  default_cloud_map_namespace=(
156
156
  self.ecs_config.cloud_map_namespace
157
157
  if self.ecs_config.cloud_map_namespace
@@ -241,6 +241,7 @@ class EcsServiceStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
241
241
  network_mode=ecs.NetworkMode(network_mode.upper()) if network_mode else ecs.NetworkMode.BRIDGE,
242
242
  execution_role=execution_role,
243
243
  task_role=task_role,
244
+ inference_accelerators=None
244
245
  )
245
246
  else:
246
247
  # Fargate task definition
@@ -252,6 +253,7 @@ class EcsServiceStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
252
253
  memory_limit_mib=int(self.ecs_config.memory),
253
254
  execution_role=execution_role,
254
255
  task_role=task_role,
256
+ inference_accelerators=None
255
257
  )
256
258
 
257
259
  # Add volumes to task definition
@@ -0,0 +1,33 @@
1
+ """
2
+ Lambda@Edge Log Retention Manager
3
+
4
+ A utility for managing log retention policies across Lambda@Edge functions
5
+ deployed in multiple AWS regions. Lambda@Edge functions automatically create
6
+ log groups in all regions where CloudFront edge locations exist, making
7
+ manual retention management challenging.
8
+
9
+ This module provides functionality to:
10
+ - Discover Lambda@Edge log groups across all AWS regions
11
+ - Set uniform retention policies
12
+ - Report on current storage usage
13
+ - Support dry-run mode for safe testing
14
+
15
+ Usage:
16
+ from edge_log_retention import set_edge_log_retention
17
+
18
+ # Dry run to see what would be changed
19
+ log_groups = set_edge_log_retention(retention_days=7, dry_run=True)
20
+
21
+ # Apply retention policies
22
+ set_edge_log_retention(retention_days=7, dry_run=False)
23
+ """
24
+
25
+ from .edge_log_retention import set_edge_log_retention
26
+
27
+ __version__ = "1.0.0"
28
+ __author__ = "CDK-Factory"
29
+ __license__ = "MIT"
30
+
31
+ __all__ = [
32
+ "set_edge_log_retention"
33
+ ]
@@ -0,0 +1,30 @@
1
+ from edge_log_retention import set_edge_log_retention
2
+ from aws_lambda_powertools import Logger
3
+ import json
4
+
5
+ logger = Logger(service="LambdaEdgeLogRetentionManager")
6
+
7
+ def lambda_handler(event, context):
8
+ """
9
+ Lambda handler for EventBridge scheduled log retention management
10
+ """
11
+ # Extract parameters from EventBridge detail section
12
+
13
+ try:
14
+ logger.info(f"Event: {event}")
15
+
16
+ detail = event.get('detail', {})
17
+ days = int(detail.get('days', 7))
18
+ dry_run = str(detail.get('dry_run', True)).lower() == 'true'
19
+
20
+ set_edge_log_retention(retention_days=days, dry_run=dry_run)
21
+ except Exception as e:
22
+
23
+ logger.error(f"Error: {e}, event: {event}, detail: {detail}")
24
+
25
+
26
+ return {
27
+ 'statusCode': 200,
28
+ 'body': json.dumps('Log retention management completed successfully')
29
+ }
30
+