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
|
@@ -18,7 +18,8 @@ from cdk_factory.configurations.deployment import DeploymentConfig
|
|
|
18
18
|
from cdk_factory.configurations.stack import StackConfig
|
|
19
19
|
from cdk_factory.configurations.resources.rds import RdsConfig
|
|
20
20
|
from cdk_factory.interfaces.istack import IStack
|
|
21
|
-
from cdk_factory.interfaces.
|
|
21
|
+
from cdk_factory.interfaces.vpc_provider_mixin import VPCProviderMixin
|
|
22
|
+
from cdk_factory.interfaces.standardized_ssm_mixin import StandardizedSsmMixin
|
|
22
23
|
from cdk_factory.stack.stack_module_registry import register_stack
|
|
23
24
|
from cdk_factory.workload.workload_factory import WorkloadConfig
|
|
24
25
|
|
|
@@ -27,7 +28,7 @@ logger = Logger(service="RdsStack")
|
|
|
27
28
|
|
|
28
29
|
@register_stack("rds_library_module")
|
|
29
30
|
@register_stack("rds_stack")
|
|
30
|
-
class RdsStack(IStack,
|
|
31
|
+
class RdsStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
|
|
31
32
|
"""
|
|
32
33
|
Reusable stack for AWS RDS.
|
|
33
34
|
Supports creating RDS instances with customizable configurations.
|
|
@@ -68,8 +69,18 @@ class RdsStack(IStack, EnhancedSsmParameterMixin):
|
|
|
68
69
|
self.rds_config = RdsConfig(stack_config.dictionary.get("rds", {}), deployment)
|
|
69
70
|
db_name = deployment.build_resource_name(self.rds_config.name)
|
|
70
71
|
|
|
71
|
-
#
|
|
72
|
-
self.
|
|
72
|
+
# Setup standardized SSM integration
|
|
73
|
+
self.setup_ssm_integration(
|
|
74
|
+
scope=self,
|
|
75
|
+
config=self.rds_config,
|
|
76
|
+
resource_type="rds",
|
|
77
|
+
resource_name=self.rds_config.name,
|
|
78
|
+
deployment=deployment,
|
|
79
|
+
workload=workload
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
# Process SSM imports
|
|
83
|
+
self.process_ssm_imports()
|
|
73
84
|
|
|
74
85
|
# Get VPC and security groups
|
|
75
86
|
self.security_groups = self._get_security_groups()
|
|
@@ -86,63 +97,18 @@ class RdsStack(IStack, EnhancedSsmParameterMixin):
|
|
|
86
97
|
# Export to SSM Parameter Store
|
|
87
98
|
self._export_ssm_parameters(db_name)
|
|
88
99
|
|
|
89
|
-
def _process_ssm_imports(self) -> None:
|
|
90
|
-
"""Process SSM imports from configuration"""
|
|
91
|
-
ssm_imports = self.rds_config.ssm_imports
|
|
92
|
-
|
|
93
|
-
if not ssm_imports:
|
|
94
|
-
logger.debug("No SSM imports configured for RDS")
|
|
95
|
-
return
|
|
96
|
-
|
|
97
|
-
logger.info(f"Processing {len(ssm_imports)} SSM imports for RDS")
|
|
98
|
-
|
|
99
|
-
for param_key, param_path in ssm_imports.items():
|
|
100
|
-
try:
|
|
101
|
-
if not param_path.startswith('/'):
|
|
102
|
-
param_path = f"/{param_path}"
|
|
103
|
-
|
|
104
|
-
construct_id = f"ssm-import-{param_key}-{hash(param_path) % 10000}"
|
|
105
|
-
param = ssm.StringParameter.from_string_parameter_name(
|
|
106
|
-
self, construct_id, param_path
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
self.ssm_imported_values[param_key] = param.string_value
|
|
110
|
-
logger.info(f"Imported SSM parameter: {param_key} from {param_path}")
|
|
111
|
-
|
|
112
|
-
except Exception as e:
|
|
113
|
-
logger.error(f"Failed to import SSM parameter {param_key} from {param_path}: {e}")
|
|
114
|
-
raise
|
|
115
|
-
|
|
116
100
|
@property
|
|
117
101
|
def vpc(self) -> ec2.IVpc:
|
|
118
|
-
"""Get the VPC for the RDS instance"""
|
|
119
|
-
if self._vpc:
|
|
102
|
+
"""Get the VPC for the RDS instance using centralized VPC provider mixin."""
|
|
103
|
+
if hasattr(self, '_vpc') and self._vpc:
|
|
120
104
|
return self._vpc
|
|
121
105
|
|
|
122
|
-
#
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
# We'll create a DB subnet group separately instead
|
|
129
|
-
vpc_attrs = {
|
|
130
|
-
"vpc_id": vpc_id,
|
|
131
|
-
"availability_zones": ["us-east-1a", "us-east-1b"]
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
# Use from_vpc_attributes() for SSM tokens
|
|
135
|
-
self._vpc = ec2.Vpc.from_vpc_attributes(self, "VPC", **vpc_attrs)
|
|
136
|
-
elif self.rds_config.vpc_id:
|
|
137
|
-
self._vpc = ec2.Vpc.from_lookup(self, "VPC", vpc_id=self.rds_config.vpc_id)
|
|
138
|
-
elif self.workload.vpc_id:
|
|
139
|
-
self._vpc = ec2.Vpc.from_lookup(self, "VPC", vpc_id=self.workload.vpc_id)
|
|
140
|
-
else:
|
|
141
|
-
raise ValueError(
|
|
142
|
-
"VPC is not defined in the configuration. "
|
|
143
|
-
"You can provide it a the rds.vpc_id in the configuration "
|
|
144
|
-
"or a top level workload.vpc_id in the workload configuration."
|
|
145
|
-
)
|
|
106
|
+
# Resolve VPC using the centralized VPC provider mixin
|
|
107
|
+
self._vpc = self.resolve_vpc(
|
|
108
|
+
config=self.rds_config,
|
|
109
|
+
deployment=self.deployment,
|
|
110
|
+
workload=self.workload
|
|
111
|
+
)
|
|
146
112
|
return self._vpc
|
|
147
113
|
|
|
148
114
|
def _get_security_groups(self) -> List[ec2.ISecurityGroup]:
|
|
@@ -150,8 +116,9 @@ class RdsStack(IStack, EnhancedSsmParameterMixin):
|
|
|
150
116
|
security_groups = []
|
|
151
117
|
|
|
152
118
|
# Check SSM imports first for security group ID
|
|
153
|
-
|
|
154
|
-
|
|
119
|
+
ssm_imports = self.get_all_ssm_imports()
|
|
120
|
+
if "security_group_rds_id" in ssm_imports:
|
|
121
|
+
sg_id = ssm_imports["security_group_rds_id"]
|
|
155
122
|
security_groups.append(
|
|
156
123
|
ec2.SecurityGroup.from_security_group_id(
|
|
157
124
|
self, "RDSSecurityGroup", sg_id
|
|
@@ -168,27 +135,60 @@ class RdsStack(IStack, EnhancedSsmParameterMixin):
|
|
|
168
135
|
|
|
169
136
|
return security_groups
|
|
170
137
|
|
|
138
|
+
def _get_subnet_selection(self) -> ec2.SubnetSelection:
|
|
139
|
+
"""
|
|
140
|
+
Get subnet selection based on available subnet types in the VPC.
|
|
141
|
+
|
|
142
|
+
RDS instances require private subnets for security, but we'll fall back
|
|
143
|
+
to available subnets if the preferred types aren't available.
|
|
144
|
+
"""
|
|
145
|
+
vpc = self.vpc
|
|
146
|
+
|
|
147
|
+
# Check for isolated subnets first (most secure for RDS)
|
|
148
|
+
if vpc.isolated_subnets:
|
|
149
|
+
logger.info("Using isolated subnets for RDS instance")
|
|
150
|
+
return ec2.SubnetSelection(subnet_type=ec2.SubnetType.PRIVATE_ISOLATED)
|
|
151
|
+
|
|
152
|
+
# Check for private subnets next
|
|
153
|
+
elif vpc.private_subnets:
|
|
154
|
+
logger.info("Using private subnets for RDS instance")
|
|
155
|
+
return ec2.SubnetSelection(subnet_type=ec2.SubnetType.PRIVATE_WITH_EGRESS)
|
|
156
|
+
|
|
157
|
+
# Fall back to public subnets (not recommended for production)
|
|
158
|
+
elif vpc.public_subnets:
|
|
159
|
+
logger.warning("Using public subnets for RDS instance - not recommended for production")
|
|
160
|
+
return ec2.SubnetSelection(subnet_type=ec2.SubnetType.PUBLIC)
|
|
161
|
+
|
|
162
|
+
else:
|
|
163
|
+
raise ValueError("No subnets available in VPC for RDS instance")
|
|
164
|
+
|
|
171
165
|
def _create_db_instance(self, db_name: str) -> rds.DatabaseInstance:
|
|
172
166
|
"""Create a new RDS instance"""
|
|
173
167
|
# Configure subnet group
|
|
174
168
|
# If we have subnet IDs from SSM, create a DB subnet group explicitly
|
|
175
169
|
db_subnet_group = None
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
170
|
+
subnet_ids = self.get_subnet_ids(self.rds_config)
|
|
171
|
+
|
|
172
|
+
if subnet_ids:
|
|
173
|
+
# For CloudFormation token resolution, we need to get the raw SSM value
|
|
174
|
+
# Use the standardized SSM imports
|
|
175
|
+
ssm_imports = self.get_all_ssm_imports()
|
|
176
|
+
if "subnet_ids" in ssm_imports:
|
|
177
|
+
subnet_ids_str = ssm_imports["subnet_ids"]
|
|
178
|
+
# Split the comma-separated token into a list for CloudFormation
|
|
179
|
+
subnet_ids_list = cdk.Fn.split(",", subnet_ids_str)
|
|
180
|
+
|
|
181
|
+
# Create DB subnet group with the token-based subnet list
|
|
182
|
+
db_subnet_group = rds.CfnDBSubnetGroup(
|
|
183
|
+
self,
|
|
184
|
+
"DBSubnetGroup",
|
|
185
|
+
db_subnet_group_description=f"Subnet group for {db_name}",
|
|
186
|
+
subnet_ids=subnet_ids_list,
|
|
187
|
+
db_subnet_group_name=f"{db_name}-subnet-group"
|
|
188
|
+
)
|
|
189
189
|
|
|
190
190
|
# Configure subnet selection for VPC (when not using SSM imports)
|
|
191
|
-
subnets = None if db_subnet_group else
|
|
191
|
+
subnets = None if db_subnet_group else self._get_subnet_selection()
|
|
192
192
|
|
|
193
193
|
# Configure engine
|
|
194
194
|
engine_version = None
|
|
@@ -285,31 +285,7 @@ class RdsStack(IStack, EnhancedSsmParameterMixin):
|
|
|
285
285
|
|
|
286
286
|
def _add_outputs(self, db_name: str) -> None:
|
|
287
287
|
"""Add CloudFormation outputs for the RDS instance"""
|
|
288
|
-
|
|
289
|
-
# Database endpoint
|
|
290
|
-
cdk.CfnOutput(
|
|
291
|
-
self,
|
|
292
|
-
f"{db_name}-endpoint",
|
|
293
|
-
value=self.db_instance.db_instance_endpoint_address,
|
|
294
|
-
export_name=f"{self.deployment.build_resource_name(db_name)}-endpoint",
|
|
295
|
-
)
|
|
296
|
-
|
|
297
|
-
# Database port
|
|
298
|
-
cdk.CfnOutput(
|
|
299
|
-
self,
|
|
300
|
-
f"{db_name}-port",
|
|
301
|
-
value=self.db_instance.db_instance_endpoint_port,
|
|
302
|
-
export_name=f"{self.deployment.build_resource_name(db_name)}-port",
|
|
303
|
-
)
|
|
304
|
-
|
|
305
|
-
# Secret ARN (if available)
|
|
306
|
-
if hasattr(self.db_instance, "secret") and self.db_instance.secret:
|
|
307
|
-
cdk.CfnOutput(
|
|
308
|
-
self,
|
|
309
|
-
f"{db_name}-secret-arn",
|
|
310
|
-
value=self.db_instance.secret.secret_arn,
|
|
311
|
-
export_name=f"{self.deployment.build_resource_name(db_name)}-secret-arn",
|
|
312
|
-
)
|
|
288
|
+
return
|
|
313
289
|
|
|
314
290
|
def _export_ssm_parameters(self, db_name: str) -> None:
|
|
315
291
|
"""Export RDS connection info and credentials to SSM Parameter Store"""
|
|
@@ -7,10 +7,15 @@ MIT License. See Project Root for the license information.
|
|
|
7
7
|
from typing import Dict, Any, List, Optional
|
|
8
8
|
|
|
9
9
|
import aws_cdk as cdk
|
|
10
|
-
from aws_cdk import
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
from aws_cdk import (
|
|
11
|
+
aws_route53 as route53,
|
|
12
|
+
aws_route53_targets as targets,
|
|
13
|
+
aws_certificatemanager as acm,
|
|
14
|
+
aws_elasticloadbalancingv2 as elbv2,
|
|
15
|
+
aws_cloudfront as cloudfront,
|
|
16
|
+
Duration,
|
|
17
|
+
CfnOutput,
|
|
18
|
+
)
|
|
14
19
|
from aws_lambda_powertools import Logger
|
|
15
20
|
from constructs import Construct
|
|
16
21
|
|
|
@@ -18,7 +23,7 @@ from cdk_factory.configurations.deployment import DeploymentConfig
|
|
|
18
23
|
from cdk_factory.configurations.stack import StackConfig
|
|
19
24
|
from cdk_factory.configurations.resources.route53 import Route53Config
|
|
20
25
|
from cdk_factory.interfaces.istack import IStack
|
|
21
|
-
from cdk_factory.interfaces.
|
|
26
|
+
from cdk_factory.interfaces.standardized_ssm_mixin import StandardizedSsmMixin
|
|
22
27
|
from cdk_factory.stack.stack_module_registry import register_stack
|
|
23
28
|
from cdk_factory.workload.workload_factory import WorkloadConfig
|
|
24
29
|
|
|
@@ -27,7 +32,7 @@ logger = Logger(service="Route53Stack")
|
|
|
27
32
|
|
|
28
33
|
@register_stack("route53_library_module")
|
|
29
34
|
@register_stack("route53_stack")
|
|
30
|
-
class Route53Stack(IStack,
|
|
35
|
+
class Route53Stack(IStack, StandardizedSsmMixin):
|
|
31
36
|
"""
|
|
32
37
|
Reusable stack for AWS Route53.
|
|
33
38
|
Supports creating hosted zones, DNS records, and certificate validation.
|
|
@@ -35,6 +40,7 @@ class Route53Stack(IStack, EnhancedSsmParameterMixin):
|
|
|
35
40
|
|
|
36
41
|
def __init__(self, scope: Construct, id: str, **kwargs) -> None:
|
|
37
42
|
super().__init__(scope, id, **kwargs)
|
|
43
|
+
|
|
38
44
|
self.route53_config = None
|
|
39
45
|
self.stack_config = None
|
|
40
46
|
self.deployment = None
|
|
@@ -42,6 +48,7 @@ class Route53Stack(IStack, EnhancedSsmParameterMixin):
|
|
|
42
48
|
self.hosted_zone = None
|
|
43
49
|
self.certificate = None
|
|
44
50
|
self.records = {}
|
|
51
|
+
self._distribution_cache = {} # Cache for reusing distributions
|
|
45
52
|
|
|
46
53
|
def build(self, stack_config: StackConfig, deployment: DeploymentConfig, workload: WorkloadConfig) -> None:
|
|
47
54
|
"""Build the Route53 stack"""
|
|
@@ -112,6 +119,232 @@ class Route53Stack(IStack, EnhancedSsmParameterMixin):
|
|
|
112
119
|
return certificate
|
|
113
120
|
|
|
114
121
|
def _create_dns_records(self) -> None:
|
|
122
|
+
self._create_dns_records_old()
|
|
123
|
+
self._create_dns_records_new()
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _get_or_create_cloudfront_distribution(self, distribution_domain: str, distribution_id: str) -> cloudfront.Distribution:
|
|
127
|
+
"""Get or create a CloudFront distribution, reusing if already created"""
|
|
128
|
+
# Create a unique cache key from distribution domain and ID
|
|
129
|
+
cache_key = f"{distribution_domain}-{distribution_id}"
|
|
130
|
+
|
|
131
|
+
if cache_key not in self._distribution_cache:
|
|
132
|
+
# Create the distribution construct with a unique ID
|
|
133
|
+
unique_id = f"CF-{distribution_domain.replace('.', '-').replace('*', 'wildcard')}-{hash(cache_key) % 10000}"
|
|
134
|
+
distribution = cloudfront.Distribution.from_distribution_attributes(
|
|
135
|
+
self, unique_id,
|
|
136
|
+
domain_name=distribution_domain,
|
|
137
|
+
distribution_id=distribution_id
|
|
138
|
+
)
|
|
139
|
+
self._distribution_cache[cache_key] = distribution
|
|
140
|
+
logger.info(f"Created CloudFront distribution construct for {distribution_domain}")
|
|
141
|
+
|
|
142
|
+
return self._distribution_cache[cache_key]
|
|
143
|
+
|
|
144
|
+
def _create_dns_records_new(self) -> None:
|
|
145
|
+
"""Create DNS records based on configuration - generic implementation"""
|
|
146
|
+
|
|
147
|
+
missing_configurations = []
|
|
148
|
+
|
|
149
|
+
for record in self.route53_config.records:
|
|
150
|
+
record_name = record.get("name", "")
|
|
151
|
+
record_type = record.get("type", "")
|
|
152
|
+
|
|
153
|
+
if not record_name or not record_type:
|
|
154
|
+
message = f"Record missing name or type: {record}"
|
|
155
|
+
logger.warning(message)
|
|
156
|
+
missing_configurations.append(message)
|
|
157
|
+
continue
|
|
158
|
+
|
|
159
|
+
# Handle alias records
|
|
160
|
+
if "alias" in record:
|
|
161
|
+
alias_config = record["alias"]
|
|
162
|
+
target_type = alias_config.get("target_type", "")
|
|
163
|
+
target_value = alias_config.get("target_value", "")
|
|
164
|
+
hosted_zone_id = alias_config.get("hosted_zone_id", "")
|
|
165
|
+
|
|
166
|
+
unique_id = f"{record_name}-{record_type}"
|
|
167
|
+
# Handle SSM parameter references in target_value
|
|
168
|
+
target_value = self.resolve_ssm_value(self, target_value, unique_id=unique_id)
|
|
169
|
+
|
|
170
|
+
if not target_type or not target_value:
|
|
171
|
+
message = f"Alias record missing target_type or target_value: {record}"
|
|
172
|
+
logger.warning(message)
|
|
173
|
+
missing_configurations.append(message)
|
|
174
|
+
continue
|
|
175
|
+
|
|
176
|
+
# Create appropriate target based on type
|
|
177
|
+
alias_target = None
|
|
178
|
+
if target_type == "cloudfront":
|
|
179
|
+
# CloudFront distribution target
|
|
180
|
+
distribution_domain = target_value
|
|
181
|
+
distribution_id = alias_config.get("distribution_id", "")
|
|
182
|
+
if not distribution_id:
|
|
183
|
+
message = f"Alias record missing distribution_id: {record}"
|
|
184
|
+
logger.warning(message)
|
|
185
|
+
missing_configurations.append(message)
|
|
186
|
+
continue
|
|
187
|
+
|
|
188
|
+
# Get or create the distribution (reuses if already created)
|
|
189
|
+
distribution = self._get_or_create_cloudfront_distribution(distribution_domain, distribution_id)
|
|
190
|
+
alias_target = route53.RecordTarget.from_alias(
|
|
191
|
+
targets.CloudFrontTarget(distribution)
|
|
192
|
+
)
|
|
193
|
+
elif target_type == "loadbalancer" or target_type == "alb":
|
|
194
|
+
# Load Balancer target
|
|
195
|
+
alias_target = route53.RecordTarget.from_alias(
|
|
196
|
+
targets.LoadBalancerTarget(
|
|
197
|
+
elbv2.ApplicationLoadBalancer.from_load_balancer_attributes(
|
|
198
|
+
self, f"ALB-{record_name}",
|
|
199
|
+
load_balancer_dns_name=target_value,
|
|
200
|
+
load_balancer_canonical_hosted_zone_id=hosted_zone_id
|
|
201
|
+
)
|
|
202
|
+
)
|
|
203
|
+
)
|
|
204
|
+
elif target_type == "elbv2":
|
|
205
|
+
# Generic ELBv2 target
|
|
206
|
+
alias_target = route53.RecordTarget.from_alias(
|
|
207
|
+
targets.LoadBalancerTarget(
|
|
208
|
+
elbv2.ApplicationLoadBalancer.from_load_balancer_attributes(
|
|
209
|
+
self, f"ELB-{record_name}",
|
|
210
|
+
load_balancer_dns_name=target_value,
|
|
211
|
+
load_balancer_canonical_hosted_zone_id=hosted_zone_id
|
|
212
|
+
)
|
|
213
|
+
)
|
|
214
|
+
)
|
|
215
|
+
else:
|
|
216
|
+
message = f"Unsupported alias target type: {target_type}"
|
|
217
|
+
logger.warning(message)
|
|
218
|
+
missing_configurations.append(message)
|
|
219
|
+
continue
|
|
220
|
+
|
|
221
|
+
# Create the alias record
|
|
222
|
+
route53.ARecord(
|
|
223
|
+
self,
|
|
224
|
+
f"AliasRecord-{record_name}-{record_type}",
|
|
225
|
+
zone=self.hosted_zone,
|
|
226
|
+
record_name=record_name,
|
|
227
|
+
target=alias_target,
|
|
228
|
+
ttl=cdk.Duration.seconds(record.get("ttl", 300))
|
|
229
|
+
) if record_type == "A" else route53.AaaaRecord(
|
|
230
|
+
self,
|
|
231
|
+
f"AliasRecord-{record_name}-{record_type}",
|
|
232
|
+
zone=self.hosted_zone,
|
|
233
|
+
record_name=record_name,
|
|
234
|
+
target=alias_target,
|
|
235
|
+
ttl=cdk.Duration.seconds(record.get("ttl", 300))
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
# Handle standard records with values
|
|
239
|
+
elif "values" in record:
|
|
240
|
+
values = record["values"]
|
|
241
|
+
if not isinstance(values, list):
|
|
242
|
+
values = [values]
|
|
243
|
+
|
|
244
|
+
# Handle SSM parameter references in values
|
|
245
|
+
processed_values = []
|
|
246
|
+
for value in values:
|
|
247
|
+
if "{{ssm:" in str(value) and "}}" in str(value):
|
|
248
|
+
# Extract SSM parameter path from template like {{ssm:/path/to/parameter}}
|
|
249
|
+
ssm_path = str(value).split("{{ssm:")[1].split("}}")[0]
|
|
250
|
+
resolved_value = self.get_ssm_imported_value(ssm_path)
|
|
251
|
+
processed_values.append(resolved_value)
|
|
252
|
+
else:
|
|
253
|
+
processed_values.append(value)
|
|
254
|
+
|
|
255
|
+
values = processed_values
|
|
256
|
+
ttl = record.get("ttl", 300)
|
|
257
|
+
|
|
258
|
+
# Create standard record based on type
|
|
259
|
+
if record_type == "A":
|
|
260
|
+
route53.ARecord(
|
|
261
|
+
self,
|
|
262
|
+
f"Record-{record_name}",
|
|
263
|
+
zone=self.hosted_zone,
|
|
264
|
+
record_name=record_name,
|
|
265
|
+
target=route53.RecordTarget.from_ip_addresses(*values),
|
|
266
|
+
ttl=cdk.Duration.seconds(ttl)
|
|
267
|
+
)
|
|
268
|
+
elif record_type == "AAAA":
|
|
269
|
+
route53.AaaaRecord(
|
|
270
|
+
self,
|
|
271
|
+
f"Record-{record_name}",
|
|
272
|
+
zone=self.hosted_zone,
|
|
273
|
+
record_name=record_name,
|
|
274
|
+
target=route53.RecordTarget.from_ip_addresses(*values),
|
|
275
|
+
ttl=cdk.Duration.seconds(ttl)
|
|
276
|
+
)
|
|
277
|
+
elif record_type == "CNAME":
|
|
278
|
+
route53.CnameRecord(
|
|
279
|
+
self,
|
|
280
|
+
f"Record-{record_name}",
|
|
281
|
+
zone=self.hosted_zone,
|
|
282
|
+
record_name=record_name,
|
|
283
|
+
domain_name=values[0], # CNAME only supports single value
|
|
284
|
+
ttl=cdk.Duration.seconds(ttl)
|
|
285
|
+
)
|
|
286
|
+
elif record_type == "MX":
|
|
287
|
+
# MX records need special handling for preference values
|
|
288
|
+
mx_targets = []
|
|
289
|
+
for value in values:
|
|
290
|
+
if isinstance(value, str) and " " in value:
|
|
291
|
+
preference, domain = value.split(" ", 1)
|
|
292
|
+
mx_targets.append(route53.MxRecordValue(
|
|
293
|
+
domain_name=domain.strip(),
|
|
294
|
+
preference=int(preference.strip())
|
|
295
|
+
))
|
|
296
|
+
else:
|
|
297
|
+
logger.warning(f"Invalid MX record format: {value}")
|
|
298
|
+
|
|
299
|
+
if mx_targets:
|
|
300
|
+
route53.MxRecord(
|
|
301
|
+
self,
|
|
302
|
+
f"Record-{record_name}",
|
|
303
|
+
zone=self.hosted_zone,
|
|
304
|
+
record_name=record_name,
|
|
305
|
+
values=mx_targets,
|
|
306
|
+
ttl=cdk.Duration.seconds(ttl)
|
|
307
|
+
)
|
|
308
|
+
elif record_type == "TXT":
|
|
309
|
+
route53.TxtRecord(
|
|
310
|
+
self,
|
|
311
|
+
f"Record-{record_name}",
|
|
312
|
+
zone=self.hosted_zone,
|
|
313
|
+
record_name=record_name,
|
|
314
|
+
values=values,
|
|
315
|
+
ttl=cdk.Duration.seconds(ttl)
|
|
316
|
+
)
|
|
317
|
+
elif record_type == "NS":
|
|
318
|
+
route53.NsRecord(
|
|
319
|
+
self,
|
|
320
|
+
f"Record-{record_name}",
|
|
321
|
+
zone=self.hosted_zone,
|
|
322
|
+
record_name=record_name,
|
|
323
|
+
values=values,
|
|
324
|
+
ttl=cdk.Duration.seconds(ttl)
|
|
325
|
+
)
|
|
326
|
+
else:
|
|
327
|
+
message = f"Unsupported record type: {record_type}"
|
|
328
|
+
logger.warning(message)
|
|
329
|
+
missing_configurations.append(message)
|
|
330
|
+
continue
|
|
331
|
+
|
|
332
|
+
else:
|
|
333
|
+
message = f"Record missing 'alias' or 'values' configuration: {record}"
|
|
334
|
+
logger.warning(message)
|
|
335
|
+
missing_configurations.append(message)
|
|
336
|
+
continue
|
|
337
|
+
|
|
338
|
+
if missing_configurations and len(missing_configurations) > 0:
|
|
339
|
+
# print all missing configurations
|
|
340
|
+
print("Missing configurations:")
|
|
341
|
+
for message in missing_configurations:
|
|
342
|
+
print(message)
|
|
343
|
+
|
|
344
|
+
messages = "\n".join(missing_configurations)
|
|
345
|
+
raise ValueError(f"Missing Configurations:\n{messages}")
|
|
346
|
+
|
|
347
|
+
def _create_dns_records_old(self) -> None:
|
|
115
348
|
"""Create DNS records based on configuration"""
|
|
116
349
|
# Create alias records
|
|
117
350
|
for alias_record in self.route53_config.aliases:
|
|
@@ -119,6 +352,12 @@ class Route53Stack(IStack, EnhancedSsmParameterMixin):
|
|
|
119
352
|
target_type = alias_record.get("target_type", "")
|
|
120
353
|
target_value = alias_record.get("target_value", "")
|
|
121
354
|
|
|
355
|
+
# target value needs to handle SSM parameters
|
|
356
|
+
if "{{ssm:" in target_value and "}}" in target_value:
|
|
357
|
+
# Extract SSM parameter path from template like {{ssm:/path/to/parameter}}
|
|
358
|
+
ssm_path = target_value.split("{{ssm:")[1].split("}}")[0]
|
|
359
|
+
target_value = self.get_ssm_imported_value(ssm_path)
|
|
360
|
+
|
|
122
361
|
if not record_name or not target_type or not target_value:
|
|
123
362
|
continue
|
|
124
363
|
|
|
@@ -174,37 +413,4 @@ class Route53Stack(IStack, EnhancedSsmParameterMixin):
|
|
|
174
413
|
def _add_outputs(self) -> None:
|
|
175
414
|
"""Add CloudFormation outputs for the Route53 resources"""
|
|
176
415
|
# Hosted Zone ID
|
|
177
|
-
|
|
178
|
-
cdk.CfnOutput(
|
|
179
|
-
self,
|
|
180
|
-
"HostedZoneId",
|
|
181
|
-
value=self.hosted_zone.hosted_zone_id,
|
|
182
|
-
export_name=f"{self.deployment.build_resource_name('hosted-zone')}-id"
|
|
183
|
-
)
|
|
184
|
-
|
|
185
|
-
# Hosted Zone Name Servers
|
|
186
|
-
if hasattr(self.hosted_zone, "name_servers") and self.hosted_zone.name_servers:
|
|
187
|
-
cdk.CfnOutput(
|
|
188
|
-
self,
|
|
189
|
-
"NameServers",
|
|
190
|
-
value=",".join(self.hosted_zone.name_servers),
|
|
191
|
-
export_name=f"{self.deployment.build_resource_name('hosted-zone')}-name-servers"
|
|
192
|
-
)
|
|
193
|
-
|
|
194
|
-
# Certificate ARN
|
|
195
|
-
if self.certificate:
|
|
196
|
-
cdk.CfnOutput(
|
|
197
|
-
self,
|
|
198
|
-
"CertificateArn",
|
|
199
|
-
value=self.certificate.certificate_arn,
|
|
200
|
-
export_name=f"{self.deployment.build_resource_name('certificate')}-arn"
|
|
201
|
-
)
|
|
202
|
-
|
|
203
|
-
# Record names
|
|
204
|
-
for name, record in self.records.items():
|
|
205
|
-
cdk.CfnOutput(
|
|
206
|
-
self,
|
|
207
|
-
f"Record-{name}",
|
|
208
|
-
value=name,
|
|
209
|
-
export_name=f"{self.deployment.build_resource_name('record')}-{name}"
|
|
210
|
-
)
|
|
416
|
+
return
|