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.

Files changed (44) hide show
  1. cdk_factory/configurations/deployment.py +12 -0
  2. cdk_factory/configurations/resources/acm.py +9 -2
  3. cdk_factory/configurations/resources/auto_scaling.py +7 -5
  4. cdk_factory/configurations/resources/ecs_cluster.py +5 -0
  5. cdk_factory/configurations/resources/ecs_service.py +24 -2
  6. cdk_factory/configurations/resources/lambda_edge.py +18 -4
  7. cdk_factory/configurations/resources/rds.py +1 -1
  8. cdk_factory/configurations/resources/route53.py +5 -0
  9. cdk_factory/configurations/resources/s3.py +9 -1
  10. cdk_factory/constructs/cloudfront/cloudfront_distribution_construct.py +1 -1
  11. cdk_factory/constructs/lambdas/policies/policy_docs.py +1 -1
  12. cdk_factory/interfaces/networked_stack_mixin.py +1 -1
  13. cdk_factory/interfaces/standardized_ssm_mixin.py +82 -10
  14. cdk_factory/stack_library/acm/acm_stack.py +5 -15
  15. cdk_factory/stack_library/api_gateway/api_gateway_stack.py +2 -2
  16. cdk_factory/stack_library/auto_scaling/{auto_scaling_stack_standardized.py → auto_scaling_stack.py} +213 -105
  17. cdk_factory/stack_library/cloudfront/cloudfront_stack.py +76 -22
  18. cdk_factory/stack_library/code_artifact/code_artifact_stack.py +3 -25
  19. cdk_factory/stack_library/cognito/cognito_stack.py +2 -2
  20. cdk_factory/stack_library/dynamodb/dynamodb_stack.py +2 -2
  21. cdk_factory/stack_library/ecs/__init__.py +2 -4
  22. cdk_factory/stack_library/ecs/{ecs_cluster_stack_standardized.py → ecs_cluster_stack.py} +52 -41
  23. cdk_factory/stack_library/ecs/ecs_service_stack.py +49 -26
  24. cdk_factory/stack_library/lambda_edge/EDGE_LOG_RETENTION_TODO.md +226 -0
  25. cdk_factory/stack_library/lambda_edge/LAMBDA_EDGE_LOG_RETENTION_BLOG.md +215 -0
  26. cdk_factory/stack_library/lambda_edge/lambda_edge_stack.py +238 -81
  27. cdk_factory/stack_library/load_balancer/load_balancer_stack.py +128 -177
  28. cdk_factory/stack_library/rds/rds_stack.py +65 -72
  29. cdk_factory/stack_library/route53/route53_stack.py +244 -38
  30. cdk_factory/stack_library/rum/rum_stack.py +3 -3
  31. cdk_factory/stack_library/security_group/security_group_full_stack.py +1 -31
  32. cdk_factory/stack_library/security_group/security_group_stack.py +1 -8
  33. cdk_factory/stack_library/simple_queue_service/sqs_stack.py +1 -34
  34. cdk_factory/stack_library/stack_base.py +5 -0
  35. cdk_factory/stack_library/vpc/{vpc_stack_standardized.py → vpc_stack.py} +6 -109
  36. cdk_factory/stack_library/websites/static_website_stack.py +7 -3
  37. cdk_factory/utilities/api_gateway_integration_utility.py +2 -2
  38. cdk_factory/utilities/environment_services.py +2 -2
  39. cdk_factory/version.py +1 -1
  40. {cdk_factory-0.17.6.dist-info → cdk_factory-0.20.0.dist-info}/METADATA +1 -1
  41. {cdk_factory-0.17.6.dist-info → cdk_factory-0.20.0.dist-info}/RECORD +44 -42
  42. {cdk_factory-0.17.6.dist-info → cdk_factory-0.20.0.dist-info}/WHEEL +0 -0
  43. {cdk_factory-0.17.6.dist-info → cdk_factory-0.20.0.dist-info}/entry_points.txt +0 -0
  44. {cdk_factory-0.17.6.dist-info → cdk_factory-0.20.0.dist-info}/licenses/LICENSE +0 -0
