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
@@ -25,7 +25,7 @@ from cdk_factory.configurations.deployment import DeploymentConfig
25
25
  from cdk_factory.configurations.stack import StackConfig
26
26
  from cdk_factory.configurations.resources.lambda_edge import LambdaEdgeConfig
27
27
  from cdk_factory.interfaces.istack import IStack
28
- from cdk_factory.interfaces.enhanced_ssm_parameter_mixin import EnhancedSsmParameterMixin
28
+ from cdk_factory.interfaces.standardized_ssm_mixin import StandardizedSsmMixin
29
29
  from cdk_factory.stack.stack_module_registry import register_stack
30
30
  from cdk_factory.workload.workload_factory import WorkloadConfig
31
31
 
@@ -34,7 +34,7 @@ logger = Logger(service="LambdaEdgeStack")
34
34
 
35
35
  @register_stack("lambda_edge_library_module")
36
36
  @register_stack("lambda_edge_stack")
37
- class LambdaEdgeStack(IStack, EnhancedSsmParameterMixin):
37
+ class LambdaEdgeStack(IStack, StandardizedSsmMixin):
38
38
  """
39
39
  Reusable stack for Lambda@Edge functions.
40
40
 
@@ -53,6 +53,8 @@ class LambdaEdgeStack(IStack, EnhancedSsmParameterMixin):
53
53
  self.workload: Optional[WorkloadConfig] = None
54
54
  self.function: Optional[_lambda.Function] = None
55
55
  self.function_version: Optional[_lambda.Version] = None
56
+ # Cache for resolved environment variables to prevent duplicate construct creation
57
+ self._resolved_env_cache: Optional[Dict[str, str]] = None
56
58
 
57
59
  def build(
58
60
  self,
@@ -98,41 +100,74 @@ class LambdaEdgeStack(IStack, EnhancedSsmParameterMixin):
98
100
  # Create version (required for Lambda@Edge)
99
101
  self._create_function_version(function_name)
100
102
 
103
+ # Configure edge log retention for regional logs
104
+ self._configure_edge_log_retention(function_name)
105
+
101
106
  # Add outputs
102
107
  self._add_outputs(function_name)
103
108
 
109
+ def _sanitize_construct_name(self, name: str) -> str:
110
+ """
111
+ Create a deterministic, valid CDK construct name from any string.
112
+ Replaces non-alphanumeric characters with dashes and limits length.
113
+ """
114
+ # Replace non-alphanumeric characters with dashes
115
+ sanitized = ''.join(c if c.isalnum() else '-' for c in name)
116
+ # Remove consecutive dashes
117
+ while '--' in sanitized:
118
+ sanitized = sanitized.replace('--', '-')
119
+ # Remove leading/trailing dashes
120
+ sanitized = sanitized.strip('-')
121
+ # Limit to 255 characters (CDK limit)
122
+ return sanitized[:255]
123
+
104
124
  def _resolve_environment_variables(self) -> Dict[str, str]:
105
125
  """
106
126
  Resolve environment variables, including SSM parameter references.
107
127
  Supports {{ssm:parameter-path}} syntax for dynamic SSM lookups.
108
128
  Uses CDK tokens that resolve at deployment time, not synthesis time.
