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
@@ -6,7 +6,7 @@ MIT License. See Project Root for license information.
6
6
 
7
7
  from typing import Optional, List, Any
8
8
  from aws_lambda_powertools import Logger
9
- from aws_cdk import aws_ec2 as ec2
9
+ from aws_cdk import aws_ec2 as ec2, aws_ssm as ssm
10
10
  from constructs import Construct
11
11
 
12
12
  logger = Logger(__name__)
@@ -18,13 +18,13 @@ class VPCProviderMixin:
18
18
 
19
19
  This mixin eliminates code duplication across stacks that need to resolve
20
20
  VPC references, providing a standardized way to handle:
21
- - SSM imported VPC parameters (works with enhanced SsmParameterMixin)
21
+ - SSM imported VPC parameters (works with enhanced StandardizedSsmMixin)
22
22
  - Configuration-based VPC resolution
23
23
  - Workload-level VPC fallback
24
24
  - Error handling and validation
25
25
 
26
26
  Note: This mixin does NOT handle SSM imports directly - it expects
27
- the SSM values to be available via the enhanced SsmParameterMixin.
27
+ the SSM values to be available via the enhanced StandardizedSsmMixin.
28
28
  """
29
29
 
30
30
  def _initialize_vpc_cache(self) -> None:
@@ -43,7 +43,7 @@ class VPCProviderMixin:
43
43
  Resolve VPC from multiple sources with standardized priority order.
44
44
 
45
45
  Priority order:
46
- 1. SSM imported VPC ID (from enhanced SsmParameterMixin)
46
+ 1. SSM imported VPC ID (from config.ssm.imports)
47
47
  2. Config-level VPC ID
48
48
  3. Workload-level VPC ID
49
49
  4. Raise error if none found
@@ -63,19 +63,22 @@ class VPCProviderMixin:
63
63
  if self._vpc:
64
64
  return self._vpc
65
65
 
66
- # Default availability zones if not provided
66
+ # Default availability zones if not provided - use region-appropriate defaults
67
67
  if not availability_zones:
68
- availability_zones = ["us-east-1a", "us-east-1b"]
68
+ region = getattr(deployment, 'region', 'us-east-1')
69
+ availability_zones = self._get_default_azs_for_region(region)
69
70
 
70
- # Check SSM imported values first (tokens from SSM parameters)
71
- # This works with the enhanced SsmParameterMixin
72
- if hasattr(self, '_ssm_imported_values') and "vpc_id" in self._ssm_imported_values:
73
- vpc_id = self._ssm_imported_values["vpc_id"]
71
+ # Check SSM imports directly from config (source of truth)
72
+ ssm_config = getattr(config, 'ssm', {})
73
+ ssm_imports = ssm_config.get('imports', {})
74
+
75
+ if ssm_imports and "vpc_id" in ssm_imports:
76
+ vpc_id = ssm_imports["vpc_id"]
74
77
 
75
78
  # Get subnet IDs first to determine AZ count
76
79
  subnet_ids = []
77
- if hasattr(self, '_ssm_imported_values') and "subnet_ids" in self._ssm_imported_values:
78
- imported_subnets = self._ssm_imported_values["subnet_ids"]
80
+ if "subnet_ids" in ssm_imports:
81
+ imported_subnets = ssm_imports["subnet_ids"]
79
82
  if isinstance(imported_subnets, str):
80
83
  subnet_ids = [s.strip() for s in imported_subnets.split(",") if s.strip()]
81
84
  elif isinstance(imported_subnets, list):
@@ -83,9 +86,11 @@ class VPCProviderMixin:
83
86
 
84
87
  # Adjust availability zones to match subnet count
85
88
  if subnet_ids and availability_zones:
86
- availability_zones = [f"us-east-1{chr(97+i)}" for i in range(len(subnet_ids))]
89
+ region = getattr(deployment, 'region', 'us-east-1')
90
+ region_code = region.split('-')[0] + '-' + region.split('-')[1]
91
+ availability_zones = [f"{region_code}{chr(97+i)}" for i in range(len(subnet_ids))]
87
92
 
88
- return self._create_vpc_from_ssm(vpc_id, availability_zones)
93
+ return self._create_vpc_from_ssm(vpc_id, availability_zones, subnet_ids if subnet_ids else None)
89
94
 
90
95
  # Check config-level VPC ID
91
96
  if hasattr(config, 'vpc_id') and config.vpc_id:
@@ -101,41 +106,40 @@ class VPCProviderMixin:
101
106
  def _create_vpc_from_ssm(
102
107
  self,
103
108
  vpc_id: str,
104
- availability_zones: List[str]
109
+ availability_zones: List[str],
110
+ subnet_ids: Optional[List[str]] = None
105
111
  ) -> ec2.IVpc:
106
112
  """
