cdk-factory 0.15.10__py3-none-any.whl → 0.18.9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of cdk-factory might be problematic. Click here for more details.

Files changed (63) hide show
  1. cdk_factory/configurations/base_config.py +23 -24
  2. cdk_factory/configurations/cdk_config.py +6 -4
  3. cdk_factory/configurations/deployment.py +12 -0
  4. cdk_factory/configurations/devops.py +1 -1
  5. cdk_factory/configurations/pipeline_stage.py +29 -5
  6. cdk_factory/configurations/resources/acm.py +85 -0
  7. cdk_factory/configurations/resources/auto_scaling.py +7 -5
  8. cdk_factory/configurations/resources/cloudfront.py +7 -2
  9. cdk_factory/configurations/resources/ecr.py +1 -1
  10. cdk_factory/configurations/resources/ecs_cluster.py +108 -0
  11. cdk_factory/configurations/resources/ecs_service.py +17 -2
  12. cdk_factory/configurations/resources/load_balancer.py +17 -4
  13. cdk_factory/configurations/resources/monitoring.py +8 -3
  14. cdk_factory/configurations/resources/rds.py +305 -19
  15. cdk_factory/configurations/resources/rum.py +7 -2
  16. cdk_factory/configurations/resources/s3.py +1 -1
  17. cdk_factory/configurations/resources/security_group_full_stack.py +7 -8
  18. cdk_factory/configurations/resources/vpc.py +19 -0
  19. cdk_factory/configurations/workload.py +32 -2
  20. cdk_factory/constructs/ecr/ecr_construct.py +9 -2
  21. cdk_factory/constructs/lambdas/policies/policy_docs.py +4 -4
  22. cdk_factory/interfaces/istack.py +6 -3
  23. cdk_factory/interfaces/networked_stack_mixin.py +75 -0
  24. cdk_factory/interfaces/standardized_ssm_mixin.py +657 -0
  25. cdk_factory/interfaces/vpc_provider_mixin.py +210 -0
  26. cdk_factory/lambdas/edge/ip_gate/handler.py +42 -40
  27. cdk_factory/pipeline/pipeline_factory.py +222 -27
  28. cdk_factory/stack/stack_factory.py +34 -0
  29. cdk_factory/stack_library/__init__.py +3 -2
  30. cdk_factory/stack_library/acm/__init__.py +6 -0
  31. cdk_factory/stack_library/acm/acm_stack.py +169 -0
  32. cdk_factory/stack_library/api_gateway/api_gateway_stack.py +84 -59
  33. cdk_factory/stack_library/auto_scaling/auto_scaling_stack.py +366 -408
  34. cdk_factory/stack_library/code_artifact/code_artifact_stack.py +2 -2
  35. cdk_factory/stack_library/cognito/cognito_stack.py +152 -92
  36. cdk_factory/stack_library/dynamodb/dynamodb_stack.py +19 -15
  37. cdk_factory/stack_library/ecr/ecr_stack.py +2 -2
  38. cdk_factory/stack_library/ecs/__init__.py +12 -0
  39. cdk_factory/stack_library/ecs/ecs_cluster_stack.py +316 -0
  40. cdk_factory/stack_library/ecs/ecs_service_stack.py +20 -39
  41. cdk_factory/stack_library/lambda_edge/lambda_edge_stack.py +2 -2
  42. cdk_factory/stack_library/load_balancer/load_balancer_stack.py +151 -118
  43. cdk_factory/stack_library/rds/rds_stack.py +85 -74
  44. cdk_factory/stack_library/route53/route53_stack.py +8 -3
  45. cdk_factory/stack_library/rum/rum_stack.py +108 -91
  46. cdk_factory/stack_library/security_group/security_group_full_stack.py +9 -22
  47. cdk_factory/stack_library/security_group/security_group_stack.py +11 -11
  48. cdk_factory/stack_library/stack_base.py +5 -0
  49. cdk_factory/stack_library/vpc/vpc_stack.py +272 -124
  50. cdk_factory/stack_library/websites/static_website_stack.py +1 -1
  51. cdk_factory/utilities/api_gateway_integration_utility.py +24 -16
  52. cdk_factory/utilities/environment_services.py +5 -5
  53. cdk_factory/utilities/json_loading_utility.py +12 -3
  54. cdk_factory/validation/config_validator.py +483 -0
  55. cdk_factory/version.py +1 -1
  56. cdk_factory/workload/workload_factory.py +1 -0
  57. {cdk_factory-0.15.10.dist-info → cdk_factory-0.18.9.dist-info}/METADATA +1 -1
  58. {cdk_factory-0.15.10.dist-info → cdk_factory-0.18.9.dist-info}/RECORD +61 -54
  59. cdk_factory/interfaces/enhanced_ssm_parameter_mixin.py +0 -321
  60. cdk_factory/interfaces/ssm_parameter_mixin.py +0 -329
  61. {cdk_factory-0.15.10.dist-info → cdk_factory-0.18.9.dist-info}/WHEEL +0 -0
  62. {cdk_factory-0.15.10.dist-info → cdk_factory-0.18.9.dist-info}/entry_points.txt +0 -0
  63. {cdk_factory-0.15.10.dist-info → cdk_factory-0.18.9.dist-info}/licenses/LICENSE +0 -0