129
+ Caches results to prevent duplicate construct creation.
109
130
  """
131
+ # Return cached result if available
132
+ if self._resolved_env_cache is not None:
133
+ return self._resolved_env_cache
134
+
110
135
  resolved_env = {}
111
136
 
112
- for key, value in self.edge_config.environment.items():
137
+ # Use the new simplified configuration structure
138
+ configuration = self.edge_config.dictionary.get("configuration", {})
139
+ runtime_config = configuration.get("runtime", {})
140
+ ui_config = configuration.get("ui", {})
141
+
142
+ for key, value in runtime_config.items():
113
143
  # Check if value is an SSM parameter reference
114
144
  if isinstance(value, str) and value.startswith("{{ssm:") and value.endswith("}}"):
115
145
  # Extract SSM parameter path
116
146
  ssm_param_path = value[6:-2] # Remove {{ssm: and }}
117
147
 
148
+ # Create deterministic construct name from parameter path
149
+ construct_name = self._sanitize_construct_name(f"env-{key}-{ssm_param_path}")
150
+
118
151
  # Import SSM parameter - this creates a token that resolves at deployment time
119
152
  param = ssm.StringParameter.from_string_parameter_name(
120
153
  self,
121
- f"env-{key}-{hash(ssm_param_path) % 10000}",
154
+ construct_name,
122
155
  ssm_param_path
123
156
  )
124
157
  resolved_value = param.string_value
125
- logger.info(f"Resolved environment variable {key} from SSM {ssm_param_path}")
158
+ logger.info(f"Resolved environment variable {key} from SSM {ssm_param_path} as {construct_name}")
126
159
  resolved_env[key] = resolved_value
127
160
  else:
128
161
  resolved_env[key] = value
129
162
 
163
+ # Cache the result
164
+ self._resolved_env_cache = resolved_env
130
165
  return resolved_env
131
166
 
132
167
  def _create_lambda_function(self, function_name: str) -> None:
133
168
  """Create the Lambda function"""
134
169
 
135
- # Resolve code path - support package references (e.g., "cdk_factory:lambdas/edge/ip_gate")
170
+ # Resolve code path - support package references (e.g., "cdk_factory:lambdas/cloudfront/ip_gate")
136
171
  code_path_str = self.edge_config.code_path
137
172
 
138
173
  if ':' in code_path_str:
@@ -185,10 +220,19 @@ class LambdaEdgeStack(IStack, EnhancedSsmParameterMixin):
185
220
  # Create runtime configuration file for Lambda@Edge
186
221
  # Since Lambda@Edge doesn't support environment variables, we bundle a config file
187
222
  # Use the full function_name (e.g., "tech-talk-dev-ip-gate") not just the base name
223
+ resolved_env = self._resolve_environment_variables()
224
+
225
+ # Get the UI configuration
226
+ configuration = self.edge_config.dictionary.get("configuration", {})
227
+ ui_config = configuration.get("ui", {})
228
+
188
229
  runtime_config = {
189
230
  'environment': self.deployment.environment,
231
+ 'workload': self.deployment.workload,
190
232
  'function_name': function_name,
191
- 'region': self.deployment.region
233
+ 'region': self.deployment.region,
234
+ 'runtime': resolved_env, # Runtime variables (SSM, etc.)
235
+ 'ui': ui_config # UI configuration (colors, messages, etc.)
192
236
  }
193
237
 
194
238
  runtime_config_path = temp_code_dir / 'runtime_config.json'
@@ -216,21 +260,17 @@ class LambdaEdgeStack(IStack, EnhancedSsmParameterMixin):
216
260
  self.edge_config.runtime,
217
261
  _lambda.Runtime.PYTHON_3_11
218
262
  )
219
-
220
- # Lambda@Edge does NOT support environment variables
221
- # Configuration must be handled via:
222
- # 1. Hardcoded in the function code
223
- # 2. Fetched from SSM Parameter Store at runtime
224
- # 3. Other configuration mechanisms
225
-
263
+
226
264
  # Log warning if environment variables are configured
227
- if self.edge_config.environment:
265
+ configuration = self.edge_config.dictionary.get("configuration", {})
266
+ runtime_config = configuration.get("runtime", {})
267
+
268
+ if runtime_config:
228
269
  logger.warning(
229
270
  f"Lambda@Edge function '{function_name}' has environment variables configured, "
230
- "but Lambda@Edge does not support environment variables. "
231
- "The function must fetch these values from SSM Parameter Store at runtime."
271
+ "but Lambda@Edge does not support environment variables. The function must fetch these values from SSM Parameter Store at runtime."
232
272
  )
233
- for key, value in self.edge_config.environment.items():
273
+ for key, value in runtime_config.items():
234
274
  logger.warning(f" - {key}: {value}")
235
275
 
236
276
  # Create execution role with CloudWatch Logs and SSM permissions
@@ -239,7 +279,8 @@ class LambdaEdgeStack(IStack, EnhancedSsmParameterMixin):
239
279
  f"{function_name}-Role",
240
280
  assumed_by=iam.CompositePrincipal(
241
281
  iam.ServicePrincipal("lambda.amazonaws.com"),
242
- iam.ServicePrincipal("edgelambda.amazonaws.com")
282
+ iam.ServicePrincipal("edgelambda.amazonaws.com"),
283
+ iam.ServicePrincipal("cloudfront.amazonaws.com") # Add CloudFront service principal
243
284
  ),
244
285
  description=f"Execution role for Lambda@Edge function {function_name}",
245
286
  managed_policies=[
@@ -250,7 +291,7 @@ class LambdaEdgeStack(IStack, EnhancedSsmParameterMixin):
250
291
  )
251
292
 
252
293
  # Add SSM read permissions if environment variables reference SSM parameters
253
- if self.edge_config.environment:
294
+ if runtime_config:
254
295
  execution_role.add_to_policy(
255
296
  iam.PolicyStatement(
256
297
  effect=iam.Effect.ALLOW,
@@ -260,12 +301,90 @@ class LambdaEdgeStack(IStack, EnhancedSsmParameterMixin):
260
301
  "ssm:GetParametersByPath"
261
302
  ],
262
303
  resources=[
263
- f"arn:aws:ssm:*:{cdk.Aws.ACCOUNT_ID}:parameter/*"
304
+ f"arn:aws:ssm:*:{self.deployment.account}:parameter/*"
264
305
  ]
265
306
  )
266
307
  )
267
308
 
268
- # Create the Lambda function WITHOUT environment variables
309
+ # Add Secrets Manager permissions for origin secret access
310
+ execution_role.add_to_policy(
311
+ iam.PolicyStatement(
312
+ effect=iam.Effect.ALLOW,
313
+ actions=[
314
+ "secretsmanager:GetSecretValue",
315
+ "secretsmanager:DescribeSecret"
316
+ ],
317
+ resources=[
318
+ f"arn:aws:secretsmanager:*:{self.deployment.account}:secret:{self.deployment.environment}/{self.workload.name}/origin-secret*"
319
+ ]
320
+ )
321
+ )
322
+
323
+ # Add ELB permissions for target health API access
324
+ execution_role.add_to_policy(
325
+ iam.PolicyStatement(
326
+ effect=iam.Effect.ALLOW,
327
+ actions=[
328
+ "elasticloadbalancing:DescribeTargetHealth",
329
+ "elasticloadbalancing:DescribeTargetGroups",
330
+ "elasticloadbalancing:DescribeLoadBalancers",
331
+ "elasticloadbalancing:DescribeListeners",
332
+ "elasticloadbalancing:DescribeTags"
333
+ ],
334
+ resources=[
335
+ f"arn:aws:elasticloadbalancing:*:{self.deployment.account}:targetgroup/*/*",
336
+ f"arn:aws:elasticloadbalancing:*:{self.deployment.account}:loadbalancer/*/*",
337
+ f"arn:aws:elasticloadbalancing:*:{self.deployment.account}:listener/*/*/*/*"
338
+ ]
339
+ )
340
+ )
341
+
342
+ # Add ACM permissions for certificate validation
343
+ execution_role.add_to_policy(
344
+ iam.PolicyStatement(
345
+ effect=iam.Effect.ALLOW,
346
+ actions=[
347
+ "acm:DescribeCertificate",
348
+ "acm:ListCertificates"
349
+ ],
350
+ resources=[
351
+ f"arn:aws:acm:*:{self.deployment.account}:certificate/*"
352
+ ]
353
+ )
354
+ )
355
+
356
+ # Add Route 53 permissions for health check access
357
+ execution_role.add_to_policy(
358
+ iam.PolicyStatement(
359
+ effect=iam.Effect.ALLOW,
360
+ actions=[
361
+ "route53:GetHealthCheckStatus",
362
+ "route53:ListHealthChecks",
363
+ "route53:GetHealthCheck"
364
+ ],
365
+ resources=[
366
+ f"arn:aws:route53:::{self.deployment.account}:health-check/*"
367
+ ]
368
+ )
369
+ )
370
+
371
+ # Add CloudWatch permissions for enhanced logging and metrics
372
+ execution_role.add_to_policy(
373
+ iam.PolicyStatement(
374
+ effect=iam.Effect.ALLOW,
375
+ actions=[
376
+ "logs:CreateLogGroup",
377
+ "logs:CreateLogStream",
378
+ "logs:PutLogEvents",
379
+ "cloudwatch:PutMetricData"
380
+ ],
381
+ resources=[
382
+ f"arn:aws:logs:*:{self.deployment.account}:log-group:/aws/lambda/*",
383
+ f"arn:aws:cloudwatch:*:{self.deployment.account}:metric:*"
384
+ ]
385
+ )
386
+ )
387
+
269
388
  self.function = _lambda.Function(
270
389
  self,
271
390
  function_name,
@@ -278,6 +397,7 @@ class LambdaEdgeStack(IStack, EnhancedSsmParameterMixin):
278
397
  description=self.edge_config.description,
279
398
  role=execution_role,
280
399
  # Lambda@Edge does NOT support environment variables
400
+ # Configuration must be fetched from SSM at runtime
281
401
  log_retention=logs.RetentionDays.ONE_WEEK,
282
402
  )
283
403
 
@@ -285,6 +405,36 @@ class LambdaEdgeStack(IStack, EnhancedSsmParameterMixin):
285
405
  for key, value in self.edge_config.tags.items():
286
406
  cdk.Tags.of(self.function).add(key, value)
287
407
 
408
+ # Add resource-based policy allowing CloudFront to invoke the Lambda function
409
+ # This is REQUIRED for Lambda@Edge to work properly
410
+ permission_kwargs = {
411
+ "principal": iam.ServicePrincipal("cloudfront.amazonaws.com"),
412
+ "action": "lambda:InvokeFunction",
413
+ }
414
+
415
+ # Optional: Add source ARN restriction if CloudFront distribution ARN is available
416
+ # This provides more secure permission scoping
417
+ distribution_arn_path = f"/{self.deployment.environment}/{self.workload.name}/cloudfront/arn"
418
+ try:
419
+ distribution_arn = ssm.StringParameter.from_string_parameter_name(
420
+ self,
421
+ "cloudfront-distribution-arn",
422
+ distribution_arn_path
423
+ ).string_value
424
+
425
+ # Add source ARN condition for more secure permission scoping
426
+ permission_kwargs["source_arn"] = distribution_arn
427
+ logger.info(f"Adding CloudFront permission with source ARN restriction: {distribution_arn}")
428
+ except Exception:
429
+ # Distribution ARN not available (common during initial deployment)
430
+ # CloudFront will scope the permission appropriately when it associates the Lambda
431
+ logger.warning(f"CloudFront distribution ARN not found at {distribution_arn_path}, using open permission")
432
+
433
+ self.function.add_permission(
434
+ "CloudFrontInvokePermission",
435
+ **permission_kwargs
436
+ )
437
+
288
438
  def _create_function_version(self, function_name: str) -> None:
289
439
  """