107
113
  Create VPC reference from SSM imported VPC ID.
108
114
 
109
115
  Args:
110
- vpc_id: The VPC ID from SSM
116
+ vpc_id: The VPC ID from SSM (can be SSM path or actual VPC ID)
111
117
  availability_zones: List of availability zones
118
+ subnet_ids: Optional list of subnet IDs from SSM
112
119
 
113
120
  Returns:
114
121
  VPC reference created from attributes
115
122
  """
123
+ # Check if vpc_id is an SSM path (starts with /) or actual VPC ID
124
+ if vpc_id.startswith('/'):
125
+ # Create CDK token for VPC ID from SSM parameter
126
+ vpc_id_token = ssm.StringParameter.from_string_parameter_name(
127
+ self, f"{self.stack_name}-VPC-ID-Token", vpc_id
128
+ ).string_value
129
+ else:
130
+ # Use the VPC ID directly (for testing or direct configuration)
131
+ vpc_id_token = vpc_id
132
+
116
133
  # Build VPC attributes
117
134
  vpc_attrs = {
118
- "vpc_id": vpc_id,
135
+ "vpc_id": vpc_id_token,
119
136
  "availability_zones": availability_zones,
120
137
  }
121
138
 
122
- # If we have subnet_ids from SSM, use the actual subnet IDs
123
- if hasattr(self, '_ssm_imported_values') and "subnet_ids" in self._ssm_imported_values:
124
- imported_subnets = self._ssm_imported_values["subnet_ids"]
125
- if isinstance(imported_subnets, str):
126
- # Split comma-separated subnet IDs
127
- subnet_ids = [s.strip() for s in imported_subnets.split(",") if s.strip()]
128
- elif isinstance(imported_subnets, list):
129
- subnet_ids = imported_subnets
130
- else:
131
- subnet_ids = []
132
-
139
+ # If we have subnet_ids from SSM, add them to the attributes
140
+ if subnet_ids:
133
141
  # Use the actual subnet IDs from SSM
134
- if subnet_ids:
135
- vpc_attrs["public_subnet_ids"] = subnet_ids
136
- else:
137
- # Fallback to dummy subnets if no valid subnet IDs
138
- vpc_attrs["public_subnet_ids"] = ["subnet-dummy1", "subnet-dummy2"]
142
+ vpc_attrs["public_subnet_ids"] = subnet_ids
139
143
 
140
144
  # Use from_vpc_attributes() for SSM tokens with unique construct name
141
145
  self._vpc = ec2.Vpc.from_vpc_attributes(self, f"{self.stack_name}-VPC", **vpc_attrs)
@@ -177,3 +181,30 @@ class VPCProviderMixin:
177
181
  Resolved VPC reference
178
182
  """
179
183
  return self.resolve_vpc(config, deployment, workload)