@@ -17,14 +17,19 @@ class BaseConfig:
17
17
  SSM parameter paths can be customized with prefixes and templates at different levels:
18
18
  1. Global level: In the workload or deployment config
19
19
  2. Stack level: In the stack config
20
- 3. Resource level: In the resource config (ssm_exports/ssm_imports)
20
+ 3. Resource level: In the resource config (ssm.exports/ssm.imports)
21
21
 
22
22
  Example configurations:
23
23
  ```json
24
24
  {
25
- "ssm_prefix_template": "/{environment}/{resource_type}/{attribute}",
26
- "ssm_exports": {
27
- "vpc_id_path": "my-vpc-id"
25
+ "ssm": {
26
+ "prefix_template": "/{environment}/{resource_type}/{attribute}",
27
+ "exports": {
28
+ "vpc_id": "my-vpc-id"
29
+ },
30
+ "imports": {
31
+ "security_group_id": "/my-app/security-group/id"
32
+ }
28
33
  }
29
34
  }
30
35
  ```
@@ -57,6 +62,16 @@ class BaseConfig:
57
62
  """
58
63
  return self.__config
59
64
 
65
+ @property
66
+ def ssm(self) -> Dict[str, Any]:
67
+ """
68
+ Get the SSM configuration for this resource.
69
+
70
+ Returns:
71
+ Dictionary containing SSM configuration with imports/exports
72
+ """
73
+ return self.__config.get("ssm", {})
74
+
60
75
  @property
61
76
  def ssm_prefix_template(self) -> str:
62
77
  """
@@ -68,7 +83,7 @@ class BaseConfig:
68
83
  Returns:
69
84
  The SSM parameter prefix template string
70
85
  """
71
- return self.__config.get("ssm_prefix_template", "/{deployment_name}/{resource_type}/{attribute}")
86
+ return self.ssm.get("prefix_template", "/{deployment_name}/{resource_type}/{attribute}")
72
87
 
73
88
  @property
74
89
  def ssm_exports(self) -> Dict[str, str]:
@@ -87,7 +102,7 @@ class BaseConfig:
87
102
  Returns:
88
103
  Dictionary mapping attribute names to SSM parameter paths for export
89
104
  """
90
- return self.__config.get("ssm_exports", {})
105
+ return self.ssm.get("exports", {})
91
106
 
92
107
  @property
93
108
  def ssm_imports(self) -> Dict[str, str]:
@@ -106,25 +121,9 @@ class BaseConfig:
106
121
  Returns:
107
122
  Dictionary mapping attribute names to SSM parameter paths for import
108
123
  """
109
- return self.__config.get("ssm_imports", {})
110
-
111
- @property
112
- def ssm_parameters(self) -> Dict[str, str]:
113
- """
114
- Get all SSM parameter path mappings (both exports and imports).
115
-
116
- This is provided for backward compatibility.
117
- New code should use ssm_exports and ssm_imports instead.
118
-
119
- Returns:
120
- Dictionary mapping attribute names to SSM parameter paths
121
- """
122
- # Merge exports and imports, with exports taking precedence
123
- combined = {**self.ssm_imports, **self.ssm_exports}
124
- # Also include any parameters directly under ssm_parameters for backward compatibility
125
- combined.update(self.__config.get("ssm_parameters", {}))
126
- return combined
124
+ return self.ssm.get("imports", {})
127
125
 
126
+
128
127
  def get(self, key: str, default: Any = None) -> Any:
129
128
  """
