cdk-factory 0.17.6__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/deployment.py +12 -0
- cdk_factory/configurations/resources/acm.py +9 -2
- cdk_factory/configurations/resources/auto_scaling.py +7 -5
- cdk_factory/configurations/resources/ecs_cluster.py +5 -0
- cdk_factory/configurations/resources/ecs_service.py +24 -2
- cdk_factory/configurations/resources/lambda_edge.py +18 -4
- cdk_factory/configurations/resources/rds.py +1 -1
- cdk_factory/configurations/resources/route53.py +5 -0
- cdk_factory/configurations/resources/s3.py +9 -1
- cdk_factory/constructs/cloudfront/cloudfront_distribution_construct.py +1 -1
- cdk_factory/constructs/lambdas/policies/policy_docs.py +1 -1
- cdk_factory/interfaces/networked_stack_mixin.py +1 -1
- cdk_factory/interfaces/standardized_ssm_mixin.py +82 -10
- cdk_factory/stack_library/acm/acm_stack.py +5 -15
- cdk_factory/stack_library/api_gateway/api_gateway_stack.py +2 -2
- cdk_factory/stack_library/auto_scaling/{auto_scaling_stack_standardized.py → auto_scaling_stack.py} +213 -105
- cdk_factory/stack_library/cloudfront/cloudfront_stack.py +76 -22
- cdk_factory/stack_library/code_artifact/code_artifact_stack.py +3 -25
- cdk_factory/stack_library/cognito/cognito_stack.py +2 -2
- cdk_factory/stack_library/dynamodb/dynamodb_stack.py +2 -2
- cdk_factory/stack_library/ecs/__init__.py +2 -4
- cdk_factory/stack_library/ecs/{ecs_cluster_stack_standardized.py → ecs_cluster_stack.py} +52 -41
- cdk_factory/stack_library/ecs/ecs_service_stack.py +49 -26
- 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 +238 -81
- cdk_factory/stack_library/load_balancer/load_balancer_stack.py +128 -177
- cdk_factory/stack_library/rds/rds_stack.py +65 -72
- cdk_factory/stack_library/route53/route53_stack.py +244 -38
- cdk_factory/stack_library/rum/rum_stack.py +3 -3
- cdk_factory/stack_library/security_group/security_group_full_stack.py +1 -31
- cdk_factory/stack_library/security_group/security_group_stack.py +1 -8
- 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_standardized.py → vpc_stack.py} +6 -109
- cdk_factory/stack_library/websites/static_website_stack.py +7 -3
- cdk_factory/utilities/api_gateway_integration_utility.py +2 -2
- cdk_factory/utilities/environment_services.py +2 -2
- cdk_factory/version.py +1 -1
- {cdk_factory-0.17.6.dist-info → cdk_factory-0.20.0.dist-info}/METADATA +1 -1
- {cdk_factory-0.17.6.dist-info → cdk_factory-0.20.0.dist-info}/RECORD +44 -42
- {cdk_factory-0.17.6.dist-info → cdk_factory-0.20.0.dist-info}/WHEEL +0 -0
- {cdk_factory-0.17.6.dist-info → cdk_factory-0.20.0.dist-info}/entry_points.txt +0 -0
- {cdk_factory-0.17.6.dist-info → cdk_factory-0.20.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -28,6 +28,18 @@ class DeploymentConfig:
|
|
|
28
28
|
self.__load()
|
|
29
29
|
|
|
30
30
|
def __load(self):
|
|
31
|
+
# Validate environment consistency
|
|
32
|
+
deployment_env = self.__deployment.get("environment")
|
|
33
|
+
workload_env = self.__workload.get("environment")
|
|
34
|
+
|
|
35
|
+
if deployment_env and workload_env and deployment_env != workload_env:
|
|
36
|
+
from aws_lambda_powertools import Logger
|
|
37
|
+
logger = Logger()
|
|
38
|
+
logger.warning(
|
|
39
|
+
f"Environment mismatch: deployment.environment='{deployment_env}' != workload.environment='{workload_env}'. "
|
|
40
|
+
f"Using workload.environment for consistency."
|
|
41
|
+
)
|
|
42
|
+
|
|
31
43
|
self.__load_pipeline()
|
|
32
44
|
self.__load_stacks()
|
|
33
45
|
|
|
@@ -59,15 +59,22 @@ class AcmConfig:
|
|
|
59
59
|
"""Certificate transparency logging preference (ENABLED or DISABLED)"""
|
|
60
60
|
return self.__config.get("certificate_transparency_logging_preference")
|
|
61
61
|
|
|
62
|
+
@property
|
|
63
|
+
def ssm(self) -> Dict[str, Any]:
|
|
64
|
+
"""SSM configuration for importing/exporting resources"""
|
|
65
|
+
return self.__config.get("ssm", {})
|
|
66
|
+
|
|
62
67
|
@property
|
|
63
68
|
def ssm_exports(self) -> Dict[str, str]:
|
|
64
69
|
"""SSM parameter paths to export certificate details"""
|
|
65
|
-
exports = self.
|
|
70
|
+
exports = self.ssm.get("exports", {})
|
|
66
71
|
|
|
67
72
|
# Provide default SSM export path if not specified
|
|
68
73
|
if not exports and self.__deployment:
|
|
74
|
+
workload_env = self.__deployment.workload.get("environment", self.__deployment.environment)
|
|
75
|
+
workload_name = self.__deployment.workload.get("name", self.__deployment.workload_name)
|
|
69
76
|
exports = {
|
|
70
|
-
"certificate_arn": f"/{
|
|
77
|
+
"certificate_arn": f"/{workload_env}/{workload_name}/certificate/arn"
|
|
71
78
|
}
|
|
72
79
|
|
|
73
80
|
return exports
|
|
@@ -70,12 +70,14 @@ class AutoScalingConfig(EnhancedBaseConfig):
|
|
|
70
70
|
return self.__config.get("termination_policies", ["DEFAULT"])
|
|
71
71
|
|
|
72
72
|
@property
|
|
73
|
-
def update_policy(self) -> Dict[str, Any]:
|
|
73
|
+
def update_policy(self) -> Optional[Dict[str, Any]]:
|
|
74
74
|
"""Update policy configuration"""
|
|
75
|
-
return self.__config.get(
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
75
|
+
return self.__config.get("update_policy")
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def instance_refresh(self) -> Optional[Dict[str, Any]]:
|
|
79
|
+
"""Instance refresh configuration for rolling updates"""
|
|
80
|
+
return self.__config.get("instance_refresh")
|
|
79
81
|
|
|
80
82
|
@property
|
|
81
83
|
def user_data_commands(self) -> List[str]:
|
|
@@ -18,6 +18,11 @@ class EcsClusterConfig:
|
|
|
18
18
|
def __init__(self, config: Dict[str, Any]) -> None:
|
|
19
19
|
self._config = config or {}
|
|
20
20
|
|
|
21
|
+
@property
|
|
22
|
+
def dictionary(self) -> Dict[str, Any]:
|
|
23
|
+
"""Access to the underlying configuration dictionary (for compatibility with SSM mixin)"""
|
|
24
|
+
return self._config
|
|
25
|
+
|
|
21
26
|
@property
|
|
22
27
|
def name(self) -> str:
|
|
23
28
|
"""Name of the ECS cluster. Supports template variables like {{WORKLOAD_NAME}}-{{ENVIRONMENT}}-cluster"""
|
|
@@ -83,14 +83,27 @@ class EcsServiceConfig:
|
|
|
83
83
|
"""Whether to assign public IP addresses"""
|
|
84
84
|
return self._config.get("assign_public_ip", False)
|
|
85
85
|
|
|
86
|
+
@property
|
|
87
|
+
def load_balancer_config(self) -> Dict[str, Any]:
|
|
88
|
+
"""Load balancer configuration"""
|
|
89
|
+
return self._config.get("load_balancer", {})
|
|
90
|
+
|
|
86
91
|
@property
|
|
87
92
|
def target_group_arns(self) -> List[str]:
|
|
88
93
|
"""Target group ARNs for load balancing"""
|
|
94
|
+
# Check if load_balancer config has target_group_arn
|
|
95
|
+
if self.load_balancer_config and self.load_balancer_config.get("target_group_arn"):
|
|
96
|
+
arn = self.load_balancer_config["target_group_arn"]
|
|
97
|
+
if arn and arn != "arn:aws:elasticloadbalancing:placeholder":
|
|
98
|
+
return [arn]
|
|
89
99
|
return self._config.get("target_group_arns", [])
|
|
90
100
|
|
|
91
101
|
@property
|
|
92
102
|
def container_port(self) -> int:
|
|
93
103
|
"""Container port for load balancer"""
|
|
104
|
+
# Check load_balancer config first
|
|
105
|
+
if self.load_balancer_config and self.load_balancer_config.get("container_port"):
|
|
106
|
+
return self.load_balancer_config["container_port"]
|
|
94
107
|
return self._config.get("container_port", 80)
|
|
95
108
|
|
|
96
109
|
@property
|
|
@@ -138,8 +151,17 @@ class EcsServiceConfig:
|
|
|
138
151
|
"""SSM parameter imports"""
|
|
139
152
|
# Check both nested and flat structures for backwards compatibility
|
|
140
153
|
if "ssm" in self._config and "imports" in self._config["ssm"]:
|
|
141
|
-
|
|
142
|
-
|
|
154
|
+
imports = self._config["ssm"]["imports"]
|
|
155
|
+
else:
|
|
156
|
+
imports = self.ssm.get("imports", {})
|
|
157
|
+
|
|
158
|
+
# Add load_balancer SSM imports if they exist
|
|
159
|
+
if self.load_balancer_config and "ssm" in self.load_balancer_config:
|
|
160
|
+
lb_ssm = self.load_balancer_config["ssm"]
|
|
161
|
+
if "imports" in lb_ssm:
|
|
162
|
+
imports.update(lb_ssm["imports"])
|
|
163
|
+
|
|
164
|
+
return imports
|
|
143
165
|
|
|
144
166
|
@property
|
|
145
167
|
def deployment_type(self) -> str:
|
|
@@ -49,16 +49,30 @@ class LambdaEdgeConfig(EnhancedBaseConfig):
|
|
|
49
49
|
|
|
50
50
|
@property
|
|
51
51
|
def timeout(self) -> int:
|
|
52
|
-
"""Timeout in seconds
|
|
52
|
+
"""Timeout in seconds
|
|
53
|
+
viewer-request: 5s
|
|
54
|
+
viewer-response: 5s
|
|
55
|
+
---
|
|
56
|
+
origin-request: 30s
|
|
57
|
+
origin-response: 30s
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
"""
|
|
53
61
|
timeout = int(self._config.get("timeout", 5))
|
|
54
|
-
|
|
55
|
-
|
|
62
|
+
|
|
63
|
+
event_type = self.event_type
|
|
64
|
+
if event_type == "viewer-request" or event_type == "viewer-response":
|
|
65
|
+
if timeout > 5:
|
|
66
|
+
raise ValueError("Lambda@Edge viewer timeout cannot exceed 5 seconds. Value was set to {}".format(timeout))
|
|
67
|
+
else:
|
|
68
|
+
if timeout > 30:
|
|
69
|
+
raise ValueError("Lambda@Edge origin timeout cannot exceed 30 seconds. Value was set to {}".format(timeout))
|
|
56
70
|
return timeout
|
|
57
71
|
|
|
58
72
|
@property
|
|
59
73
|
def code_path(self) -> str:
|
|
60
74
|
"""Path to Lambda function code directory"""
|
|
61
|
-
return self._config.get("code_path", "./lambdas/
|
|
75
|
+
return self._config.get("code_path", "./lambdas/cloudfront/ip_gate")
|
|
62
76
|
|
|
63
77
|
@property
|
|
64
78
|
def environment(self) -> Dict[str, str]:
|
|
@@ -106,3 +106,8 @@ class Route53Config:
|
|
|
106
106
|
def tags(self) -> Dict[str, str]:
|
|
107
107
|
"""Tags to apply to the Route53 resources"""
|
|
108
108
|
return self.__config.get("tags", {})
|
|
109
|
+
|
|
110
|
+
@property
|
|
111
|
+
def records(self) -> List[Dict[str, Any]]:
|
|
112
|
+
"""Records to create"""
|
|
113
|
+
return self.__config.get("records", [])
|
|
@@ -158,7 +158,15 @@ class S3BucketConfig(EnhancedBaseConfig):
|
|
|
158
158
|
value = self.__config.get("block_public_access")
|
|
159
159
|
|
|
160
160
|
if value and isinstance(value, str):
|
|
161
|
-
if value.lower() == "
|
|
161
|
+
if value.lower() == "disabled":
|
|
162
|
+
# For public website hosting, disable block public access
|
|
163
|
+
return s3.BlockPublicAccess(
|
|
164
|
+
block_public_acls=False,
|
|
165
|
+
block_public_policy=False,
|
|
166
|
+
ignore_public_acls=False,
|
|
167
|
+
restrict_public_buckets=False
|
|
168
|
+
)
|
|
169
|
+
elif value.lower() == "block_acls":
|
|
162
170
|
return s3.BlockPublicAccess.BLOCK_ACLS
|
|
163
171
|
# elif value.lower() == "block_public_acls":
|
|
164
172
|
# return s3.BlockPublicAccess.block_public_acls
|
|
@@ -46,7 +46,7 @@ class ResourceResolver:
|
|
|
46
46
|
ssm_config = lambda_dict.get("ssm", {})
|
|
47
47
|
|
|
48
48
|
if ssm_config.get("enabled", False):
|
|
49
|
-
self._ssm_mixin.
|
|
49
|
+
self._ssm_mixin.setup_ssm_integration(
|
|
50
50
|
scope=self.scope,
|
|
51
51
|
config=lambda_dict,
|
|
52
52
|
resource_type="lambda",
|
|
@@ -26,7 +26,7 @@ class NetworkedStackMixin(StandardizedSsmMixin, VPCProviderMixin):
|
|
|
26
26
|
# SSM initialization is handled automatically by StandardizedSsmMixin.__init__
|
|
27
27
|
|
|
28
28
|
def _build(self, stack_config, deployment, workload):
|
|
29
|
-
self.
|
|
29
|
+
self.setup_ssm_integration(scope=self, config=stack_config.dictionary, resource_type="my-resource", resource_name="my-name")
|
|
30
30
|
self.vpc = self.resolve_vpc(stack_config, deployment, workload)
|
|
31
31
|
"""
|
|
32
32
|
|
|
@@ -24,6 +24,8 @@ import os
|
|
|
24
24
|
import re
|
|
25
25
|
from typing import Dict, Any, Optional, List, Union
|
|
26
26
|
from aws_cdk import aws_ssm as ssm
|
|
27
|
+
from aws_cdk import aws_ec2 as ec2
|
|
28
|
+
from aws_cdk import aws_ecs as ecs
|
|
27
29
|
from constructs import Construct
|
|
28
30
|
from aws_lambda_powertools import Logger
|
|
29
31
|
from cdk_factory.configurations.deployment import DeploymentConfig
|
|
@@ -117,6 +119,7 @@ class StandardizedSsmMixin:
|
|
|
117
119
|
"""Export multiple resource values to SSM Parameter Store."""
|
|
118
120
|
params = {}
|
|
119
121
|
|
|
122
|
+
invalid_export_keys = []
|
|
120
123
|
# Only export parameters that are explicitly configured in ssm_exports
|
|
121
124
|
if not hasattr(config, 'ssm_exports') or not config.ssm_exports:
|
|
122
125
|
logger.debug("No SSM exports configured")
|
|
@@ -136,8 +139,17 @@ class StandardizedSsmMixin:
|
|
|
136
139
|
)
|
|
137
140
|
params[key] = param
|
|
138
141
|
else:
|
|
142
|
+
invalid_export_keys.append(key)
|
|
139
143
|
logger.warning(f"SSM export configured for '{key}' but no value found in resource_values")
|
|
140
144
|
|
|
145
|
+
if invalid_export_keys:
|
|
146
|
+
message = f"Export SSM Error\n🚨 SSM exports configured for '{invalid_export_keys}' but no values found in resource_values"
|
|
147
|
+
available_keys = list(resource_values.keys())
|
|
148
|
+
message = f"{message}\n✅ Available keys: {available_keys}"
|
|
149
|
+
message = f"{message}\n👉 Please update to the correct key or remove from the export list."
|
|
150
|
+
logger.warning(message)
|
|
151
|
+
raise ValueError(message)
|
|
152
|
+
|
|
141
153
|
return params
|
|
142
154
|
|
|
143
155
|
def normalize_resource_name(self, name: str, for_export: bool = False) -> str:
|
|
@@ -151,7 +163,7 @@ class StandardizedSsmMixin:
|
|
|
151
163
|
normalized = normalized.strip('-')
|
|
152
164
|
return normalized
|
|
153
165
|
|
|
154
|
-
def
|
|
166
|
+
def setup_ssm_integration(
|
|
155
167
|
self,
|
|
156
168
|
scope: Construct,
|
|
157
169
|
config: Any,
|
|
@@ -200,7 +212,7 @@ class StandardizedSsmMixin:
|
|
|
200
212
|
logger.info(f"SSM imports: {len(self.ssm_config.get('imports', {}))}")
|
|
201
213
|
logger.info(f"SSM exports: {len(self.ssm_config.get('exports', {}))}")
|
|
202
214
|
|
|
203
|
-
def
|
|
215
|
+
def process_ssm_imports(self) -> None:
|
|
204
216
|
"""
|
|
205
217
|
Process SSM imports using standardized approach.
|
|
206
218
|
|
|
@@ -228,7 +240,7 @@ class StandardizedSsmMixin:
|
|
|
228
240
|
logger.error(error_msg)
|
|
229
241
|
raise ValueError(error_msg)
|
|
230
242
|
|
|
231
|
-
def
|
|
243
|
+
def export_ssm_parameters(self, resource_values: Dict[str, Any]) -> Dict[str, str]:
|
|
232
244
|
"""
|
|
233
245
|
Export SSM parameters using standardized approach.
|
|
234
246
|
|
|
@@ -267,6 +279,23 @@ class StandardizedSsmMixin:
|
|
|
267
279
|
|
|
268
280
|
return exported_params
|
|
269
281
|
|
|
282
|
+
def resolve_ssm_value(self, scope: Construct, value: str, unique_id: str)-> str:
|
|
283
|
+
if isinstance(value, str) and value.startswith("{{ssm:") and value.endswith("}}"):
|
|
284
|
+
# Extract SSM parameter path
|
|
285
|
+
ssm_param_path = value[6:-2] # Remove {{ssm: and }}
|
|
286
|
+
|
|
287
|
+
# Import SSM parameter - this creates a token that resolves at deployment time
|
|
288
|
+
param = ssm.StringParameter.from_string_parameter_name(
|
|
289
|
+
scope=scope,
|
|
290
|
+
id=f"{unique_id}-env-{hash(ssm_param_path) % 10000}",
|
|
291
|
+
string_parameter_name=ssm_param_path
|
|
292
|
+
)
|
|
293
|
+
resolved_value = param.string_value
|
|
294
|
+
logger.info(f"Resolved SSM parameter {ssm_param_path}")
|
|
295
|
+
return resolved_value
|
|
296
|
+
else:
|
|
297
|
+
return value
|
|
298
|
+
|
|
270
299
|
def _resolve_ssm_import(self, import_value: Union[str, List[str]], import_key: str) -> Union[str, List[str]]:
|
|
271
300
|
"""
|
|
272
301
|
Resolve SSM import value with proper error handling and validation.
|
|
@@ -336,16 +365,18 @@ class StandardizedSsmMixin:
|
|
|
336
365
|
# Prepare template variables
|
|
337
366
|
variables = {}
|
|
338
367
|
|
|
339
|
-
|
|
368
|
+
# Always prioritize workload environment for consistency
|
|
369
|
+
if self.workload:
|
|
370
|
+
variables["ENVIRONMENT"] = self.workload.dictionary.get("environment", "test")
|
|
371
|
+
variables["WORKLOAD_NAME"] = self.workload.dictionary.get("name", "test-workload")
|
|
372
|
+
variables["AWS_REGION"] = os.getenv("AWS_REGION", "us-east-1")
|
|
373
|
+
elif self.deployment:
|
|
374
|
+
# Fallback to deployment only if workload not available
|
|
340
375
|
variables["ENVIRONMENT"] = self.deployment.environment
|
|
341
376
|
variables["WORKLOAD_NAME"] = self.deployment.workload_name
|
|
342
377
|
variables["AWS_REGION"] = getattr(self.deployment, 'region', None) or os.getenv("AWS_REGION", "us-east-1")
|
|
343
|
-
elif self.workload:
|
|
344
|
-
variables["ENVIRONMENT"] = getattr(self.workload, 'environment', 'test')
|
|
345
|
-
variables["WORKLOAD_NAME"] = getattr(self.workload, 'name', 'test-workload')
|
|
346
|
-
variables["AWS_REGION"] = os.getenv("AWS_REGION", "us-east-1")
|
|
347
378
|
else:
|
|
348
|
-
#
|
|
379
|
+
# Final fallback to environment variables
|
|
349
380
|
variables["ENVIRONMENT"] = os.getenv("ENVIRONMENT", "test")
|
|
350
381
|
variables["WORKLOAD_NAME"] = os.getenv("WORKLOAD_NAME", "test-workload")
|
|
351
382
|
variables["AWS_REGION"] = os.getenv("AWS_REGION", "us-east-1")
|
|
@@ -396,7 +427,7 @@ class StandardizedSsmMixin:
|
|
|
396
427
|
resource_type = segments[3]
|
|
397
428
|
|
|
398
429
|
# Check for valid environment patterns
|
|
399
|
-
if environment not in ["dev", "staging", "prod", "test"]:
|
|
430
|
+
if environment not in ["dev", "staging", "prod", "test", "alpha", "beta", "sandbox"]:
|
|
400
431
|
logger.warning(f"{context}: Unusual environment segment: {environment}")
|
|
401
432
|
|
|
402
433
|
# Check for valid resource type patterns
|
|
@@ -529,6 +560,44 @@ class StandardizedSsmMixin:
|
|
|
529
560
|
return self._ssm_exported_values.copy()
|
|
530
561
|
|
|
531
562
|
|
|
563
|
+
def get_subnet_ids(self, config) -> List[str]:
|
|
564
|
+
"""
|
|
565
|
+
Helper function to parse subnet IDs from SSM imports.
|
|
566
|
+
|
|
567
|
+
This common pattern handles:
|
|
568
|
+
1. Comma-separated subnet ID strings from SSM
|
|
569
|
+
2. List of subnet IDs from SSM
|
|
570
|
+
3. Fallback to config attributes
|
|
571
|
+
|
|
572
|
+
Args:
|
|
573
|
+
config: Configuration object that might have subnet_ids attribute
|
|
574
|
+
|
|
575
|
+
Returns:
|
|
576
|
+
List of subnet IDs (empty list if not found or invalid format)
|
|
577
|
+
"""
|
|
578
|
+
# Use the standardized SSM imports
|
|
579
|
+
ssm_imports = self.get_all_ssm_imports()
|
|
580
|
+
if "subnet_ids" in ssm_imports:
|
|
581
|
+
subnet_ids = ssm_imports["subnet_ids"]
|
|
582
|
+
|
|
583
|
+
# Handle comma-separated string or list
|
|
584
|
+
if isinstance(subnet_ids, str):
|
|
585
|
+
# Split comma-separated string
|
|
586
|
+
parsed_ids = [sid.strip() for sid in subnet_ids.split(',') if sid.strip()]
|
|
587
|
+
return parsed_ids
|
|
588
|
+
elif isinstance(subnet_ids, list):
|
|
589
|
+
return subnet_ids
|
|
590
|
+
else:
|
|
591
|
+
logger.warning(f"Unexpected subnet_ids type: {type(subnet_ids)}")
|
|
592
|
+
return []
|
|
593
|
+
|
|
594
|
+
# Fallback: Check config attributes
|
|
595
|
+
elif hasattr(config, 'subnet_ids') and config.subnet_ids:
|
|
596
|
+
return config.subnet_ids
|
|
597
|
+
|
|
598
|
+
else:
|
|
599
|
+
logger.warning("No subnet IDs found, using default behavior")
|
|
600
|
+
return []
|
|
532
601
|
|
|
533
602
|
class ValidationResult:
|
|
534
603
|
"""Result of configuration validation."""
|
|
@@ -610,3 +679,6 @@ class SsmStandardValidator:
|
|
|
610
679
|
errors.append(f"{context}: SSM path should use template variables: {path}")
|
|
611
680
|
|
|
612
681
|
return errors
|
|
682
|
+
|
|
683
|
+
|
|
684
|
+
|
|
@@ -25,6 +25,7 @@ logger = Logger(service="AcmStack")
|
|
|
25
25
|
|
|
26
26
|
@register_stack("acm_stack")
|
|
27
27
|
@register_stack("certificate_stack")
|
|
28
|
+
@register_stack("certificate_library_module")
|
|
28
29
|
class AcmStack(IStack, StandardizedSsmMixin):
|
|
29
30
|
"""
|
|
30
31
|
Reusable stack for AWS Certificate Manager.
|
|
@@ -152,18 +153,7 @@ class AcmStack(IStack, StandardizedSsmMixin):
|
|
|
152
153
|
|
|
153
154
|
def _add_outputs(self, cert_name: str) -> None:
|
|
154
155
|
"""Add CloudFormation outputs"""
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
description=f"Certificate ARN for {self.acm_config.domain_name}",
|
|
160
|
-
export_name=f"{cert_name}-arn",
|
|
161
|
-
)
|
|
162
|
-
|
|
163
|
-
cdk.CfnOutput(
|
|
164
|
-
self,
|
|
165
|
-
"DomainName",
|
|
166
|
-
value=self.acm_config.domain_name,
|
|
167
|
-
description="Primary domain name for the certificate",
|
|
168
|
-
export_name=f"{cert_name}-domain",
|
|
169
|
-
)
|
|
156
|
+
|
|
157
|
+
return
|
|
158
|
+
|
|
159
|
+
|
|
@@ -744,7 +744,7 @@ class ApiGatewayStack(IStack, StandardizedSsmMixin):
|
|
|
744
744
|
# Setup enhanced SSM integration with proper resource type and name
|
|
745
745
|
api_name = self.api_config.name or "api-gateway"
|
|
746
746
|
|
|
747
|
-
self.
|
|
747
|
+
self.setup_ssm_integration(
|
|
748
748
|
scope=self,
|
|
749
749
|
config=self.stack_config.dictionary.get("api_gateway", {}),
|
|
750
750
|
resource_type="api-gateway",
|
|
@@ -775,7 +775,7 @@ class ApiGatewayStack(IStack, StandardizedSsmMixin):
|
|
|
775
775
|
resource_values["authorizer_id"] = authorizer.authorizer_id
|
|
776
776
|
|
|
777
777
|
# Use enhanced SSM parameter export
|
|
778
|
-
exported_params = self.
|
|
778
|
+
exported_params = self.export_ssm_parameters(resource_values)
|
|
779
779
|
|
|
780
780
|
if exported_params:
|
|
781
781
|
logger.info(
|