184
+
185
+ def _get_default_azs_for_region(self, region: str) -> List[str]:
186
+ """
187
+ Get default availability zones for a given region.
188
+
189
+ Args:
190
+ region: AWS region name (e.g., 'us-east-1', 'us-west-2')
191
+
192
+ Returns:
193
+ List of availability zone names for the region
194
+ """
195
+ # Common AZ mappings for major AWS regions
196
+ region_az_map = {
197
+ 'us-east-1': ['us-east-1a', 'us-east-1b'],
198
+ 'us-east-2': ['us-east-2a', 'us-east-2b'],
199
+ 'us-west-1': ['us-west-1a', 'us-west-1b'],
200
+ 'us-west-2': ['us-west-2a', 'us-west-2b'],
201
+ 'eu-west-1': ['eu-west-1a', 'eu-west-1b'],
202
+ 'eu-west-2': ['eu-west-2a', 'eu-west-2b'],
203
+ 'eu-central-1': ['eu-central-1a', 'eu-central-1b'],
204
+ 'ap-southeast-1': ['ap-southeast-1a', 'ap-southeast-1b'],
205
+ 'ap-southeast-2': ['ap-southeast-2a', 'ap-southeast-2b'],
206
+ 'ap-northeast-1': ['ap-northeast-1a', 'ap-northeast-1b'],
207
+ }
208
+
209
+ # Return region-specific AZs if available, otherwise fall back to us-east-1
210
+ return region_az_map.get(region, ['us-east-1a', 'us-east-1b'])
@@ -11,11 +11,18 @@ import ipaddress
11
11
  import boto3
12
12
  from functools import lru_cache
13
13
 
14
+ # Simple logging for Lambda@Edge (since we can't use external logging libraries)
15
+ import logging
16
+
17
+ # Configure basic logging
18
+ logger = logging.getLogger()
19
+ logger.setLevel(logging.INFO)
20
+
14
21
  # SSM client - will be created in the region where the function executes
15
22
  ssm = None
16
23
 
17
24
  @lru_cache(maxsize=128)
18
- def get_ssm_parameter(parameter_name: str, region: str = 'us-east-1', default: str = None) -> str:
25
+ def get_ssm_parameter(parameter_name: str, region: str = None, default: str = None) -> str:
19
26
  """
20
27
  Fetch SSM parameter with caching.
21
28
  Lambda@Edge cannot use environment variables, so we fetch from SSM.
@@ -23,14 +30,18 @@ def get_ssm_parameter(parameter_name: str, region: str = 'us-east-1', default: s
23
30
  The sentinel value 'NONE' indicates an explicitly unset/disabled parameter.
24
31
 
25
32
  Args:
26
- parameter_name: Name of the SSM parameter
27
- region: AWS region (default us-east-1)
28
- default: Default value to return if parameter not found (optional)
33
+ parameter_name: SSM parameter name
34
+ region: AWS region (defaults to us-east-1 for Lambda@Edge compatibility)
35
+ default: Default value if parameter not found
29
36
 
30
37
  Returns:
31
38
  Parameter value, default value if parameter not found, or empty string if value is 'NONE'
32
39
  """
33
40
  global ssm
41
+ # Default to us-east-1 for Lambda@Edge compatibility if no region specified
42
+ if region is None:
43
+ region = 'us-east-1'
44
+
34
45
  if ssm is None:
35
46
  ssm = boto3.client('ssm', region_name=region)
36
47
 