130
129
  Get a configuration value by key.
@@ -183,11 +183,13 @@ class CdkConfig:
183
183
  if self._resolved_config_file_path is None:
184
184
  raise ValueError("Config file path is not set")
185
185
 
186
- file_name = f".dynamic_{os.path.basename(self._resolved_config_file_path)}"
186
+ file_name = os.path.join(".dynamic", os.path.basename(self._resolved_config_file_path))
187
187
  path = os.path.join(Path(self._resolved_config_file_path).parent, file_name)
188
-
188
+
189
+ if not os.path.exists(Path(path).parent):
190
+ os.makedirs(Path(path).parent)
189
191
  cdk = config.get("cdk", {})
190
- if replacements and len(replacements) > 0:
192
+ if replacements:
191
193
  config = JsonLoadingUtility.recursive_replace(config, replacements)
192
194
  print(f"📀 Saving config to {path}")
193
195
  # add the original cdk back
@@ -214,7 +216,7 @@ class CdkConfig:
214
216
  value = static_value
215
217
  elif environment_variable_name is not None and not value:
216
218
  value = os.environ.get(environment_variable_name, None)
217
- if value is None and required:
219
+ if (value is None or str(value).strip() == "") and required:
218
220
  raise ValueError(
219
221
  f"Failed to get value for environment variable {environment_variable_name}"
220
222
  )
@@ -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
 
@@ -64,7 +64,7 @@ class DevOps:
64
64
  )
65
65
  if (
66
66
  not self.__code_repository.repository
67
- or len(self.__code_repository.repository) == 0
67
+ or not self.__code_repository.repository
68
68
  ):