290
440
  Create a version of the Lambda function.
@@ -300,36 +450,49 @@ class LambdaEdgeStack(IStack, EnhancedSsmParameterMixin):
300
450
  f"Version for Lambda@Edge deployment - {self.edge_config.description}"
301
451
  )
302
452
 
303
- def _add_outputs(self, function_name: str) -> None:
304
- """Add CloudFormation outputs and SSM exports"""
453
+ def _configure_edge_log_retention(self, function_name: str) -> None:
454
+ """
455
+ Configure log retention for Lambda@Edge log groups in all edge regions
305
456
 
306
- # CloudFormation outputs
307
- cdk.CfnOutput(
308
- self,
309
- "FunctionName",
310
- value=self.function.function_name,
311
- description="Lambda function name",
312
- export_name=f"{function_name}-name"
313
- )
457
+ TODO: IMPLEMENT POST-DEPLOYMENT SOLUTION
458
+ --------------------------------------
459
+ Lambda@Edge log groups are created on-demand when the function is invoked
460
+ at edge locations, not during deployment. This means we cannot set retention
461
+ policies during CloudFormation deployment.
314
462
 
315
- cdk.CfnOutput(
316
- self,
317
- "FunctionArn",
318
- value=self.function.function_arn,
319
- description="Lambda function ARN (unversioned)",
320
- export_name=f"{function_name}-arn"
321
- )
463
+ Possible solutions to implement:
464
+ 1. EventBridge rule that triggers on log group creation
465
+ 2. Custom Lambda function that runs periodically to set retention
466
+ 3. Post-deployment script that waits for log groups to appear
467
+ 4. CloudWatch Logs subscription filter that handles new log groups
322
468
 
323
- cdk.CfnOutput(
324
- self,
325
- "FunctionVersionArn",
326
- value=self.function_version.function_arn,
327
- description="Lambda function version ARN (use this for Lambda@Edge)",
328
- export_name=f"{function_name}-version-arn"
469
+ Current behavior: DISABLED to prevent deployment failures
470
+ """
471
+
472
+ # DISABLED: Edge log groups don't exist during deployment
473
+ # Lambda@Edge creates log groups on-demand at edge locations
474
+ # Setting retention policies during deployment fails with "log group does not exist"
475
+
476
+ edge_retention_days = self.edge_config.dictionary.get("edge_log_retention_days", 7)
477
+ logger.warning(
478
+ f"Edge log retention configuration disabled - log groups are created on-demand. "
479
+ f"Desired retention: {edge_retention_days} days. "
480
+ f"See TODO in _configure_edge_log_retention() for implementation approach."
329
481
  )
