cdk-factory 0.16.15__py3-none-any.whl → 0.20.0__py3-none-any.whl

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

Potentially problematic release.


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

Files changed (66) hide show
  1. cdk_factory/configurations/base_config.py +23 -24
  2. cdk_factory/configurations/cdk_config.py +1 -1
  3. cdk_factory/configurations/deployment.py +12 -0
  4. cdk_factory/configurations/devops.py +1 -1
  5. cdk_factory/configurations/resources/acm.py +9 -2
  6. cdk_factory/configurations/resources/auto_scaling.py +7 -5
  7. cdk_factory/configurations/resources/cloudfront.py +7 -2
  8. cdk_factory/configurations/resources/ecr.py +1 -1
  9. cdk_factory/configurations/resources/ecs_cluster.py +12 -5
  10. cdk_factory/configurations/resources/ecs_service.py +30 -3
  11. cdk_factory/configurations/resources/lambda_edge.py +18 -4
  12. cdk_factory/configurations/resources/load_balancer.py +8 -9
  13. cdk_factory/configurations/resources/monitoring.py +8 -3
  14. cdk_factory/configurations/resources/rds.py +8 -9
  15. cdk_factory/configurations/resources/route53.py +5 -0
  16. cdk_factory/configurations/resources/rum.py +7 -2
  17. cdk_factory/configurations/resources/s3.py +10 -2
  18. cdk_factory/configurations/resources/security_group_full_stack.py +7 -8
  19. cdk_factory/configurations/resources/vpc.py +19 -0
  20. cdk_factory/configurations/workload.py +32 -2
  21. cdk_factory/constructs/cloudfront/cloudfront_distribution_construct.py +1 -1
  22. cdk_factory/constructs/ecr/ecr_construct.py +9 -2
  23. cdk_factory/constructs/lambdas/policies/policy_docs.py +4 -4
  24. cdk_factory/interfaces/istack.py +4 -4
  25. cdk_factory/interfaces/networked_stack_mixin.py +6 -6
  26. cdk_factory/interfaces/standardized_ssm_mixin.py +684 -0
  27. cdk_factory/interfaces/vpc_provider_mixin.py +64 -33
  28. cdk_factory/lambdas/edge/ip_gate/handler.py +42 -40
  29. cdk_factory/pipeline/pipeline_factory.py +3 -3
  30. cdk_factory/stack_library/__init__.py +3 -2
  31. cdk_factory/stack_library/acm/acm_stack.py +7 -17
  32. cdk_factory/stack_library/api_gateway/api_gateway_stack.py +84 -59
  33. cdk_factory/stack_library/auto_scaling/auto_scaling_stack.py +454 -537
  34. cdk_factory/stack_library/cloudfront/cloudfront_stack.py +76 -22
  35. cdk_factory/stack_library/code_artifact/code_artifact_stack.py +5 -27
  36. cdk_factory/stack_library/cognito/cognito_stack.py +152 -92
  37. cdk_factory/stack_library/dynamodb/dynamodb_stack.py +19 -15
  38. cdk_factory/stack_library/ecr/ecr_stack.py +2 -2
  39. cdk_factory/stack_library/ecs/__init__.py +1 -3
  40. cdk_factory/stack_library/ecs/ecs_cluster_stack.py +159 -75
  41. cdk_factory/stack_library/ecs/ecs_service_stack.py +59 -52
  42. cdk_factory/stack_library/lambda_edge/EDGE_LOG_RETENTION_TODO.md +226 -0
  43. cdk_factory/stack_library/lambda_edge/LAMBDA_EDGE_LOG_RETENTION_BLOG.md +215 -0
  44. cdk_factory/stack_library/lambda_edge/lambda_edge_stack.py +240 -83
  45. cdk_factory/stack_library/load_balancer/load_balancer_stack.py +139 -212
  46. cdk_factory/stack_library/rds/rds_stack.py +74 -98
  47. cdk_factory/stack_library/route53/route53_stack.py +246 -40
  48. cdk_factory/stack_library/rum/rum_stack.py +108 -91
  49. cdk_factory/stack_library/security_group/security_group_full_stack.py +10 -53
  50. cdk_factory/stack_library/security_group/security_group_stack.py +12 -19
  51. cdk_factory/stack_library/simple_queue_service/sqs_stack.py +1 -34
  52. cdk_factory/stack_library/stack_base.py +5 -0
  53. cdk_factory/stack_library/vpc/vpc_stack.py +171 -130
  54. cdk_factory/stack_library/websites/static_website_stack.py +7 -3
  55. cdk_factory/utilities/api_gateway_integration_utility.py +24 -16
  56. cdk_factory/utilities/environment_services.py +5 -5
  57. cdk_factory/utilities/json_loading_utility.py +1 -1
  58. cdk_factory/validation/config_validator.py +483 -0
  59. cdk_factory/version.py +1 -1
  60. {cdk_factory-0.16.15.dist-info → cdk_factory-0.20.0.dist-info}/METADATA +1 -1
  61. {cdk_factory-0.16.15.dist-info → cdk_factory-0.20.0.dist-info}/RECORD +64 -62
  62. cdk_factory/interfaces/enhanced_ssm_parameter_mixin.py +0 -321
  63. cdk_factory/interfaces/ssm_parameter_mixin.py +0 -454
  64. {cdk_factory-0.16.15.dist-info → cdk_factory-0.20.0.dist-info}/WHEEL +0 -0
  65. {cdk_factory-0.16.15.dist-info → cdk_factory-0.20.0.dist-info}/entry_points.txt +0 -0
  66. {cdk_factory-0.16.15.dist-info → cdk_factory-0.20.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,5 +1,5 @@
1
1
  """
2
- VPC Stack Pattern for CDK-Factory
2
+ VPC Stack Pattern for CDK-Factory (Standardized SSM Version)
3
3
  Maintainers: Eric Wilson
4
4
  MIT License. See Project Root for the license information.
5
5
  """
@@ -15,23 +15,35 @@ from cdk_factory.configurations.deployment import DeploymentConfig
15
15
  from cdk_factory.configurations.stack import StackConfig
16
16
  from cdk_factory.configurations.resources.vpc import VpcConfig
17
17
  from cdk_factory.interfaces.istack import IStack
18
- from cdk_factory.interfaces.enhanced_ssm_parameter_mixin import EnhancedSsmParameterMixin
18
+ from cdk_factory.interfaces.standardized_ssm_mixin import StandardizedSsmMixin
19
19
  from cdk_factory.stack.stack_module_registry import register_stack
20
20
  from cdk_factory.workload.workload_factory import WorkloadConfig
21
21
 
22
- logger = Logger(service="VpcStack")
22
+ logger = Logger(service="VpcStackStandardized")
23
23
 
24
24
 
25
25
  @register_stack("vpc_library_module")
26
26
  @register_stack("vpc_stack")
27
- class VpcStack(IStack, EnhancedSsmParameterMixin):
27
+ class VpcStack(IStack, StandardizedSsmMixin):
28
28
  """
29
- Reusable stack for AWS VPC.
30
- Supports creating VPCs with customizable CIDR blocks, subnets, and networking components.
29
+ Reusable stack for AWS VPC with standardized SSM integration.
30
+
31
+ This version uses the StandardizedSsmMixin to provide consistent SSM parameter
32
+ handling across all CDK Factory modules.
33
+
34
+ Key Features:
35
+ - Standardized SSM import/export patterns
36
+ - Template variable resolution
37
+ - Comprehensive validation
38
+ - Clear error handling
39
+ - Backward compatibility
31
40
  """
32
41
 
33
42
  def __init__(self, scope: Construct, id: str, **kwargs) -> None:
43
+ # Initialize parent classes properly
34
44
  super().__init__(scope, id, **kwargs)
45
+
46
+ # Initialize module attributes
35
47
  self.vpc_config = None
36
48
  self.stack_config = None
37
49
  self.deployment = None
@@ -61,15 +73,21 @@ class VpcStack(IStack, EnhancedSsmParameterMixin):
61
73
  self.vpc_config = VpcConfig(stack_config.dictionary.get("vpc", {}), deployment)
62
74
  vpc_name = deployment.build_resource_name(self.vpc_config.name)
63
75
 
64
- # Setup enhanced SSM integration
65
- self.setup_enhanced_ssm_integration(self, self.vpc_config)
76
+ # Setup standardized SSM integration
77
+ self.setup_ssm_integration(
78
+ scope=self,
79
+ config=self.vpc_config,
80
+ resource_type="vpc",
81
+ resource_name=vpc_name,
82
+ deployment=deployment,
83
+ workload=workload
84
+ )
85
+
86
+ # Process SSM imports using standardized method
87
+ self.process_ssm_imports()
66
88
 
67
89
  # Import any required resources from SSM
68
- imported_resources = self.auto_import_resources({
69
- "deployment_name": deployment.name,
70
- "environment": deployment.environment,
71
- "workload_name": workload.name
72
- })
90
+ imported_resources = self.get_all_ssm_imports()
73
91
 
74
92
  if imported_resources:
75
93
  logger.info(f"Imported resources from SSM: {list(imported_resources.keys())}")
@@ -79,6 +97,11 @@ class VpcStack(IStack, EnhancedSsmParameterMixin):
79
97
 
80
98
  # Add outputs
81
99
  self._add_outputs(vpc_name)
100
+
101
+ # Export SSM parameters
102
+ self._export_ssm_parameters()
103
+
104
+ logger.info(f"VPC {vpc_name} built successfully")
82
105
 
83
106
  def _create_vpc(self, vpc_name: str) -> ec2.Vpc:
84
107
  """Create a VPC with the specified configuration"""
@@ -97,23 +120,25 @@ class VpcStack(IStack, EnhancedSsmParameterMixin):
97
120
  # Explicitly list AZs for the region to avoid dummy values
98
121
  max_azs = self.vpc_config.max_azs or 2
99
122
  if region == "us-east-1":
100
- availability_zones = [f"us-east-1{chr(97+i)}" for i in range(max_azs)] # us-east-1a, us-east-1b, etc.
123
+ availability_zones = ["us-east-1a", "us-east-1b", "us-east-1c"][:max_azs]
101
124
  elif region == "us-east-2":
102
- availability_zones = [f"us-east-2{chr(97+i)}" for i in range(max_azs)]
125
+ availability_zones = ["us-east-2a", "us-east-2b", "us-east-2c"][:max_azs]
103
126
  elif region == "us-west-1":
104
- availability_zones = [f"us-west-1{chr(97+i)}" for i in range(max_azs)]
127
+ availability_zones = ["us-west-1a", "us-west-1c"][:max_azs]
105
128
  elif region == "us-west-2":
106
- availability_zones = [f"us-west-2{chr(97+i)}" for i in range(max_azs)]
107
-
129
+ availability_zones = ["us-west-2a", "us-west-2b", "us-west-2c"][:max_azs]
130
+
108
131
  # Build VPC properties
109
- # Note: CDK doesn't allow both 'availability_zones' and 'max_azs' - use one or the other
110
132
  vpc_props = {
111
133
  "vpc_name": vpc_name,
112
- "cidr": self.vpc_config.cidr,
134
+ "ip_addresses": ec2.IpAddresses.cidr(self.vpc_config.cidr),
113
135
  "nat_gateways": nat_gateway_count,
114
136
  "subnet_configuration": subnet_configuration,
115
137
  "enable_dns_hostnames": self.vpc_config.enable_dns_hostnames,
116
138
  "enable_dns_support": self.vpc_config.enable_dns_support,
139
+ "max_azs": self.vpc_config.max_azs if not availability_zones else None,
140
+ "availability_zones": availability_zones, # Use explicit AZs when available
141
+ "restrict_default_security_group": self.vpc_config.get("restrict_default_security_group", False),
117
142
  "gateway_endpoints": (
118
143
  {
119
144
  "S3": ec2.GatewayVpcEndpointOptions(
@@ -125,15 +150,19 @@ class VpcStack(IStack, EnhancedSsmParameterMixin):
125
150
  ),
126
151
  }
127
152
 
128
- # Use either availability_zones or max_azs, not both
129
- if availability_zones:
130
- vpc_props["availability_zones"] = availability_zones
131
- else:
132
- vpc_props["max_azs"] = self.vpc_config.max_azs
133
-
134
153
  # Create the VPC
135
154
  vpc = ec2.Vpc(self, vpc_name, **vpc_props)
136
155
 
156
+ # Add IAM permissions for default security group restriction if enabled
157
+ if self.vpc_config.get("restrict_default_security_group", False):
158
+ self._add_default_sg_restriction_permissions(vpc)
159
+ else:
160
+ # Note: When disabling, existing restrictions remain
161
+ # This is AWS CDK's behavior - custom resources clean up themselves,
162
+ # but security group rules they created persist
163
+ # Users can manually clean up if needed via AWS Console
164
+ pass
165
+
137
166
  # Add interface endpoints if specified
138
167
  if self.vpc_config.enable_interface_endpoints:
139
168
  self._add_interface_endpoints(vpc, self.vpc_config.interface_endpoints)
@@ -147,152 +176,164 @@ class VpcStack(IStack, EnhancedSsmParameterMixin):
147
176
  def _get_subnet_configuration(self) -> List[ec2.SubnetConfiguration]:
148
177
  """Configure the subnets for the VPC"""
149
178
  subnet_configs = []
150
-
179
+
151
180
  # Public subnets
152
- if self.vpc_config.public_subnets:
181
+ if self.vpc_config.subnets.get("public", {}).get("enabled", True):
182
+ public_config = self.vpc_config.subnets["public"]
153
183
  subnet_configs.append(
154
184
  ec2.SubnetConfiguration(
155
185
  name=self.vpc_config.public_subnet_name,
156
186
  subnet_type=ec2.SubnetType.PUBLIC,
157
- cidr_mask=self.vpc_config.public_subnet_mask,
187
+ cidr_mask=public_config.get("cidr_mask", 24),
188
+ map_public_ip_on_launch=public_config.get("map_public_ip", True),
158
189
  )
159
190
  )
160
-
191
+
161
192
  # Private subnets
162
- if self.vpc_config.private_subnets:
193
+ if self.vpc_config.subnets.get("private", {}).get("enabled", True):
194
+ private_config = self.vpc_config.subnets["private"]
163
195
  subnet_configs.append(
164
196
  ec2.SubnetConfiguration(
165
197
  name=self.vpc_config.private_subnet_name,
166
198
  subnet_type=ec2.SubnetType.PRIVATE_WITH_EGRESS,
167
- cidr_mask=self.vpc_config.private_subnet_mask,
199
+ cidr_mask=private_config.get("cidr_mask", 24),
168
200
  )
169
201
  )
170
-
202
+
171
203
  # Isolated subnets
172
- if self.vpc_config.isolated_subnets:
204
+ if self.vpc_config.subnets.get("isolated", {}).get("enabled", False):
205
+ isolated_config = self.vpc_config.subnets["isolated"]
173
206
  subnet_configs.append(
174
207
  ec2.SubnetConfiguration(
175
208
  name=self.vpc_config.isolated_subnet_name,
176
209
  subnet_type=ec2.SubnetType.PRIVATE_ISOLATED,
177
- cidr_mask=self.vpc_config.isolated_subnet_mask,
210
+ cidr_mask=isolated_config.get("cidr_mask", 24),
178
211
  )
179
212
  )
180
-
213
+
181
214
  return subnet_configs
182
215
 
183
- def _get_nat_gateway_configuration(self) -> Dict[str, Any]:
184
- """Configure NAT gateways for the VPC"""
185
- return self.vpc_config.nat_gateways
186
-
187
216
  def _add_interface_endpoints(self, vpc: ec2.Vpc, endpoints: List[str]) -> None:
188
- """Add interface endpoints to the VPC"""
189
- # Common interface endpoints
217
+ """Add VPC interface endpoints"""
190
218
  endpoint_services = {
191
219
  "ecr.api": ec2.InterfaceVpcEndpointAwsService.ECR,
192
220
  "ecr.dkr": ec2.InterfaceVpcEndpointAwsService.ECR_DOCKER,
193
- "logs": ec2.InterfaceVpcEndpointAwsService.CLOUDWATCH_LOGS,
194
- "ssm": ec2.InterfaceVpcEndpointAwsService.SSM,
221
+ "ec2": ec2.InterfaceVpcEndpointAwsService.EC2,
222
+ "ecs": ec2.InterfaceVpcEndpointAwsService.ECS,
223
+ "lambda": ec2.InterfaceVpcEndpointAwsService.LAMBDA,
195
224
  "secretsmanager": ec2.InterfaceVpcEndpointAwsService.SECRETS_MANAGER,
196
- "lambda": ec2.InterfaceVpcEndpointAwsService.LAMBDA_,
197
- "sts": ec2.InterfaceVpcEndpointAwsService.STS,
225
+ "ssm": ec2.InterfaceVpcEndpointAwsService.SSM,
226
+ "kms": ec2.InterfaceVpcEndpointAwsService.KMS,
198
227
  }
199
-
200
- # Add specified endpoints
201
- for endpoint in endpoints:
202
- if endpoint in endpoint_services:
228
+
229
+ for endpoint_name in endpoints:
230
+ if endpoint_name in endpoint_services:
203
231
  vpc.add_interface_endpoint(
204
- f"{endpoint}-endpoint", service=endpoint_services[endpoint]
232
+ f"{endpoint_name}-endpoint",
233
+ service=endpoint_services[endpoint_name],
234
+ subnets=ec2.SubnetSelection(
235
+ subnet_type=ec2.SubnetType.PRIVATE_WITH_EGRESS
236
+ ),
205
237
  )
238
+ logger.info(f"Added interface endpoint: {endpoint_name}")
206
239
  else:
207
- logger.warning(f"Unsupported interface endpoint: {endpoint}")
240
+ logger.warning(f"Unknown interface endpoint: {endpoint_name}")
208
241
 
209
242
  def _add_outputs(self, vpc_name: str) -> None:
210
243
  """Add CloudFormation outputs for the VPC"""
211
- if self.vpc:
212
- cdk.CfnOutput(
213
- self,
214
- f"{vpc_name}-id",
215
- value=self.vpc.vpc_id,
216
- export_name=f"{self.deployment.build_resource_name(vpc_name)}-id",
217
- )
218
-
219
- cdk.CfnOutput(
220
- self,
221
- f"{vpc_name}-public-subnets",
222
- value=",".join(
223
- [subnet.subnet_id for subnet in self.vpc.public_subnets]
224
- ),
225
- export_name=f"{self.deployment.build_resource_name(vpc_name)}-public-subnets",
226
- )
227
-
228
- if self.vpc.private_subnets:
229
- cdk.CfnOutput(
230
- self,
231
- f"{vpc_name}-private-subnets",
232
- value=",".join(
233
- [subnet.subnet_id for subnet in self.vpc.private_subnets]
234
- ),
235
- export_name=f"{self.deployment.build_resource_name(vpc_name)}-private-subnets",
236
- )
244
+ return
245
+
237
246
 
238
- if hasattr(self.vpc, "isolated_subnets") and self.vpc.isolated_subnets:
239
- cdk.CfnOutput(
240
- self,
241
- f"{vpc_name}-isolated-subnets",
242
- value=",".join(
243
- [subnet.subnet_id for subnet in self.vpc.isolated_subnets]
244
- ),
245
- export_name=f"{self.deployment.build_resource_name(vpc_name)}-isolated-subnets",
246
- )
247
-
248
- # Export SSM parameters if configured
249
- self._export_ssm_parameters(vpc_name)
250
-
251
- def _export_ssm_parameters(self, vpc_name: str) -> None:
252
- """Export VPC resources to SSM Parameter Store using enhanced auto-export"""
247
+ def _export_ssm_parameters(self) -> None:
248
+ """Export SSM parameters using standardized approach"""
253
249
  if not self.vpc:
250
+ logger.warning("No VPC to export")
254
251
  return
255
252
 
256
- # Create a dictionary of VPC resources to export
257
- vpc_resources = {
253
+ # Prepare resource values for export
254
+ resource_values = {
258
255
  "vpc_id": self.vpc.vpc_id,
259
- "vpc_cidr": self.vpc.vpc_cidr_block,
256
+ "public_subnet_ids": ",".join([subnet.subnet_id for subnet in self.vpc.public_subnets]) if self.vpc.public_subnets else "",
257
+ "private_subnet_ids": ",".join([subnet.subnet_id for subnet in self.vpc.private_subnets]) if self.vpc.private_subnets else "",
258
+ "isolated_subnet_ids": ",".join([subnet.subnet_id for subnet in self.vpc.isolated_subnets]) if self.vpc.isolated_subnets else "",
260
259
  }
260
+
261
+ # Add route table IDs if available - commented out due to CDK API issues
262
+ # public_route_table_ids = []
263
+ # if self.vpc.public_subnets:
264
+ # for subnet in self.vpc.public_subnets:
265
+ # # Access route table through the subnet's route table association
266
+ # for association in subnet.node.children:
267
+ # if hasattr(association, 'route_table_id') and association.route_table_id:
268
+ # public_route_table_ids.append(association.route_table_id)
269
+ #
270
+ # if public_route_table_ids:
271
+ # resource_values["public_route_table_ids"] = public_route_table_ids
272
+ #
273
+ # private_route_table_ids = []
274
+ # if self.vpc.private_subnets:
275
+ # for subnet in self.vpc.private_subnets:
276
+ # # Access route table through the subnet's route table association
277
+ # for association in subnet.node.children:
278
+ # if hasattr(association, 'route_table_id') and association.route_table_id:
279
+ # private_route_table_ids.append(association.route_table_id)
280
+ #
281
+ # if private_route_table_ids:
282
+ # resource_values["private_route_table_ids"] = private_route_table_ids
283
+
284
+ # Add NAT Gateway IDs if available - simplified to avoid None values
285
+ nat_gateway_ids = []
286
+ for subnet in self.vpc.public_subnets:
287
+ if hasattr(subnet, 'node') and subnet.node:
288
+ for child in subnet.node.children:
289
+ if hasattr(child, 'nat_gateway_id') and child.nat_gateway_id:
290
+ nat_gateway_ids.append(child.nat_gateway_id)
291
+ if nat_gateway_ids:
292
+ resource_values["nat_gateway_ids"] = ",".join(nat_gateway_ids)
293
+
294
+ # Add Internet Gateway ID if available
295
+ if hasattr(self.vpc, 'internet_gateway_id') and self.vpc.internet_gateway_id:
296
+ resource_values["internet_gateway_id"] = self.vpc.internet_gateway_id
261
297
 
262
- # Add subnet IDs as comma-separated lists
263
- if self.vpc.public_subnets:
264
- vpc_resources["public_subnet_ids"] = ",".join(
265
- [subnet.subnet_id for subnet in self.vpc.public_subnets]
266
- )
267
-
268
- if self.vpc.private_subnets:
269
- vpc_resources["private_subnet_ids"] = ",".join(
270
- [subnet.subnet_id for subnet in self.vpc.private_subnets]
271
- )
272
-
273
- if hasattr(self.vpc, "isolated_subnets") and self.vpc.isolated_subnets:
274
- vpc_resources["isolated_subnet_ids"] = ",".join(
275
- [subnet.subnet_id for subnet in self.vpc.isolated_subnets]
276
- )
298
+ # Export using standardized SSM mixin
299
+ exported_params = self.export_ssm_parameters(resource_values)
300
+
301
+ logger.info(f"Exported SSM parameters: {exported_params}")
277
302
 
278
- # Use enhanced auto-export with context
279
- context = {
280
- "deployment_name": self.deployment.name,
281
- "environment": self.deployment.environment,
282
- "workload_name": self.workload.name
283
- }
303
+ def _add_default_sg_restriction_permissions(self, vpc: ec2.Vpc) -> None:
304
+ """
305
+ Add IAM permissions required for default security group restriction.
284
306
 
285
- exported_params = self.auto_export_resources(vpc_resources, context)
307
+ CDK creates a custom resource that needs ec2:AuthorizeSecurityGroupIngress
308
+ permission to restrict the default security group.
309
+ """
310
+ from aws_cdk import aws_iam as iam
286
311
 
287
- if exported_params:
288
- logger.info(f"Auto-exported VPC resources to SSM: {list(exported_params.keys())}")
289
- else:
290
- # Fall back to legacy method for backward compatibility
291
- self.export_resource_to_ssm(
292
- scope=self,
293
- resource_values=vpc_resources,
294
- config=self.vpc_config,
295
- resource_name=vpc_name,
296
- resource_type="vpc",
297
- context=context
298
- )
312
+ # Find the custom resource role that CDK creates for default SG restriction
313
+ # The role follows a naming pattern: {VpcName}-CustomVpcRestrictDefaultSGCustomResource*
314
+
315
+ # Grant the required permissions to all roles in this stack that might need it
316
+ # This is a broad approach since we can't easily predict the exact role name
317
+ for child in self.node.children:
318
+ if hasattr(child, 'role') and hasattr(child.role, 'add_to_policy'):
319
+ child.role.add_to_policy(iam.PolicyStatement(
320
+ actions=[
321
+ "ec2:AuthorizeSecurityGroupIngress",
322
+ "ec2:RevokeSecurityGroupIngress",
323
+ "ec2:UpdateSecurityGroupRuleDescriptionsIngress"
324
+ ],
325
+ resources=[vpc.vpc_default_security_group.security_group_arn]
326
+ ))
327
+
328
+ # Backward compatibility methods
329
+ def auto_export_resources(self, resource_values: Dict[str, Any], context: Dict[str, Any] = None) -> Dict[str, str]:
330
+ """Backward compatibility method for existing modules."""
331
+ return self.export_ssm_parameters(resource_values)
332
+
333
+ def auto_import_resources(self, context: Dict[str, Any] = None) -> Dict[str, Any]:
334
+ """Backward compatibility method for existing modules."""
335
+ return self.get_all_ssm_imports()
336
+
337
+
338
+ # Backward compatibility alias
339
+ VpcStackStandardized = VpcStack
@@ -113,12 +113,16 @@ class StaticWebSiteStack(IStack):
113
113
  self, stack_config: StackConfig, workload: WorkloadConfig
114
114
  ) -> str:
115
115
  source = stack_config.dictionary.get("src", {}).get("path")
116
+ if not source:
117
+ raise ValueError("Source path is required for static website stack")
116
118
  for base in workload.paths:
117
- candidate = Path(os.path.join(Path(base), source)).resolve()
119
+ if base is None:
120
+ continue
121
+ candidate = Path(os.path.join(str(Path(base)), source)).resolve()
118
122
 
119
123
  if candidate.exists():
120
124
  return str(candidate)
121
- raise ValueError(f"Could not find the source path: {source}")
125
+ raise ValueError(f"Could not find the source path for static site: {source}")
122
126
 
123
127
  def __setup_cloudfront_distribution(
124
128
  self,
@@ -225,7 +229,7 @@ class StaticWebSiteStack(IStack):
225
229
  bucket: The S3 bucket
226
230
  cloudfront_distribution: The CloudFront distribution construct
227
231
  """
228
- ssm_exports = stack_config.dictionary.get("ssm_exports", {})
232
+ ssm_exports = stack_config.dictionary.get("ssm", {}).get("exports", {})
229
233
 
230
234
  if not ssm_exports:
231
235
  logger.debug("No SSM exports configured for this stack")
@@ -537,34 +537,42 @@ class ApiGatewayIntegrationUtility:
537
537
 
538
538
  if ssm_path:
539
539
  # Use enhanced SSM parameter import with auto-discovery support
540
- from cdk_factory.interfaces.enhanced_ssm_parameter_mixin import (
541
- EnhancedSsmParameterMixin,
540
+ from cdk_factory.interfaces.standardized_ssm_mixin import (
541
+ StandardizedSsmMixin,
542
542
  )
543
543
 
544
- ssm_mixin = EnhancedSsmParameterMixin()
544
+ ssm_mixin = StandardizedSsmMixin()
545
545
 
546
546
  # Setup enhanced SSM integration for auto-import
547
547
  # Use "user-pool" as resource identifier for SSM paths to match cognito exports
548
- ssm_mixin.setup_enhanced_ssm_integration(
548
+ api_gateway_config = stack_config.dictionary.get("api_gateway", {}).copy()
549
+
550
+ # Configure SSM imports for auto-discovery
551
+ if ssm_path == "auto":
552
+ if "ssm" not in api_gateway_config:
553
+ api_gateway_config["ssm"] = {}
554
+ if "imports" not in api_gateway_config["ssm"]:
555
+ api_gateway_config["ssm"]["imports"] = {}
556
+ api_gateway_config["ssm"]["imports"]["user_pool_arn"] = "/{{ORGANIZATION}}/{{ENVIRONMENT}}/cognito/user-pool/arn"
557
+
558
+ ssm_mixin.setup_ssm_integration(
549
559
  scope=self.scope,
550
- config=stack_config.dictionary.get("api_gateway", {}),
560
+ config=api_gateway_config,
551
561
  resource_type="cognito",
552
562
  resource_name="user-pool",
553
563
  )
554
564
 
555
- # If ssm_path is "auto", use auto-import mechanism
565
+ # Get user pool ARN using new pattern - read directly from config
556
566
  if ssm_path == "auto":
557
567
  logger.info("Using auto-import for user pool ARN")
558
- imported_values = ssm_mixin.auto_import_resources()
559
- user_pool_arn = imported_values.get("user_pool_arn")
568
+ ssm_imports = api_gateway_config.get("ssm", {}).get("imports", {})
569
+ user_pool_arn = ssm_imports.get("user_pool_arn")
560
570
  else:
561
571
  # Use direct parameter import for specific SSM path
562
572
  logger.info(
563
573
  f"Looking up user pool ARN from SSM parameter: {ssm_path}"
564
574
  )
565
- user_pool_arn = ssm_mixin._import_enhanced_ssm_parameter(
566
- ssm_path, "user_pool_arn"
567
- )
575
+ user_pool_arn = ssm_mixin._resolve_single_ssm_import(ssm_path, "user_pool_arn")
568
576
 
569
577
  # Extract user pool ID from ARN if we have it
570
578
  if user_pool_arn and not user_pool_id:
@@ -866,15 +874,15 @@ class ApiGatewayIntegrationUtility:
866
874
 
867
875
  if ssm_config.get("enabled", False):
868
876
  try:
869
- from cdk_factory.interfaces.enhanced_ssm_parameter_mixin import (
870
- EnhancedSsmParameterMixin,
877
+ from cdk_factory.interfaces.standardized_ssm_mixin import (
878
+ StandardizedSsmMixin,
871
879
  )
872
880
 
873
- ssm_mixin = EnhancedSsmParameterMixin()
881
+ ssm_mixin = StandardizedSsmMixin()
874
882
 
875
883
  # Setup enhanced SSM integration for auto-import
876
884
  # Use consistent resource name for cross-stack compatibility
877
- ssm_mixin.setup_enhanced_ssm_integration(
885
+ ssm_mixin.setup_ssm_integration(
878
886
  scope=self.scope,
879
887
  config=api_gateway_config,
880
888
  resource_type="api-gateway",
@@ -900,7 +908,7 @@ class ApiGatewayIntegrationUtility:
900
908
  logger.info(
901
909
  f"Looking up authorizer ID from SSM parameter: {import_value}"
902
910
  )
903
- authorizer_id = ssm_mixin._import_enhanced_ssm_parameter(
911
+ authorizer_id = ssm_mixin._resolve_single_ssm_import(
904
912
  import_value, "authorizer_id"
905
913
  )
906
914
  if authorizer_id:
@@ -23,9 +23,9 @@ logger = Logger(__name__)
23
23
 
24
24
  class EnvironmentVariables:
25
25
  """
26
- Easy access to allo the environment variables we use in the appliction.
27
- It's a best practice to use this vs doing and os.getevn in each application.
28
- This helps us track all the enviroment variables in use
26
+ Easy access to allow the environment variables we use in the application.
27
+ It's a best practice to use this vs doing and os.getenv in each application.
28
+ This helps us track all the environment variables in use
29
29
  """
30
30
 
31
31
  @staticmethod
@@ -173,9 +173,9 @@ class EnvironmentServices:
173
173
  environment = {}
174
174
  # more verbose
175
175
  environment["WORKLOAD_NAME"] = deployment.workload.get("name", "NA")
176
- environment["ENVIRONMENT_NAME"] = deployment.environment
176
+ environment["ENVIRONMENT_NAME"] = deployment.workload.get("environment", deployment.environment)
177
177
  environment["DEPLOYMENT_NAME"] = deployment.name
178
- environment["ENVIRONMENT"] = deployment.environment
178
+ environment["ENVIRONMENT"] = deployment.workload.get("environment", deployment.environment)
179
179
  environment["PIPELINE"] = deployment.pipeline.get("name", "NA")
180
180
  environment["ACCOUNT"] = deployment.account
181
181
  environment["DEPLOYMENT"] = deployment.name
@@ -212,7 +212,7 @@ class JsonLoadingUtility:
212
212
  replacements = {
213
213
  "{{workload-name}}": "geekcafe",
214
214
  "{{deployment-name}}": "dev",
215
- "{{awsAccount}}": "123456789012",
215
+ "{{awsAccount}}": os.environ.get("AWS_ACCOUNT", "123456789012"),
216
216
  "{{hostedZoneName}}": "sandbox.geekcafe.com",
217
217
  "{{placeholder}}": "DYNAMIC_VALUE"
218
218
  }