@@ -40,20 +51,20 @@ def get_ssm_parameter(parameter_name: str, region: str = 'us-east-1', default: s
40
51
 
41
52
  # Treat 'NONE' sentinel as empty/unset
42
53
  if value == 'NONE':
43
- print(f"SSM parameter {parameter_name} is set to 'NONE' (explicitly disabled)")
54
+ logger.info(f"SSM parameter {parameter_name} is set to 'NONE' (explicitly disabled)")
44
55
  return ''
45
56
 
46
57
  return value
47
58
  except ssm.exceptions.ParameterNotFound:
48
59
  if default is not None:
49
- print(f"SSM parameter {parameter_name} not found, using default: {default}")
60
+ logger.info(f"SSM parameter {parameter_name} not found, using default: {default}")
50
61
  return default
51
- print(f"SSM parameter {parameter_name} not found and no default provided")
62
+ logger.error(f"SSM parameter {parameter_name} not found and no default provided")
52
63
  raise
53
64
  except Exception as e:
54
- print(f"Error fetching SSM parameter {parameter_name}: {str(e)}")
65
+ logger.error(f"Error fetching SSM parameter {parameter_name}: {str(e)}")
55
66
  if default is not None:
56
- print(f"Returning default value due to error: {default}")
67
+ logger.info(f"Returning default value due to error: {default}")
57
68
  return default
58
69
  raise
59
70
 
@@ -89,7 +100,7 @@ def is_ip_allowed(client_ip: str, allowed_cidrs: list) -> bool:
89
100
  try:
90
101
  client_ip_obj = ipaddress.ip_address(client_ip)
91
102
  except ValueError as e:
92
- print(f"Invalid client IP address: {e}")
103
+ logger.error(f"Invalid client IP address: {e}")
93
104
  return False
94
105
 
95
106
  # Check each CIDR individually, skipping invalid ones
@@ -100,7 +111,7 @@ def is_ip_allowed(client_ip: str, allowed_cidrs: list) -> bool:
100
111
  return True
101
112
  except ValueError as e:
102
113
  # Invalid CIDR, log and continue checking others
103
- print(f"Invalid CIDR '{cidr}': {e}")
114
+ logger.warning(f"Invalid CIDR '{cidr}': {e}")
104
115
  continue
105
116
 
106
117
  return False
@@ -130,24 +141,23 @@ def lambda_handler(event, context):
130
141
  env = runtime_config.get('environment', 'dev')
131
142
  function_base_name = runtime_config.get('function_name', 'ip-gate')
132
143
 
133
- print(f"Runtime config loaded: environment={env}, function_name={function_base_name}")
144
+ logger.info(f"Runtime config loaded: environment={env}, function_name={function_base_name}")
134
145
  except FileNotFoundError:
135
146
  # Fallback: extract from Lambda context (less reliable)
136
- print("Warning: runtime_config.json not found, falling back to function name parsing")
147
+ logger.warning("runtime_config.json not found, falling back to function name parsing")
137
148
  function_full_name = context.function_name
138
149
 
139
150
  # Parse environment from function name as fallback
140
151
  parts = function_full_name.split('-')
141
152
  common_envs = ['dev', 'prod', 'staging', 'test', 'qa', 'uat']
142
- env = 'dev'
143
-
153
+ env = 'dev' # Default fallback
144
154
  for part in parts:
145
155
  if part in common_envs:
146
156
  env = part
147
157
  break
148
158
 
149
159
  function_base_name = 'ip-gate'
150
- print(f"Fallback: environment={env}, function_name={function_base_name}")
160
+ logger.info(f"Fallback: environment={env}, function_name={function_base_name}")
151
161
 
152
162
  # Full function name for SSM paths
153
163
  # Lambda@Edge replicas prepend region (e.g., "us-east-1.tech-talk-dev-ip-gate")
@@ -156,46 +166,46 @@ def lambda_handler(event, context):
156
166
  if '.' in function_name and function_name.split('.')[0].startswith('us-'):
157
167
  # Strip region prefix (e.g., "us-east-1." -> "tech-talk-dev-ip-gate")
158
168
  function_name = '.'.join(function_name.split('.')[1:])
159
- print(f"Stripped region prefix from function name: {function_name}")
169
+ logger.info(f"Stripped region prefix from function name: {function_name}")
160
170
 
161
- print(f"Lambda function ARN: {context.invoked_function_arn}")
162
- print(f"Using function name for SSM lookups: {function_name}")
171
+ logger.info(f"Lambda function ARN: {context.invoked_function_arn}")
172
+ logger.info(f"Using function name for SSM lookups: {function_name}")
163
173
 
164
174
  try:
165
175
  # Fetch configuration from SSM Parameter Store
166
176
  # Auto-generated paths: /{env}/{function-name}/{key}
167
- gate_enabled = get_ssm_parameter(f'/{env}/{function_name}/gate-enabled', 'us-east-1')
177
+ gate_enabled = get_ssm_parameter(f'/{env}/{function_name}/gate-enabled', region='us-east-1')
168
178
 
169
179
  # If gating is disabled, allow all traffic
170
180
  # Empty string (from 'NONE' sentinel) is treated as disabled
171
181
  if not gate_enabled or gate_enabled.lower() not in ('true', '1', 'yes'):
172
- print(f"IP gating is disabled (GATE_ENABLED={gate_enabled or 'NONE'})")
182
+ logger.info(f"IP gating is disabled (GATE_ENABLED={gate_enabled or 'NONE'})")
173
183
  return request
174
184
 
175
185
  # Get allowed CIDRs and backup host
176
- allow_cidrs_str = get_ssm_parameter(f'/{env}/{function_name}/allow-cidrs', 'us-east-1')
177
- dns_alias = get_ssm_parameter(f'/{env}/{function_name}/dns-alias', 'us-east-1')
186
+ allow_cidrs_str = get_ssm_parameter(f'/{env}/{function_name}/allow-cidrs', region='us-east-1')
187
+ dns_alias = get_ssm_parameter(f'/{env}/{function_name}/dns-alias', region='us-east-1')
178
188
 
179
189
  # Parse allowed CIDRs (empty string results in empty list)
180
190
  allowed_cidrs = [cidr.strip() for cidr in allow_cidrs_str.split(',') if cidr.strip()]
181
191
 
182
192
  # Get client IP
183
193
  client_ip = get_client_ip(request)
184
- print(f"Client IP: {client_ip}")
194
+ logger.info(f"Client IP: {client_ip}")
185
195
 
186
196
  # Check if IP is allowed
187
197
  if is_ip_allowed(client_ip, allowed_cidrs):
188
- print(f"IP {client_ip} is allowed")
198
+ logger.info(f"IP {client_ip} is allowed")
189
199
  return request
190
200
 
191
201
  # IP not allowed - either redirect or proxy backup page
192
202
  # Check response mode from SSM (default: redirect for backward compatibility)
193
203
  response_mode_param = f"/{env}/{function_name}/response-mode"
194
- response_mode = get_ssm_parameter(response_mode_param, 'us-east-1', default='redirect')
204
+ response_mode = get_ssm_parameter(response_mode_param, region='us-east-1', default='redirect')
195
205
 
196
206
  if response_mode == 'proxy':
197
207
  # Proxy mode: Fetch and return backup content (keeps URL the same)
198
- print(f"IP {client_ip} is NOT allowed, proxying content from {dns_alias}")
208
+ logger.info(f"IP {client_ip} is NOT allowed, proxying content from {dns_alias}")
199
209
 
200
210
  try:
201
211
  import urllib3
@@ -230,17 +240,17 @@ def lambda_handler(event, context):
230
240
  'body': alias_response.data.decode('utf-8')
231
241
  }
