cdk-factory 0.16.15__py3-none-any.whl → 0.20.0__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.

Files changed (66) hide show
  1. cdk_factory/configurations/base_config.py +23 -24
  2. cdk_factory/configurations/cdk_config.py +1 -1
  3. cdk_factory/configurations/deployment.py +12 -0
  4. cdk_factory/configurations/devops.py +1 -1
  5. cdk_factory/configurations/resources/acm.py +9 -2
  6. cdk_factory/configurations/resources/auto_scaling.py +7 -5
  7. cdk_factory/configurations/resources/cloudfront.py +7 -2
  8. cdk_factory/configurations/resources/ecr.py +1 -1
  9. cdk_factory/configurations/resources/ecs_cluster.py +12 -5
  10. cdk_factory/configurations/resources/ecs_service.py +30 -3
  11. cdk_factory/configurations/resources/lambda_edge.py +18 -4
  12. cdk_factory/configurations/resources/load_balancer.py +8 -9
  13. cdk_factory/configurations/resources/monitoring.py +8 -3
  14. cdk_factory/configurations/resources/rds.py +8 -9
  15. cdk_factory/configurations/resources/route53.py +5 -0
  16. cdk_factory/configurations/resources/rum.py +7 -2
  17. cdk_factory/configurations/resources/s3.py +10 -2
  18. cdk_factory/configurations/resources/security_group_full_stack.py +7 -8
  19. cdk_factory/configurations/resources/vpc.py +19 -0
  20. cdk_factory/configurations/workload.py +32 -2
  21. cdk_factory/constructs/cloudfront/cloudfront_distribution_construct.py +1 -1
  22. cdk_factory/constructs/ecr/ecr_construct.py +9 -2
  23. cdk_factory/constructs/lambdas/policies/policy_docs.py +4 -4
  24. cdk_factory/interfaces/istack.py +4 -4
  25. cdk_factory/interfaces/networked_stack_mixin.py +6 -6
  26. cdk_factory/interfaces/standardized_ssm_mixin.py +684 -0
  27. cdk_factory/interfaces/vpc_provider_mixin.py +64 -33
  28. cdk_factory/lambdas/edge/ip_gate/handler.py +42 -40
  29. cdk_factory/pipeline/pipeline_factory.py +3 -3
  30. cdk_factory/stack_library/__init__.py +3 -2
  31. cdk_factory/stack_library/acm/acm_stack.py +7 -17
  32. cdk_factory/stack_library/api_gateway/api_gateway_stack.py +84 -59
  33. cdk_factory/stack_library/auto_scaling/auto_scaling_stack.py +454 -537
  34. cdk_factory/stack_library/cloudfront/cloudfront_stack.py +76 -22
  35. cdk_factory/stack_library/code_artifact/code_artifact_stack.py +5 -27
  36. cdk_factory/stack_library/cognito/cognito_stack.py +152 -92
  37. cdk_factory/stack_library/dynamodb/dynamodb_stack.py +19 -15
  38. cdk_factory/stack_library/ecr/ecr_stack.py +2 -2
  39. cdk_factory/stack_library/ecs/__init__.py +1 -3
  40. cdk_factory/stack_library/ecs/ecs_cluster_stack.py +159 -75
  41. cdk_factory/stack_library/ecs/ecs_service_stack.py +59 -52
  42. cdk_factory/stack_library/lambda_edge/EDGE_LOG_RETENTION_TODO.md +226 -0
  43. cdk_factory/stack_library/lambda_edge/LAMBDA_EDGE_LOG_RETENTION_BLOG.md +215 -0
  44. cdk_factory/stack_library/lambda_edge/lambda_edge_stack.py +240 -83
  45. cdk_factory/stack_library/load_balancer/load_balancer_stack.py +139 -212
  46. cdk_factory/stack_library/rds/rds_stack.py +74 -98
  47. cdk_factory/stack_library/route53/route53_stack.py +246 -40
  48. cdk_factory/stack_library/rum/rum_stack.py +108 -91
  49. cdk_factory/stack_library/security_group/security_group_full_stack.py +10 -53
  50. cdk_factory/stack_library/security_group/security_group_stack.py +12 -19
  51. cdk_factory/stack_library/simple_queue_service/sqs_stack.py +1 -34
  52. cdk_factory/stack_library/stack_base.py +5 -0
  53. cdk_factory/stack_library/vpc/vpc_stack.py +171 -130
  54. cdk_factory/stack_library/websites/static_website_stack.py +7 -3
  55. cdk_factory/utilities/api_gateway_integration_utility.py +24 -16
  56. cdk_factory/utilities/environment_services.py +5 -5
  57. cdk_factory/utilities/json_loading_utility.py +1 -1
  58. cdk_factory/validation/config_validator.py +483 -0
  59. cdk_factory/version.py +1 -1
  60. {cdk_factory-0.16.15.dist-info → cdk_factory-0.20.0.dist-info}/METADATA +1 -1
  61. {cdk_factory-0.16.15.dist-info → cdk_factory-0.20.0.dist-info}/RECORD +64 -62
  62. cdk_factory/interfaces/enhanced_ssm_parameter_mixin.py +0 -321
  63. cdk_factory/interfaces/ssm_parameter_mixin.py +0 -454
  64. {cdk_factory-0.16.15.dist-info → cdk_factory-0.20.0.dist-info}/WHEEL +0 -0
  65. {cdk_factory-0.16.15.dist-info → cdk_factory-0.20.0.dist-info}/entry_points.txt +0 -0
  66. {cdk_factory-0.16.15.dist-info → cdk_factory-0.20.0.dist-info}/licenses/LICENSE +0 -0
