cdk-factory 0.9.12__py3-none-any.whl → 0.10.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.
- cdk_factory/configurations/resources/auto_scaling.py +27 -0
- cdk_factory/configurations/resources/cloudfront.py +101 -11
- cdk_factory/configurations/resources/ecs_service.py +12 -0
- cdk_factory/configurations/resources/lambda_edge.py +92 -0
- cdk_factory/configurations/resources/monitoring.py +74 -0
- cdk_factory/constructs/cloudfront/cloudfront_distribution_construct.py +51 -1
- cdk_factory/lambdas/edge/ip_gate/handler.py +104 -0
- cdk_factory/stack_library/auto_scaling/auto_scaling_stack.py +99 -0
- cdk_factory/stack_library/cloudfront/__init__.py +6 -0
- cdk_factory/stack_library/cloudfront/cloudfront_stack.py +627 -0
- cdk_factory/stack_library/ecs/ecs_service_stack.py +90 -0
- cdk_factory/stack_library/lambda_edge/__init__.py +6 -0
- cdk_factory/stack_library/lambda_edge/lambda_edge_stack.py +217 -0
- cdk_factory/stack_library/monitoring/__init__.py +6 -0
- cdk_factory/stack_library/monitoring/monitoring_stack.py +492 -0
- cdk_factory/version.py +1 -1
- cdk_factory/workload/workload_factory.py +2 -0
- {cdk_factory-0.9.12.dist-info → cdk_factory-0.10.0.dist-info}/METADATA +1 -1
- {cdk_factory-0.9.12.dist-info → cdk_factory-0.10.0.dist-info}/RECORD +22 -13
- {cdk_factory-0.9.12.dist-info → cdk_factory-0.10.0.dist-info}/WHEEL +0 -0
- {cdk_factory-0.9.12.dist-info → cdk_factory-0.10.0.dist-info}/entry_points.txt +0 -0
- {cdk_factory-0.9.12.dist-info → cdk_factory-0.10.0.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,217 @@
|
|
|
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
|
|
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_lambda_powertools import Logger
|
|
17
|
+
from constructs import Construct
|
|
18
|
+
|
|
19
|
+
from cdk_factory.configurations.deployment import DeploymentConfig
|
|
20
|
+
from cdk_factory.configurations.stack import StackConfig
|
|
21
|
+
from cdk_factory.configurations.resources.lambda_edge import LambdaEdgeConfig
|
|
22
|
+
from cdk_factory.interfaces.istack import IStack
|
|
23
|
+
from cdk_factory.interfaces.enhanced_ssm_parameter_mixin import EnhancedSsmParameterMixin
|
|
24
|
+
from cdk_factory.stack.stack_module_registry import register_stack
|
|
25
|
+
from cdk_factory.workload.workload_factory import WorkloadConfig
|
|
26
|
+
|
|
27
|
+
logger = Logger(service="LambdaEdgeStack")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@register_stack("lambda_edge_library_module")
|
|
31
|
+
@register_stack("lambda_edge_stack")
|
|
32
|
+
class LambdaEdgeStack(IStack, EnhancedSsmParameterMixin):
|
|
33
|
+
"""
|
|
34
|
+
Reusable stack for Lambda@Edge functions.
|
|
35
|
+
|
|
36
|
+
Lambda@Edge constraints:
|
|
37
|
+
- Must be deployed in us-east-1
|
|
38
|
+
- Requires versioned functions (not $LATEST)
|
|
39
|
+
- Max timeout: 5s for origin-request, 30s for viewer-request
|
|
40
|
+
- No environment variables in viewer-request/response (origin-request/response only)
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __init__(self, scope: Construct, id: str, **kwargs) -> None:
|
|
44
|
+
super().__init__(scope, id, **kwargs)
|
|
45
|
+
self.edge_config: Optional[LambdaEdgeConfig] = None
|
|
46
|
+
self.stack_config: Optional[StackConfig] = None
|
|
47
|
+
self.deployment: Optional[DeploymentConfig] = None
|
|
48
|
+
self.workload: Optional[WorkloadConfig] = None
|
|
49
|
+
self.function: Optional[_lambda.Function] = None
|
|
50
|
+
self.function_version: Optional[_lambda.Version] = None
|
|
51
|
+
|
|
52
|
+
def build(
|
|
53
|
+
self,
|
|
54
|
+
stack_config: StackConfig,
|
|
55
|
+
deployment: DeploymentConfig,
|
|
56
|
+
workload: WorkloadConfig,
|
|
57
|
+
) -> None:
|
|
58
|
+
"""Build the Lambda@Edge stack"""
|
|
59
|
+
self._build(stack_config, deployment, workload)
|
|
60
|
+
|
|
61
|
+
def _build(
|
|
62
|
+
self,
|
|
63
|
+
stack_config: StackConfig,
|
|
64
|
+
deployment: DeploymentConfig,
|
|
65
|
+
workload: WorkloadConfig,
|
|
66
|
+
) -> None:
|
|
67
|
+
"""Internal build method for the Lambda@Edge stack"""
|
|
68
|
+
self.stack_config = stack_config
|
|
69
|
+
self.deployment = deployment
|
|
70
|
+
self.workload = workload
|
|
71
|
+
|
|
72
|
+
# Validate region (Lambda@Edge must be in us-east-1)
|
|
73
|
+
if self.region != "us-east-1":
|
|
74
|
+
logger.warning(
|
|
75
|
+
f"Lambda@Edge must be deployed in us-east-1, but stack region is {self.region}. "
|
|
76
|
+
"Make sure your deployment config specifies us-east-1."
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# Load Lambda@Edge configuration
|
|
80
|
+
self.edge_config = LambdaEdgeConfig(
|
|
81
|
+
stack_config.dictionary.get("lambda_edge", {}),
|
|
82
|
+
deployment
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
function_name = deployment.build_resource_name(self.edge_config.name)
|
|
86
|
+
|
|
87
|
+
# Create Lambda function
|
|
88
|
+
self._create_lambda_function(function_name)
|
|
89
|
+
|
|
90
|
+
# Create version (required for Lambda@Edge)
|
|
91
|
+
self._create_function_version(function_name)
|
|
92
|
+
|
|
93
|
+
# Add outputs
|
|
94
|
+
self._add_outputs(function_name)
|
|
95
|
+
|
|
96
|
+
def _create_lambda_function(self, function_name: str) -> None:
|
|
97
|
+
"""Create the Lambda function"""
|
|
98
|
+
|
|
99
|
+
# Resolve code path (relative to runtime directory or absolute)
|
|
100
|
+
code_path = Path(self.edge_config.code_path)
|
|
101
|
+
if not code_path.is_absolute():
|
|
102
|
+
# Assume relative to the project root
|
|
103
|
+
code_path = Path.cwd() / code_path
|
|
104
|
+
|
|
105
|
+
if not code_path.exists():
|
|
106
|
+
raise FileNotFoundError(
|
|
107
|
+
f"Lambda code path does not exist: {code_path}\n"
|
|
108
|
+
f"Current working directory: {Path.cwd()}"
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
logger.info(f"Loading Lambda code from: {code_path}")
|
|
112
|
+
|
|
113
|
+
# Map runtime string to CDK Runtime
|
|
114
|
+
runtime_map = {
|
|
115
|
+
"python3.11": _lambda.Runtime.PYTHON_3_11,
|
|
116
|
+
"python3.10": _lambda.Runtime.PYTHON_3_10,
|
|
117
|
+
"python3.9": _lambda.Runtime.PYTHON_3_9,
|
|
118
|
+
"python3.12": _lambda.Runtime.PYTHON_3_12,
|
|
119
|
+
"nodejs18.x": _lambda.Runtime.NODEJS_18_X,
|
|
120
|
+
"nodejs20.x": _lambda.Runtime.NODEJS_20_X,
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
runtime = runtime_map.get(
|
|
124
|
+
self.edge_config.runtime,
|
|
125
|
+
_lambda.Runtime.PYTHON_3_11
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
# Create execution role with CloudWatch Logs permissions
|
|
129
|
+
execution_role = iam.Role(
|
|
130
|
+
self,
|
|
131
|
+
f"{function_name}-Role",
|
|
132
|
+
assumed_by=iam.CompositePrincipal(
|
|
133
|
+
iam.ServicePrincipal("lambda.amazonaws.com"),
|
|
134
|
+
iam.ServicePrincipal("edgelambda.amazonaws.com")
|
|
135
|
+
),
|
|
136
|
+
description=f"Execution role for Lambda@Edge function {function_name}",
|
|
137
|
+
managed_policies=[
|
|
138
|
+
iam.ManagedPolicy.from_aws_managed_policy_name(
|
|
139
|
+
"service-role/AWSLambdaBasicExecutionRole"
|
|
140
|
+
)
|
|
141
|
+
]
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# Create the Lambda function
|
|
145
|
+
self.function = _lambda.Function(
|
|
146
|
+
self,
|
|
147
|
+
function_name,
|
|
148
|
+
function_name=function_name,
|
|
149
|
+
runtime=runtime,
|
|
150
|
+
handler=self.edge_config.handler,
|
|
151
|
+
code=_lambda.Code.from_asset(str(code_path)),
|
|
152
|
+
memory_size=self.edge_config.memory_size,
|
|
153
|
+
timeout=cdk.Duration.seconds(self.edge_config.timeout),
|
|
154
|
+
description=self.edge_config.description,
|
|
155
|
+
role=execution_role,
|
|
156
|
+
environment=self.edge_config.environment,
|
|
157
|
+
log_retention=logs.RetentionDays.ONE_WEEK,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
# Add tags
|
|
161
|
+
for key, value in self.edge_config.tags.items():
|
|
162
|
+
cdk.Tags.of(self.function).add(key, value)
|
|
163
|
+
|
|
164
|
+
def _create_function_version(self, function_name: str) -> None:
|
|
165
|
+
"""
|
|
166
|
+
Create a version of the Lambda function.
|
|
167
|
+
Lambda@Edge requires versioned functions (cannot use $LATEST).
|
|
168
|
+
"""
|
|
169
|
+
self.function_version = self.function.current_version
|
|
170
|
+
|
|
171
|
+
# Add description to version
|
|
172
|
+
cfn_version = self.function_version.node.default_child
|
|
173
|
+
if cfn_version:
|
|
174
|
+
cfn_version.add_property_override(
|
|
175
|
+
"Description",
|
|
176
|
+
f"Version for Lambda@Edge deployment - {self.edge_config.description}"
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
def _add_outputs(self, function_name: str) -> None:
|
|
180
|
+
"""Add CloudFormation outputs and SSM exports"""
|
|
181
|
+
|
|
182
|
+
# CloudFormation outputs
|
|
183
|
+
cdk.CfnOutput(
|
|
184
|
+
self,
|
|
185
|
+
"FunctionName",
|
|
186
|
+
value=self.function.function_name,
|
|
187
|
+
description="Lambda function name",
|
|
188
|
+
export_name=f"{function_name}-name"
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
cdk.CfnOutput(
|
|
192
|
+
self,
|
|
193
|
+
"FunctionArn",
|
|
194
|
+
value=self.function.function_arn,
|
|
195
|
+
description="Lambda function ARN (unversioned)",
|
|
196
|
+
export_name=f"{function_name}-arn"
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
cdk.CfnOutput(
|
|
200
|
+
self,
|
|
201
|
+
"FunctionVersionArn",
|
|
202
|
+
value=self.function_version.function_arn,
|
|
203
|
+
description="Lambda function version ARN (use this for Lambda@Edge)",
|
|
204
|
+
export_name=f"{function_name}-version-arn"
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
# SSM Parameter Store exports (if configured)
|
|
208
|
+
ssm_exports = self.edge_config.dictionary.get("ssm_exports", {})
|
|
209
|
+
if ssm_exports:
|
|
210
|
+
export_values = {
|
|
211
|
+
"function_name": self.function.function_name,
|
|
212
|
+
"function_arn": self.function.function_arn,
|
|
213
|
+
"function_version_arn": self.function_version.function_arn,
|
|
214
|
+
"function_version": self.function_version.version,
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
self.store_ssm_parameters(self.edge_config, export_values)
|