69
69
  raise ValueError(
70
70
  "Code Repository is not defined in the configuration "
@@ -117,8 +117,32 @@ class PipelineStageConfig:
117
117
  @property
118
118
  def builds(self) -> List[Dict[str, Any]]:
119
119
  """
120
- Returns the stages for this pipeline
121
- """
122
- builds = self.workload.get("builds", [])
123
-
124
- return builds
120
+ Returns the builds configured for this stage.
121
+
122
+ If the stage has a "builds" array of strings, resolve them to the
123
+ corresponding build objects defined at workload["builds"].
124
+ Otherwise, return an empty list.
125
+ """
126
+ stage_build_refs = self.dictionary.get("builds", [])
127
+ if not stage_build_refs:
128
+ return []
129
+
130
+ workload_builds: List[Dict[str, Any]] = self.workload.get("builds", [])
131
+ by_name: Dict[str, Dict[str, Any]] = {}
132
+ for b in workload_builds:
133
+ name = b.get("name") or b.get("id")
134
+ if name:
135
+ by_name[name] = b
136
+
137
+ resolved: List[Dict[str, Any]] = []
138
+ for ref in stage_build_refs:
139
+ if isinstance(ref, str):
140
+ if ref in by_name:
141
+ resolved.append(by_name[ref])
142
+ else:
143
+ raise ValueError(f"Build '{ref}' referenced by stage '{self.name}' not found in workload.builds")
144
+ elif isinstance(ref, dict):
145
+ # Allow inline build definitions at the stage level as a fallback
146
+ resolved.append(ref)
147
+
148
+ return resolved
@@ -0,0 +1,85 @@
1
+ """
2
+ AcmConfig - supports ACM (AWS Certificate Manager) settings for AWS CDK.
3
+ Maintainers: Eric Wilson
4
+ MIT License. See Project Root for license information.
5
+ """
6
+
7
+ from typing import Any, Dict, List, Optional
8
+
9
+
10
+ class AcmConfig:
11
+ """
12
+ ACM Configuration - supports AWS Certificate Manager settings.
13
+ Each property reads from the config dict and provides a sensible default if not set.
14
+ """
15
+
16
+ def __init__(self, config: dict = None, deployment=None) -> None:
17
+ self.__config = config or {}
18
+ self.__deployment = deployment
19
+
20
+ @property
21
+ def name(self) -> str:
22
+ """Certificate configuration name"""
23
+ return self.__config.get("name", "certificate")
24
+
25
+ @property
26
+ def domain_name(self) -> str:
27
+ """Primary domain name for the certificate"""
28
+ domain = self.__config.get("domain_name")
29
+ if not domain:
30
+ raise ValueError("domain_name is required for ACM certificate")
31
+ return domain
32
+
33
+ @property
34
+ def subject_alternative_names(self) -> List[str]:
35
+ """Subject alternative names (SANs) for the certificate"""
36
+ sans = self.__config.get("subject_alternative_names", [])
37
+ # Also check for alternate_names for backward compatibility
38
+ if not sans:
39
+ sans = self.__config.get("alternate_names", [])
40
+ return sans
41
+
42
+ @property
43
+ def hosted_zone_id(self) -> Optional[str]:
44
+ """Route53 hosted zone ID for DNS validation"""
45
+ return self.__config.get("hosted_zone_id")
46
+
47
+ @property
48
+ def hosted_zone_name(self) -> Optional[str]:
49
+ """Route53 hosted zone name (used for looking up zone)"""
50
+ return self.__config.get("hosted_zone_name")
51
+
52
+ @property
53
+ def validation_method(self) -> str:
54
+ """Certificate validation method (DNS or EMAIL)"""
55
+ return self.__config.get("validation_method", "DNS")
56
+
57
+ @property
58
+ def certificate_transparency_logging_preference(self) -> Optional[str]:
59
+ """Certificate transparency logging preference (ENABLED or DISABLED)"""
60
+ return self.__config.get("certificate_transparency_logging_preference")
61
+
62
+ @property
63
+ def ssm(self) -> Dict[str, Any]:
64
+ """SSM configuration for importing/exporting resources"""
65
+ return self.__config.get("ssm", {})
66
+
67
+ @property
68
+ def ssm_exports(self) -> Dict[str, str]:
69
+ """SSM parameter paths to export certificate details"""
70
+ exports = self.ssm.get("exports", {})
71
+
72
+ # Provide default SSM export path if not specified
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)
76
+ exports = {
77
+ "certificate_arn": f"/{workload_env}/{workload_name}/certificate/arn"
78
+ }
79
+
80
+ return exports
81
+
82
+ @property
83
+ def tags(self) -> Dict[str, str]:
84
+ """Tags to apply to the certificate"""
85
+ return self.__config.get("tags", {})
@@ -70,12 +70,9 @@ 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
- "update_policy",
77
- {"min_instances_in_service": 1, "max_batch_size": 1, "pause_time": 300},
78
- )
75
+ return self.__config.get("update_policy")
79
76
 
80
77
  @property
81
78
  def user_data_commands(self) -> List[str]:
@@ -175,3 +172,8 @@ class AutoScalingConfig(EnhancedBaseConfig):
175
172
  def key_name(self) -> Optional[str]:
176
173
  """EC2 key pair name for SSH access"""
177
174
  return self.__config.get("key_name")
175
+
176
+ @property
177
+ def ssm_imports(self) -> Dict[str, Any]:
178
+ """SSM imports for the Auto Scaling Group"""
179
+ return self.__config.get("ssm", {}).get("imports", {})
@@ -102,15 +102,20 @@ class CloudFrontConfig(EnhancedBaseConfig):
102
102
  """Resource tags"""
103
103
  return self._config.get("tags", {})
104
104
 
105
+ @property
106
+ def ssm(self) -> Dict[str, Any]:
107
+ """SSM configuration"""
108
+ return self._config.get("ssm", {})
109
+
105
110
  @property
106
111
  def ssm_exports(self) -> Dict[str, str]:
107
112
  """SSM parameter exports"""
108
- return self._config.get("ssm_exports", {})
113
+ return self.ssm.get("exports", {})
109
114
 
110
115
  @property
111
116
  def ssm_imports(self) -> Dict[str, str]:
112
117
  """SSM parameter imports"""
113
- return self._config.get("ssm_imports", {})
118
+ return self.ssm.get("imports", {})
114
119
 
115
120
  @property
116
121
  def hosted_zone_id(self) -> str:
@@ -128,7 +128,7 @@ class ECRConfig(EnhancedBaseConfig):
128
128
  Example:
129
129
  {
130
130
  "enabled": true,
131
- "accounts": ["123456789012", "987654321098"],
131
+ "accounts": [os.environ.get("ECR_ALLOWED_ACCOUNT_1"), os.environ.get("ECR_ALLOWED_ACCOUNT_2")],
132
132
  "services": [
133
133
  {
134
134
  "name": "lambda",
@@ -0,0 +1,108 @@
1
+ """
2
+ ECS Cluster Configuration
3
+
4
+ Defines the configuration schema for ECS cluster stacks.
5
+ """
6
+
7
+ from typing import Optional, Dict, Any, List
8
+
9
+
10
+ class EcsClusterConfig:
11
+ """
12
+ Configuration for an ECS cluster.
13
+
14
+ This class defines all the configurable parameters for an ECS cluster,
15
+ providing explicit control over cluster creation and settings.
16
+ """
17
+
18
+ def __init__(self, config: Dict[str, Any]) -> None:
19
+ self._config = config or {}
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
+
26
+ @property
27
+ def name(self) -> str:
28
+ """Name of the ECS cluster. Supports template variables like {{WORKLOAD_NAME}}-{{ENVIRONMENT}}-cluster"""
29
+ return self._config.get("name", "cluster")
30
+
31
+ @property
32
+ def container_insights(self) -> bool:
33
+ """Enable container insights for the cluster"""
34
+ return self._config.get("container_insights", True)
35
+
36
+ @property
37
+ def cluster_settings(self) -> Optional[List[Dict[str, str]]]:
38
+ """Additional cluster settings as name-value pairs"""
39
+ return self._config.get("cluster_settings")
40
+
41
+ @property
42
+ def cloud_map_namespace(self) -> Optional[Dict[str, Any]]:
43
+ """Cloud Map namespace configuration for service discovery"""
44
+ return self._config.get("cloud_map_namespace")
45
+
46
+ @property
47
+ def execute_command_configuration(self) -> Optional[Dict[str, Any]]:
48
+ """Execute command configuration for ECS"""
49
+ return self._config.get("execute_command_configuration")
50
+
51
+ @property
52
+ def vpc_id(self) -> Optional[str]:
53
+ """VPC ID where the cluster should be created"""
54
+ return self._config.get("vpc_id")
55
+
56
+ @property
57
+ def ssm_vpc_id(self) -> Optional[str]:
58
+ """SSM parameter path to import VPC ID"""
59
+ return self._config.get("ssm_vpc_id")
60
+
61
+ @property
62
+ def create_instance_role(self) -> bool:
63
+ """Whether to create an ECS instance role"""
64
+ return self._config.get("create_instance_role", True)
65
+
66
+ @property
67
+ def instance_role_name(self) -> Optional[str]:
68
+ """Custom name for the ECS instance role"""
69
+ return self._config.get("instance_role_name")
70
+
71
+ @property
72
+ def instance_profile_name(self) -> Optional[str]:
73
+ """Custom name for the ECS instance profile"""
74
+ return self._config.get("instance_profile_name")
75
+
76
+ @property
77
+ def managed_policies(self) -> List[str]:
78
+ """List of AWS managed policies to attach to the instance role"""
79
+ return self._config.get("managed_policies", [
80
+ "service-role/AmazonEC2ContainerServiceforEC2Role",
81
+ "AmazonSSMManagedInstanceCore"
82
+ ])
83
+
84
+ @property
85
+ def inline_policies(self) -> Optional[Dict[str, Dict[str, Any]]]:
86
+ """Inline IAM policies to attach to the instance role"""
87
+ return self._config.get("inline_policies")
88
+
89
+ @property
90
+ def export_ssm_parameters(self) -> bool:
91
+ """Whether to export cluster information to SSM parameters"""
92
+ return self._config.get("export_ssm_parameters", True)
93
+
94
+
95
+ @property
96
+ def ssm(self) -> Dict[str, Any]:
97
+ """SSM configuration"""
98
+ return self._config.get("ssm", {})
99
+
100
+ @property
101
+ def ssm_exports(self) -> Dict[str, str]:
102
+ """SSM parameter exports"""
103
+ return self.ssm.get("exports", {})
104
+
105
+ @property
106
+ def ssm_imports(self) -> Dict[str, Any]:
107
+ """SSM parameter imports"""
108
+ return self.ssm.get("imports", {})
@@ -123,10 +123,15 @@ class EcsServiceConfig:
123
123
  """Resource tags"""
124
124
  return self._config.get("tags", {})
125
125
 
126
+ @property
127
+ def ssm(self) -> Dict[str, Any]:
128
+ """SSM configuration"""
129
+ return self._config.get("ssm", {})
130
+
126
131
  @property
127
132
  def ssm_exports(self) -> Dict[str, str]:
128
133
  """SSM parameter exports"""
129
- return self._config.get("ssm_exports", {})
134
+ return self.ssm.get("exports", {})
130
135
 
131
136
  @property
132
137
  def ssm_imports(self) -> Dict[str, Any]:
@@ -134,13 +139,23 @@ class EcsServiceConfig:
134
139
  # Check both nested and flat structures for backwards compatibility
135
140
  if "ssm" in self._config and "imports" in self._config["ssm"]:
136
141
  return self._config["ssm"]["imports"]
137
- return self._config.get("ssm_imports", {})
142
+ return self.ssm.get("imports", {})
138
143
 
139
144
  @property
140
145
  def deployment_type(self) -> str:
141
146
  """Deployment type: production, maintenance, or blue-green"""
142
147
  return self._config.get("deployment_type", "production")
143
148
 
149
+ @property
150
+ def deployment_circuit_breaker(self) -> Dict[str, Any]:
151
+ """Deployment circuit breaker configuration"""
152
+ return self._config.get("deployment_circuit_breaker", {})
153
+
154
+ @property
155
+ def deployment_configuration(self) -> Dict[str, Any]:
156
+ """Deployment configuration (maximum_percent, minimum_healthy_percent)"""
157
+ return self._config.get("deployment_configuration", {})
158
+
144
159
  @property
145
160
  def is_maintenance_mode(self) -> bool:
146
161
  """Whether this is a maintenance mode deployment"""
@@ -144,10 +144,23 @@ class LoadBalancerConfig(EnhancedBaseConfig):
144
144
  "block_response", default_response
145
145
  )
146
146
 
147
+ @property
148
+ def ssm(self) -> Dict[str, Any]:
149
+ """SSM configuration"""
150
+ return self.__config.get("ssm", {})
151
+
147
152
  @property
148
153
  def ssm_imports(self) -> Dict[str, Any]:
149
154
  """SSM parameter imports for the Load Balancer"""
150
- # Check both nested and flat structures for backwards compatibility
151
- if "ssm" in self.__config and "imports" in self.__config["ssm"]:
152
- return self.__config["ssm"]["imports"]
153
- return self.__config.get("ssm_imports", {})
155
+ return self.ssm.get("imports", {})
156
+
157
+ @property
158
+ def ssm_exports(self) -> Dict[str, Any]:
159
+ """SSM parameter exports for the Load Balancer"""
160
+ return self.ssm.get("exports", {})
161
+
162
+ @property
163
+ def ssm_parameters(self) -> Dict[str, Any]:
164
+ """SSM parameters for the Load Balancer (only exports, not imports)"""
165
+ # For LoadBalancer, only return exports to prevent trying to export imported values
166
+ return self.ssm_exports
@@ -18,7 +18,7 @@ class MonitoringConfig(EnhancedBaseConfig):
18
18
  super().__init__(
19
19
  config or {},
20
20
  resource_type="monitoring",
21
- resource_name=config.get("name", "monitoring") if config else "monitoring"
21
+ resource_name=config.get("name", "monitoring") if config else "monitoring",
22
22
  )
23
23
  self._config = config or {}
24
24
  self._deployment = deployment
@@ -63,12 +63,17 @@ class MonitoringConfig(EnhancedBaseConfig):
63
63
  """Resource tags"""
64
64
  return self._config.get("tags", {})
65
65
 
66
+ @property
67
+ def ssm(self) -> Dict[str, Any]:
68
+ """SSM configuration"""
69
+ return self._config.get("ssm", {})
70
+
66
71
  @property
67
72
  def ssm_exports(self) -> Dict[str, str]:
68
73
  """SSM parameter exports"""
69
- return self._config.get("ssm_exports", {})
74
+ return self.ssm.get("exports", {})
70
75
 
71
76
  @property
72
77
  def ssm_imports(self) -> Dict[str, str]:
73
78
  """SSM parameter imports for resource ARNs"""
74
- return self._config.get("ssm_imports", {})
79
+ return self.ssm.get("imports", {})