@@ -15,9 +15,7 @@ from aws_cdk import Size
15
15
  from aws_cdk import aws_lambda as _lambda
16
16
  from constructs import Construct
17
17
  from cdk_factory.interfaces.istack import IStack
18
- from cdk_factory.interfaces.enhanced_ssm_parameter_mixin import (
19
- EnhancedSsmParameterMixin,
20
- )
18
+ from cdk_factory.interfaces.standardized_ssm_mixin import StandardizedSsmMixin
21
19
  from aws_lambda_powertools import Logger
22
20
  from cdk_factory.stack.stack_module_registry import register_stack
23
21
  from cdk_factory.utils.api_gateway_utilities import ApiGatewayUtilities
@@ -43,7 +41,7 @@ logger = Logger(service="ApiGatewayStack")
43
41
 
44
42
  @register_stack("api_gateway_library_module")
45
43
  @register_stack("api_gateway_stack")
46
- class ApiGatewayStack(IStack, EnhancedSsmParameterMixin):
44
+ class ApiGatewayStack(IStack, StandardizedSsmMixin):
47
45
  """
48
46
  Reusable stack for AWS API Gateway (REST API).
49
47
  Supports all major RestApi parameters.
@@ -307,15 +305,14 @@ class ApiGatewayStack(IStack, EnhancedSsmParameterMixin):
307
305
  """Setup Cognito authorizer if configured AND if any routes need it"""
308
306
  if not self.api_config.cognito_authorizer:
309
307
  return None
310
-
308
+
311
309
  # Check if any routes actually need the authorizer
312
310
  # Don't create it if all routes are public (authorization_type: NONE)
313
311
  routes = self.api_config.routes or []
314
312
  needs_authorizer = any(
315
- route.get("authorization_type") != "NONE"
316
- for route in routes
313
+ route.get("authorization_type") != "NONE" for route in routes
317
314
  )
318
-
315
+
319
316
  # If we're not creating an authorizer but Cognito is configured,
320
317
  # inform the integration utility so it can still perform security validations
321
318
  if not needs_authorizer:
@@ -344,17 +341,19 @@ class ApiGatewayStack(IStack, EnhancedSsmParameterMixin):
344
341
  method = route.get("method", "GET").upper()
345
342
  path_suffix = route["path"].strip("/").replace("/", "-") or "health"
346
343
  return f"{method.lower()}-{path_suffix}"
347
-
344
+
348
345
  def _setup_lambda_routes(self, api_gateway, api_id, routes, authorizer):
349
346
  """Setup Lambda routes and integrations"""
350
347
  for route in routes:
351
348
  # Check if this route references an existing Lambda via SSM
352
349
  lambda_arn_ssm_path = route.get("lambda_arn_ssm_path")
353
350
  lambda_name_ref = route.get("lambda_name")
354
-
351
+
355
352
  if lambda_arn_ssm_path or lambda_name_ref:
356
353
  # Import existing Lambda from SSM
357
- self._setup_existing_lambda_route(api_gateway, api_id, route, authorizer)
354
+ self._setup_existing_lambda_route(
355
+ api_gateway, api_id, route, authorizer
356
+ )
358
357
  else:
359
358
  # Create new Lambda (legacy pattern)
360
359
  self._setup_single_lambda_route(api_gateway, api_id, route, authorizer)
@@ -366,28 +365,30 @@ class ApiGatewayStack(IStack, EnhancedSsmParameterMixin):
366
365
  """
