cdk-factory 0.9.11__py3-none-any.whl → 0.9.13__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 (25) hide show
  1. cdk_factory/app.py +39 -8
  2. cdk_factory/configurations/resources/auto_scaling.py +27 -0
  3. cdk_factory/configurations/resources/cloudfront.py +101 -11
  4. cdk_factory/configurations/resources/ecs_service.py +12 -0
  5. cdk_factory/configurations/resources/lambda_edge.py +97 -0
  6. cdk_factory/configurations/resources/monitoring.py +74 -0
  7. cdk_factory/configurations/resources/s3.py +3 -0
  8. cdk_factory/constructs/cloudfront/cloudfront_distribution_construct.py +69 -1
  9. cdk_factory/lambdas/edge/ip_gate/handler.py +104 -0
  10. cdk_factory/pipeline/pipeline_factory.py +1 -0
  11. cdk_factory/stack_library/auto_scaling/auto_scaling_stack.py +99 -0
  12. cdk_factory/stack_library/cloudfront/__init__.py +6 -0
  13. cdk_factory/stack_library/cloudfront/cloudfront_stack.py +627 -0
  14. cdk_factory/stack_library/ecs/ecs_service_stack.py +90 -0
  15. cdk_factory/stack_library/lambda_edge/__init__.py +6 -0
  16. cdk_factory/stack_library/lambda_edge/lambda_edge_stack.py +258 -0
  17. cdk_factory/stack_library/monitoring/__init__.py +6 -0
  18. cdk_factory/stack_library/monitoring/monitoring_stack.py +492 -0
  19. cdk_factory/version.py +1 -1
  20. cdk_factory/workload/workload_factory.py +2 -0
  21. {cdk_factory-0.9.11.dist-info → cdk_factory-0.9.13.dist-info}/METADATA +1 -1
  22. {cdk_factory-0.9.11.dist-info → cdk_factory-0.9.13.dist-info}/RECORD +25 -16
  23. {cdk_factory-0.9.11.dist-info → cdk_factory-0.9.13.dist-info}/WHEEL +0 -0
  24. {cdk_factory-0.9.11.dist-info → cdk_factory-0.9.13.dist-info}/entry_points.txt +0 -0
  25. {cdk_factory-0.9.11.dist-info → cdk_factory-0.9.13.dist-info}/licenses/LICENSE +0 -0
@@ -198,9 +198,78 @@ class EcsServiceStack(IStack, EnhancedSsmParameterMixin):
198
198
  task_role=task_role,
199
199
  )
200
200
 
201
+ # Add volumes to task definition
202
+ self._add_volumes_to_task()
203
+
201
204
  # Add containers
202
205
  self._add_containers_to_task()
203
206
 
207
+ def _add_volumes_to_task(self) -> None:
208
+ """
209
+ Add volumes to the task definition.
210
+ Supports host volumes for EC2 launch type and EFS volumes for both.
211
+ """
212
+ volumes = self.ecs_config.volumes
213
+
214
+ if not volumes:
215
+ return # No volumes to add
216
+
217
+ for volume_config in volumes:
218
+ volume_name = volume_config.get("name")
219
+ if not volume_name:
220
+ logger.warning("Volume name is required, skipping")
221
+ continue
222
+
223
+ # Check volume type
224
+ if "host" in volume_config:
225
+ # Host volume (bind mount for EC2)
226
+ if self.ecs_config.launch_type != "EC2":
227
+ logger.warning(f"Host volumes are only supported for EC2 launch type, skipping volume {volume_name}")
228
+ continue
229
+
230
+ host_config = volume_config["host"]
231
+ source_path = host_config.get("source_path")
232
+
233
+ if not source_path:
234
+ logger.warning(f"Host source_path is required for volume {volume_name}, skipping")
235
+ continue
236
+
237
+ # Add host volume to task definition
238
+ self.task_definition.add_volume(
239
+ name=volume_name,
240
+ host=ecs.Host(source_path=source_path)
241
+ )
242
+
243
+ logger.info(f"Added host volume: {volume_name} -> {source_path}")
244
+
245
+ elif "efs" in volume_config:
246
+ # EFS volume (supported for both EC2 and Fargate)
247
+ efs_config = volume_config["efs"]
248
+ file_system_id = efs_config.get("file_system_id")
249
+
250
+ if not file_system_id:
251
+ logger.warning(f"EFS file_system_id is required for volume {volume_name}, skipping")
252
+ continue
253
+
254
+ # Build EFS volume configuration
255
+ efs_volume_config = ecs.EfsVolumeConfiguration(
256
+ file_system_id=file_system_id,
257
+ root_directory=efs_config.get("root_directory", "/"),
258
+ transit_encryption="ENABLED" if efs_config.get("transit_encryption", True) else "DISABLED",
259
+ authorization_config=ecs.AuthorizationConfig(
260
+ access_point_id=efs_config.get("access_point_id")
261
+ ) if efs_config.get("access_point_id") else None
262
+ )
263
+
264
+ self.task_definition.add_volume(
265
+ name=volume_name,
266
+ efs_volume_configuration=efs_volume_config
267
+ )
268
+
269
+ logger.info(f"Added EFS volume: {volume_name} -> {file_system_id}")
270
+ else:
271
+ logger.warning(f"Volume {volume_name} must specify either 'host' or 'efs' configuration")
272
+
204
273
  def _add_containers_to_task(self) -> None:
205
274
  """Add container definitions to the task"""
206
275
  container_definitions = self.ecs_config.container_definitions
@@ -262,6 +331,27 @@ class EcsServiceStack(IStack, EnhancedSsmParameterMixin):
262
331
  protocol=ecs.Protocol.TCP,
263
332
  )
264
333
  )
334
+
335
+ # Add mount points (bind volumes to container)
336
+ mount_points = container_config.get("mount_points", [])
337
+ for mount_point in mount_points:
338
+ source_volume = mount_point.get("source_volume")
339
+ container_path = mount_point.get("container_path")
340
+ read_only = mount_point.get("read_only", False)
341
+
342
+ if not source_volume or not container_path:
343
+ logger.warning(f"Mount point requires source_volume and container_path, skipping")
344
+ continue
345
+
346
+ container.add_mount_points(
347
+ ecs.MountPoint(
348
+ source_volume=source_volume,
349
+ container_path=container_path,
350
+ read_only=read_only
351
+ )
352
+ )
353
+
354
+ logger.info(f"Added mount point: {source_volume} -> {container_path} (read_only={read_only})")
265
355
 
266
356
  def _load_secrets(self, secrets_config: Dict[str, str]) -> Dict[str, ecs.Secret]:
267
357
  """Load secrets from Secrets Manager or SSM Parameter Store"""