@@ -7,10 +7,15 @@ MIT License. See Project Root for the license information.
7
7
  from typing import Dict, Any, List, Optional
8
8
 
9
9
  import aws_cdk as cdk
10
- from aws_cdk import aws_route53 as route53
11
- from aws_cdk import aws_route53_targets as targets
12
- from aws_cdk import aws_certificatemanager as acm
13
- from aws_cdk import aws_elasticloadbalancingv2 as elbv2
10
+ from aws_cdk import (
11
+ aws_route53 as route53,
12
+ aws_route53_targets as targets,
13
+ aws_certificatemanager as acm,
14
+ aws_elasticloadbalancingv2 as elbv2,
15
+ aws_cloudfront as cloudfront,
16
+ Duration,
17
+ CfnOutput,
18
+ )
14
19
  from aws_lambda_powertools import Logger
15
20
  from constructs import Construct
16
21
 
@@ -35,6 +40,7 @@ class Route53Stack(IStack, StandardizedSsmMixin):
35
40
 
36
41
  def __init__(self, scope: Construct, id: str, **kwargs) -> None:
37
42
  super().__init__(scope, id, **kwargs)
43
+
38
44
  self.route53_config = None
39
45
  self.stack_config = None
40
46
  self.deployment = None
@@ -42,6 +48,7 @@ class Route53Stack(IStack, StandardizedSsmMixin):
42
48
  self.hosted_zone = None
43
49
  self.certificate = None
44
50
  self.records = {}
51
+ self._distribution_cache = {} # Cache for reusing distributions
45
52
 
46
53
  def build(self, stack_config: StackConfig, deployment: DeploymentConfig, workload: WorkloadConfig) -> None:
47
54
  """Build the Route53 stack"""
@@ -112,6 +119,232 @@ class Route53Stack(IStack, StandardizedSsmMixin):
112
119
  return certificate
113
120
 
114
121
  def _create_dns_records(self) -> None:
122
+ self._create_dns_records_old()
123
+ self._create_dns_records_new()
124
+
125
+
126
+ def _get_or_create_cloudfront_distribution(self, distribution_domain: str, distribution_id: str) -> cloudfront.Distribution:
127
+ """Get or create a CloudFront distribution, reusing if already created"""
128
+ # Create a unique cache key from distribution domain and ID
129
+ cache_key = f"{distribution_domain}-{distribution_id}"
130
+
131
+ if cache_key not in self._distribution_cache:
132
+ # Create the distribution construct with a unique ID
133
+ unique_id = f"CF-{distribution_domain.replace('.', '-').replace('*', 'wildcard')}-{hash(cache_key) % 10000}"
134
+ distribution = cloudfront.Distribution.from_distribution_attributes(
135
+ self, unique_id,
136
+ domain_name=distribution_domain,
137
+ distribution_id=distribution_id
138
+ )
139
+ self._distribution_cache[cache_key] = distribution
140
+ logger.info(f"Created CloudFront distribution construct for {distribution_domain}")
141
+
142
+ return self._distribution_cache[cache_key]
143
+
144
+ def _create_dns_records_new(self) -> None:
145
+ """Create DNS records based on configuration - generic implementation"""
146
+
147
+ missing_configurations = []
148
+
149
+ for record in self.route53_config.records:
150
+ record_name = record.get("name", "")
151
+ record_type = record.get("type", "")
152
+
153
+ if not record_name or not record_type:
154
+ message = f"Record missing name or type: {record}"
155
+ logger.warning(message)
156
+ missing_configurations.append(message)
157
+ continue
158
+
159
+ # Handle alias records
160
+ if "alias" in record:
161
+ alias_config = record["alias"]
162
+ target_type = alias_config.get("target_type", "")
163
+ target_value = alias_config.get("target_value", "")
164
+ hosted_zone_id = alias_config.get("hosted_zone_id", "")
165
+
166
+ unique_id = f"{record_name}-{record_type}"
167
+ # Handle SSM parameter references in target_value
168
+ target_value = self.resolve_ssm_value(self, target_value, unique_id=unique_id)
169
+
170
+ if not target_type or not target_value:
171
+ message = f"Alias record missing target_type or target_value: {record}"
172
+ logger.warning(message)
173
+ missing_configurations.append(message)
174
+ continue
175
+
176
+ # Create appropriate target based on type
177
+ alias_target = None
178
+ if target_type == "cloudfront":
179
+ # CloudFront distribution target
180
+ distribution_domain = target_value
181
+ distribution_id = alias_config.get("distribution_id", "")
182
+ if not distribution_id:
183
+ message = f"Alias record missing distribution_id: {record}"
184
+ logger.warning(message)
185
+ missing_configurations.append(message)
186
+ continue
187
+
188
+ # Get or create the distribution (reuses if already created)
189
+ distribution = self._get_or_create_cloudfront_distribution(distribution_domain, distribution_id)
190
+ alias_target = route53.RecordTarget.from_alias(
191
+ targets.CloudFrontTarget(distribution)
192
+ )
193
+ elif target_type == "loadbalancer" or target_type == "alb":
194
+ # Load Balancer target
195
+ alias_target = route53.RecordTarget.from_alias(
196
+ targets.LoadBalancerTarget(
197
+ elbv2.ApplicationLoadBalancer.from_load_balancer_attributes(
198
+ self, f"ALB-{record_name}",
199
+ load_balancer_dns_name=target_value,
200
+ load_balancer_canonical_hosted_zone_id=hosted_zone_id
201
+ )
202
+ )
203
+ )
204
+ elif target_type == "elbv2":
205
+ # Generic ELBv2 target
206
+ alias_target = route53.RecordTarget.from_alias(
207
+ targets.LoadBalancerTarget(
208
+ elbv2.ApplicationLoadBalancer.from_load_balancer_attributes(
209
+ self, f"ELB-{record_name}",
210
+ load_balancer_dns_name=target_value,
211
+ load_balancer_canonical_hosted_zone_id=hosted_zone_id
212
+ )
213
+ )
214
+ )
215
+ else:
216
+ message = f"Unsupported alias target type: {target_type}"
217
+ logger.warning(message)
218
+ missing_configurations.append(message)
219
+ continue
220
+
221
+ # Create the alias record
222
+ route53.ARecord(
223
+ self,
224
+ f"AliasRecord-{record_name}-{record_type}",
225
+ zone=self.hosted_zone,
226
+ record_name=record_name,
227
+ target=alias_target,
228
+ ttl=cdk.Duration.seconds(record.get("ttl", 300))
229
+ ) if record_type == "A" else route53.AaaaRecord(
230
+ self,
231
+ f"AliasRecord-{record_name}-{record_type}",
232
+ zone=self.hosted_zone,
233
+ record_name=record_name,
234
+ target=alias_target,
235
+ ttl=cdk.Duration.seconds(record.get("ttl", 300))
236
+ )
237
+
238
+ # Handle standard records with values
239
+ elif "values" in record:
240
+ values = record["values"]
241
+ if not isinstance(values, list):
242
+ values = [values]
243
+
244
+ # Handle SSM parameter references in values
245
+ processed_values = []
246
+ for value in values:
247
+ if "{{ssm:" in str(value) and "}}" in str(value):
248
+ # Extract SSM parameter path from template like {{ssm:/path/to/parameter}}
249
+ ssm_path = str(value).split("{{ssm:")[1].split("}}")[0]
250
+ resolved_value = self.get_ssm_imported_value(ssm_path)
251
+ processed_values.append(resolved_value)
252
+ else:
253
+ processed_values.append(value)
254
+
255
+ values = processed_values
256
+ ttl = record.get("ttl", 300)
257
+
258
+ # Create standard record based on type
259
+ if record_type == "A":
260
+ route53.ARecord(
261
+ self,
262
+ f"Record-{record_name}",
263
+ zone=self.hosted_zone,
264
+ record_name=record_name,
265
+ target=route53.RecordTarget.from_ip_addresses(*values),
266
+ ttl=cdk.Duration.seconds(ttl)
267
+ )
268
+ elif record_type == "AAAA":
269
+ route53.AaaaRecord(
270
+ self,
271
+ f"Record-{record_name}",
272
+ zone=self.hosted_zone,
273
+ record_name=record_name,
274
+ target=route53.RecordTarget.from_ip_addresses(*values),
275
+ ttl=cdk.Duration.seconds(ttl)
276
+ )
277
+ elif record_type == "CNAME":
278
+ route53.CnameRecord(
279
+ self,
280
+ f"Record-{record_name}",
281
+ zone=self.hosted_zone,
282
+ record_name=record_name,
283
+ domain_name=values[0], # CNAME only supports single value
284
+ ttl=cdk.Duration.seconds(ttl)
285
+ )
286
+ elif record_type == "MX":
287
+ # MX records need special handling for preference values
288
+ mx_targets = []
289
+ for value in values:
290
+ if isinstance(value, str) and " " in value:
291
+ preference, domain = value.split(" ", 1)
292
+ mx_targets.append(route53.MxRecordValue(
293
+ domain_name=domain.strip(),
294
+ preference=int(preference.strip())
295
+ ))
296
+ else:
297
+ logger.warning(f"Invalid MX record format: {value}")
298
+
299
+ if mx_targets:
300
+ route53.MxRecord(
301
+ self,
302
+ f"Record-{record_name}",
303
+ zone=self.hosted_zone,
304
+ record_name=record_name,
305
+ values=mx_targets,
306
+ ttl=cdk.Duration.seconds(ttl)
307
+ )
308
+ elif record_type == "TXT":
309
+ route53.TxtRecord(
310
+ self,
311
+ f"Record-{record_name}",
312
+ zone=self.hosted_zone,
313
+ record_name=record_name,
314
+ values=values,
315
+ ttl=cdk.Duration.seconds(ttl)
316
+ )
317
+ elif record_type == "NS":
318
+ route53.NsRecord(
319
+ self,
320
+ f"Record-{record_name}",
321
+ zone=self.hosted_zone,
322
+ record_name=record_name,
323
+ values=values,
324
+ ttl=cdk.Duration.seconds(ttl)
325
+ )
326
+ else:
327
+ message = f"Unsupported record type: {record_type}"
328
+ logger.warning(message)
329
+ missing_configurations.append(message)
330
+ continue
331
+
332
+ else:
333
+ message = f"Record missing 'alias' or 'values' configuration: {record}"
334
+ logger.warning(message)
335
+ missing_configurations.append(message)
336
+ continue
337
+
338
+ if missing_configurations and len(missing_configurations) > 0:
339
+ # print all missing configurations
340
+ print("Missing configurations:")
341
+ for message in missing_configurations:
342
+ print(message)
343
+
344
+ messages = "\n".join(missing_configurations)
345
+ raise ValueError(f"Missing Configurations:\n{messages}")
346
+
347
+ def _create_dns_records_old(self) -> None:
115
348
  """Create DNS records based on configuration"""