367
366
  route_path = route["path"]
368
367
  method = route.get("method", "GET").upper()
369
- suffix = self._get_route_suffix(route) # Use shared method for consistent suffix calculation
370
-
368
+ suffix = self._get_route_suffix(
369
+ route
370
+ ) # Use shared method for consistent suffix calculation
371
+
371
372
  # Get Lambda ARN from SSM Parameter Store
372
373
  lambda_arn = self._get_lambda_arn_from_ssm(route)
373
-
374
+
374
375
  if not lambda_arn:
375
376
  raise ValueError(
376
377
  f"Could not resolve Lambda ARN for route {route_path}. "
377
378
  f"Ensure Lambda stack has deployed and exported ARN to SSM."
378
379
  )
379
-
380
+
380
381
  # Import Lambda function from ARN using fromFunctionAttributes
381
382
  # This allows us to add permissions even for imported functions
382
383
  lambda_fn = _lambda.Function.from_function_attributes(
383
384
  self,
384
385
  f"{api_id}-imported-lambda-{suffix}",
385
386
  function_arn=lambda_arn,
386
- same_environment=True # Allow permission grants for same-account imports
387
+ same_environment=True, # Allow permission grants for same-account imports
387
388
  )
388
-
389
+
389
390
  logger.info(f"Imported Lambda for route {route_path}: {lambda_arn}")
390
-
391
+
391
392
  # Add explicit resource-based permission for this specific API Gateway
392
393
  # This is CRITICAL for cross-stack Lambda integrations
393
394
  _lambda.CfnPermission(
@@ -396,26 +397,26 @@ class ApiGatewayStack(IStack, EnhancedSsmParameterMixin):
396
397
  action="lambda:InvokeFunction",
397
398
  function_name=lambda_fn.function_arn,
398
399
  principal="apigateway.amazonaws.com",
399
- source_arn=f"arn:aws:execute-api:{self.region}:{self.account}:{api_gateway.rest_api_id}/*/{method}{route_path}"
400
+ source_arn=f"arn:aws:execute-api:{self.region}:{self.account}:{api_gateway.rest_api_id}/*/{method}{route_path}",
400
401
  )
401
-
402
+
402
403
  logger.info(f"Granted API Gateway invoke permissions for Lambda: {lambda_arn}")
403
-
404
+
404
405
  # Setup API Gateway resource
405
406
  resource = (
406
407
  api_gateway.root.resource_for_path(route_path)
407
408
  if route_path != "/"
408
409
  else api_gateway.root
409
410
  )
410
-
411
+
411
412
  # Setup Lambda integration
412
413
  self._setup_lambda_integration(
413
414
  api_gateway, api_id, route, lambda_fn, authorizer, suffix
414
415
  )
415
-
416
+
416
417
  # Setup CORS using centralized utility
417
418
  self.integration_utility.setup_route_cors(resource, route_path, route)
418
-
419
+
419
420
  def _get_lambda_arn_from_ssm(self, route: dict) -> str:
420
421
  """
421
422
  Get Lambda ARN from SSM Parameter Store.
@@ -429,44 +430,57 @@ class ApiGatewayStack(IStack, EnhancedSsmParameterMixin):
429
430
  param = ssm.StringParameter.from_string_parameter_name(
430
431
  self,
431
432
  f"lambda-arn-param-{hash(lambda_arn_ssm_path) % 10000}",
432
- lambda_arn_ssm_path
433
+ lambda_arn_ssm_path,
433
434
  )
434
435
  return param.string_value
435
436
  except Exception as e:
436
- logger.error(f"Failed to retrieve Lambda ARN from SSM path {lambda_arn_ssm_path}: {e}")
437
+ logger.error(
438
+ f"Failed to retrieve Lambda ARN from SSM path {lambda_arn_ssm_path}: {e}"
439
+ )
437
440
  raise
438
-
441
+
439
442
  # Option 2: Auto-discovery via lambda_name
440
443
  lambda_name = route.get("lambda_name")
441
444
  if lambda_name:
442
445
  # Build SSM path using convention from lambda_stack
443
- ssm_imports_config = self.stack_config.dictionary.get("api_gateway", {}).get("ssm", {}).get("imports", {})
446
+ ssm_imports_config = (
447
+ self.stack_config.dictionary.get("api_gateway", {})
448
+ .get("ssm", {})
449
+ .get("imports", {})
450
+ )
444
451
  # Try 'workload' first, fall back to 'organization' for backward compatibility
445
- workload = ssm_imports_config.get("workload", ssm_imports_config.get("organization", self.deployment.workload_name))
446
- environment = ssm_imports_config.get("environment", self.deployment.environment)
447
-
452
+ workload = ssm_imports_config.get(
453
+ "workload",
454
+ ssm_imports_config.get("organization", self.deployment.workload_name),
455
+ )
456
+ environment = ssm_imports_config.get(
457
+ "environment", self.deployment.environment
458
+ )
459
+
448
460
  ssm_path = f"/{workload}/{environment}/lambda/{lambda_name}/arn"
449
461
  logger.info(f"Auto-discovering Lambda ARN from SSM: {ssm_path}")
450
-
462
+
451
463
  try:
452
464
  param = ssm.StringParameter.from_string_parameter_name(
453
- self,
454
- f"lambda-arn-{lambda_name}-param",
455
- ssm_path
465
+ self, f"lambda-arn-{lambda_name}-param", ssm_path
456
466
  )
457
467
  return param.string_value
458
468
  except Exception as e:
459
- logger.error(f"Failed to auto-discover Lambda ARN for '{lambda_name}' from {ssm_path}: {e}")
469
+ logger.error(
470
+ f"Failed to auto-discover Lambda ARN for '{lambda_name}' from {ssm_path}: {e}"
471
+ )
460
472
  raise ValueError(
461
473
  f"Lambda ARN not found in SSM for '{lambda_name}'. "
462
474
  f"Ensure the Lambda stack has deployed and exported the ARN to: {ssm_path}"
463
475
  )
464
-
476
+
465
477
  return None
466
478
 
467
479
  def _setup_single_lambda_route(self, api_gateway, api_id, route, authorizer):
468
480
  """Setup a single Lambda route with integration and CORS"""
469
- suffix = self._get_route_suffix(route) # Use shared method for consistent suffix calculation
481
+ suffix = self._get_route_suffix(
482
+ route
483
+ ) # Use shared method for consistent suffix calculation
470
484
  src = route.get("src")
471
485
  handler = route.get("handler")
472
486
 
@@ -496,7 +510,7 @@ class ApiGatewayStack(IStack, EnhancedSsmParameterMixin):
496
510
  def _validate_authorization_configuration(self, route, has_cognito_authorizer):
497
511
  """
498
512
  Validate authorization configuration using the shared utility method.
499
-
513
+
500
514
  This delegates to the ApiGatewayIntegrationUtility for consistent validation
501
515
  across both API Gateway stack and Lambda stack patterns.