232
242
 
233
- print(f"Successfully proxied backup page (status {alias_response.status})")
243
+ logger.info(f"Successfully proxied backup page (status {alias_response.status})")
234
244
  return response
235
245
 
236
246
  except Exception as proxy_error:
237
- print(f"Error proxying backup content: {str(proxy_error)}")
247
+ logger.error(f"Error proxying backup content: {str(proxy_error)}")
238
248
  # Fall back to redirect if proxy fails
239
- print(f"Falling back to redirect mode")
249
+ logger.info(f"Falling back to redirect mode")
240
250
  response_mode = 'redirect'
241
251
 
242
252
  # Redirect mode (default): HTTP 302 redirect to backup site
243
- print(f"IP {client_ip} is NOT allowed, redirecting to {dns_alias}")
253
+ logger.info(f"IP {client_ip} is NOT allowed, redirecting to {dns_alias}")
244
254
 
245
255
  response = {
246
256
  'status': '302',
@@ -249,14 +259,6 @@ def lambda_handler(event, context):
249
259
  'location': [{
250
260
  'key': 'Location',
251
261
  'value': f'https://{dns_alias}'
252
- }],
253
- 'cache-control': [{
254
- 'key': 'Cache-Control',
255
- 'value': 'no-cache, no-store, must-revalidate'
256
- }],
257
- 'x-ip-gate-mode': [{
258
- 'key': 'X-IP-Gate-Mode',
259
- 'value': 'redirect'
260
262
  }]
261
263
  }
262
264
  }
@@ -264,7 +266,7 @@ def lambda_handler(event, context):
264
266
  return response
265
267
 