116
349
  # Create alias records
117
350
  for alias_record in self.route53_config.aliases:
@@ -119,6 +352,12 @@ class Route53Stack(IStack, StandardizedSsmMixin):
119
352
  target_type = alias_record.get("target_type", "")
120
353
  target_value = alias_record.get("target_value", "")
121
354
 
355
+ # target value needs to handle SSM parameters
356
+ if "{{ssm:" in target_value and "}}" in target_value:
357
+ # Extract SSM parameter path from template like {{ssm:/path/to/parameter}}
358
+ ssm_path = target_value.split("{{ssm:")[1].split("}}")[0]
359
+ target_value = self.get_ssm_imported_value(ssm_path)
360
+
122
361
  if not record_name or not target_type or not target_value:
123
362
  continue
124
363
 
@@ -174,37 +413,4 @@ class Route53Stack(IStack, StandardizedSsmMixin):
174
413
  def _add_outputs(self) -> None:
175
414
  """Add CloudFormation outputs for the Route53 resources"""
176
415
  # Hosted Zone ID
177
- if self.hosted_zone:
178
- cdk.CfnOutput(
179
- self,
180
- "HostedZoneId",
181
- value=self.hosted_zone.hosted_zone_id,
182
- export_name=f"{self.deployment.build_resource_name('hosted-zone')}-id"
183
- )
184
-
185
- # Hosted Zone Name Servers
186
- if hasattr(self.hosted_zone, "name_servers") and self.hosted_zone.name_servers:
187
- cdk.CfnOutput(
188
- self,
189
- "NameServers",
190
- value=",".join(self.hosted_zone.name_servers),
191
- export_name=f"{self.deployment.build_resource_name('hosted-zone')}-name-servers"
192
- )
193
-
194
- # Certificate ARN
195
- if self.certificate:
196
- cdk.CfnOutput(
197
- self,
198
- "CertificateArn",
199
- value=self.certificate.certificate_arn,
200
- export_name=f"{self.deployment.build_resource_name('certificate')}-arn"
201
- )
202
-
203
- # Record names
204
- for name, record in self.records.items():
205
- cdk.CfnOutput(
206
- self,
207
- f"Record-{name}",
208
- value=name,
209
- export_name=f"{self.deployment.build_resource_name('record')}-{name}"
210
- )
416
+ return
@@ -72,7 +72,7 @@ class RumStack(IStack, StandardizedSsmMixin):
72
72
  "cognito_identity_pool_id"