502
516
  """
@@ -505,14 +519,16 @@ class ApiGatewayStack(IStack, EnhancedSsmParameterMixin):
505
519
  route_config_dict = dict(route) # Create a copy
506
520
  if "path" in route_config_dict:
507
521
  route_config_dict["route"] = route_config_dict["path"]
508
-
522
+
509
523
  api_route_config = ApiGatewayConfigRouteConfig(route_config_dict)
510
-
524
+
511
525
  # Use the utility's enhanced validation method
512
- validated_config = self.integration_utility._validate_and_adjust_authorization_configuration(
513
- api_route_config, has_cognito_authorizer
526
+ validated_config = (
527
+ self.integration_utility._validate_and_adjust_authorization_configuration(
528
+ api_route_config, has_cognito_authorizer
529
+ )
514
530
  )
515
-
531
+
516
532
  # Return the validated authorization type for use in the stack
517
533
  return validated_config.authorization_type
518
534
 
@@ -521,10 +537,10 @@ class ApiGatewayStack(IStack, EnhancedSsmParameterMixin):
521
537
  ):
522
538
  """Setup Lambda integration for a route"""
523
539
  route_path = route["path"]
524
-
540
+
525
541
  # Handle authorization type fallback logic before validation
526
542
  authorization_type = route.get("authorization_type", "COGNITO")
527
-
543
+
528
544
  # If no Cognito authorizer available and default COGNITO, fall back to NONE
529
545
  if (
530
546
  not authorizer
@@ -533,19 +549,22 @@ class ApiGatewayStack(IStack, EnhancedSsmParameterMixin):
533
549
  ):
534
550
  authorization_type = "NONE"
535
551
  import logging
552
+
536
553
  logger = logging.getLogger(__name__)
537
554
  logger.info(
538
555
  f"No Cognito authorizer available for route {route_path} ({route.get('method', 'unknown')}), "
539
556
  f"defaulting to public access (NONE authorization)"
540
557
  )
541
-
558
+
542
559
  # Create a route config with the resolved authorization type for validation
543
560
  route_for_validation = dict(route)
544
561
  route_for_validation["authorization_type"] = authorization_type
545
-
562
+
546
563
  # Validate authorization configuration using the utility
547
- validated_authorization_type = self._validate_authorization_configuration(route_for_validation, authorizer is not None)
548
-
564
+ validated_authorization_type = self._validate_authorization_configuration(
565
+ route_for_validation, authorizer is not None
566
+ )
567
+
549
568
  # Use the validated authorization type
550
569
  authorization_type = validated_authorization_type
551
570
 
@@ -587,10 +606,10 @@ class ApiGatewayStack(IStack, EnhancedSsmParameterMixin):
587
606
  ):
588
607
  """Setup fallback Lambda integration for routes without src"""
589
608
  route_path = route["path"]
590
-
609
+
591
610
  # Handle authorization type fallback logic before validation
592
611
  authorization_type = route.get("authorization_type", "COGNITO")
593
-
612
+
594
613
  # If no Cognito authorizer available and default COGNITO, fall back to NONE
595
614
  if (
596
615
  not authorizer
@@ -599,19 +618,22 @@ class ApiGatewayStack(IStack, EnhancedSsmParameterMixin):
599
618
  ):
600
619
  authorization_type = "NONE"
601
620
  import logging
621
+
602
622
  logger = logging.getLogger(__name__)
603
623
  logger.info(
604
624
  f"No Cognito authorizer available for route {route_path} ({route.get('method', 'unknown')}), "
605
625
  f"defaulting to public access (NONE authorization)"
606
626
  )
607
-
627
+
608
628
  # Create a route config with the resolved authorization type for validation
609
629
  route_for_validation = dict(route)
610
630
  route_for_validation["authorization_type"] = authorization_type
611
-
631
+
612
632
  # Validate authorization configuration using the utility
613
- validated_authorization_type = self._validate_authorization_configuration(route_for_validation, authorizer is not None)
614
-
633
+ validated_authorization_type = self._validate_authorization_configuration(
634
+ route_for_validation, authorizer is not None
635
+ )
636
+
615
637
  # Use the validated authorization type
616
638
  authorization_type = validated_authorization_type
617
639
 
@@ -661,7 +683,10 @@ class ApiGatewayStack(IStack, EnhancedSsmParameterMixin):
661
683
  # This is a bit of a hack, but we need to set the deployment stage
662
684
  # so that api_gateway.url works properly
663
685
  object.__setattr__(api_gateway, "_deployment_stage_internal", stage)
664
- except:
686
+ except (AttributeError, TypeError) as e:
687
+ # Log the error but don't fail the entire deployment
688
+ # This is a non-critical operation for URL generation
689
+ logger.warning(f"Could not set deployment stage internal property: {e}")
665
690
  pass
666
691
 
667
692
  def __finalize_api_gateway_deployments(self):
@@ -719,7 +744,7 @@ class ApiGatewayStack(IStack, EnhancedSsmParameterMixin):
719
744
  # Setup enhanced SSM integration with proper resource type and name
720
745
  api_name = self.api_config.name or "api-gateway"
721
746
 
722
- self.setup_enhanced_ssm_integration(
747
+ self.setup_ssm_integration(
723
748
  scope=self,
724
749
  config=self.stack_config.dictionary.get("api_gateway", {}),
725
750
  resource_type="api-gateway",
@@ -750,7 +775,7 @@ class ApiGatewayStack(IStack, EnhancedSsmParameterMixin):
750
775
  resource_values["authorizer_id"] = authorizer.authorizer_id
751
776
 
752
777
  # Use enhanced SSM parameter export
753
- exported_params = self.auto_export_resources(resource_values)
778
+ exported_params = self.export_ssm_parameters(resource_values)
754
779
 
755
780
  if exported_params:
756
781
  logger.info(