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.
- cdk_factory/configurations/base_config.py +23 -24
- cdk_factory/configurations/cdk_config.py +1 -1
- cdk_factory/configurations/deployment.py +12 -0
- cdk_factory/configurations/devops.py +1 -1
- cdk_factory/configurations/resources/acm.py +9 -2
- cdk_factory/configurations/resources/auto_scaling.py +7 -5
- cdk_factory/configurations/resources/cloudfront.py +7 -2
- cdk_factory/configurations/resources/ecr.py +1 -1
- cdk_factory/configurations/resources/ecs_cluster.py +12 -5
- cdk_factory/configurations/resources/ecs_service.py +30 -3
- cdk_factory/configurations/resources/lambda_edge.py +18 -4
- cdk_factory/configurations/resources/load_balancer.py +8 -9
- cdk_factory/configurations/resources/monitoring.py +8 -3
- cdk_factory/configurations/resources/rds.py +8 -9
- cdk_factory/configurations/resources/route53.py +5 -0
- cdk_factory/configurations/resources/rum.py +7 -2
- cdk_factory/configurations/resources/s3.py +10 -2
- cdk_factory/configurations/resources/security_group_full_stack.py +7 -8
- cdk_factory/configurations/resources/vpc.py +19 -0
- cdk_factory/configurations/workload.py +32 -2
- cdk_factory/constructs/cloudfront/cloudfront_distribution_construct.py +1 -1
- cdk_factory/constructs/ecr/ecr_construct.py +9 -2
- cdk_factory/constructs/lambdas/policies/policy_docs.py +4 -4
- cdk_factory/interfaces/istack.py +4 -4
- cdk_factory/interfaces/networked_stack_mixin.py +6 -6
- cdk_factory/interfaces/standardized_ssm_mixin.py +684 -0
- cdk_factory/interfaces/vpc_provider_mixin.py +64 -33
- cdk_factory/lambdas/edge/ip_gate/handler.py +42 -40
- cdk_factory/pipeline/pipeline_factory.py +3 -3
- cdk_factory/stack_library/__init__.py +3 -2
- cdk_factory/stack_library/acm/acm_stack.py +7 -17
- cdk_factory/stack_library/api_gateway/api_gateway_stack.py +84 -59
- cdk_factory/stack_library/auto_scaling/auto_scaling_stack.py +454 -537
- cdk_factory/stack_library/cloudfront/cloudfront_stack.py +76 -22
- cdk_factory/stack_library/code_artifact/code_artifact_stack.py +5 -27
- cdk_factory/stack_library/cognito/cognito_stack.py +152 -92
- cdk_factory/stack_library/dynamodb/dynamodb_stack.py +19 -15
- cdk_factory/stack_library/ecr/ecr_stack.py +2 -2
- cdk_factory/stack_library/ecs/__init__.py +1 -3
- cdk_factory/stack_library/ecs/ecs_cluster_stack.py +159 -75
- cdk_factory/stack_library/ecs/ecs_service_stack.py +59 -52
- 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 +240 -83
- cdk_factory/stack_library/load_balancer/load_balancer_stack.py +139 -212
- cdk_factory/stack_library/rds/rds_stack.py +74 -98
- cdk_factory/stack_library/route53/route53_stack.py +246 -40
- cdk_factory/stack_library/rum/rum_stack.py +108 -91
- cdk_factory/stack_library/security_group/security_group_full_stack.py +10 -53
- cdk_factory/stack_library/security_group/security_group_stack.py +12 -19
- 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.py +171 -130
- cdk_factory/stack_library/websites/static_website_stack.py +7 -3
- cdk_factory/utilities/api_gateway_integration_utility.py +24 -16
- cdk_factory/utilities/environment_services.py +5 -5
- cdk_factory/utilities/json_loading_utility.py +1 -1
- cdk_factory/validation/config_validator.py +483 -0
- cdk_factory/version.py +1 -1
- {cdk_factory-0.16.15.dist-info → cdk_factory-0.20.0.dist-info}/METADATA +1 -1
- {cdk_factory-0.16.15.dist-info → cdk_factory-0.20.0.dist-info}/RECORD +64 -62
- cdk_factory/interfaces/enhanced_ssm_parameter_mixin.py +0 -321
- cdk_factory/interfaces/ssm_parameter_mixin.py +0 -454
- {cdk_factory-0.16.15.dist-info → cdk_factory-0.20.0.dist-info}/WHEEL +0 -0
- {cdk_factory-0.16.15.dist-info → cdk_factory-0.20.0.dist-info}/entry_points.txt +0 -0
- {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.
|
|
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,
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
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(
|
|
446
|
-
|
|
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(
|
|
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(
|
|
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 =
|
|
513
|
-
|
|
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(
|
|
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(
|
|
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.
|
|
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.
|
|
778
|
+
exported_params = self.export_ssm_parameters(resource_values)
|
|
754
779
|
|
|
755
780
|
if exported_params:
|
|
756
781
|
logger.info(
|