73
73
  ] = "/{{ORGANIZATION}}/{{ENVIRONMENT}}/cognito/user-pool/identity-pool-id"
74
74
 
75
- self.setup_standardized_ssm_integration(
75
+ self.setup_ssm_integration(
76
76
  scope=self,
77
77
  config=rum_config,
78
78
  resource_type="rum",
@@ -80,7 +80,7 @@ class RumStack(IStack, StandardizedSsmMixin):
80
80
  )
81
81
 
82
82
  # Process SSM imports using standardized method
83
- self.process_standardized_ssm_imports()
83
+ self.process_ssm_imports()
84
84
 
85
85
  # Import or create Cognito resources
86
86
  identity_pool_id, guest_role_arn = self._setup_cognito_integration()
@@ -341,7 +341,7 @@ class RumStack(IStack, StandardizedSsmMixin):
341
341
  resource_values["user_pool_id"] = self.user_pool.user_pool_id
342
342
 
343
343
  # Use enhanced SSM parameter export
344
- exported_params = self.export_standardized_ssm_parameters(resource_values)
344
+ exported_params = self.export_ssm_parameters(resource_values)
345
345
 
346
346
  if exported_params:
347
347
  logger.info(f"Exported {len(exported_params)} RUM parameters to SSM")
@@ -194,37 +194,7 @@ class SecurityGroupsStack(IStack, VPCProviderMixin):
194
194
  description="Uptime Robot",
195
195
  )
196
196
 
197
- # =========================================================
198
- # Outputs (exports)
199
- # =========================================================
200
- cdk.CfnOutput(
201
- self,
202
- "WebFleetAlbSecurityGroupOut",
203
- value=alb_sg.ref,
204
- description="Web Fleet Application Load Balancer Security Group",
205
- export_name=f"{self.deployment.environment}-{self.workload.name}-WebFleetAlbSecurityGroup",
206
- )
207
- cdk.CfnOutput(
208
- self,
209
- "WebFleetInstancesSecurityGroupOut",
210
- value=web_fleet_sg.ref,
211
- description="Web Fleet Instances Security Group",
212
- export_name=f"{self.deployment.environment}-{self.workload.name}-WebFleetInstancesSecurityGroup",
213
- )
214
- cdk.CfnOutput(
215
- self,
216
- "MySqlDbSecurityGroupOut",
217
- value=mysql_sg.ref,
218
- description="MySql Security Group",
219
- export_name=f"{self.deployment.environment}-{self.workload.name}-MySqlDbSecurityGroup",
220
- )
221
- cdk.CfnOutput(
222
- self,
223
- "WebMonitoringSecurityGroupOut",
224
- value=monitoring_sg.ref,
225
- description="Web Fleet Application Load Balancer Security Group",
226
- export_name=f"{self.deployment.environment}-{self.workload.name}-WebMonitoringSecurityGroup",
227
- )
197
+
228
198
 