266
268
  except Exception as e:
267
- print(f"Error in IP gating function: {str(e)}")
269
+ logger.error(f"Error in IP gating function: {str(e)}")
268
270
  # On error, allow the request to proceed (fail open)
269
271
  # Change this to fail closed if preferred
270
272
  return request
@@ -118,7 +118,7 @@ class PipelineFactoryStack(IStack):
118
118
  f"\t🚨 Deployment for Environment: {deployment.environment} "
119
119
  f"is disabled."
120
120
  )
121
- if len(pipeline_deployments) == 0:
121
+ if not pipeline_deployments:
122
122
  print(f"\t⛔️ No Pipeline Deployments configured for {self.workload.name}.")
123
123
 
124
124
  return len(pipeline_deployments)
@@ -216,7 +216,7 @@ class PipelineFactoryStack(IStack):
216
216
  wave_name = stage.wave_name
217
217
 
218
218
  # if we don't have any stacks we'll need to use the wave
219
- if len(stage.stacks) == 0:
219
+ if not stage.stacks:
220
220
  wave_name = stage.name
221
221
 
222
222
  if wave_name:
@@ -519,7 +519,7 @@ class PipelineFactoryStack(IStack):
519
519
  f"\t\t ⚠️ Stack {stack_config.name} is disabled in stage: {stage_config.name}"
520
520
  )
521
521
 
522
- if len(cf_stacks) == 0:
522
+ if not cf_stacks:
523
523
  print(f"\t\t ⚠️ No stacks added to stage: {stage_config.name}")
524
524
  print(f"\t\t ⚠️ Internal Stack Count: {len(stage_config.stacks)}")
525
525
 
@@ -12,9 +12,10 @@ paths = []
12
12
 
13
13
  try:
14
14
  paths = __path__
15
- except: # noqa: E722, pylint: disable=bare-except
15
+ except (AttributeError, ImportError) as e:
16
+ # Fallback to using os.path if __path__ is not available
16
17
  import os
17
-
18
+
18
19
  paths.append(os.path.dirname(__file__))
19
20
 
20
21
 
@@ -16,7 +16,7 @@ from cdk_factory.configurations.deployment import DeploymentConfig
16
16
  from cdk_factory.configurations.stack import StackConfig
17
17
  from cdk_factory.configurations.resources.acm import AcmConfig
18
18
  from cdk_factory.interfaces.istack import IStack
19
- from cdk_factory.interfaces.enhanced_ssm_parameter_mixin import EnhancedSsmParameterMixin
19
+ from cdk_factory.interfaces.standardized_ssm_mixin import StandardizedSsmMixin
20
20
  from cdk_factory.stack.stack_module_registry import register_stack
21
21
  from cdk_factory.workload.workload_factory import WorkloadConfig
22
22
 
@@ -25,7 +25,8 @@ logger = Logger(service="AcmStack")
25
25
 
26
26
  @register_stack("acm_stack")
27
27
  @register_stack("certificate_stack")
28
- class AcmStack(IStack, EnhancedSsmParameterMixin):
28
+ @register_stack("certificate_library_module")
29
+ class AcmStack(IStack, StandardizedSsmMixin):
29
30
  """
30
31
  Reusable stack for AWS Certificate Manager.
31
32
  Supports creating ACM certificates with DNS validation via Route53.
@@ -152,18 +153,7 @@ class AcmStack(IStack, EnhancedSsmParameterMixin):
152
153
 
153
154
  def _add_outputs(self, cert_name: str) -> None:
154
155
  """Add CloudFormation outputs"""
155
- cdk.CfnOutput(
156
- self,
157
- "CertificateArn",
158
- value=self.certificate.certificate_arn,
159
- description=f"Certificate ARN for {self.acm_config.domain_name}",
160
- export_name=f"{cert_name}-arn",
161
- )
162
-
163
- cdk.CfnOutput(
164
- self,
165
- "DomainName",
166
- value=self.acm_config.domain_name,
167
- description="Primary domain name for the certificate",
168
- export_name=f"{cert_name}-domain",
169
- )
156
+
157
+ return
158
+
159
+