@@ -0,0 +1,6 @@
1
+ """
2
+ Lambda@Edge Stack Library Module
3
+ """
4
+ from cdk_factory.stack_library.lambda_edge.lambda_edge_stack import LambdaEdgeStack
5
+
6
+ __all__ = ["LambdaEdgeStack"]
@@ -0,0 +1,258 @@
1
+ """
2
+ Lambda@Edge Stack Pattern for CDK-Factory
3
+ Supports deploying Lambda functions for CloudFront edge locations.
4
+ Geek Cafe, LLC
5
+ Maintainers: Eric Wilson
6
+ MIT License. See Project Root for the license information.
7
+ """
8
+
9
+ from typing import Optional, Dict
10
+ from pathlib import Path
11
+
12
+ import aws_cdk as cdk
13
+ from aws_cdk import aws_lambda as _lambda
14
+ from aws_cdk import aws_iam as iam
15
+ from aws_cdk import aws_logs as logs
16
+ from aws_cdk import aws_ssm as ssm
17
+ from aws_lambda_powertools import Logger
18
+ from constructs import Construct
19
+
20
+ from cdk_factory.configurations.deployment import DeploymentConfig
21
+ from cdk_factory.configurations.stack import StackConfig
22
+ from cdk_factory.configurations.resources.lambda_edge import LambdaEdgeConfig
23
+ from cdk_factory.interfaces.istack import IStack
24
+ from cdk_factory.interfaces.enhanced_ssm_parameter_mixin import EnhancedSsmParameterMixin
25
+ from cdk_factory.stack.stack_module_registry import register_stack
26
+ from cdk_factory.workload.workload_factory import WorkloadConfig
27
+
28
+ logger = Logger(service="LambdaEdgeStack")
29
+
30
+
31
+ @register_stack("lambda_edge_library_module")
32
+ @register_stack("lambda_edge_stack")
33
+ class LambdaEdgeStack(IStack, EnhancedSsmParameterMixin):
34
+ """
35
+ Reusable stack for Lambda@Edge functions.
36
+
37
+ Lambda@Edge constraints:
38
+ - Must be deployed in us-east-1
39
+ - Requires versioned functions (not $LATEST)
40
+ - Max timeout: 5s for origin-request, 30s for viewer-request
41
+ - No environment variables in viewer-request/response (origin-request/response only)
42
+ """
43
+
44
+ def __init__(self, scope: Construct, id: str, **kwargs) -> None:
45
+ super().__init__(scope, id, **kwargs)
46
+ self.edge_config: Optional[LambdaEdgeConfig] = None
47
+ self.stack_config: Optional[StackConfig] = None
48
+ self.deployment: Optional[DeploymentConfig] = None
49
+ self.workload: Optional[WorkloadConfig] = None
50
+ self.function: Optional[_lambda.Function] = None
51
+ self.function_version: Optional[_lambda.Version] = None
52
+
53
+ def build(
54
+ self,
55
+ stack_config: StackConfig,
56
+ deployment: DeploymentConfig,
57
+ workload: WorkloadConfig,
58
+ ) -> None:
59
+ """Build the Lambda@Edge stack"""
60
+ self._build(stack_config, deployment, workload)
61
+
62
+ def _build(
63
+ self,
64
+ stack_config: StackConfig,
65
+ deployment: DeploymentConfig,
66
+ workload: WorkloadConfig,
67
+ ) -> None:
68
+ """Internal build method for the Lambda@Edge stack"""
69
+ self.stack_config = stack_config
70
+ self.deployment = deployment
71
+ self.workload = workload
72
+
73
+ # Validate region (Lambda@Edge must be in us-east-1)
74
+ if self.region != "us-east-1":
75
+ logger.warning(
76
+ f"Lambda@Edge must be deployed in us-east-1, but stack region is {self.region}. "
77
+ "Make sure your deployment config specifies us-east-1."
78
+ )
79
+
80
+ # Load Lambda@Edge configuration
81
+ self.edge_config = LambdaEdgeConfig(
82
+ stack_config.dictionary.get("lambda_edge", {}),
83
+ deployment
84
+ )
85
+
86
+ function_name = deployment.build_resource_name(self.edge_config.name)
87
+
88
+ # Create Lambda function
89
+ self._create_lambda_function(function_name)
90
+
91
+ # Create version (required for Lambda@Edge)
92
+ self._create_function_version(function_name)
93
+
94
+ # Add outputs
95
+ self._add_outputs(function_name)
96
+
97
+ def _resolve_environment_variables(self) -> Dict[str, str]:
98
+ """
99
+ Resolve environment variables, including SSM parameter references.
100
+ Supports {{ssm:parameter-path}} syntax for dynamic SSM lookups.
101
+ Uses CDK tokens that resolve at deployment time, not synthesis time.
102
+ """
103
+ resolved_env = {}
104
+
105
+ for key, value in self.edge_config.environment.items():
106
+ # Check if value is an SSM parameter reference
107
+ if isinstance(value, str) and value.startswith("{{ssm:") and value.endswith("}}"):
108
+ # Extract SSM parameter path
109
+ ssm_param_path = value[6:-2] # Remove {{ssm: and }}
110
+
111
+ # Import SSM parameter - this creates a token that resolves at deployment time
112
+ param = ssm.StringParameter.from_string_parameter_name(
113
+ self,
114
+ f"env-{key}-{hash(ssm_param_path) % 10000}",
115
+ ssm_param_path
116
+ )
117
+ resolved_value = param.string_value
118
+ logger.info(f"Resolved environment variable {key} from SSM {ssm_param_path}")
119
+ resolved_env[key] = resolved_value
120
+ else:
121
+ resolved_env[key] = value
122
+
123
+ return resolved_env
124
+
125
+ def _create_lambda_function(self, function_name: str) -> None:
126
+ """Create the Lambda function"""
127
+
128
+ # Resolve code path (relative to runtime directory or absolute)
129
+ code_path = Path(self.edge_config.code_path)
130
+ if not code_path.is_absolute():
131
+ # Assume relative to the project root
132
+ code_path = Path.cwd() / code_path
133
+
134
+ if not code_path.exists():
135
+ raise FileNotFoundError(
136
+ f"Lambda code path does not exist: {code_path}\n"
137
+ f"Current working directory: {Path.cwd()}"
138
+ )
139
+
140
+ logger.info(f"Loading Lambda code from: {code_path}")
141
+
142
+ # Map runtime string to CDK Runtime
143
+ runtime_map = {
144
+ "python3.11": _lambda.Runtime.PYTHON_3_11,
145
+ "python3.10": _lambda.Runtime.PYTHON_3_10,
146
+ "python3.9": _lambda.Runtime.PYTHON_3_9,
147
+ "python3.12": _lambda.Runtime.PYTHON_3_12,
148
+ "nodejs18.x": _lambda.Runtime.NODEJS_18_X,
149
+ "nodejs20.x": _lambda.Runtime.NODEJS_20_X,
150
+ }
151
+
152
+ runtime = runtime_map.get(
153
+ self.edge_config.runtime,
154
+ _lambda.Runtime.PYTHON_3_11
155
+ )
156
+
157
+ # Resolve environment variables (handles SSM parameter references)
158
+ resolved_environment = self._resolve_environment_variables()
159
+
160
+ # Create execution role with CloudWatch Logs permissions
161
+ execution_role = iam.Role(
162
+ self,
163
+ f"{function_name}-Role",
164
+ assumed_by=iam.CompositePrincipal(
165
+ iam.ServicePrincipal("lambda.amazonaws.com"),
166
+ iam.ServicePrincipal("edgelambda.amazonaws.com")
167
+ ),
168
+ description=f"Execution role for Lambda@Edge function {function_name}",
169
+ managed_policies=[
170
+ iam.ManagedPolicy.from_aws_managed_policy_name(
171
+ "service-role/AWSLambdaBasicExecutionRole"
172
+ )
173
+ ]
174
+ )
175
+
176
+ # Create the Lambda function
177
+ self.function = _lambda.Function(
178
+ self,
179
+ function_name,
180
+ function_name=function_name,
181
+ runtime=runtime,
182
+ handler=self.edge_config.handler,
183
+ code=_lambda.Code.from_asset(str(code_path)),
184
+ memory_size=self.edge_config.memory_size,
185
+ timeout=cdk.Duration.seconds(self.edge_config.timeout),
186
+ description=self.edge_config.description,
187
+ role=execution_role,
188
+ environment=resolved_environment,
189
+ log_retention=logs.RetentionDays.ONE_WEEK,
190
+ )
191
+
192
+ # Add tags
193
+ for key, value in self.edge_config.tags.items():
194
+ cdk.Tags.of(self.function).add(key, value)
195
+
196
+ def _create_function_version(self, function_name: str) -> None:
197
+ """
198
+ Create a version of the Lambda function.
199
+ Lambda@Edge requires versioned functions (cannot use $LATEST).
200
+ """
201
+ self.function_version = self.function.current_version
202
+
203
+ # Add description to version
204
+ cfn_version = self.function_version.node.default_child
205
+ if cfn_version:
206
+ cfn_version.add_property_override(
207
+ "Description",
208
+ f"Version for Lambda@Edge deployment - {self.edge_config.description}"
209
+ )
210
+
211
+ def _add_outputs(self, function_name: str) -> None:
212
+ """Add CloudFormation outputs and SSM exports"""
213
+
214
+ # CloudFormation outputs
215
+ cdk.CfnOutput(
216
+ self,
217
+ "FunctionName",
218
+ value=self.function.function_name,
219
+ description="Lambda function name",
220
+ export_name=f"{function_name}-name"
221
+ )
222
+
223
+ cdk.CfnOutput(
224
+ self,
225
+ "FunctionArn",
226
+ value=self.function.function_arn,
227
+ description="Lambda function ARN (unversioned)",
228
+ export_name=f"{function_name}-arn"
229
+ )
230
+
231
+ cdk.CfnOutput(
232
+ self,
233
+ "FunctionVersionArn",
234
+ value=self.function_version.function_arn,
235
+ description="Lambda function version ARN (use this for Lambda@Edge)",
236
+ export_name=f"{function_name}-version-arn"
237
+ )
238
+
239
+ # SSM Parameter Store exports (if configured)
240
+ ssm_exports = self.edge_config.dictionary.get("ssm_exports", {})
241
+ if ssm_exports:
242
+ export_values = {
243
+ "function_name": self.function.function_name,
244
+ "function_arn": self.function.function_arn,
245
+ "function_version_arn": self.function_version.function_arn,
246
+ "function_version": self.function_version.version,
247
+ }
248
+
249
+ # Export each value to SSM using the enhanced parameter mixin
250
+ for key, param_path in ssm_exports.items():
251
+ if key in export_values:
252
+ self.export_ssm_parameter(
253
+ self,
254
+ f"{key}-param",
255
+ export_values[key],
256
+ param_path,
257
+ description=f"{key} for Lambda@Edge function {function_name}"
258
+ )
@@ -0,0 +1,6 @@
1
+ """
2
+ Monitoring Stack Library Module
3
+ """
4
+ from cdk_factory.stack_library.monitoring.monitoring_stack import MonitoringStack
5
+
6
+ __all__ = ["MonitoringStack"]