229
199
  # =========================================================
230
200
  # SSM Parameter Store Exports
@@ -337,14 +337,7 @@ class SecurityGroupStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
337
337
 
338
338
  def _export_cfn_outputs(self, sg_name: str) -> None:
339
339
  """Add CloudFormation outputs for the Security Group"""
340
- if self.security_group:
341
- # Security Group ID
342
- cdk.CfnOutput(
343
- self,
344
- f"{sg_name}-id",
345
- value=self.security_group.security_group_id,
346
- export_name=f"{self.deployment.build_resource_name(sg_name)}-id",
347
- )
340
+ return
348
341
 
349
342
  def _export_ssm_parameters(self, sg_name: str) -> None:
350
343
  """Add SSM parameters for the Security Group"""
@@ -131,37 +131,4 @@ class SQSStack(IStack):
131
131
 
132
132
  def _add_outputs(self) -> None:
133
133
  """Add CloudFormation outputs for the SQS queues"""
134
- for queue_name, queue in self.queues.items():
135
- # Queue ARN
136
- cdk.CfnOutput(
137
- self,
138
- f"{queue_name}-arn",
139
- value=queue.queue_arn,
140
- export_name=f"{self.deployment.build_resource_name(queue_name)}-arn"
141
- )
142
-
143
- # Queue URL
144
- cdk.CfnOutput(
145
- self,
146
- f"{queue_name}-url",
147
- value=queue.queue_url,
148
- export_name=f"{self.deployment.build_resource_name(queue_name)}-url"
149
- )
150
-
151
- # Also add outputs for DLQs
152
- for dlq_name, dlq in self.dead_letter_queues.items():
153
- # DLQ ARN
154
- cdk.CfnOutput(
155
- self,
156
- f"{dlq_name}-arn",
157
- value=dlq.queue_arn,
158
- export_name=f"{self.deployment.build_resource_name(dlq_name)}-arn"
159
- )
160
-
161
- # DLQ URL
162
- cdk.CfnOutput(
163
- self,
164
- f"{dlq_name}-url",
165
- value=dlq.queue_url,
166
- export_name=f"{self.deployment.build_resource_name(dlq_name)}-url"
167
- )
134
+ return
@@ -54,6 +54,11 @@ class StackStandards:
54
54
  git_hash = GitUtilities.get_git_commit_hash()
55
55
  if git_hash:
56
56
  aws_cdk.Tags.of(scope).add("ApplicationGitHash", git_hash)
57
+
58
+ # Add CDK Factory version for tracking and debugging
59
+ from cdk_factory.version import __version__
60
+ aws_cdk.Tags.of(scope).add("CdkFactoryVersion", __version__)
61
+
57
62
  aws_cdk.Tags.of(scope).add(
58
63
  "DeploymentDateUTC", str(datetime.datetime.now(datetime.UTC))
59
64
  )
@@ -74,7 +74,7 @@ class VpcStack(IStack, StandardizedSsmMixin):
74
74
  vpc_name = deployment.build_resource_name(self.vpc_config.name)
75
75
 
76
76
  # Setup standardized SSM integration