330
482
 
483
+ # TODO: Implement one of these solutions:
484
+ # 1. EventBridge + Lambda: Trigger on log group creation and set retention
485
+ # 2. Periodic Lambda: Scan for edge log groups and apply retention policies
486
+ # 3. Post-deployment script: Wait for log groups to appear after edge replication
487
+ # 4. CloudWatch Logs subscription: Process new log group events
488
+
489
+ return
490
+
491
+ def _add_outputs(self, function_name: str) -> None:
492
+ """Add CloudFormation outputs and SSM exports"""
493
+
331
494
  # SSM Parameter Store exports (if configured)
332
- ssm_exports = self.edge_config.dictionary.get("ssm_exports", {})
495
+ ssm_exports = self.edge_config.dictionary.get("ssm", {}).get("exports", {})
333
496
  if ssm_exports:
334
497
  export_values = {
335
498
  "function_name": self.function.function_name,
@@ -349,40 +512,34 @@ class LambdaEdgeStack(IStack, EnhancedSsmParameterMixin):
349
512
  description=f"{key} for Lambda@Edge function {function_name}"
350
513
  )
351
514
 
352
- # Export environment variables as SSM parameters
353
- # Since Lambda@Edge doesn't support environment variables, we export them
354
- # to SSM so the Lambda function can fetch them at runtime
355
- if self.edge_config.environment:
356
- logger.info("Exporting Lambda@Edge environment variables as SSM parameters")
357
- env_ssm_exports = self.edge_config.dictionary.get("environment_ssm_exports", {})
358
-
359
- # If no explicit environment_ssm_exports, create default SSM paths
360
- if not env_ssm_exports:
361
- # Auto-generate SSM parameter names based on environment variable names
362
- for env_key in self.edge_config.environment.keys():
363
- # Use snake_case version of the key for SSM path
364
- ssm_key = env_key.lower().replace('_', '-')
365
- env_ssm_exports[env_key] = f"/{self.deployment.environment}/{function_name}/{ssm_key}"
366
-
367
- # Resolve and export environment variables to SSM
368
- resolved_env = self._resolve_environment_variables()
369
- for env_key, ssm_path in env_ssm_exports.items():
370
- if env_key in resolved_env:
371
- env_value = resolved_env[env_key]
372
-
373
- # Handle empty values - SSM doesn't allow empty strings
374
- # Use sentinel value "NONE" to indicate explicitly unset
375
- if not env_value or (isinstance(env_value, str) and env_value.strip() == ""):
376
- env_value = "NONE"
377
- logger.info(
378
- f"Environment variable {env_key} is empty - setting SSM parameter to 'NONE'. "
379
- f"Lambda function should treat 'NONE' as unset/disabled."
380
- )
381
-
382
- self.export_ssm_parameter(
383
- self,
384
- f"env-{env_key}-param",
385
- env_value,
386
- ssm_path,
387
- description=f"Configuration for Lambda@Edge: {env_key}"
388
- )
515
+ # Export the complete configuration as a single SSM parameter
516
+ config_ssm_path = f"/{self.deployment.environment}/{self.workload.name}/lambda-edge/config"
517
+ configuration = self.edge_config.dictionary.get("configuration", {})
518
+ environment_variables = configuration.get("environment_variables", {})
519
+
520
+ # Build full configuration that Lambda@Edge expects
521
+ full_config = {
522
+ "environment_variables": environment_variables,
523
+ "runtime": configuration.get("runtime", {}),
524
+ "ui": configuration.get("ui", {})
525
+ }
526
+
527
+ self.export_ssm_parameter(
528
+ self,
529
+ "full-config-param",
530
+ json.dumps(full_config),
531
+ config_ssm_path,
532
+ description=f"Complete Lambda@Edge configuration for {function_name} - update this for dynamic changes"
533
+ )
534
+
535
+ # Export cache TTL parameter for dynamic cache control
536
+ cache_ttl_ssm_path = f"/{self.deployment.environment}/{self.workload.name}/lambda-edge/cache-ttl"
537
+ default_cache_ttl = self.edge_config.dictionary.get("cache_ttl_seconds", 300) # Default 5 minutes
538
+
539
+ self.export_ssm_parameter(
540
+ self,
541
+ "cache-ttl-param",
542
+ str(default_cache_ttl),
543
+ cache_ttl_ssm_path,
544
+ description=f"Lambda@Edge configuration cache TTL in seconds for {function_name} - adjust for maintenance windows (30-3600)"
545
+ )