cdk-factory 0.15.0__py3-none-any.whl → 0.15.1__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/resources/rds.py +5 -0
- cdk_factory/configurations/resources/route53.py +7 -2
- cdk_factory/configurations/resources/security_group_full_stack.py +5 -0
- cdk_factory/stack_library/cloudfront/cloudfront_stack.py +278 -150
- cdk_factory/stack_library/rds/rds_stack.py +110 -28
- cdk_factory/stack_library/security_group/security_group_full_stack.py +57 -4
- cdk_factory/version.py +1 -1
- {cdk_factory-0.15.0.dist-info → cdk_factory-0.15.1.dist-info}/METADATA +1 -1
- {cdk_factory-0.15.0.dist-info → cdk_factory-0.15.1.dist-info}/RECORD +12 -12
- {cdk_factory-0.15.0.dist-info → cdk_factory-0.15.1.dist-info}/WHEEL +0 -0
- {cdk_factory-0.15.0.dist-info → cdk_factory-0.15.1.dist-info}/entry_points.txt +0 -0
- {cdk_factory-0.15.0.dist-info → cdk_factory-0.15.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -130,3 +130,8 @@ class RdsConfig(EnhancedBaseConfig):
|
|
|
130
130
|
def vpc_id(self, value: str):
|
|
131
131
|
"""Sets the VPC ID for the Security Group"""
|
|
132
132
|
self.__config["vpc_id"] = value
|
|
133
|
+
|
|
134
|
+
@property
|
|
135
|
+
def ssm_imports(self) -> Dict[str, str]:
|
|
136
|
+
"""SSM parameter imports for the RDS instance"""
|
|
137
|
+
return self.__config.get("ssm_imports", {})
|
|
@@ -26,11 +26,16 @@ class Route53Config:
|
|
|
26
26
|
def hosted_zone_id(self) -> Optional[str]:
|
|
27
27
|
"""Hosted zone ID"""
|
|
28
28
|
return self.__config.get("hosted_zone_id")
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def existing_hosted_zone_id(self) -> Optional[str]:
|
|
32
|
+
"""Existing hosted zone ID (alias for hosted_zone_id)"""
|
|
33
|
+
return self.__config.get("existing_hosted_zone_id") or self.__config.get("hosted_zone_id")
|
|
29
34
|
|
|
30
35
|
@property
|
|
31
36
|
def domain_name(self) -> Optional[str]:
|
|
32
|
-
"""Domain name"""
|
|
33
|
-
return self.__config.get("domain_name")
|
|
37
|
+
"""Domain name (also checks hosted_zone_name)"""
|
|
38
|
+
return self.__config.get("domain_name") or self.__config.get("hosted_zone_name")
|
|
34
39
|
|
|
35
40
|
@property
|
|
36
41
|
def record_names(self) -> List[str]:
|
|
@@ -61,3 +61,8 @@ class SecurityGroupFullStackConfig:
|
|
|
61
61
|
def tags(self) -> Dict[str, str]:
|
|
62
62
|
"""Tags to apply to the Security Group"""
|
|
63
63
|
return self.__config.get("tags", {})
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def ssm_imports(self) -> Dict[str, str]:
|
|
67
|
+
"""SSM parameter imports for the Security Group"""
|
|
68
|
+
return self.__config.get("ssm_imports", {})
|
|
@@ -22,6 +22,8 @@ from constructs import Construct
|
|
|
22
22
|
from cdk_factory.interfaces.istack import IStack
|
|
23
23
|
from cdk_factory.stack.stack_module_registry import register_stack
|
|
24
24
|
from cdk_factory.configurations.stack import StackConfig
|
|
25
|
+
from cdk_factory.configurations.deployment import DeploymentConfig
|
|
26
|
+
from cdk_factory.configurations.workload import WorkloadConfig
|
|
25
27
|
from cdk_factory.configurations.resources.cloudfront import CloudFrontConfig
|
|
26
28
|
|
|
27
29
|
logger = logging.getLogger(__name__)
|
|
@@ -39,110 +41,153 @@ class CloudFrontStack(IStack):
|
|
|
39
41
|
- Custom error responses
|
|
40
42
|
"""
|
|
41
43
|
|
|
42
|
-
def __init__(
|
|
43
|
-
self,
|
|
44
|
-
scope: Construct,
|
|
45
|
-
id: str,
|
|
46
|
-
stack_config: StackConfig,
|
|
47
|
-
deployment,
|
|
48
|
-
**kwargs,
|
|
49
|
-
) -> None:
|
|
44
|
+
def __init__(self, scope: Construct, id: str, **kwargs) -> None:
|
|
50
45
|
super().__init__(scope, id, **kwargs)
|
|
51
46
|
|
|
52
|
-
self.stack_config =
|
|
53
|
-
self.deployment =
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
cloudfront_dict = stack_config.dictionary.get("cloudfront", {})
|
|
57
|
-
self.cf_config = CloudFrontConfig(cloudfront_dict, deployment)
|
|
58
|
-
|
|
47
|
+
self.stack_config = None
|
|
48
|
+
self.deployment = None
|
|
49
|
+
self.cf_config = None
|
|
50
|
+
|
|
59
51
|
# Resources
|
|
60
52
|
self.distribution: Optional[cloudfront.Distribution] = None
|
|
61
53
|
self.certificate: Optional[acm.ICertificate] = None
|
|
62
54
|
self.origins_map: Dict[str, cloudfront.IOrigin] = {}
|
|
63
55
|
|
|
56
|
+
# SSM imported values
|
|
57
|
+
self.ssm_imported_values: Dict[str, str] = {}
|
|
58
|
+
|
|
64
59
|
def build(
|
|
65
60
|
self,
|
|
61
|
+
stack_config: StackConfig,
|
|
62
|
+
deployment: DeploymentConfig,
|
|
63
|
+
workload: WorkloadConfig,
|
|
66
64
|
vpc=None,
|
|
67
65
|
target_groups=None,
|
|
68
66
|
security_groups=None,
|
|
69
67
|
shared=None,
|
|
70
68
|
):
|
|
71
69
|
"""Build the CloudFront distribution"""
|
|
72
|
-
|
|
70
|
+
|
|
71
|
+
self.stack_config = stack_config
|
|
72
|
+
self.deployment = deployment
|
|
73
|
+
|
|
74
|
+
# CloudFront config
|
|
75
|
+
cloudfront_dict = stack_config.dictionary.get("cloudfront", {})
|
|
76
|
+
self.cf_config = CloudFrontConfig(cloudfront_dict, deployment)
|
|
77
|
+
|
|
73
78
|
logger.info(f"Building CloudFront distribution: {self.cf_config.name}")
|
|
74
|
-
|
|
79
|
+
|
|
80
|
+
# Process SSM imports first
|
|
81
|
+
self._process_ssm_imports()
|
|
82
|
+
|
|
75
83
|
# Create certificate if needed
|
|
76
84
|
if self.cf_config.certificate and self.cf_config.aliases:
|
|
77
85
|
self._create_certificate()
|
|
78
|
-
|
|
86
|
+
|
|
79
87
|
# Create origins
|
|
80
88
|
self._create_origins()
|
|
81
|
-
|
|
89
|
+
|
|
82
90
|
# Create distribution
|
|
83
91
|
self._create_distribution()
|
|
84
|
-
|
|
92
|
+
|
|
85
93
|
# Export SSM parameters
|
|
86
94
|
self._export_ssm_parameters()
|
|
87
|
-
|
|
95
|
+
|
|
88
96
|
# Create CloudFormation outputs
|
|
89
97
|
self._create_outputs()
|
|
90
|
-
|
|
98
|
+
|
|
91
99
|
return self
|
|
92
100
|
|
|
101
|
+
def _process_ssm_imports(self) -> None:
|
|
102
|
+
"""
|
|
103
|
+
Process SSM imports from configuration.
|
|
104
|
+
Follows the same pattern as API Gateway stack - imports SSM parameters as CDK tokens.
|
|
105
|
+
"""
|
|
106
|
+
ssm_imports = self.cf_config.ssm_imports
|
|
107
|
+
|
|
108
|
+
if not ssm_imports:
|
|
109
|
+
logger.debug("No SSM imports configured for CloudFront")
|
|
110
|
+
return
|
|
111
|
+
|
|
112
|
+
logger.info(f"Processing {len(ssm_imports)} SSM imports for CloudFront")
|
|
113
|
+
|
|
114
|
+
for param_key, param_path in ssm_imports.items():
|
|
115
|
+
try:
|
|
116
|
+
# Ensure parameter path starts with /
|
|
117
|
+
if not param_path.startswith("/"):
|
|
118
|
+
param_path = f"/{param_path}"
|
|
119
|
+
|
|
120
|
+
# Create unique construct ID from parameter path
|
|
121
|
+
construct_id = f"ssm-import-{param_key}-{hash(param_path) % 10000}"
|
|
122
|
+
|
|
123
|
+
# Import SSM parameter - this creates a CDK token that resolves at deployment time
|
|
124
|
+
param = ssm.StringParameter.from_string_parameter_name(
|
|
125
|
+
self, construct_id, param_path
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
# Store the token value for use in configuration
|
|
129
|
+
self.ssm_imported_values[param_key] = param.string_value
|
|
130
|
+
logger.info(f"Imported SSM parameter: {param_key} from {param_path}")
|
|
131
|
+
|
|
132
|
+
except Exception as e:
|
|
133
|
+
logger.error(
|
|
134
|
+
f"Failed to import SSM parameter {param_key} from {param_path}: {e}"
|
|
135
|
+
)
|
|
136
|
+
raise
|
|
137
|
+
|
|
93
138
|
def _create_certificate(self) -> None:
|
|
94
139
|
"""Create or import ACM certificate for CloudFront (must be in us-east-1)"""
|
|
95
140
|
cert_config = self.cf_config.certificate
|
|
96
|
-
|
|
141
|
+
|
|
97
142
|
if not cert_config:
|
|
98
143
|
return
|
|
99
|
-
|
|
144
|
+
|
|
100
145
|
# Check if certificate ARN is provided
|
|
101
146
|
cert_arn = cert_config.get("arn")
|
|
102
147
|
if cert_arn:
|
|
103
148
|
self.certificate = acm.Certificate.from_certificate_arn(
|
|
104
|
-
self,
|
|
105
|
-
"Certificate",
|
|
106
|
-
certificate_arn=cert_arn
|
|
149
|
+
self, "Certificate", certificate_arn=cert_arn
|
|
107
150
|
)
|
|
108
151
|
logger.info(f"Using existing certificate: {cert_arn}")
|
|
109
152
|
return
|
|
110
|
-
|
|
153
|
+
|
|
111
154
|
# Check if we should import from SSM
|
|
112
155
|
ssm_param = cert_config.get("ssm_parameter")
|
|
113
156
|
if ssm_param:
|
|
114
157
|
cert_arn = ssm.StringParameter.value_from_lookup(self, ssm_param)
|
|
115
158
|
self.certificate = acm.Certificate.from_certificate_arn(
|
|
116
|
-
self,
|
|
117
|
-
"Certificate",
|
|
118
|
-
certificate_arn=cert_arn
|
|
159
|
+
self, "Certificate", certificate_arn=cert_arn
|
|
119
160
|
)
|
|
120
161
|
logger.info(f"Using certificate from SSM: {ssm_param}")
|
|
121
162
|
return
|
|
122
|
-
|
|
123
|
-
logger.warning(
|
|
163
|
+
|
|
164
|
+
logger.warning(
|
|
165
|
+
"No certificate ARN provided - CloudFront will use default certificate"
|
|
166
|
+
)
|
|
124
167
|
|
|
125
168
|
def _create_origins(self) -> None:
|
|
126
169
|
"""Create CloudFront origins (custom, S3, etc.)"""
|
|
127
170
|
origins_config = self.cf_config.origins
|
|
128
|
-
|
|
171
|
+
|
|
129
172
|
if not origins_config:
|
|
130
|
-
raise ValueError(
|
|
131
|
-
|
|
173
|
+
raise ValueError(
|
|
174
|
+
"At least one origin is required for CloudFront distribution"
|
|
175
|
+
)
|
|
176
|
+
|
|
132
177
|
for origin_config in origins_config:
|
|
133
178
|
origin_id = origin_config.get("id")
|
|
134
179
|
origin_type = origin_config.get("type", "custom")
|
|
135
|
-
|
|
180
|
+
|
|
136
181
|
if not origin_id:
|
|
137
182
|
raise ValueError("Origin ID is required")
|
|
138
|
-
|
|
183
|
+
|
|
139
184
|
if origin_type == "custom":
|
|
140
185
|
origin = self._create_custom_origin(origin_config)
|
|
141
186
|
elif origin_type == "s3":
|
|
142
187
|
origin = self._create_s3_origin(origin_config)
|
|
143
188
|
else:
|
|
144
189
|
raise ValueError(f"Unsupported origin type: {origin_type}")
|
|
145
|
-
|
|
190
|
+
|
|
146
191
|
self.origins_map[origin_id] = origin
|
|
147
192
|
logger.info(f"Created {origin_type} origin: {origin_id}")
|
|
148
193
|
|
|
@@ -150,30 +195,41 @@ class CloudFrontStack(IStack):
|
|
|
150
195
|
"""Create custom origin (ALB, API Gateway, etc.)"""
|
|
151
196
|
domain_name = config.get("domain_name")
|
|
152
197
|
origin_id = config.get("id")
|
|
153
|
-
|
|
198
|
+
|
|
154
199
|
if not domain_name:
|
|
155
200
|
raise ValueError("domain_name is required for custom origin")
|
|
156
|
-
|
|
157
|
-
# Check if domain name is
|
|
158
|
-
if domain_name.startswith("{{
|
|
201
|
+
|
|
202
|
+
# Check if domain name is a placeholder from ssm_imports
|
|
203
|
+
if domain_name.startswith("{{") and domain_name.endswith("}}"):
|
|
204
|
+
placeholder_key = domain_name[2:-2] # Remove {{ and }}
|
|
205
|
+
if placeholder_key in self.ssm_imported_values:
|
|
206
|
+
domain_name = self.ssm_imported_values[placeholder_key]
|
|
207
|
+
logger.info(f"Resolved domain from SSM import: {placeholder_key}")
|
|
208
|
+
else:
|
|
209
|
+
logger.warning(f"Placeholder {domain_name} not found in SSM imports")
|
|
210
|
+
|
|
211
|
+
# Legacy support: Check if domain name is an SSM parameter reference
|
|
212
|
+
elif domain_name.startswith("{{ssm:") and domain_name.endswith("}}"):
|
|
159
213
|
# Extract SSM parameter name
|
|
160
214
|
ssm_param = domain_name[6:-2] # Remove {{ssm: and }}
|
|
161
215
|
domain_name = ssm.StringParameter.value_from_lookup(self, ssm_param)
|
|
162
|
-
logger.info(f"Resolved domain from SSM {ssm_param}: {domain_name}")
|
|
163
|
-
|
|
216
|
+
logger.info(f"Resolved domain from SSM lookup {ssm_param}: {domain_name}")
|
|
217
|
+
|
|
164
218
|
# Build custom headers (e.g., X-Origin-Secret)
|
|
165
219
|
custom_headers = {}
|
|
166
220
|
custom_headers_config = config.get("custom_headers", {})
|
|
167
|
-
|
|
221
|
+
|
|
168
222
|
for header_name, header_value in custom_headers_config.items():
|
|
169
223
|
# Check if value is from Secrets Manager
|
|
170
224
|
if isinstance(header_value, str) and header_value.startswith("{{secrets:"):
|
|
171
225
|
# For now, just log a warning - Secrets Manager integration needs IAM permissions
|
|
172
|
-
logger.warning(
|
|
226
|
+
logger.warning(
|
|
227
|
+
f"Secrets Manager references not yet supported in custom headers: {header_value}"
|
|
228
|
+
)
|
|
173
229
|
continue
|
|
174
|
-
|
|
230
|
+
|
|
175
231
|
custom_headers[header_name] = header_value
|
|
176
|
-
|
|
232
|
+
|
|
177
233
|
# Protocol policy
|
|
178
234
|
protocol_policy_str = config.get("protocol_policy", "https-only")
|
|
179
235
|
protocol_policy_map = {
|
|
@@ -181,12 +237,18 @@ class CloudFrontStack(IStack):
|
|
|
181
237
|
"https-only": cloudfront.OriginProtocolPolicy.HTTPS_ONLY,
|
|
182
238
|
"match-viewer": cloudfront.OriginProtocolPolicy.MATCH_VIEWER,
|
|
183
239
|
}
|
|
184
|
-
protocol_policy = protocol_policy_map.get(
|
|
185
|
-
|
|
240
|
+
protocol_policy = protocol_policy_map.get(
|
|
241
|
+
protocol_policy_str, cloudfront.OriginProtocolPolicy.HTTPS_ONLY
|
|
242
|
+
)
|
|
243
|
+
|
|
186
244
|
# SSL protocols
|
|
187
245
|
origin_ssl_protocols_list = config.get("origin_ssl_protocols", ["TLSv1.2"])
|
|
188
|
-
ssl_protocols =
|
|
189
|
-
|
|
246
|
+
ssl_protocols = (
|
|
247
|
+
[cloudfront.OriginSslPolicy.TLS_V1_2]
|
|
248
|
+
if "TLSv1.2" in origin_ssl_protocols_list
|
|
249
|
+
else []
|
|
250
|
+
)
|
|
251
|
+
|
|
190
252
|
# Create custom origin with explicit origin ID
|
|
191
253
|
return origins.HttpOrigin(
|
|
192
254
|
domain_name,
|
|
@@ -207,37 +269,41 @@ class CloudFrontStack(IStack):
|
|
|
207
269
|
"""Create S3 origin"""
|
|
208
270
|
# S3 origin implementation
|
|
209
271
|
# This would use origins.S3Origin
|
|
210
|
-
raise NotImplementedError(
|
|
272
|
+
raise NotImplementedError(
|
|
273
|
+
"S3 origin support - use existing static_website_stack for S3"
|
|
274
|
+
)
|
|
211
275
|
|
|
212
276
|
def _create_distribution(self) -> None:
|
|
213
277
|
"""Create CloudFront distribution"""
|
|
214
|
-
|
|
278
|
+
|
|
215
279
|
# Get default origin
|
|
216
280
|
default_behavior_config = self.cf_config.default_cache_behavior
|
|
217
281
|
target_origin_id = default_behavior_config.get("target_origin_id")
|
|
218
|
-
|
|
282
|
+
|
|
219
283
|
if not target_origin_id or target_origin_id not in self.origins_map:
|
|
220
284
|
raise ValueError(f"Default cache behavior must reference a valid origin ID")
|
|
221
|
-
|
|
285
|
+
|
|
222
286
|
default_origin = self.origins_map[target_origin_id]
|
|
223
|
-
|
|
287
|
+
|
|
224
288
|
# Build default behavior
|
|
225
|
-
default_behavior = self._build_cache_behavior(
|
|
226
|
-
|
|
289
|
+
default_behavior = self._build_cache_behavior(
|
|
290
|
+
default_behavior_config, default_origin
|
|
291
|
+
)
|
|
292
|
+
|
|
227
293
|
# Build additional behaviors
|
|
228
294
|
additional_behaviors = {}
|
|
229
295
|
for behavior_config in self.cf_config.cache_behaviors:
|
|
230
296
|
path_pattern = behavior_config.get("path_pattern")
|
|
231
297
|
origin_id = behavior_config.get("target_origin_id")
|
|
232
|
-
|
|
298
|
+
|
|
233
299
|
if not path_pattern or not origin_id or origin_id not in self.origins_map:
|
|
234
300
|
logger.warning(f"Invalid cache behavior config, skipping")
|
|
235
301
|
continue
|
|
236
|
-
|
|
302
|
+
|
|
237
303
|
origin = self.origins_map[origin_id]
|
|
238
304
|
behavior = self._build_cache_behavior_options(behavior_config)
|
|
239
305
|
additional_behaviors[path_pattern] = behavior
|
|
240
|
-
|
|
306
|
+
|
|
241
307
|
# Build error responses
|
|
242
308
|
error_responses = []
|
|
243
309
|
for error_config in self.cf_config.custom_error_responses:
|
|
@@ -248,10 +314,12 @@ class CloudFrontStack(IStack):
|
|
|
248
314
|
http_status=error_code,
|
|
249
315
|
response_http_status=error_config.get("response_http_status"),
|
|
250
316
|
response_page_path=error_config.get("response_page_path"),
|
|
251
|
-
ttl=Duration.seconds(
|
|
317
|
+
ttl=Duration.seconds(
|
|
318
|
+
error_config.get("error_caching_min_ttl", 10)
|
|
319
|
+
),
|
|
252
320
|
)
|
|
253
321
|
)
|
|
254
|
-
|
|
322
|
+
|
|
255
323
|
# HTTP version
|
|
256
324
|
http_version_map = {
|
|
257
325
|
"http1_1": cloudfront.HttpVersion.HTTP1_1,
|
|
@@ -265,12 +333,11 @@ class CloudFrontStack(IStack):
|
|
|
265
333
|
except AttributeError:
|
|
266
334
|
# Fall back to HTTP2 if HTTP2_AND_3/HTTP3 not available in this CDK version
|
|
267
335
|
default_version = cloudfront.HttpVersion.HTTP2
|
|
268
|
-
|
|
336
|
+
|
|
269
337
|
http_version = http_version_map.get(
|
|
270
|
-
self.cf_config.http_version.lower(),
|
|
271
|
-
default_version
|
|
338
|
+
self.cf_config.http_version.lower(), default_version
|
|
272
339
|
)
|
|
273
|
-
|
|
340
|
+
|
|
274
341
|
# Price class
|
|
275
342
|
price_class_map = {
|
|
276
343
|
"PriceClass_100": cloudfront.PriceClass.PRICE_CLASS_100,
|
|
@@ -278,10 +345,9 @@ class CloudFrontStack(IStack):
|
|
|
278
345
|
"PriceClass_All": cloudfront.PriceClass.PRICE_CLASS_ALL,
|
|
279
346
|
}
|
|
280
347
|
price_class = price_class_map.get(
|
|
281
|
-
self.cf_config.price_class,
|
|
282
|
-
cloudfront.PriceClass.PRICE_CLASS_100
|
|
348
|
+
self.cf_config.price_class, cloudfront.PriceClass.PRICE_CLASS_100
|
|
283
349
|
)
|
|
284
|
-
|
|
350
|
+
|
|
285
351
|
# Create distribution
|
|
286
352
|
self.distribution = cloudfront.Distribution(
|
|
287
353
|
self,
|
|
@@ -298,16 +364,16 @@ class CloudFrontStack(IStack):
|
|
|
298
364
|
default_root_object=self.cf_config.default_root_object,
|
|
299
365
|
web_acl_id=self.cf_config.waf_web_acl_id,
|
|
300
366
|
)
|
|
301
|
-
|
|
302
|
-
logger.info(
|
|
367
|
+
|
|
368
|
+
logger.info(
|
|
369
|
+
f"Created CloudFront distribution: {self.distribution.distribution_id}"
|
|
370
|
+
)
|
|
303
371
|
|
|
304
372
|
def _build_cache_behavior(
|
|
305
|
-
self,
|
|
306
|
-
config: Dict[str, Any],
|
|
307
|
-
origin: cloudfront.IOrigin
|
|
373
|
+
self, config: Dict[str, Any], origin: cloudfront.IOrigin
|
|
308
374
|
) -> cloudfront.BehaviorOptions:
|
|
309
375
|
"""Build cache behavior with origin"""
|
|
310
|
-
|
|
376
|
+
|
|
311
377
|
# Viewer protocol policy
|
|
312
378
|
viewer_protocol_str = config.get("viewer_protocol_policy", "redirect-to-https")
|
|
313
379
|
viewer_protocol_map = {
|
|
@@ -316,35 +382,43 @@ class CloudFrontStack(IStack):
|
|
|
316
382
|
"https-only": cloudfront.ViewerProtocolPolicy.HTTPS_ONLY,
|
|
317
383
|
}
|
|
318
384
|
viewer_protocol_policy = viewer_protocol_map.get(
|
|
319
|
-
viewer_protocol_str,
|
|
320
|
-
cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS
|
|
385
|
+
viewer_protocol_str, cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS
|
|
321
386
|
)
|
|
322
|
-
|
|
387
|
+
|
|
323
388
|
# Allowed methods
|
|
324
389
|
allowed_methods_str = config.get("allowed_methods", ["GET", "HEAD"])
|
|
325
|
-
if
|
|
390
|
+
if (
|
|
391
|
+
"DELETE" in allowed_methods_str
|
|
392
|
+
or "PUT" in allowed_methods_str
|
|
393
|
+
or "PATCH" in allowed_methods_str
|
|
394
|
+
or "POST" in allowed_methods_str
|
|
395
|
+
):
|
|
326
396
|
allowed_methods = cloudfront.AllowedMethods.ALLOW_ALL
|
|
327
397
|
elif "OPTIONS" in allowed_methods_str:
|
|
328
398
|
allowed_methods = cloudfront.AllowedMethods.ALLOW_GET_HEAD_OPTIONS
|
|
329
399
|
else:
|
|
330
400
|
allowed_methods = cloudfront.AllowedMethods.ALLOW_GET_HEAD
|
|
331
|
-
|
|
401
|
+
|
|
332
402
|
# Cached methods
|
|
333
403
|
cached_methods_str = config.get("cached_methods", ["GET", "HEAD"])
|
|
334
404
|
if "OPTIONS" in cached_methods_str:
|
|
335
405
|
cached_methods = cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS
|
|
336
406
|
else:
|
|
337
407
|
cached_methods = cloudfront.CachedMethods.CACHE_GET_HEAD
|
|
338
|
-
|
|
408
|
+
|
|
339
409
|
# Cache policy
|
|
340
410
|
cache_policy = self._build_cache_policy(config.get("cache_policy", {}))
|
|
341
|
-
|
|
411
|
+
|
|
342
412
|
# Origin request policy
|
|
343
|
-
origin_request_policy = self._build_origin_request_policy(
|
|
344
|
-
|
|
413
|
+
origin_request_policy = self._build_origin_request_policy(
|
|
414
|
+
config.get("origin_request_policy", {})
|
|
415
|
+
)
|
|
416
|
+
|
|
345
417
|
# Lambda@Edge associations
|
|
346
|
-
edge_lambdas = self._build_lambda_edge_associations(
|
|
347
|
-
|
|
418
|
+
edge_lambdas = self._build_lambda_edge_associations(
|
|
419
|
+
config.get("lambda_edge_associations", [])
|
|
420
|
+
)
|
|
421
|
+
|
|
348
422
|
return cloudfront.BehaviorOptions(
|
|
349
423
|
origin=origin,
|
|
350
424
|
viewer_protocol_policy=viewer_protocol_policy,
|
|
@@ -362,24 +436,26 @@ class CloudFrontStack(IStack):
|
|
|
362
436
|
# For now, return basic structure
|
|
363
437
|
return {}
|
|
364
438
|
|
|
365
|
-
def _build_cache_policy(
|
|
439
|
+
def _build_cache_policy(
|
|
440
|
+
self, config: Dict[str, Any]
|
|
441
|
+
) -> Optional[cloudfront.ICachePolicy]:
|
|
366
442
|
"""Build or reference cache policy"""
|
|
367
443
|
if not config:
|
|
368
444
|
# Use managed caching disabled policy for dynamic content
|
|
369
445
|
return cloudfront.CachePolicy.CACHING_DISABLED
|
|
370
|
-
|
|
446
|
+
|
|
371
447
|
policy_name = config.get("name")
|
|
372
|
-
|
|
448
|
+
|
|
373
449
|
# Check for managed policies
|
|
374
450
|
managed_policies = {
|
|
375
451
|
"CachingOptimized": cloudfront.CachePolicy.CACHING_OPTIMIZED,
|
|
376
452
|
"CachingDisabled": cloudfront.CachePolicy.CACHING_DISABLED,
|
|
377
453
|
"CachingOptimizedForUncompressedObjects": cloudfront.CachePolicy.CACHING_OPTIMIZED_FOR_UNCOMPRESSED_OBJECTS,
|
|
378
454
|
}
|
|
379
|
-
|
|
455
|
+
|
|
380
456
|
if policy_name in managed_policies:
|
|
381
457
|
return managed_policies[policy_name]
|
|
382
|
-
|
|
458
|
+
|
|
383
459
|
# Create custom cache policy
|
|
384
460
|
return cloudfront.CachePolicy(
|
|
385
461
|
self,
|
|
@@ -390,47 +466,65 @@ class CloudFrontStack(IStack):
|
|
|
390
466
|
min_ttl=Duration.seconds(config.get("min_ttl", 0)),
|
|
391
467
|
max_ttl=Duration.seconds(config.get("max_ttl", 31536000)),
|
|
392
468
|
enable_accept_encoding_gzip=config.get("enable_accept_encoding_gzip", True),
|
|
393
|
-
enable_accept_encoding_brotli=config.get(
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
469
|
+
enable_accept_encoding_brotli=config.get(
|
|
470
|
+
"enable_accept_encoding_brotli", True
|
|
471
|
+
),
|
|
472
|
+
header_behavior=self._build_cache_header_behavior(
|
|
473
|
+
config.get("headers_config", {})
|
|
474
|
+
),
|
|
475
|
+
query_string_behavior=self._build_cache_query_string_behavior(
|
|
476
|
+
config.get("query_strings_config", {})
|
|
477
|
+
),
|
|
478
|
+
cookie_behavior=self._build_cache_cookie_behavior(
|
|
479
|
+
config.get("cookies_config", {})
|
|
480
|
+
),
|
|
397
481
|
)
|
|
398
482
|
|
|
399
|
-
def _build_origin_request_policy(
|
|
483
|
+
def _build_origin_request_policy(
|
|
484
|
+
self, config: Dict[str, Any]
|
|
485
|
+
) -> Optional[cloudfront.IOriginRequestPolicy]:
|
|
400
486
|
"""Build or reference origin request policy"""
|
|
401
487
|
if not config:
|
|
402
488
|
# Use managed all viewer policy
|
|
403
489
|
return cloudfront.OriginRequestPolicy.ALL_VIEWER
|
|
404
|
-
|
|
490
|
+
|
|
405
491
|
policy_name = config.get("name")
|
|
406
|
-
|
|
492
|
+
|
|
407
493
|
# Check for managed policies
|
|
408
494
|
managed_policies = {
|
|
409
495
|
"AllViewer": cloudfront.OriginRequestPolicy.ALL_VIEWER,
|
|
410
496
|
"AllViewerExceptHostHeader": cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER,
|
|
411
|
-
"AllViewerAndCloudFrontHeaders2022": cloudfront.OriginRequestPolicy.
|
|
497
|
+
"AllViewerAndCloudFrontHeaders2022": cloudfront.OriginRequestPolicy.ALL_VIEWER_AND_CLOUDFRONT_2022,
|
|
412
498
|
"CORS-CustomOrigin": cloudfront.OriginRequestPolicy.CORS_CUSTOM_ORIGIN,
|
|
413
499
|
"CORS-S3Origin": cloudfront.OriginRequestPolicy.CORS_S3_ORIGIN,
|
|
414
500
|
}
|
|
415
|
-
|
|
501
|
+
|
|
416
502
|
if policy_name in managed_policies:
|
|
417
503
|
return managed_policies[policy_name]
|
|
418
|
-
|
|
504
|
+
|
|
419
505
|
# Create custom origin request policy
|
|
420
506
|
return cloudfront.OriginRequestPolicy(
|
|
421
507
|
self,
|
|
422
508
|
f"OriginRequestPolicy-{policy_name}",
|
|
423
509
|
origin_request_policy_name=policy_name,
|
|
424
510
|
comment=config.get("comment", ""),
|
|
425
|
-
header_behavior=self._build_origin_header_behavior(
|
|
426
|
-
|
|
427
|
-
|
|
511
|
+
header_behavior=self._build_origin_header_behavior(
|
|
512
|
+
config.get("headers_config", {})
|
|
513
|
+
),
|
|
514
|
+
query_string_behavior=self._build_origin_query_string_behavior(
|
|
515
|
+
config.get("query_strings_config", {})
|
|
516
|
+
),
|
|
517
|
+
cookie_behavior=self._build_origin_cookie_behavior(
|
|
518
|
+
config.get("cookies_config", {})
|
|
519
|
+
),
|
|
428
520
|
)
|
|
429
521
|
|
|
430
|
-
def _build_cache_header_behavior(
|
|
522
|
+
def _build_cache_header_behavior(
|
|
523
|
+
self, config: Dict[str, Any]
|
|
524
|
+
) -> cloudfront.CacheHeaderBehavior:
|
|
431
525
|
"""Build cache header behavior"""
|
|
432
526
|
behavior = config.get("behavior", "none")
|
|
433
|
-
|
|
527
|
+
|
|
434
528
|
if behavior == "none":
|
|
435
529
|
return cloudfront.CacheHeaderBehavior.none()
|
|
436
530
|
elif behavior == "whitelist":
|
|
@@ -439,10 +533,12 @@ class CloudFrontStack(IStack):
|
|
|
439
533
|
else:
|
|
440
534
|
return cloudfront.CacheHeaderBehavior.none()
|
|
441
535
|
|
|
442
|
-
def _build_cache_query_string_behavior(
|
|
536
|
+
def _build_cache_query_string_behavior(
|
|
537
|
+
self, config: Dict[str, Any]
|
|
538
|
+
) -> cloudfront.CacheQueryStringBehavior:
|
|
443
539
|
"""Build cache query string behavior"""
|
|
444
540
|
behavior = config.get("behavior", "none")
|
|
445
|
-
|
|
541
|
+
|
|
446
542
|
if behavior == "none":
|
|
447
543
|
return cloudfront.CacheQueryStringBehavior.none()
|
|
448
544
|
elif behavior == "all":
|
|
@@ -456,10 +552,12 @@ class CloudFrontStack(IStack):
|
|
|
456
552
|
else:
|
|
457
553
|
return cloudfront.CacheQueryStringBehavior.none()
|
|
458
554
|
|
|
459
|
-
def _build_cache_cookie_behavior(
|
|
555
|
+
def _build_cache_cookie_behavior(
|
|
556
|
+
self, config: Dict[str, Any]
|
|
557
|
+
) -> cloudfront.CacheCookieBehavior:
|
|
460
558
|
"""Build cache cookie behavior"""
|
|
461
559
|
behavior = config.get("behavior", "none")
|
|
462
|
-
|
|
560
|
+
|
|
463
561
|
if behavior == "none":
|
|
464
562
|
return cloudfront.CacheCookieBehavior.none()
|
|
465
563
|
elif behavior == "all":
|
|
@@ -473,44 +571,52 @@ class CloudFrontStack(IStack):
|
|
|
473
571
|
else:
|
|
474
572
|
return cloudfront.CacheCookieBehavior.none()
|
|
475
573
|
|
|
476
|
-
def _build_origin_header_behavior(
|
|
574
|
+
def _build_origin_header_behavior(
|
|
575
|
+
self, config: Dict[str, Any]
|
|
576
|
+
) -> cloudfront.OriginRequestHeaderBehavior:
|
|
477
577
|
"""Build origin request header behavior"""
|
|
478
578
|
behavior = config.get("behavior", "none")
|
|
479
|
-
|
|
579
|
+
|
|
480
580
|
if behavior == "none":
|
|
481
581
|
return cloudfront.OriginRequestHeaderBehavior.none()
|
|
482
582
|
elif behavior == "all":
|
|
483
|
-
return cloudfront.OriginRequestHeaderBehavior.
|
|
583
|
+
return cloudfront.OriginRequestHeaderBehavior.all()
|
|
484
584
|
elif behavior == "whitelist":
|
|
485
585
|
headers = config.get("headers", [])
|
|
486
586
|
return cloudfront.OriginRequestHeaderBehavior.allow_list(*headers)
|
|
487
587
|
elif behavior == "allViewerAndWhitelistCloudFront":
|
|
488
|
-
|
|
489
|
-
return cloudfront.OriginRequestHeaderBehavior.
|
|
588
|
+
# For now, just forward all headers - this matches the intent
|
|
589
|
+
return cloudfront.OriginRequestHeaderBehavior.all()
|
|
490
590
|
else:
|
|
491
591
|
return cloudfront.OriginRequestHeaderBehavior.none()
|
|
492
592
|
|
|
493
|
-
def _build_origin_query_string_behavior(
|
|
593
|
+
def _build_origin_query_string_behavior(
|
|
594
|
+
self, config: Dict[str, Any]
|
|
595
|
+
) -> cloudfront.OriginRequestQueryStringBehavior:
|
|
494
596
|
"""Build origin request query string behavior"""
|
|
495
597
|
behavior = config.get("behavior", "none")
|
|
496
|
-
|
|
598
|
+
|
|
497
599
|
if behavior == "none":
|
|
498
600
|
return cloudfront.OriginRequestQueryStringBehavior.none()
|
|
499
601
|
elif behavior == "all":
|
|
500
602
|
return cloudfront.OriginRequestQueryStringBehavior.all()
|
|
501
603
|
elif behavior == "whitelist":
|
|
502
604
|
query_strings = config.get("query_strings", [])
|
|
503
|
-
return cloudfront.OriginRequestQueryStringBehavior.allow_list(
|
|
605
|
+
return cloudfront.OriginRequestQueryStringBehavior.allow_list(
|
|
606
|
+
*query_strings
|
|
607
|
+
)
|
|
504
608
|
elif behavior == "allExcept":
|
|
505
609
|
query_strings = config.get("query_strings", [])
|
|
506
610
|
return cloudfront.OriginRequestQueryStringBehavior.deny_list(*query_strings)
|
|
507
611
|
else:
|
|
508
612
|
return cloudfront.OriginRequestQueryStringBehavior.none()
|
|
509
613
|
|
|
510
|
-
def _build_origin_cookie_behavior(
|
|
614
|
+
def _build_origin_cookie_behavior(
|
|
615
|
+
self, config: Dict[str, Any]
|
|
616
|
+
) -> cloudfront.OriginRequestCookieBehavior:
|
|
511
617
|
"""Build origin request cookie behavior"""
|
|
512
618
|
behavior = config.get("behavior", "none")
|
|
513
|
-
|
|
619
|
+
|
|
514
620
|
if behavior == "none":
|
|
515
621
|
return cloudfront.OriginRequestCookieBehavior.none()
|
|
516
622
|
elif behavior == "all":
|
|
@@ -521,26 +627,39 @@ class CloudFrontStack(IStack):
|
|
|
521
627
|
else:
|
|
522
628
|
return cloudfront.OriginRequestCookieBehavior.none()
|
|
523
629
|
|
|
524
|
-
def _build_lambda_edge_associations(
|
|
630
|
+
def _build_lambda_edge_associations(
|
|
631
|
+
self, associations: List[Dict[str, Any]]
|
|
632
|
+
) -> Optional[List[cloudfront.EdgeLambda]]:
|
|
525
633
|
"""Build Lambda@Edge associations"""
|
|
526
634
|
if not associations:
|
|
527
635
|
return None
|
|
528
|
-
|
|
636
|
+
|
|
529
637
|
edge_lambdas = []
|
|
530
|
-
|
|
638
|
+
|
|
531
639
|
for assoc in associations:
|
|
532
640
|
event_type_str = assoc.get("event_type", "origin-request")
|
|
533
641
|
lambda_arn = assoc.get("lambda_arn")
|
|
534
|
-
|
|
642
|
+
|
|
535
643
|
if not lambda_arn:
|
|
536
644
|
continue
|
|
537
|
-
|
|
538
|
-
# Check if ARN is
|
|
539
|
-
if lambda_arn.startswith("{{
|
|
645
|
+
|
|
646
|
+
# Check if ARN is a placeholder from ssm_imports
|
|
647
|
+
if lambda_arn.startswith("{{") and lambda_arn.endswith("}}"):
|
|
648
|
+
placeholder_key = lambda_arn[2:-2] # Remove {{ and }}
|
|
649
|
+
if placeholder_key in self.ssm_imported_values:
|
|
650
|
+
lambda_arn = self.ssm_imported_values[placeholder_key]
|
|
651
|
+
logger.info(
|
|
652
|
+
f"Resolved Lambda ARN from SSM import: {placeholder_key}"
|
|
653
|
+
)
|
|
654
|
+
else:
|
|
655
|
+
logger.warning(f"Placeholder {lambda_arn} not found in SSM imports")
|
|
656
|
+
|
|
657
|
+
# Legacy support: Check if ARN is an SSM parameter reference
|
|
658
|
+
elif lambda_arn.startswith("{{ssm:") and lambda_arn.endswith("}}"):
|
|
540
659
|
ssm_param = lambda_arn[6:-2]
|
|
541
660
|
lambda_arn = ssm.StringParameter.value_from_lookup(self, ssm_param)
|
|
542
|
-
logger.info(f"Resolved Lambda ARN from SSM {ssm_param}")
|
|
543
|
-
|
|
661
|
+
logger.info(f"Resolved Lambda ARN from SSM lookup {ssm_param}")
|
|
662
|
+
|
|
544
663
|
# Map event type
|
|
545
664
|
event_type_map = {
|
|
546
665
|
"viewer-request": cloudfront.LambdaEdgeEventType.VIEWER_REQUEST,
|
|
@@ -548,56 +667,65 @@ class CloudFrontStack(IStack):
|
|
|
548
667
|
"origin-response": cloudfront.LambdaEdgeEventType.ORIGIN_RESPONSE,
|
|
549
668
|
"viewer-response": cloudfront.LambdaEdgeEventType.VIEWER_RESPONSE,
|
|
550
669
|
}
|
|
551
|
-
|
|
552
|
-
event_type = event_type_map.get(
|
|
553
|
-
|
|
670
|
+
|
|
671
|
+
event_type = event_type_map.get(
|
|
672
|
+
event_type_str, cloudfront.LambdaEdgeEventType.ORIGIN_REQUEST
|
|
673
|
+
)
|
|
674
|
+
|
|
554
675
|
# Import Lambda version
|
|
555
676
|
lambda_version = _lambda.Version.from_version_arn(
|
|
556
|
-
self,
|
|
557
|
-
f"LambdaEdge-{event_type_str}",
|
|
558
|
-
version_arn=lambda_arn
|
|
677
|
+
self, f"LambdaEdge-{event_type_str}", version_arn=lambda_arn
|
|
559
678
|
)
|
|
560
|
-
|
|
679
|
+
|
|
561
680
|
edge_lambdas.append(
|
|
562
681
|
cloudfront.EdgeLambda(
|
|
563
682
|
function_version=lambda_version,
|
|
564
683
|
event_type=event_type,
|
|
565
|
-
include_body=assoc.get("include_body", False)
|
|
684
|
+
include_body=assoc.get("include_body", False),
|
|
566
685
|
)
|
|
567
686
|
)
|
|
568
|
-
|
|
687
|
+
|
|
569
688
|
return edge_lambdas if edge_lambdas else None
|
|
570
689
|
|
|
571
690
|
def _export_ssm_parameters(self) -> None:
|
|
572
691
|
"""Export distribution info to SSM Parameter Store"""
|
|
573
692
|
ssm_exports = self.cf_config.ssm_exports
|
|
574
|
-
|
|
693
|
+
|
|
575
694
|
if not ssm_exports:
|
|
576
695
|
return
|
|
577
|
-
|
|
696
|
+
|
|
578
697
|
if "distribution_id" in ssm_exports:
|
|
698
|
+
param_name = ssm_exports["distribution_id"]
|
|
699
|
+
if not param_name.startswith("/"):
|
|
700
|
+
param_name = f"/{param_name}"
|
|
579
701
|
ssm.StringParameter(
|
|
580
702
|
self,
|
|
581
703
|
"DistributionIdParam",
|
|
582
|
-
parameter_name=
|
|
704
|
+
parameter_name=param_name,
|
|
583
705
|
string_value=self.distribution.distribution_id,
|
|
584
706
|
description=f"CloudFront Distribution ID for {self.cf_config.name}",
|
|
585
707
|
)
|
|
586
|
-
|
|
708
|
+
|
|
587
709
|
if "distribution_domain" in ssm_exports:
|
|
710
|
+
param_name = ssm_exports["distribution_domain"]
|
|
711
|
+
if not param_name.startswith("/"):
|
|
712
|
+
param_name = f"/{param_name}"
|
|
588
713
|
ssm.StringParameter(
|
|
589
714
|
self,
|
|
590
715
|
"DistributionDomainParam",
|
|
591
|
-
parameter_name=
|
|
716
|
+
parameter_name=param_name,
|
|
592
717
|
string_value=self.distribution.distribution_domain_name,
|
|
593
718
|
description=f"CloudFront Distribution Domain for {self.cf_config.name}",
|
|
594
719
|
)
|
|
595
|
-
|
|
720
|
+
|
|
596
721
|
if "distribution_arn" in ssm_exports:
|
|
722
|
+
param_name = ssm_exports["distribution_arn"]
|
|
723
|
+
if not param_name.startswith("/"):
|
|
724
|
+
param_name = f"/{param_name}"
|
|
597
725
|
ssm.StringParameter(
|
|
598
726
|
self,
|
|
599
727
|
"DistributionArnParam",
|
|
600
|
-
parameter_name=
|
|
728
|
+
parameter_name=param_name,
|
|
601
729
|
string_value=f"arn:aws:cloudfront::{self.account}:distribution/{self.distribution.distribution_id}",
|
|
602
730
|
description=f"CloudFront Distribution ARN for {self.cf_config.name}",
|
|
603
731
|
)
|
|
@@ -610,14 +738,14 @@ class CloudFrontStack(IStack):
|
|
|
610
738
|
value=self.distribution.distribution_id,
|
|
611
739
|
description="CloudFront Distribution ID",
|
|
612
740
|
)
|
|
613
|
-
|
|
741
|
+
|
|
614
742
|
CfnOutput(
|
|
615
743
|
self,
|
|
616
744
|
"DistributionDomain",
|
|
617
745
|
value=self.distribution.distribution_domain_name,
|
|
618
746
|
description="CloudFront Distribution Domain Name",
|
|
619
747
|
)
|
|
620
|
-
|
|
748
|
+
|
|
621
749
|
if self.cf_config.aliases:
|
|
622
750
|
CfnOutput(
|
|
623
751
|
self,
|
|
@@ -9,6 +9,7 @@ from typing import Dict, Any, List, Optional
|
|
|
9
9
|
import aws_cdk as cdk
|
|
10
10
|
from aws_cdk import aws_rds as rds
|
|
11
11
|
from aws_cdk import aws_ec2 as ec2
|
|
12
|
+
from aws_cdk import aws_ssm as ssm
|
|
12
13
|
from aws_cdk import Duration
|
|
13
14
|
from aws_lambda_powertools import Logger
|
|
14
15
|
from constructs import Construct
|
|
@@ -41,6 +42,8 @@ class RdsStack(IStack, EnhancedSsmParameterMixin):
|
|
|
41
42
|
self.db_instance = None
|
|
42
43
|
self.security_groups = []
|
|
43
44
|
self._vpc = None
|
|
45
|
+
# SSM imported values
|
|
46
|
+
self.ssm_imported_values: Dict[str, str] = {}
|
|
44
47
|
|
|
45
48
|
def build(
|
|
46
49
|
self,
|
|
@@ -65,6 +68,9 @@ class RdsStack(IStack, EnhancedSsmParameterMixin):
|
|
|
65
68
|
self.rds_config = RdsConfig(stack_config.dictionary.get("rds", {}), deployment)
|
|
66
69
|
db_name = deployment.build_resource_name(self.rds_config.name)
|
|
67
70
|
|
|
71
|
+
# Process SSM imports first
|
|
72
|
+
self._process_ssm_imports()
|
|
73
|
+
|
|
68
74
|
# Get VPC and security groups
|
|
69
75
|
self.security_groups = self._get_security_groups()
|
|
70
76
|
|
|
@@ -77,18 +83,58 @@ class RdsStack(IStack, EnhancedSsmParameterMixin):
|
|
|
77
83
|
# Add outputs
|
|
78
84
|
self._add_outputs(db_name)
|
|
79
85
|
|
|
86
|
+
def _process_ssm_imports(self) -> None:
|
|
87
|
+
"""Process SSM imports from configuration"""
|
|
88
|
+
ssm_imports = self.rds_config.ssm_imports
|
|
89
|
+
|
|
90
|
+
if not ssm_imports:
|
|
91
|
+
logger.debug("No SSM imports configured for RDS")
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
logger.info(f"Processing {len(ssm_imports)} SSM imports for RDS")
|
|
95
|
+
|
|
96
|
+
for param_key, param_path in ssm_imports.items():
|
|
97
|
+
try:
|
|
98
|
+
if not param_path.startswith('/'):
|
|
99
|
+
param_path = f"/{param_path}"
|
|
100
|
+
|
|
101
|
+
construct_id = f"ssm-import-{param_key}-{hash(param_path) % 10000}"
|
|
102
|
+
param = ssm.StringParameter.from_string_parameter_name(
|
|
103
|
+
self, construct_id, param_path
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
self.ssm_imported_values[param_key] = param.string_value
|
|
107
|
+
logger.info(f"Imported SSM parameter: {param_key} from {param_path}")
|
|
108
|
+
|
|
109
|
+
except Exception as e:
|
|
110
|
+
logger.error(f"Failed to import SSM parameter {param_key} from {param_path}: {e}")
|
|
111
|
+
raise
|
|
112
|
+
|
|
80
113
|
@property
|
|
81
114
|
def vpc(self) -> ec2.IVpc:
|
|
82
115
|
"""Get the VPC for the RDS instance"""
|
|
83
|
-
# Assuming VPC is provided by the workload
|
|
84
116
|
if self._vpc:
|
|
85
117
|
return self._vpc
|
|
86
|
-
|
|
118
|
+
|
|
119
|
+
# Check SSM imported values first (tokens from SSM parameters)
|
|
120
|
+
if "vpc_id" in self.ssm_imported_values:
|
|
121
|
+
vpc_id = self.ssm_imported_values["vpc_id"]
|
|
122
|
+
|
|
123
|
+
# When using tokens, we can't provide subnet lists to from_vpc_attributes
|
|
124
|
+
# because CDK validates subnet count against AZ count at synthesis time
|
|
125
|
+
# We'll create a DB subnet group separately instead
|
|
126
|
+
vpc_attrs = {
|
|
127
|
+
"vpc_id": vpc_id,
|
|
128
|
+
"availability_zones": ["us-east-1a", "us-east-1b"]
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
# Use from_vpc_attributes() for SSM tokens
|
|
132
|
+
self._vpc = ec2.Vpc.from_vpc_attributes(self, "VPC", **vpc_attrs)
|
|
133
|
+
elif self.rds_config.vpc_id:
|
|
87
134
|
self._vpc = ec2.Vpc.from_lookup(self, "VPC", vpc_id=self.rds_config.vpc_id)
|
|
88
|
-
|
|
135
|
+
elif self.workload.vpc_id:
|
|
89
136
|
self._vpc = ec2.Vpc.from_lookup(self, "VPC", vpc_id=self.workload.vpc_id)
|
|
90
137
|
else:
|
|
91
|
-
# Use default VPC if not provided
|
|
92
138
|
raise ValueError(
|
|
93
139
|
"VPC is not defined in the configuration. "
|
|
94
140
|
"You can provide it a the rds.vpc_id in the configuration "
|
|
@@ -99,19 +145,47 @@ class RdsStack(IStack, EnhancedSsmParameterMixin):
|
|
|
99
145
|
def _get_security_groups(self) -> List[ec2.ISecurityGroup]:
|
|
100
146
|
"""Get security groups for the RDS instance"""
|
|
101
147
|
security_groups = []
|
|
102
|
-
|
|
148
|
+
|
|
149
|
+
# Check SSM imports first for security group ID
|
|
150
|
+
if "security_group_rds_id" in self.ssm_imported_values:
|
|
151
|
+
sg_id = self.ssm_imported_values["security_group_rds_id"]
|
|
152
|
+
security_groups.append(
|
|
153
|
+
ec2.SecurityGroup.from_security_group_id(
|
|
154
|
+
self, "RDSSecurityGroup", sg_id
|
|
155
|
+
)
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
# Also check config for any additional security group IDs
|
|
159
|
+
for idx, sg_id in enumerate(self.rds_config.security_group_ids):
|
|
103
160
|
security_groups.append(
|
|
104
161
|
ec2.SecurityGroup.from_security_group_id(
|
|
105
|
-
self, f"SecurityGroup-{
|
|
162
|
+
self, f"SecurityGroup-{idx}", sg_id
|
|
106
163
|
)
|
|
107
164
|
)
|
|
165
|
+
|
|
108
166
|
return security_groups
|
|
109
167
|
|
|
110
168
|
def _create_db_instance(self, db_name: str) -> rds.DatabaseInstance:
|
|
111
169
|
"""Create a new RDS instance"""
|
|
112
|
-
# Configure subnet
|
|
113
|
-
#
|
|
114
|
-
|
|
170
|
+
# Configure subnet group
|
|
171
|
+
# If we have subnet IDs from SSM, create a DB subnet group explicitly
|
|
172
|
+
db_subnet_group = None
|
|
173
|
+
if "subnet_ids" in self.ssm_imported_values:
|
|
174
|
+
subnet_ids_str = self.ssm_imported_values["subnet_ids"]
|
|
175
|
+
# Split the comma-separated token into a list
|
|
176
|
+
subnet_ids_list = cdk.Fn.split(",", subnet_ids_str)
|
|
177
|
+
|
|
178
|
+
# Create DB subnet group with the token-based subnet list
|
|
179
|
+
db_subnet_group = rds.CfnDBSubnetGroup(
|
|
180
|
+
self,
|
|
181
|
+
"DBSubnetGroup",
|
|
182
|
+
db_subnet_group_description=f"Subnet group for {db_name}",
|
|
183
|
+
subnet_ids=subnet_ids_list,
|
|
184
|
+
db_subnet_group_name=f"{db_name}-subnet-group"
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
# Configure subnet selection for VPC (when not using SSM imports)
|
|
188
|
+
subnets = None if db_subnet_group else ec2.SubnetSelection(subnet_type=ec2.SubnetType.PRIVATE_ISOLATED)
|
|
115
189
|
|
|
116
190
|
# Configure engine
|
|
117
191
|
engine_version = None
|
|
@@ -147,28 +221,36 @@ class RdsStack(IStack, EnhancedSsmParameterMixin):
|
|
|
147
221
|
removal_policy = cdk.RemovalPolicy.RETAIN
|
|
148
222
|
|
|
149
223
|
# Create the database instance
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
instance_type=instance_type,
|
|
157
|
-
credentials=rds.Credentials.from_generated_secret(
|
|
224
|
+
# Build common properties
|
|
225
|
+
db_props = {
|
|
226
|
+
"engine": engine,
|
|
227
|
+
"vpc": self.vpc,
|
|
228
|
+
"instance_type": instance_type,
|
|
229
|
+
"credentials": rds.Credentials.from_generated_secret(
|
|
158
230
|
username=self.rds_config.username,
|
|
159
231
|
secret_name=self.rds_config.secret_name,
|
|
160
232
|
),
|
|
161
|
-
database_name
|
|
162
|
-
multi_az
|
|
163
|
-
allocated_storage
|
|
164
|
-
storage_encrypted
|
|
165
|
-
security_groups
|
|
166
|
-
deletion_protection
|
|
167
|
-
backup_retention
|
|
168
|
-
cloudwatch_logs_exports
|
|
169
|
-
enable_performance_insights
|
|
170
|
-
removal_policy
|
|
171
|
-
|
|
233
|
+
"database_name": self.rds_config.database_name,
|
|
234
|
+
"multi_az": self.rds_config.multi_az,
|
|
235
|
+
"allocated_storage": self.rds_config.allocated_storage,
|
|
236
|
+
"storage_encrypted": self.rds_config.storage_encrypted,
|
|
237
|
+
"security_groups": self.security_groups if self.security_groups else None,
|
|
238
|
+
"deletion_protection": self.rds_config.deletion_protection,
|
|
239
|
+
"backup_retention": Duration.days(self.rds_config.backup_retention),
|
|
240
|
+
"cloudwatch_logs_exports": self.rds_config.cloudwatch_logs_exports,
|
|
241
|
+
"enable_performance_insights": self.rds_config.enable_performance_insights,
|
|
242
|
+
"removal_policy": removal_policy,
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
# Use either subnet group or vpc_subnets depending on what's available
|
|
246
|
+
if db_subnet_group:
|
|
247
|
+
db_props["subnet_group"] = rds.SubnetGroup.from_subnet_group_name(
|
|
248
|
+
self, "ImportedSubnetGroup", db_subnet_group.ref
|
|
249
|
+
)
|
|
250
|
+
else:
|
|
251
|
+
db_props["vpc_subnets"] = subnets
|
|
252
|
+
|
|
253
|
+
db_instance = rds.DatabaseInstance(self, db_name, **db_props)
|
|
172
254
|
|
|
173
255
|
# Add tags
|
|
174
256
|
for key, value in self.rds_config.tags.items():
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from typing import Dict, Any, List, Optional
|
|
2
2
|
|
|
3
3
|
import aws_cdk as cdk
|
|
4
|
-
from aws_cdk import aws_ec2 as ec2
|
|
4
|
+
from aws_cdk import aws_ec2 as ec2, aws_ssm as ssm
|
|
5
5
|
from aws_lambda_powertools import Logger
|
|
6
6
|
from constructs import Construct
|
|
7
7
|
|
|
@@ -31,6 +31,8 @@ class SecurityGroupsStack(IStack):
|
|
|
31
31
|
# Flag to determine if we're in test mode
|
|
32
32
|
self._test_mode = False
|
|
33
33
|
self._vpc = None
|
|
34
|
+
# SSM imported values
|
|
35
|
+
self.ssm_imported_values: Dict[str, str] = {}
|
|
34
36
|
|
|
35
37
|
def build(
|
|
36
38
|
self,
|
|
@@ -57,6 +59,9 @@ class SecurityGroupsStack(IStack):
|
|
|
57
59
|
deployment=deployment,
|
|
58
60
|
)
|
|
59
61
|
|
|
62
|
+
# Process SSM imports first
|
|
63
|
+
self._process_ssm_imports()
|
|
64
|
+
|
|
60
65
|
env_name = self.deployment.environment
|
|
61
66
|
|
|
62
67
|
# =========================================================
|
|
@@ -220,17 +225,65 @@ class SecurityGroupsStack(IStack):
|
|
|
220
225
|
export_name=f"{self.deployment.environment}-{self.workload.name}-WebMonitoringSecurityGroup",
|
|
221
226
|
)
|
|
222
227
|
|
|
228
|
+
def _process_ssm_imports(self) -> None:
|
|
229
|
+
"""
|
|
230
|
+
Process SSM imports from configuration.
|
|
231
|
+
Follows the same pattern as API Gateway and CloudFront stacks.
|
|
232
|
+
"""
|
|
233
|
+
ssm_imports = self.sg_config.ssm_imports
|
|
234
|
+
|
|
235
|
+
if not ssm_imports:
|
|
236
|
+
logger.debug("No SSM imports configured for Security Groups")
|
|
237
|
+
return
|
|
238
|
+
|
|
239
|
+
logger.info(f"Processing {len(ssm_imports)} SSM imports for Security Groups")
|
|
240
|
+
|
|
241
|
+
for param_key, param_path in ssm_imports.items():
|
|
242
|
+
try:
|
|
243
|
+
# Ensure parameter path starts with /
|
|
244
|
+
if not param_path.startswith('/'):
|
|
245
|
+
param_path = f"/{param_path}"
|
|
246
|
+
|
|
247
|
+
# Create unique construct ID from parameter path
|
|
248
|
+
construct_id = f"ssm-import-{param_key}-{hash(param_path) % 10000}"
|
|
249
|
+
|
|
250
|
+
# Import SSM parameter - this creates a CDK token that resolves at deployment time
|
|
251
|
+
param = ssm.StringParameter.from_string_parameter_name(
|
|
252
|
+
self, construct_id, param_path
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
# Store the token value for use in configuration
|
|
256
|
+
self.ssm_imported_values[param_key] = param.string_value
|
|
257
|
+
logger.info(f"Imported SSM parameter: {param_key} from {param_path}")
|
|
258
|
+
|
|
259
|
+
except Exception as e:
|
|
260
|
+
logger.error(f"Failed to import SSM parameter {param_key} from {param_path}: {e}")
|
|
261
|
+
raise
|
|
262
|
+
|
|
223
263
|
@property
|
|
224
264
|
def vpc(self) -> ec2.IVpc:
|
|
225
265
|
"""Get the VPC for the Security Group"""
|
|
226
266
|
if self._vpc:
|
|
227
267
|
return self._vpc
|
|
228
|
-
|
|
268
|
+
|
|
269
|
+
# Check SSM imported values first (tokens from SSM parameters)
|
|
270
|
+
if "vpc_id" in self.ssm_imported_values:
|
|
271
|
+
vpc_id = self.ssm_imported_values["vpc_id"]
|
|
272
|
+
|
|
273
|
+
# Build VPC attributes
|
|
274
|
+
vpc_attrs = {
|
|
275
|
+
"vpc_id": vpc_id,
|
|
276
|
+
"availability_zones": ["us-east-1a", "us-east-1b"]
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
# Use from_vpc_attributes() instead of from_lookup() because SSM imports return tokens
|
|
280
|
+
# from_lookup() requires concrete values and queries AWS during synthesis
|
|
281
|
+
self._vpc = ec2.Vpc.from_vpc_attributes(self, "VPC", **vpc_attrs)
|
|
282
|
+
elif self.sg_config.vpc_id:
|
|
229
283
|
self._vpc = ec2.Vpc.from_lookup(self, "VPC", vpc_id=self.sg_config.vpc_id)
|
|
230
284
|
elif self.workload.vpc_id:
|
|
231
285
|
self._vpc = ec2.Vpc.from_lookup(self, "VPC", vpc_id=self.workload.vpc_id)
|
|
232
|
-
|
|
233
286
|
else:
|
|
234
|
-
raise ValueError("VPC ID is not defined in the configuration.")
|
|
287
|
+
raise ValueError("VPC ID is not defined in the configuration or SSM imports.")
|
|
235
288
|
|
|
236
289
|
return self._vpc
|
cdk_factory/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.15.
|
|
1
|
+
__version__ = "0.15.1"
|
|
@@ -2,7 +2,7 @@ cdk_factory/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
2
2
|
cdk_factory/app.py,sha256=RnX0-pwdTAPAdKJK_j13Zl8anf9zYKBwboR0KA8K8xM,10346
|
|
3
3
|
cdk_factory/cdk.json,sha256=SKZKhJ2PBpFH78j-F8S3VDYW-lf76--Q2I3ON-ZIQfw,3106
|
|
4
4
|
cdk_factory/cli.py,sha256=FGbCTS5dYCNsfp-etshzvFlGDCjC28r6rtzYbe7KoHI,6407
|
|
5
|
-
cdk_factory/version.py,sha256=
|
|
5
|
+
cdk_factory/version.py,sha256=PQVflpJdJDETGf2g1BB5OHCS_5KtgteERschfoSBtss,23
|
|
6
6
|
cdk_factory/builds/README.md,sha256=9BBWd7bXpyKdMU_g2UljhQwrC9i5O_Tvkb6oPvndoZk,90
|
|
7
7
|
cdk_factory/commands/command_loader.py,sha256=QbLquuP_AdxtlxlDy-2IWCQ6D-7qa58aphnDPtp_uTs,3744
|
|
8
8
|
cdk_factory/configurations/base_config.py,sha256=JKjhNsy0RCUZy1s8n5D_aXXI-upR9izaLtCTfKYiV9k,9624
|
|
@@ -38,16 +38,16 @@ cdk_factory/configurations/resources/lambda_layers.py,sha256=gVeP_-LC3Eq0lkPaG_J
|
|
|
38
38
|
cdk_factory/configurations/resources/lambda_triggers.py,sha256=MD7cdMNKEulNBhtMLIFnWJuJ5R-yyIqa0LHUgbSQerA,834
|
|
39
39
|
cdk_factory/configurations/resources/load_balancer.py,sha256=DHVKuEDaTfbB0UKYBt7UQQCPCM4FY-ThT1T52lcwg_E,4897
|
|
40
40
|
cdk_factory/configurations/resources/monitoring.py,sha256=zsfDMa7yph33Ql8iP7lIqqLAyixh-Mesi0imtZJFdcE,2310
|
|
41
|
-
cdk_factory/configurations/resources/rds.py,sha256
|
|
41
|
+
cdk_factory/configurations/resources/rds.py,sha256=NhXOPTqjfOZhJWyNd0yhBnkk1J0VOPgNLe6VldaJe6k,4628
|
|
42
42
|
cdk_factory/configurations/resources/resource_mapping.py,sha256=cwv3n63RJ6E59ErsmSTdkW4i-g8huhHtKI0ExbRhJxA,2182
|
|
43
43
|
cdk_factory/configurations/resources/resource_naming.py,sha256=VE9S2cpzp11qqPL2z1sX79wXH0o1SntO2OG74nEmWC8,5508
|
|
44
44
|
cdk_factory/configurations/resources/resource_types.py,sha256=1WQHyDoErb-M-tETZZzyLDtbq_jdC85-I403dM48pgE,2317
|
|
45
|
-
cdk_factory/configurations/resources/route53.py,sha256=
|
|
45
|
+
cdk_factory/configurations/resources/route53.py,sha256=FwfAL90amoQqn7DSGcIprlfNhbJ08T80joplxLg99Ko,3453
|
|
46
46
|
cdk_factory/configurations/resources/route53_hosted_zone.py,sha256=qjEYPCSxSOx5blr9EULv892ezxkCs--yrLa1ngWbyXM,880
|
|
47
47
|
cdk_factory/configurations/resources/rum.py,sha256=5aNLhyJEl97spby2gEV59RsMIQpUto2hGh1DeSyIp_I,5149
|
|
48
48
|
cdk_factory/configurations/resources/s3.py,sha256=LBwTOZ4tOxNbgiu1fFGHOTyF5jlzeVphc_9VAqNw8zA,6042
|
|
49
49
|
cdk_factory/configurations/resources/security_group.py,sha256=8kQtaaRVEn2aDm8XoC7QFh2mDOFbPbgobmssIuqU8MA,2259
|
|
50
|
-
cdk_factory/configurations/resources/security_group_full_stack.py,sha256=
|
|
50
|
+
cdk_factory/configurations/resources/security_group_full_stack.py,sha256=J56ui5cR4ULcT-20LdK43UNXhcicB2M45Wl8Y9SIWCA,2202
|
|
51
51
|
cdk_factory/configurations/resources/sqs.py,sha256=fAh2dqttJ6PX46enFRULuiLEu3TEj0Vb2xntAOgUpYE,4346
|
|
52
52
|
cdk_factory/configurations/resources/vpc.py,sha256=sNn6w76bHFwmt6N76gZZhqpsuNB9860C1SZu6tebaXY,3835
|
|
53
53
|
cdk_factory/constructs/cloudfront/cloudfront_distribution_construct.py,sha256=gFQw96rfSX7n3-YaK4AWyF2NNzJezgZpmnAcxZpmgxs,22036
|
|
@@ -86,7 +86,7 @@ cdk_factory/stack_library/aws_lambdas/lambda_stack.py,sha256=SFbBPvvCopbyiuYtq-O
|
|
|
86
86
|
cdk_factory/stack_library/buckets/README.md,sha256=XkK3UNVtRLE7NtUvbhCOBBYUYi8hlrrSaI1s3GJVrqI,78
|
|
87
87
|
cdk_factory/stack_library/buckets/bucket_stack.py,sha256=SLoZqSffAqmeBBEVUQg54D_8Ad5UKdkjEAmKAVgAqQo,1778
|
|
88
88
|
cdk_factory/stack_library/cloudfront/__init__.py,sha256=Zfx50q4xIJ4ZEoVIzUBDTKbRE9DKDM6iyVIFhtQXvww,153
|
|
89
|
-
cdk_factory/stack_library/cloudfront/cloudfront_stack.py,sha256
|
|
89
|
+
cdk_factory/stack_library/cloudfront/cloudfront_stack.py,sha256=kBMWxHlB48aRcFGIX71mRso0tEuEaQxcTBr3YCJOr4s,30210
|
|
90
90
|
cdk_factory/stack_library/code_artifact/code_artifact_stack.py,sha256=vySYIjWGTdVfMcUOyJdW6gTL1maHWq9ThzfrN_rVL5A,6290
|
|
91
91
|
cdk_factory/stack_library/cognito/cognito_stack.py,sha256=zEHkKVCIeyZywPs_GIMXCXyCux9RAKdl5kba3wy8wtQ,24608
|
|
92
92
|
cdk_factory/stack_library/dynamodb/dynamodb_stack.py,sha256=TVyOrUhgaSuN8uymkpaQcpOaSA0lkYJ8QUMgakTCKus,6771
|
|
@@ -101,13 +101,13 @@ cdk_factory/stack_library/load_balancer/load_balancer_stack.py,sha256=t5JUe5lMUb
|
|
|
101
101
|
cdk_factory/stack_library/monitoring/__init__.py,sha256=k1G_KDx47Aw0UugaL99PN_TKlyLK4nkJVApCaAK7GJg,153
|
|
102
102
|
cdk_factory/stack_library/monitoring/monitoring_stack.py,sha256=N_1YvEXE7fboH_S3kv_dSKZsufxMuPdFMjGzlNFpuSo,19283
|
|
103
103
|
cdk_factory/stack_library/rds/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
104
|
-
cdk_factory/stack_library/rds/rds_stack.py,sha256
|
|
104
|
+
cdk_factory/stack_library/rds/rds_stack.py,sha256=jvG3mcz5CQHv2NV-KwjGX8XgxtPiixRQTdBtaLb6sw4,12161
|
|
105
105
|
cdk_factory/stack_library/route53/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
106
106
|
cdk_factory/stack_library/route53/route53_stack.py,sha256=R-6DW7gIjeg25uBT5ZMLNDiQUOSZMipc-Tw6f8POVvI,8081
|
|
107
107
|
cdk_factory/stack_library/rum/__init__.py,sha256=gUrWQdzd4rZ2J0YzAQC8PsEGAS7QgyYjB2ZCUKWasy4,90
|
|
108
108
|
cdk_factory/stack_library/rum/rum_stack.py,sha256=OvQ6tsjYcXS8adqU_Xh0A_VKdnPtQnij4cG67nNqSVo,13611
|
|
109
109
|
cdk_factory/stack_library/security_group/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
110
|
-
cdk_factory/stack_library/security_group/security_group_full_stack.py,sha256=
|
|
110
|
+
cdk_factory/stack_library/security_group/security_group_full_stack.py,sha256=zu-xrz2KuojJGoN4-sTzD14sT9DwYaJpgwFl3wPiNXw,10907
|
|
111
111
|
cdk_factory/stack_library/security_group/security_group_stack.py,sha256=2zxd5ozgQ4GP0xi-Ni7SyChtEAOzC0nXeGz78DPXwPg,14445
|
|
112
112
|
cdk_factory/stack_library/simple_queue_service/sqs_stack.py,sha256=jJksWrvrvgZUMM01RZ317DOIxqIJbkYYSYu38w0jHpc,6039
|
|
113
113
|
cdk_factory/stack_library/vpc/__init__.py,sha256=7pIqP97Gf2AJbv9Ebp1WbQGHYhgEbWJ52L1MzeXBybA,42
|
|
@@ -129,8 +129,8 @@ cdk_factory/utilities/lambda_function_utilities.py,sha256=S1GvBsY_q2cyUiaud3HORJ
|
|
|
129
129
|
cdk_factory/utilities/os_execute.py,sha256=5Op0LY_8Y-pUm04y1k8MTpNrmQvcLmQHPQITEP7EuSU,1019
|
|
130
130
|
cdk_factory/utils/api_gateway_utilities.py,sha256=If7Xu5s_UxmuV-kL3JkXxPLBdSVUKoLtohm0IUFoiV8,4378
|
|
131
131
|
cdk_factory/workload/workload_factory.py,sha256=mM8GU_5mKq_0OyK060T3JrUSUiGAcKf0eqNlT9mfaws,6028
|
|
132
|
-
cdk_factory-0.15.
|
|
133
|
-
cdk_factory-0.15.
|
|
134
|
-
cdk_factory-0.15.
|
|
135
|
-
cdk_factory-0.15.
|
|
136
|
-
cdk_factory-0.15.
|
|
132
|
+
cdk_factory-0.15.1.dist-info/METADATA,sha256=QzCc3WHsPfsvYM3Jr7gxaRiRZ8emBOlgd-TnaJ2Rt7w,2451
|
|
133
|
+
cdk_factory-0.15.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
134
|
+
cdk_factory-0.15.1.dist-info/entry_points.txt,sha256=S1DPe0ORcdiwEALMN_WIo3UQrW_g4YdQCLEsc_b0Swg,53
|
|
135
|
+
cdk_factory-0.15.1.dist-info/licenses/LICENSE,sha256=NOtdOeLwg2il_XBJdXUPFPX8JlV4dqTdDGAd2-khxT8,1066
|
|
136
|
+
cdk_factory-0.15.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|