77
- self.setup_standardized_ssm_integration(
77
+ self.setup_ssm_integration(
78
78
  scope=self,
79
79
  config=self.vpc_config,
80
80
  resource_type="vpc",
@@ -84,7 +84,7 @@ class VpcStack(IStack, StandardizedSsmMixin):
84
84
  )
85
85
 
86
86
  # Process SSM imports using standardized method
87
- self.process_standardized_ssm_imports()
87
+ self.process_ssm_imports()
88
88
 
89
89
  # Import any required resources from SSM
90
90
  imported_resources = self.get_all_ssm_imports()
@@ -241,111 +241,8 @@ class VpcStack(IStack, StandardizedSsmMixin):
241
241
 
242
242
  def _add_outputs(self, vpc_name: str) -> None:
243
243
  """Add CloudFormation outputs for the VPC"""
244
- if not self.vpc:
245
- return
246
-
247
- # VPC outputs
248
- cdk.CfnOutput(
249
- self,
250
- f"{vpc_name}-VpcId",
251
- value=self.vpc.vpc_id,
252
- description=f"VPC ID for {vpc_name}",
253
- export_name=f"{self.deployment.workload_name}-{self.deployment.environment}-vpc-id",
254
- )
255
-
256
- # Subnet outputs
257
- public_subnet_ids = [subnet.subnet_id for subnet in self.vpc.public_subnets]
258
- if public_subnet_ids:
259
- cdk.CfnOutput(
260
- self,
261
- f"{vpc_name}-PublicSubnetIds",
262
- value=",".join(public_subnet_ids),
263
- description=f"Public subnet IDs for {vpc_name}",
264
- export_name=f"{self.deployment.workload_name}-{self.deployment.environment}-public-subnet-ids",
265
- )
266
-
267
- private_subnet_ids = [subnet.subnet_id for subnet in self.vpc.private_subnets]
268
- if private_subnet_ids:
269
- cdk.CfnOutput(
270
- self,
271
- f"{vpc_name}-PrivateSubnetIds",
272
- value=",".join(private_subnet_ids),
273
- description=f"Private subnet IDs for {vpc_name}",
274
- export_name=f"{self.deployment.workload_name}-{self.deployment.environment}-private-subnet-ids",
275
- )
276
-
277
- isolated_subnet_ids = [subnet.subnet_id for subnet in self.vpc.isolated_subnets]
278
- if isolated_subnet_ids:
279
- cdk.CfnOutput(
280
- self,
281
- f"{vpc_name}-IsolatedSubnetIds",
282
- value=",".join(isolated_subnet_ids),
283
- description=f"Isolated subnet IDs for {vpc_name}",
284
- export_name=f"{self.deployment.workload_name}-{self.deployment.environment}-isolated-subnet-ids",
285
- )
286
-
287
- # Route table outputs - simplified to avoid route table access issues
288
- # Skip route table outputs for now as they're causing CDK API issues
289
- # public_route_table_ids = []
290
- # if self.vpc.public_subnets:
291
- # for subnet in self.vpc.public_subnets:
292
- # # Access route table through the subnet's route table association
293
- # for association in subnet.node.children:
294
- # if hasattr(association, 'route_table_id') and association.route_table_id:
295
- # public_route_table_ids.append(association.route_table_id)
296
- #
297
- # if public_route_table_ids:
298
- # cdk.CfnOutput(
299
- # self,
300
- # f"{vpc_name}-PublicRouteTableIds",
301
- # value=",".join(public_route_table_ids),
302
- # description=f"Public route table IDs for {vpc_name}",
303
- # export_name=f"{self.deployment.workload_name}-{self.deployment.environment}-public-route-table-ids",
304
- # )
305
- #
306
- # private_route_table_ids = []
307
- # if self.vpc.private_subnets:
308
- # for subnet in self.vpc.private_subnets:
309
- # # Access route table through the subnet's route table association
310
- # for association in subnet.node.children:
311
- # if hasattr(association, 'route_table_id') and association.route_table_id:
312
- # private_route_table_ids.append(association.route_table_id)
313
- #
314
- # if private_route_table_ids:
315
- # cdk.CfnOutput(
316
- # self,
317
- # f"{vpc_name}-PrivateRouteTableIds",
318
- # value=",".join(private_route_table_ids),
319
- # description=f"Private route table IDs for {vpc_name}",
320
- # export_name=f"{self.deployment.workload_name}-{self.deployment.environment}-private-route-table-ids",
321
- # )
322
-
323
- # Internet Gateway output
324
- if hasattr(self.vpc, 'internet_gateway_id') and self.vpc.internet_gateway_id:
325
- cdk.CfnOutput(
326
- self,
327
- f"{vpc_name}-InternetGatewayId",
328
- value=self.vpc.internet_gateway_id,
329
- description=f"Internet Gateway ID for {vpc_name}",
330
- export_name=f"{self.deployment.workload_name}-{self.deployment.environment}-internet-gateway-id",
331
- )
332
-
333
- # NAT Gateway outputs - simplified to avoid None values
334
- nat_gateway_ids = []
335
- for subnet in self.vpc.public_subnets:
336
- if hasattr(subnet, 'node') and subnet.node:
337
- for child in subnet.node.children:
338
- if hasattr(child, 'nat_gateway_id') and child.nat_gateway_id:
339
- nat_gateway_ids.append(child.nat_gateway_id)
340
-
341
- if nat_gateway_ids:
342
- cdk.CfnOutput(
343
- self,
344
- f"{vpc_name}-NatGatewayIds",
345
- value=",".join(nat_gateway_ids),
346
- description=f"NAT Gateway IDs for {vpc_name}",
347
- export_name=f"{self.deployment.workload_name}-{self.deployment.environment}-nat-gateway-ids",
348
- )
244
+ return
245
+
349
246
 
350
247
  def _export_ssm_parameters(self) -> None:
351
248
  """Export SSM parameters using standardized approach"""
@@ -399,7 +296,7 @@ class VpcStack(IStack, StandardizedSsmMixin):
399
296
  resource_values["internet_gateway_id"] = self.vpc.internet_gateway_id
400
297
 
401
298
  # Export using standardized SSM mixin
402
- exported_params = self.export_standardized_ssm_parameters(resource_values)
299
+ exported_params = self.export_ssm_parameters(resource_values)
403
300
 
404
301
  logger.info(f"Exported SSM parameters: {exported_params}")
405
302
 
@@ -431,7 +328,7 @@ class VpcStack(IStack, StandardizedSsmMixin):
431
328
  # Backward compatibility methods
432
329
  def auto_export_resources(self, resource_values: Dict[str, Any], context: Dict[str, Any] = None) -> Dict[str, str]:
433
330
  """Backward compatibility method for existing modules."""
434
- return self.export_standardized_ssm_parameters(resource_values)
331
+ return self.export_ssm_parameters(resource_values)
435
332
 
436
333
  def auto_import_resources(self, context: Dict[str, Any] = None) -> Dict[str, Any]:
437
334
  """Backward compatibility method for existing modules."""
@@ -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")
@@ -555,7 +555,7 @@ class ApiGatewayIntegrationUtility:
555
555
  api_gateway_config["ssm"]["imports"] = {}
556
556
  api_gateway_config["ssm"]["imports"]["user_pool_arn"] = "/{{ORGANIZATION}}/{{ENVIRONMENT}}/cognito/user-pool/arn"
557
557
 
558
- ssm_mixin.setup_standardized_ssm_integration(
558
+ ssm_mixin.setup_ssm_integration(
559
559
  scope=self.scope,
560
560
  config=api_gateway_config,
561
561
  resource_type="cognito",
@@ -882,7 +882,7 @@ class ApiGatewayIntegrationUtility:
882
882
 
883
883
  # Setup enhanced SSM integration for auto-import
884
884
  # Use consistent resource name for cross-stack compatibility
885
- ssm_mixin.setup_standardized_ssm_integration(
885
+ ssm_mixin.setup_ssm_integration(
886
886
  scope=self.scope,
887
887
  config=api_gateway_config,
888
888
  resource_type="api-gateway",
@@ -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
cdk_factory/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.17.6"
1
+ __version__ = "0.20.0"