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
|
@@ -6,6 +6,8 @@ MIT License. See Project Root for the license information.
|
|
|
6
6
|
|
|
7
7
|
from typing import Dict, Any, List, Optional
|
|
8
8
|
|
|
9
|
+
import base64
|
|
10
|
+
import hashlib
|
|
9
11
|
import aws_cdk as cdk
|
|
10
12
|
from aws_cdk import aws_elasticloadbalancingv2 as elbv2
|
|
11
13
|
from aws_cdk import aws_ec2 as ec2
|
|
@@ -19,7 +21,8 @@ from cdk_factory.configurations.deployment import DeploymentConfig
|
|
|
19
21
|
from cdk_factory.configurations.stack import StackConfig
|
|
20
22
|
from cdk_factory.configurations.resources.load_balancer import LoadBalancerConfig
|
|
21
23
|
from cdk_factory.interfaces.istack import IStack
|
|
22
|
-
from cdk_factory.interfaces.
|
|
24
|
+
from cdk_factory.interfaces.vpc_provider_mixin import VPCProviderMixin
|
|
25
|
+
from cdk_factory.interfaces.standardized_ssm_mixin import StandardizedSsmMixin
|
|
23
26
|
from cdk_factory.stack.stack_module_registry import register_stack
|
|
24
27
|
from cdk_factory.workload.workload_factory import WorkloadConfig
|
|
25
28
|
|
|
@@ -30,7 +33,7 @@ logger = Logger(service="LoadBalancerStack")
|
|
|
30
33
|
@register_stack("alb_stack")
|
|
31
34
|
@register_stack("load_balancer_library_module")
|
|
32
35
|
@register_stack("load_balancer_stack")
|
|
33
|
-
class LoadBalancerStack(IStack,
|
|
36
|
+
class LoadBalancerStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
|
|
34
37
|
"""
|
|
35
38
|
Reusable stack for AWS Load Balancers.
|
|
36
39
|
Supports creating Application and Network Load Balancers with customizable configurations.
|
|
@@ -49,7 +52,7 @@ class LoadBalancerStack(IStack, EnhancedSsmParameterMixin):
|
|
|
49
52
|
self._hosted_zone = None
|
|
50
53
|
self._record_names = None
|
|
51
54
|
# SSM imported values
|
|
52
|
-
self.
|
|
55
|
+
self._ssm_imported_values: Dict[str, str] = {}
|
|
53
56
|
|
|
54
57
|
def build(
|
|
55
58
|
self,
|
|
@@ -76,8 +79,18 @@ class LoadBalancerStack(IStack, EnhancedSsmParameterMixin):
|
|
|
76
79
|
)
|
|
77
80
|
lb_name = deployment.build_resource_name(self.lb_config.name)
|
|
78
81
|
|
|
79
|
-
#
|
|
80
|
-
self.
|
|
82
|
+
# Setup standardized SSM integration
|
|
83
|
+
self.setup_ssm_integration(
|
|
84
|
+
scope=self,
|
|
85
|
+
config=self.lb_config,
|
|
86
|
+
resource_type="load_balancer",
|
|
87
|
+
resource_name=self.lb_config.name,
|
|
88
|
+
deployment=deployment,
|
|
89
|
+
workload=workload
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
# Process SSM imports
|
|
93
|
+
self.process_ssm_imports()
|
|
81
94
|
|
|
82
95
|
self._prep_dns()
|
|
83
96
|
|
|
@@ -155,16 +168,22 @@ class LoadBalancerStack(IStack, EnhancedSsmParameterMixin):
|
|
|
155
168
|
|
|
156
169
|
# If subnets is None, check if we have SSM-imported subnet_ids as a token
|
|
157
170
|
# We need to use Fn.Split to convert the comma-separated string to an array
|
|
158
|
-
if subnets is None
|
|
159
|
-
|
|
160
|
-
if
|
|
161
|
-
|
|
162
|
-
#
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
"
|
|
166
|
-
cdk.
|
|
167
|
-
|
|
171
|
+
if subnets is None:
|
|
172
|
+
subnet_ids = self.get_subnet_ids(self.lb_config)
|
|
173
|
+
if subnet_ids:
|
|
174
|
+
# For CloudFormation token resolution, we still need Fn.split
|
|
175
|
+
# but we use the helper to determine if subnet IDs are available
|
|
176
|
+
ssm_imports = self.get_all_ssm_imports()
|
|
177
|
+
if "subnet_ids" in ssm_imports:
|
|
178
|
+
subnet_ids_value = ssm_imports["subnet_ids"]
|
|
179
|
+
if cdk.Token.is_unresolved(subnet_ids_value):
|
|
180
|
+
logger.info("Using Fn.Split to convert comma-separated subnet IDs token to array")
|
|
181
|
+
# Use CloudFormation escape hatch to set Subnets property with Fn.Split
|
|
182
|
+
cfn_lb = load_balancer.node.default_child
|
|
183
|
+
cfn_lb.add_property_override(
|
|
184
|
+
"Subnets",
|
|
185
|
+
cdk.Fn.split(",", subnet_ids_value)
|
|
186
|
+
)
|
|
168
187
|
|
|
169
188
|
# Add tags
|
|
170
189
|
for key, value in self.lb_config.tags.items():
|
|
@@ -174,100 +193,25 @@ class LoadBalancerStack(IStack, EnhancedSsmParameterMixin):
|
|
|
174
193
|
|
|
175
194
|
@property
|
|
176
195
|
def vpc(self) -> ec2.IVpc:
|
|
177
|
-
"""Get the VPC for the Load Balancer"""
|
|
196
|
+
"""Get the VPC for the Load Balancer using centralized VPC provider mixin."""
|
|
178
197
|
if self._vpc:
|
|
179
198
|
return self._vpc
|
|
180
|
-
|
|
181
|
-
# Check SSM imported values first (tokens from SSM parameters)
|
|
182
|
-
if "vpc_id" in self.ssm_imported_values:
|
|
183
|
-
vpc_id = self.ssm_imported_values["vpc_id"]
|
|
184
|
-
|
|
185
|
-
# Build VPC attributes
|
|
186
|
-
vpc_attrs = {
|
|
187
|
-
"vpc_id": vpc_id,
|
|
188
|
-
"availability_zones": ["us-east-1a", "us-east-1b"],
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
# If we have subnet_ids from SSM, provide dummy public subnets
|
|
192
|
-
# The actual subnets will be set via CloudFormation escape hatch
|
|
193
|
-
if "subnet_ids" in self.ssm_imported_values:
|
|
194
|
-
# Provide dummy subnet IDs - these will be overridden by the escape hatch
|
|
195
|
-
# We need at least one dummy subnet per AZ to satisfy CDK's validation
|
|
196
|
-
vpc_attrs["public_subnet_ids"] = ["subnet-dummy1", "subnet-dummy2"]
|
|
197
|
-
|
|
198
|
-
# Use from_vpc_attributes() instead of from_lookup() because SSM imports return tokens
|
|
199
|
-
self._vpc = ec2.Vpc.from_vpc_attributes(self, "VPC", **vpc_attrs)
|
|
200
|
-
elif self.lb_config.vpc_id:
|
|
201
|
-
self._vpc = ec2.Vpc.from_lookup(self, "VPC", vpc_id=self.lb_config.vpc_id)
|
|
202
|
-
elif self.workload.vpc_id:
|
|
203
|
-
self._vpc = ec2.Vpc.from_lookup(self, "VPC", vpc_id=self.workload.vpc_id)
|
|
204
|
-
else:
|
|
205
|
-
# Use default VPC if not provided
|
|
206
|
-
raise ValueError(
|
|
207
|
-
"VPC is not defined in the configuration. "
|
|
208
|
-
"You can provide it a the load_balancer.vpc_id in the configuration "
|
|
209
|
-
"or a top level workload.vpc_id in the workload configuration."
|
|
210
|
-
)
|
|
211
|
-
|
|
212
|
-
return self._vpc
|
|
213
|
-
|
|
214
|
-
def _process_ssm_imports(self) -> None:
|
|
215
|
-
"""
|
|
216
|
-
Process SSM imports from configuration.
|
|
217
|
-
Follows the same pattern as RDS and Security Group stacks.
|
|
218
|
-
"""
|
|
219
|
-
from aws_cdk import aws_ssm as ssm
|
|
220
|
-
|
|
221
|
-
ssm_imports = self.lb_config.ssm_imports
|
|
222
|
-
|
|
223
|
-
if not ssm_imports:
|
|
224
|
-
logger.debug("No SSM imports configured for Load Balancer")
|
|
225
|
-
return
|
|
226
|
-
|
|
227
|
-
logger.info(f"Processing {len(ssm_imports)} SSM imports for Load Balancer")
|
|
228
199
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
param_path = f"/{param_path}"
|
|
237
|
-
|
|
238
|
-
construct_id = f"ssm-import-{param_key}-{idx}-{hash(param_path) % 10000}"
|
|
239
|
-
param = ssm.StringParameter.from_string_parameter_name(
|
|
240
|
-
self, construct_id, param_path
|
|
241
|
-
)
|
|
242
|
-
imported_list.append(param.string_value)
|
|
243
|
-
|
|
244
|
-
self.ssm_imported_values[param_key] = imported_list
|
|
245
|
-
logger.info(f"Imported SSM parameter list: {param_key} with {len(imported_list)} items")
|
|
246
|
-
else:
|
|
247
|
-
# Handle string values
|
|
248
|
-
param_path = param_value
|
|
249
|
-
if not param_path.startswith('/'):
|
|
250
|
-
param_path = f"/{param_path}"
|
|
251
|
-
|
|
252
|
-
construct_id = f"ssm-import-{param_key}-{hash(param_path) % 10000}"
|
|
253
|
-
param = ssm.StringParameter.from_string_parameter_name(
|
|
254
|
-
self, construct_id, param_path
|
|
255
|
-
)
|
|
256
|
-
|
|
257
|
-
self.ssm_imported_values[param_key] = param.string_value
|
|
258
|
-
logger.info(f"Imported SSM parameter: {param_key} from {param_path}")
|
|
259
|
-
|
|
260
|
-
except Exception as e:
|
|
261
|
-
logger.error(f"Failed to import SSM parameter {param_key}: {e}")
|
|
262
|
-
raise
|
|
200
|
+
# Use the centralized VPC resolution from VPCProviderMixin
|
|
201
|
+
self._vpc = self.resolve_vpc(
|
|
202
|
+
config=self.lb_config,
|
|
203
|
+
deployment=self.deployment,
|
|
204
|
+
workload=self.workload
|
|
205
|
+
)
|
|
206
|
+
return self._vpc
|
|
263
207
|
|
|
264
208
|
def _get_security_groups(self) -> List[ec2.ISecurityGroup]:
|
|
265
209
|
"""Get security groups for the Load Balancer"""
|
|
266
210
|
security_groups = []
|
|
267
211
|
|
|
268
212
|
# Check SSM imported values first
|
|
269
|
-
if "security_groups" in self.
|
|
270
|
-
sg_ids = self.
|
|
213
|
+
if "security_groups" in self._ssm_imported_values:
|
|
214
|
+
sg_ids = self._ssm_imported_values["security_groups"]
|
|
271
215
|
if not isinstance(sg_ids, list):
|
|
272
216
|
sg_ids = [sg_ids]
|
|
273
217
|
else:
|
|
@@ -285,9 +229,16 @@ class LoadBalancerStack(IStack, EnhancedSsmParameterMixin):
|
|
|
285
229
|
"""Get subnets for the Load Balancer"""
|
|
286
230
|
subnets = []
|
|
287
231
|
|
|
288
|
-
#
|
|
289
|
-
|
|
290
|
-
|
|
232
|
+
# Use the standardized helper function to get subnet IDs
|
|
233
|
+
subnet_ids = self.get_subnet_ids(self.lb_config)
|
|
234
|
+
|
|
235
|
+
if not subnet_ids:
|
|
236
|
+
return None
|
|
237
|
+
|
|
238
|
+
# Check if we have unresolved tokens from SSM
|
|
239
|
+
ssm_imports = self.get_all_ssm_imports()
|
|
240
|
+
if "subnet_ids" in ssm_imports:
|
|
241
|
+
subnet_ids_value = ssm_imports["subnet_ids"]
|
|
291
242
|
|
|
292
243
|
# Check if this is a CDK token (unresolved SSM parameter)
|
|
293
244
|
if cdk.Token.is_unresolved(subnet_ids_value):
|
|
@@ -296,25 +247,40 @@ class LoadBalancerStack(IStack, EnhancedSsmParameterMixin):
|
|
|
296
247
|
# The ALB construct will handle the token-based subnet IDs
|
|
297
248
|
logger.info("Subnet IDs are unresolved tokens, will use vpc_subnets with token resolution")
|
|
298
249
|
return None
|
|
299
|
-
elif isinstance(subnet_ids_value, str):
|
|
300
|
-
# If it's a resolved string, split it
|
|
301
|
-
subnet_ids = [s.strip() for s in subnet_ids_value.split(',')]
|
|
302
|
-
elif isinstance(subnet_ids_value, list):
|
|
303
|
-
subnet_ids = subnet_ids_value
|
|
304
|
-
else:
|
|
305
|
-
subnet_ids = [subnet_ids_value]
|
|
306
|
-
else:
|
|
307
|
-
subnet_ids = self.lb_config.subnets
|
|
308
|
-
|
|
309
|
-
if not subnet_ids:
|
|
310
|
-
return None
|
|
311
250
|
|
|
251
|
+
# Convert subnet IDs to subnet objects
|
|
312
252
|
for idx, subnet_id in enumerate(subnet_ids):
|
|
313
253
|
subnets.append(
|
|
314
254
|
ec2.Subnet.from_subnet_id(self, f"Subnet-{idx}", subnet_id)
|
|
315
255
|
)
|
|
316
256
|
return subnets
|
|
317
257
|
|
|
258
|
+
def _generate_target_group_name(self, lb_name: str, tg_name: str, max_length: int = 32) -> str:
|
|
259
|
+
"""Generate a unique target group name that doesn't begin/end with hyphens"""
|
|
260
|
+
full_name = f"{lb_name}-{tg_name}"
|
|
261
|
+
|
|
262
|
+
if len(full_name) <= max_length:
|
|
263
|
+
# No truncation needed, just ensure no leading/trailing hyphens
|
|
264
|
+
return full_name.strip('-')
|
|
265
|
+
|
|
266
|
+
# Need to truncate - use hash suffix for uniqueness
|
|
267
|
+
# Reserve space for hash (typically 8 chars) and separator
|
|
268
|
+
hash_length = 8
|
|
269
|
+
separator_length = 1
|
|
270
|
+
max_name_length = max_length - hash_length - separator_length
|
|
271
|
+
|
|
272
|
+
# Take the prefix and ensure it doesn't end with hyphen
|
|
273
|
+
prefix = full_name[:max_name_length].rstrip('-')
|
|
274
|
+
|
|
275
|
+
# Generate hash of the full name for uniqueness
|
|
276
|
+
hash_bytes = hashlib.sha256(full_name.encode()).digest()
|
|
277
|
+
hash_suffix = base64.urlsafe_b64encode(hash_bytes).decode()[:hash_length]
|
|
278
|
+
|
|
279
|
+
# Ensure hash doesn't start with hyphen (replace any non-alphanumeric chars)
|
|
280
|
+
hash_suffix = ''.join(c for c in hash_suffix if c.isalnum())[:hash_length]
|
|
281
|
+
|
|
282
|
+
return f"{prefix}-{hash_suffix}"
|
|
283
|
+
|
|
318
284
|
def _create_target_groups(self, lb_name: str) -> None:
|
|
319
285
|
"""Create target groups for the Load Balancer"""
|
|
320
286
|
|
|
@@ -322,6 +288,9 @@ class LoadBalancerStack(IStack, EnhancedSsmParameterMixin):
|
|
|
322
288
|
tg_name = tg_config.get("name", f"tg-{idx}")
|
|
323
289
|
tg_id = f"{lb_name}-{tg_name}"
|
|
324
290
|
|
|
291
|
+
# Generate a unique target group name that doesn't begin/end with hyphens
|
|
292
|
+
tg_name_sanitized = self._generate_target_group_name(lb_name, tg_name)
|
|
293
|
+
|
|
325
294
|
# Configure health check
|
|
326
295
|
health_check = self._configure_health_check(
|
|
327
296
|
tg_config.get("health_check", {})
|
|
@@ -332,7 +301,7 @@ class LoadBalancerStack(IStack, EnhancedSsmParameterMixin):
|
|
|
332
301
|
target_group = elbv2.ApplicationTargetGroup(
|
|
333
302
|
self,
|
|
334
303
|
tg_id,
|
|
335
|
-
target_group_name=
|
|
304
|
+
target_group_name=tg_name_sanitized,
|
|
336
305
|
vpc=self.vpc,
|
|
337
306
|
port=tg_config.get("port", 80),
|
|
338
307
|
protocol=elbv2.ApplicationProtocol(
|
|
@@ -347,7 +316,7 @@ class LoadBalancerStack(IStack, EnhancedSsmParameterMixin):
|
|
|
347
316
|
target_group = elbv2.NetworkTargetGroup(
|
|
348
317
|
self,
|
|
349
318
|
tg_id,
|
|
350
|
-
target_group_name=
|
|
319
|
+
target_group_name=tg_name_sanitized,
|
|
351
320
|
vpc=self.vpc,
|
|
352
321
|
port=tg_config.get("port", 80),
|
|
353
322
|
protocol=elbv2.Protocol(tg_config.get("protocol", "TCP")),
|
|
@@ -357,6 +326,8 @@ class LoadBalancerStack(IStack, EnhancedSsmParameterMixin):
|
|
|
357
326
|
health_check=health_check,
|
|
358
327
|
)
|
|
359
328
|
|
|
329
|
+
|
|
330
|
+
|
|
360
331
|
# Store target group for later use
|
|
361
332
|
self.target_groups[tg_name] = target_group
|
|
362
333
|
|
|
@@ -397,6 +368,11 @@ class LoadBalancerStack(IStack, EnhancedSsmParameterMixin):
|
|
|
397
368
|
if protocol.upper() == "HTTPS":
|
|
398
369
|
certificates = self._get_certificates()
|
|
399
370
|
|
|
371
|
+
if not certificates and protocol.upper() == "HTTPS":
|
|
372
|
+
message = "No certificates found for HTTPS listener. Please attach a certificate or create a certificate stack."
|
|
373
|
+
logger.warning(message)
|
|
374
|
+
raise ValueError(message)
|
|
375
|
+
|
|
400
376
|
listener = elbv2.ApplicationListener(
|
|
401
377
|
self,
|
|
402
378
|
listener_id,
|
|
@@ -449,8 +425,9 @@ class LoadBalancerStack(IStack, EnhancedSsmParameterMixin):
|
|
|
449
425
|
certificates = []
|
|
450
426
|
|
|
451
427
|
# Check SSM imported values first (takes priority)
|
|
452
|
-
|
|
453
|
-
|
|
428
|
+
ssm_imports = self.get_all_ssm_imports()
|
|
429
|
+
if "certificate_arns" in ssm_imports:
|
|
430
|
+
cert_arns = ssm_imports["certificate_arns"]
|
|
454
431
|
if not isinstance(cert_arns, list):
|
|
455
432
|
cert_arns = [cert_arns]
|
|
456
433
|
for cert_arn in cert_arns:
|
|
@@ -479,44 +456,42 @@ class LoadBalancerStack(IStack, EnhancedSsmParameterMixin):
|
|
|
479
456
|
# Configure conditions
|
|
480
457
|
conditions = []
|
|
481
458
|
|
|
482
|
-
#
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
http_headers = rule_config.get("http_headers", {})
|
|
494
|
-
for header_name, header_values in http_headers.items():
|
|
495
|
-
conditions.append(
|
|
496
|
-
elbv2.ListenerCondition.http_header(header_name, header_values)
|
|
497
|
-
)
|
|
498
|
-
|
|
499
|
-
# Query strings
|
|
500
|
-
query_strings = rule_config.get("query_strings", [])
|
|
501
|
-
if query_strings:
|
|
502
|
-
query_string_conditions = []
|
|
503
|
-
for qs in query_strings:
|
|
504
|
-
query_string_conditions.append(
|
|
505
|
-
elbv2.QueryStringCondition(
|
|
506
|
-
key=qs.get("key"), value=qs.get("value")
|
|
459
|
+
# Parse AWS ALB conditions format
|
|
460
|
+
aws_conditions = rule_config.get("conditions", [])
|
|
461
|
+
for condition in aws_conditions:
|
|
462
|
+
field = condition.get("field")
|
|
463
|
+
if field == "http-header" and "http_header_config" in condition:
|
|
464
|
+
header_config = condition["http_header_config"]
|
|
465
|
+
header_name = header_config.get("header_name")
|
|
466
|
+
header_values = header_config.get("values", [])
|
|
467
|
+
if header_name and header_values:
|
|
468
|
+
conditions.append(
|
|
469
|
+
elbv2.ListenerCondition.http_header(header_name, header_values)
|
|
507
470
|
)
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
471
|
+
elif field == "path-pattern" and "values" in condition:
|
|
472
|
+
path_patterns = condition.get("values", [])
|
|
473
|
+
if path_patterns:
|
|
474
|
+
conditions.append(elbv2.ListenerCondition.path_patterns(path_patterns))
|
|
475
|
+
elif field == "host-header" and "values" in condition:
|
|
476
|
+
host_headers = condition.get("values", [])
|
|
477
|
+
if host_headers:
|
|
478
|
+
conditions.append(elbv2.ListenerCondition.host_headers(host_headers))
|
|
512
479
|
|
|
513
480
|
# Configure actions
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
)
|
|
481
|
+
target_group = None
|
|
482
|
+
|
|
483
|
+
# Parse AWS ALB actions format
|
|
484
|
+
aws_actions = rule_config.get("actions", [])
|
|
485
|
+
for action in aws_actions:
|
|
486
|
+
if action.get("type") == "forward":
|
|
487
|
+
target_group_name = action.get("target_group")
|
|
488
|
+
target_group = (
|
|
489
|
+
self.target_groups.get(target_group_name) if target_group_name else None
|
|
490
|
+
)
|
|
491
|
+
break # Use first forward action
|
|
518
492
|
|
|
519
|
-
|
|
493
|
+
# Validate that we have both conditions and target group before creating rule
|
|
494
|
+
if target_group and conditions:
|
|
520
495
|
# Create rule with forward action
|
|
521
496
|
elbv2.ApplicationListenerRule(
|
|
522
497
|
self,
|
|
@@ -526,6 +501,15 @@ class LoadBalancerStack(IStack, EnhancedSsmParameterMixin):
|
|
|
526
501
|
conditions=conditions,
|
|
527
502
|
target_groups=[target_group],
|
|
528
503
|
)
|
|
504
|
+
elif not conditions:
|
|
505
|
+
logger.warning(
|
|
506
|
+
f"Skipping listener rule '{rule_id}' - no conditions defined. "
|
|
507
|
+
f"CDK requires at least one condition for every rule."
|
|
508
|
+
)
|
|
509
|
+
elif not target_group:
|
|
510
|
+
logger.warning(
|
|
511
|
+
f"Skipping listener rule '{rule_id}' - no valid target group found."
|
|
512
|
+
)
|
|
529
513
|
|
|
530
514
|
def _add_ip_whitelist_rules(
|
|
531
515
|
self,
|
|
@@ -672,36 +656,7 @@ class LoadBalancerStack(IStack, EnhancedSsmParameterMixin):
|
|
|
672
656
|
|
|
673
657
|
def _export_cfn_outputs(self, lb_name: str) -> None:
|
|
674
658
|
"""Add CloudFormation outputs for the Load Balancer"""
|
|
675
|
-
|
|
676
|
-
# Load Balancer DNS Name
|
|
677
|
-
cdk.CfnOutput(
|
|
678
|
-
self,
|
|
679
|
-
f"{lb_name}-dns-name",
|
|
680
|
-
value=self.load_balancer.load_balancer_dns_name,
|
|
681
|
-
export_name=f"{self.deployment.build_resource_name(lb_name)}-dns-name",
|
|
682
|
-
)
|
|
683
|
-
|
|
684
|
-
# Load Balancer ARN
|
|
685
|
-
cdk.CfnOutput(
|
|
686
|
-
self,
|
|
687
|
-
f"{lb_name}-arn",
|
|
688
|
-
value=self.load_balancer.load_balancer_arn,
|
|
689
|
-
export_name=f"{self.deployment.build_resource_name(lb_name)}-arn",
|
|
690
|
-
)
|
|
691
|
-
|
|
692
|
-
# Target Group ARNs
|
|
693
|
-
for tg_name, target_group in self.target_groups.items():
|
|
694
|
-
# Normalize target group name for consistent CloudFormation export naming
|
|
695
|
-
normalized_tg_name = self.normalize_resource_name(
|
|
696
|
-
tg_name, for_export=True
|
|
697
|
-
)
|
|
698
|
-
cdk.CfnOutput(
|
|
699
|
-
self,
|
|
700
|
-
f"{lb_name}-{normalized_tg_name}-arn",
|
|
701
|
-
value=target_group.target_group_arn,
|
|
702
|
-
export_name=f"{self.deployment.build_resource_name(lb_name)}-{normalized_tg_name}-arn",
|
|
703
|
-
)
|
|
704
|
-
|
|
659
|
+
return
|
|
705
660
|
def _export_ssm_parameters(self, lb_name: str) -> None:
|
|
706
661
|
"""Export Load Balancer resources to SSM Parameter Store if configured"""
|
|
707
662
|
if not self.load_balancer:
|
|
@@ -732,32 +687,4 @@ class LoadBalancerStack(IStack, EnhancedSsmParameterMixin):
|
|
|
732
687
|
|
|
733
688
|
def _export_cfn_outputs(self, lb_name: str) -> None:
|
|
734
689
|
"""Add CloudFormation outputs for the Load Balancer"""
|
|
735
|
-
|
|
736
|
-
# Load Balancer DNS Name
|
|
737
|
-
cdk.CfnOutput(
|
|
738
|
-
self,
|
|
739
|
-
f"{lb_name}-dns-name",
|
|
740
|
-
value=self.load_balancer.load_balancer_dns_name,
|
|
741
|
-
export_name=f"{self.deployment.build_resource_name(lb_name)}-dns-name",
|
|
742
|
-
)
|
|
743
|
-
|
|
744
|
-
# Load Balancer ARN
|
|
745
|
-
cdk.CfnOutput(
|
|
746
|
-
self,
|
|
747
|
-
f"{lb_name}-arn",
|
|
748
|
-
value=self.load_balancer.load_balancer_arn,
|
|
749
|
-
export_name=f"{self.deployment.build_resource_name(lb_name)}-arn",
|
|
750
|
-
)
|
|
751
|
-
|
|
752
|
-
# Target Group ARNs
|
|
753
|
-
for tg_name, target_group in self.target_groups.items():
|
|
754
|
-
# Normalize target group name for consistent CloudFormation export naming
|
|
755
|
-
normalized_tg_name = self.normalize_resource_name(
|
|
756
|
-
tg_name, for_export=True
|
|
757
|
-
)
|
|
758
|
-
cdk.CfnOutput(
|
|
759
|
-
self,
|
|
760
|
-
f"{lb_name}-{normalized_tg_name}-arn",
|
|
761
|
-
value=target_group.target_group_arn,
|
|
762
|
-
export_name=f"{self.deployment.build_resource_name(lb_name)}-{normalized_tg_name}-arn",
|
|
763
|
-
)
|
|
690
|
+
return
|