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.
- cdk_factory/configurations/base_config.py +23 -24
- cdk_factory/configurations/cdk_config.py +1 -1
- cdk_factory/configurations/deployment.py +12 -0
- cdk_factory/configurations/devops.py +1 -1
- cdk_factory/configurations/resources/acm.py +9 -2
- cdk_factory/configurations/resources/auto_scaling.py +7 -5
- cdk_factory/configurations/resources/cloudfront.py +7 -2
- cdk_factory/configurations/resources/ecr.py +1 -1
- cdk_factory/configurations/resources/ecs_cluster.py +12 -5
- cdk_factory/configurations/resources/ecs_service.py +30 -3
- cdk_factory/configurations/resources/lambda_edge.py +18 -4
- cdk_factory/configurations/resources/load_balancer.py +8 -9
- cdk_factory/configurations/resources/monitoring.py +8 -3
- cdk_factory/configurations/resources/rds.py +8 -9
- cdk_factory/configurations/resources/route53.py +5 -0
- cdk_factory/configurations/resources/rum.py +7 -2
- cdk_factory/configurations/resources/s3.py +10 -2
- cdk_factory/configurations/resources/security_group_full_stack.py +7 -8
- cdk_factory/configurations/resources/vpc.py +19 -0
- cdk_factory/configurations/workload.py +32 -2
- cdk_factory/constructs/cloudfront/cloudfront_distribution_construct.py +1 -1
- cdk_factory/constructs/ecr/ecr_construct.py +9 -2
- cdk_factory/constructs/lambdas/policies/policy_docs.py +4 -4
- cdk_factory/interfaces/istack.py +4 -4
- cdk_factory/interfaces/networked_stack_mixin.py +6 -6
- cdk_factory/interfaces/standardized_ssm_mixin.py +684 -0
- cdk_factory/interfaces/vpc_provider_mixin.py +64 -33
- cdk_factory/lambdas/edge/ip_gate/handler.py +42 -40
- cdk_factory/pipeline/pipeline_factory.py +3 -3
- cdk_factory/stack_library/__init__.py +3 -2
- cdk_factory/stack_library/acm/acm_stack.py +7 -17
- cdk_factory/stack_library/api_gateway/api_gateway_stack.py +84 -59
- cdk_factory/stack_library/auto_scaling/auto_scaling_stack.py +454 -537
- cdk_factory/stack_library/cloudfront/cloudfront_stack.py +76 -22
- cdk_factory/stack_library/code_artifact/code_artifact_stack.py +5 -27
- cdk_factory/stack_library/cognito/cognito_stack.py +152 -92
- cdk_factory/stack_library/dynamodb/dynamodb_stack.py +19 -15
- cdk_factory/stack_library/ecr/ecr_stack.py +2 -2
- cdk_factory/stack_library/ecs/__init__.py +1 -3
- cdk_factory/stack_library/ecs/ecs_cluster_stack.py +159 -75
- cdk_factory/stack_library/ecs/ecs_service_stack.py +59 -52
- cdk_factory/stack_library/lambda_edge/EDGE_LOG_RETENTION_TODO.md +226 -0
- cdk_factory/stack_library/lambda_edge/LAMBDA_EDGE_LOG_RETENTION_BLOG.md +215 -0
- cdk_factory/stack_library/lambda_edge/lambda_edge_stack.py +240 -83
- cdk_factory/stack_library/load_balancer/load_balancer_stack.py +139 -212
- cdk_factory/stack_library/rds/rds_stack.py +74 -98
- cdk_factory/stack_library/route53/route53_stack.py +246 -40
- cdk_factory/stack_library/rum/rum_stack.py +108 -91
- cdk_factory/stack_library/security_group/security_group_full_stack.py +10 -53
- cdk_factory/stack_library/security_group/security_group_stack.py +12 -19
- cdk_factory/stack_library/simple_queue_service/sqs_stack.py +1 -34
- cdk_factory/stack_library/stack_base.py +5 -0
- cdk_factory/stack_library/vpc/vpc_stack.py +171 -130
- cdk_factory/stack_library/websites/static_website_stack.py +7 -3
- cdk_factory/utilities/api_gateway_integration_utility.py +24 -16
- cdk_factory/utilities/environment_services.py +5 -5
- cdk_factory/utilities/json_loading_utility.py +1 -1
- cdk_factory/validation/config_validator.py +483 -0
- cdk_factory/version.py +1 -1
- {cdk_factory-0.16.15.dist-info → cdk_factory-0.20.0.dist-info}/METADATA +1 -1
- {cdk_factory-0.16.15.dist-info → cdk_factory-0.20.0.dist-info}/RECORD +64 -62
- cdk_factory/interfaces/enhanced_ssm_parameter_mixin.py +0 -321
- cdk_factory/interfaces/ssm_parameter_mixin.py +0 -454
- {cdk_factory-0.16.15.dist-info → cdk_factory-0.20.0.dist-info}/WHEEL +0 -0
- {cdk_factory-0.16.15.dist-info → cdk_factory-0.20.0.dist-info}/entry_points.txt +0 -0
- {cdk_factory-0.16.15.dist-info → cdk_factory-0.20.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"""
|
|
2
|
-
ECS Cluster Stack Module
|
|
2
|
+
ECS Cluster Stack Module (Standardized SSM Version)
|
|
3
3
|
|
|
4
4
|
Provides a dedicated stack for creating and configuring ECS clusters
|
|
5
|
-
with proper configurability
|
|
5
|
+
with proper configurability, explicit resource management, and standardized SSM integration.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import logging
|
|
@@ -10,6 +10,7 @@ from typing import Optional, Dict, Any
|
|
|
10
10
|
|
|
11
11
|
from aws_cdk import (
|
|
12
12
|
aws_ecs as ecs,
|
|
13
|
+
aws_ec2 as ec2,
|
|
13
14
|
aws_iam as iam,
|
|
14
15
|
CfnOutput,
|
|
15
16
|
)
|
|
@@ -19,6 +20,7 @@ from cdk_factory.configurations.stack import StackConfig
|
|
|
19
20
|
from cdk_factory.configurations.deployment import DeploymentConfig
|
|
20
21
|
from cdk_factory.configurations.workload import WorkloadConfig
|
|
21
22
|
from cdk_factory.interfaces.vpc_provider_mixin import VPCProviderMixin
|
|
23
|
+
from cdk_factory.interfaces.standardized_ssm_mixin import StandardizedSsmMixin
|
|
22
24
|
from cdk_factory.configurations.resources.ecs_cluster import EcsClusterConfig
|
|
23
25
|
from cdk_factory.stack.stack_module_registry import register_stack
|
|
24
26
|
from cdk_factory.interfaces.istack import IStack
|
|
@@ -27,16 +29,18 @@ logger = logging.getLogger(__name__)
|
|
|
27
29
|
|
|
28
30
|
|
|
29
31
|
@register_stack("ecs_cluster_stack")
|
|
30
|
-
class EcsClusterStack(IStack, VPCProviderMixin):
|
|
32
|
+
class EcsClusterStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
|
|
31
33
|
"""
|
|
32
|
-
A dedicated stack for creating and managing ECS clusters.
|
|
34
|
+
A dedicated stack for creating and managing ECS clusters with standardized SSM integration.
|
|
33
35
|
|
|
34
36
|
This stack provides explicit configuration of ECS clusters including:
|
|
35
37
|
- Cluster naming
|
|
36
38
|
- Container insights
|
|
37
39
|
- Cluster settings
|
|
38
|
-
- SSM parameter exports
|
|
40
|
+
- Standardized SSM parameter exports
|
|
39
41
|
- IAM role configurations
|
|
42
|
+
- Template variable resolution
|
|
43
|
+
- Comprehensive validation
|
|
40
44
|
"""
|
|
41
45
|
|
|
42
46
|
def __init__(self, scope: Construct, id: str, **kwargs) -> None:
|
|
@@ -58,11 +62,6 @@ class EcsClusterStack(IStack, VPCProviderMixin):
|
|
|
58
62
|
self.ecs_cluster: Optional[ecs.Cluster] = None
|
|
59
63
|
self.instance_role: Optional[iam.Role] = None
|
|
60
64
|
self.instance_profile: Optional[iam.CfnInstanceProfile] = None
|
|
61
|
-
|
|
62
|
-
# SSM imported values
|
|
63
|
-
self.ssm_imported_values: Dict[str, Any] = {}
|
|
64
|
-
|
|
65
|
-
|
|
66
65
|
|
|
67
66
|
def build(
|
|
68
67
|
self,
|
|
@@ -87,15 +86,30 @@ class EcsClusterStack(IStack, VPCProviderMixin):
|
|
|
87
86
|
# Initialize VPC cache from mixin
|
|
88
87
|
self._initialize_vpc_cache()
|
|
89
88
|
|
|
90
|
-
# Load ECS cluster configuration
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
89
|
+
# Load ECS cluster configuration with full stack config for SSM access
|
|
90
|
+
ecs_cluster_dict = stack_config.dictionary.get("ecs_cluster", {})
|
|
91
|
+
# Merge SSM config from root level into ECS config for VPC resolution
|
|
92
|
+
if "ssm" in stack_config.dictionary:
|
|
93
|
+
ecs_cluster_dict["ssm"] = stack_config.dictionary["ssm"]
|
|
94
94
|
|
|
95
|
-
|
|
95
|
+
self.ecs_config: EcsClusterConfig = EcsClusterConfig(ecs_cluster_dict)
|
|
96
96
|
|
|
97
|
-
|
|
98
|
-
|
|
97
|
+
cluster_name = deployment.build_resource_name(self.ecs_config.name)
|
|
98
|
+
|
|
99
|
+
logger.info(f"Creating ECS Cluster stack: {cluster_name}")
|
|
100
|
+
|
|
101
|
+
# Setup standardized SSM integration
|
|
102
|
+
self.setup_ssm_integration(
|
|
103
|
+
scope=self,
|
|
104
|
+
config=self.ecs_config,
|
|
105
|
+
resource_type="ecs_cluster",
|
|
106
|
+
resource_name=cluster_name,
|
|
107
|
+
deployment=deployment,
|
|
108
|
+
workload=workload
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
# Process SSM imports using standardized method
|
|
112
|
+
self.process_ssm_imports()
|
|
99
113
|
|
|
100
114
|
# Create the ECS cluster
|
|
101
115
|
self._create_ecs_cluster()
|
|
@@ -106,7 +120,12 @@ class EcsClusterStack(IStack, VPCProviderMixin):
|
|
|
106
120
|
# Export cluster information
|
|
107
121
|
self._export_cluster_info()
|
|
108
122
|
|
|
109
|
-
|
|
123
|
+
# Export SSM parameters
|
|
124
|
+
logger.info("Starting SSM parameter export for ECS cluster")
|
|
125
|
+
self._export_ssm_parameters()
|
|
126
|
+
logger.info("Completed SSM parameter export for ECS cluster")
|
|
127
|
+
|
|
128
|
+
logger.info(f"ECS Cluster stack created: {cluster_name}")
|
|
110
129
|
|
|
111
130
|
def _create_ecs_cluster(self):
|
|
112
131
|
"""Create the ECS cluster with explicit configuration."""
|
|
@@ -117,25 +136,22 @@ class EcsClusterStack(IStack, VPCProviderMixin):
|
|
|
117
136
|
|
|
118
137
|
# Add container insights if enabled
|
|
119
138
|
if self.ecs_config.container_insights:
|
|
120
|
-
cluster_settings.append({"name": "
|
|
139
|
+
cluster_settings.append({"name": "containerInsightsV2", "value": "enabled"})
|
|
121
140
|
|
|
122
141
|
# Add custom cluster settings
|
|
123
142
|
if self.ecs_config.cluster_settings:
|
|
124
143
|
cluster_settings.extend(self.ecs_config.cluster_settings)
|
|
125
144
|
|
|
126
|
-
#
|
|
127
|
-
self.vpc = self.
|
|
128
|
-
config=self.ecs_config,
|
|
129
|
-
deployment=self.deployment,
|
|
130
|
-
workload=self.workload
|
|
131
|
-
)
|
|
145
|
+
# Get VPC using standardized approach
|
|
146
|
+
self.vpc = self._get_vpc()
|
|
132
147
|
|
|
148
|
+
# Create the ECS cluster
|
|
133
149
|
self.ecs_cluster = ecs.Cluster(
|
|
134
150
|
self,
|
|
135
151
|
"ECSCluster",
|
|
136
152
|
cluster_name=self.ecs_config.name,
|
|
137
153
|
vpc=self.vpc,
|
|
138
|
-
|
|
154
|
+
container_insights_v2=ecs.ContainerInsights.ENABLED if self.ecs_config.container_insights else ecs.ContainerInsights.DISABLED,
|
|
139
155
|
default_cloud_map_namespace=(
|
|
140
156
|
self.ecs_config.cloud_map_namespace
|
|
141
157
|
if self.ecs_config.cloud_map_namespace
|
|
@@ -148,11 +164,26 @@ class EcsClusterStack(IStack, VPCProviderMixin):
|
|
|
148
164
|
),
|
|
149
165
|
)
|
|
150
166
|
|
|
151
|
-
logger.info(f"
|
|
167
|
+
logger.info(f"ECS cluster created: {self.ecs_config.name}")
|
|
168
|
+
|
|
169
|
+
def _get_vpc(self):
|
|
170
|
+
"""
|
|
171
|
+
Get VPC using the centralized VPC provider mixin.
|
|
172
|
+
"""
|
|
173
|
+
|
|
174
|
+
# Use the stack_config (not ecs_config) to ensure SSM imports are available
|
|
175
|
+
return self.resolve_vpc(
|
|
176
|
+
config=self.ecs_config,
|
|
177
|
+
deployment=self.deployment,
|
|
178
|
+
workload=self.workload
|
|
179
|
+
)
|
|
152
180
|
|
|
153
181
|
def _create_iam_roles(self):
|
|
154
182
|
"""Create IAM roles for the ECS cluster if configured."""
|
|
183
|
+
logger.info(f"create_instance_role setting: {self.ecs_config.create_instance_role}")
|
|
184
|
+
|
|
155
185
|
if not self.ecs_config.create_instance_role:
|
|
186
|
+
logger.info("Skipping instance role creation (disabled in config)")
|
|
156
187
|
return
|
|
157
188
|
|
|
158
189
|
logger.info("Creating ECS instance role")
|
|
@@ -162,71 +193,124 @@ class EcsClusterStack(IStack, VPCProviderMixin):
|
|
|
162
193
|
self,
|
|
163
194
|
"ECSInstanceRole",
|
|
164
195
|
assumed_by=iam.ServicePrincipal("ec2.amazonaws.com"),
|
|
165
|
-
|
|
166
|
-
|
|
196
|
+
managed_policies=[
|
|
197
|
+
iam.ManagedPolicy.from_aws_managed_policy_name("service-role/AmazonEC2ContainerServiceforEC2Role"),
|
|
198
|
+
iam.ManagedPolicy.from_aws_managed_policy_name("AmazonEC2ContainerRegistryReadOnly"),
|
|
199
|
+
iam.ManagedPolicy.from_aws_managed_policy_name("AmazonSSMManagedInstanceCore"),
|
|
200
|
+
],
|
|
201
|
+
role_name=f"{self.ecs_config.name}-ecs-instance-role",
|
|
167
202
|
)
|
|
168
203
|
|
|
169
|
-
|
|
170
|
-
for policy in self.ecs_config.managed_policies:
|
|
171
|
-
self.instance_role.add_managed_policy(
|
|
172
|
-
iam.ManagedPolicy.from_aws_managed_policy_name(policy)
|
|
173
|
-
)
|
|
174
|
-
|
|
175
|
-
# Add inline policies if provided
|
|
176
|
-
if self.ecs_config.inline_policies:
|
|
177
|
-
for policy_name, policy_document in self.ecs_config.inline_policies.items():
|
|
178
|
-
self.instance_role.add_to_policy(
|
|
179
|
-
iam.PolicyStatement.from_json(policy_document)
|
|
180
|
-
)
|
|
204
|
+
logger.info(f"Created ECS instance role: {self.instance_role.role_name}")
|
|
181
205
|
|
|
182
206
|
# Create instance profile
|
|
183
207
|
self.instance_profile = iam.CfnInstanceProfile(
|
|
184
208
|
self,
|
|
185
209
|
"ECSInstanceProfile",
|
|
210
|
+
instance_profile_name=f"{self.ecs_config.name}-ecs-instance-profile",
|
|
186
211
|
roles=[self.instance_role.role_name],
|
|
187
|
-
instance_profile_name=self.ecs_config.instance_profile_name
|
|
188
|
-
or f"{self.ecs_config.name}-instance-profile",
|
|
189
212
|
)
|
|
190
213
|
|
|
191
|
-
logger.info("Created ECS instance
|
|
214
|
+
logger.info(f"Created ECS instance profile: {self.instance_profile.instance_profile_name}")
|
|
192
215
|
|
|
193
|
-
|
|
194
|
-
"""Export cluster information via SSM parameters and CloudFormation outputs."""
|
|
195
|
-
logger.info("Exporting ECS cluster information")
|
|
216
|
+
logger.info("ECS instance role and profile created")
|
|
196
217
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
if not
|
|
200
|
-
logger.info("No SSM exports configured for ECS Cluster")
|
|
218
|
+
def _export_cluster_info(self):
|
|
219
|
+
"""Export cluster information as CloudFormation outputs."""
|
|
220
|
+
if not self.ecs_cluster:
|
|
201
221
|
return
|
|
202
222
|
|
|
203
|
-
|
|
223
|
+
cluster_name = self.deployment.build_resource_name(self.ecs_config.name)
|
|
204
224
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
225
|
+
# Export cluster name
|
|
226
|
+
CfnOutput(
|
|
227
|
+
self,
|
|
228
|
+
f"{cluster_name}-ClusterName",
|
|
229
|
+
value=self.ecs_cluster.cluster_name,
|
|
230
|
+
description=f"ECS Cluster Name for {cluster_name}",
|
|
231
|
+
export_name=f"{self.deployment.workload_name}-{self.deployment.environment}-ecs-cluster-name",
|
|
232
|
+
)
|
|
213
233
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
234
|
+
# Export cluster ARN
|
|
235
|
+
CfnOutput(
|
|
236
|
+
self,
|
|
237
|
+
f"{cluster_name}-ClusterArn",
|
|
238
|
+
value=self.ecs_cluster.cluster_arn,
|
|
239
|
+
description=f"ECS Cluster ARN for {cluster_name}",
|
|
240
|
+
export_name=f"{self.deployment.workload_name}-{self.deployment.environment}-ecs-cluster-arn",
|
|
241
|
+
)
|
|
222
242
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
243
|
+
# Export security group if available
|
|
244
|
+
if hasattr(self.ecs_cluster, 'connections') and self.ecs_cluster.connections:
|
|
245
|
+
security_groups = self.ecs_cluster.connections.security_groups
|
|
246
|
+
if security_groups:
|
|
247
|
+
CfnOutput(
|
|
248
|
+
self,
|
|
249
|
+
f"{cluster_name}-SecurityGroupId",
|
|
250
|
+
value=security_groups[0].security_group_id,
|
|
251
|
+
description=f"ECS Cluster Security Group ID for {cluster_name}",
|
|
252
|
+
export_name=f"{self.deployment.workload_name}-{self.deployment.environment}-ecs-cluster-sg-id",
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
# Export instance profile if created
|
|
256
|
+
if self.instance_profile:
|
|
257
|
+
CfnOutput(
|
|
258
|
+
self,
|
|
259
|
+
f"{cluster_name}-InstanceProfileArn",
|
|
260
|
+
value=self.instance_profile.attr_arn,
|
|
261
|
+
description=f"ECS Instance Profile ARN for {cluster_name}",
|
|
262
|
+
export_name=f"{self.deployment.workload_name}-{self.deployment.environment}-ecs-instance-profile-arn",
|
|
230
263
|
)
|
|
264
|
+
|
|
265
|
+
logger.info("ECS cluster information exported as outputs")
|
|
266
|
+
|
|
267
|
+
def _export_ssm_parameters(self) -> None:
|
|
268
|
+
"""Export SSM parameters using standardized approach"""
|
|
269
|
+
logger.info("=== Starting SSM Parameter Export ===")
|
|
231
270
|
|
|
271
|
+
if not self.ecs_cluster:
|
|
272
|
+
logger.warning("No ECS cluster to export")
|
|
273
|
+
return
|
|
274
|
+
|
|
275
|
+
logger.info(f"ECS cluster found: {self.ecs_cluster.cluster_name}")
|
|
276
|
+
logger.info(f"SSM exports configured: {self.ssm_config.get('exports', {})}")
|
|
277
|
+
|
|
278
|
+
# Prepare resource values for export
|
|
279
|
+
resource_values = {
|
|
280
|
+
"cluster_name": self.ecs_cluster.cluster_name,
|
|
281
|
+
"cluster_arn": self.ecs_cluster.cluster_arn,
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
# Add instance role ARN if created
|
|
285
|
+
if self.instance_role:
|
|
286
|
+
resource_values["instance_role_arn"] = self.instance_role.role_arn
|
|
287
|
+
logger.info(f"Instance role ARN added: {self.instance_role.role_name}")
|
|
288
|
+
else:
|
|
289
|
+
logger.info("No instance role to export")
|
|
290
|
+
|
|
291
|
+
# Add security group ID if available
|
|
292
|
+
if hasattr(self.ecs_cluster, 'connections') and self.ecs_cluster.connections:
|
|
293
|
+
security_groups = self.ecs_cluster.connections.security_groups
|
|
294
|
+
if security_groups:
|
|
295
|
+
resource_values["security_group_id"] = security_groups[0].security_group_id
|
|
296
|
+
logger.info(f"Security group ID added: {security_groups[0].security_group_id}")
|
|
232
297
|
|
|
298
|
+
# Add instance profile ARN if created
|
|
299
|
+
if self.instance_profile:
|
|
300
|
+
resource_values["instance_profile_arn"] = self.instance_profile.attr_arn
|
|
301
|
+
logger.info(f"Instance profile ARN added: {self.instance_profile.instance_profile_name}")
|
|
302
|
+
|
|
303
|
+
# Export using standardized SSM mixin
|
|
304
|
+
logger.info(f"Resource values available for export: {list(resource_values.keys())}")
|
|
305
|
+
for key, value in resource_values.items():
|
|
306
|
+
logger.info(f" {key}: {value}")
|
|
307
|
+
|
|
308
|
+
try:
|
|
309
|
+
exported_params = self.export_ssm_parameters(resource_values)
|
|
310
|
+
logger.info(f"Successfully exported SSM parameters: {exported_params}")
|
|
311
|
+
except Exception as e:
|
|
312
|
+
logger.error(f"Failed to export SSM parameters: {str(e)}")
|
|
313
|
+
raise
|
|
314
|
+
|
|
315
|
+
# Backward compatibility alias
|
|
316
|
+
EcsClusterStackStandardized = EcsClusterStack
|
|
@@ -23,7 +23,8 @@ from cdk_factory.configurations.deployment import DeploymentConfig
|
|
|
23
23
|
from cdk_factory.configurations.stack import StackConfig
|
|
24
24
|
from cdk_factory.configurations.resources.ecs_service import EcsServiceConfig
|
|
25
25
|
from cdk_factory.interfaces.istack import IStack
|
|
26
|
-
from cdk_factory.interfaces.
|
|
26
|
+
from cdk_factory.interfaces.vpc_provider_mixin import VPCProviderMixin
|
|
27
|
+
from cdk_factory.interfaces.standardized_ssm_mixin import StandardizedSsmMixin
|
|
27
28
|
from cdk_factory.stack.stack_module_registry import register_stack
|
|
28
29
|
from cdk_factory.workload.workload_factory import WorkloadConfig
|
|
29
30
|
|
|
@@ -33,7 +34,7 @@ logger = Logger(service="EcsServiceStack")
|
|
|
33
34
|
@register_stack("ecs_service_library_module")
|
|
34
35
|
@register_stack("ecs_service_stack")
|
|
35
36
|
@register_stack("fargate_service_stack")
|
|
36
|
-
class EcsServiceStack(IStack,
|
|
37
|
+
class EcsServiceStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
|
|
37
38
|
"""
|
|
38
39
|
Reusable stack for ECS/Fargate services with Docker container support.
|
|
39
40
|
Supports blue-green deployments, maintenance mode, and auto-scaling.
|
|
@@ -100,32 +101,16 @@ class EcsServiceStack(IStack, EnhancedSsmParameterMixin):
|
|
|
100
101
|
|
|
101
102
|
# Add outputs
|
|
102
103
|
self._add_outputs(service_name)
|
|
104
|
+
self._export_to_ssm(service_name)
|
|
103
105
|
|
|
104
106
|
def _load_vpc(self) -> None:
|
|
105
|
-
"""Load VPC
|
|
106
|
-
#
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
"vpc_id": vpc_id,
|
|
113
|
-
"availability_zones": ["us-east-1a", "us-east-1b"]
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
# Use from_vpc_attributes() for SSM tokens
|
|
117
|
-
self._vpc = ec2.Vpc.from_vpc_attributes(self, "VPC", **vpc_attrs)
|
|
118
|
-
else:
|
|
119
|
-
vpc_id = self.ecs_config.vpc_id or self.workload.vpc_id
|
|
120
|
-
|
|
121
|
-
if not vpc_id:
|
|
122
|
-
raise ValueError("VPC ID is required for ECS service")
|
|
123
|
-
|
|
124
|
-
self._vpc = ec2.Vpc.from_lookup(
|
|
125
|
-
self,
|
|
126
|
-
"VPC",
|
|
127
|
-
vpc_id=vpc_id
|
|
128
|
-
)
|
|
107
|
+
"""Load VPC using the centralized VPC provider mixin."""
|
|
108
|
+
# Use the centralized VPC resolution from VPCProviderMixin
|
|
109
|
+
self._vpc = self.resolve_vpc(
|
|
110
|
+
config=self.ecs_config,
|
|
111
|
+
deployment=self.deployment,
|
|
112
|
+
workload=self.workload
|
|
113
|
+
)
|
|
129
114
|
|
|
130
115
|
def _process_ssm_imports(self) -> None:
|
|
131
116
|
"""
|
|
@@ -241,6 +226,9 @@ class EcsServiceStack(IStack, EnhancedSsmParameterMixin):
|
|
|
241
226
|
"CloudWatchAgentServerPolicy"
|
|
242
227
|
)
|
|
243
228
|
)
|
|
229
|
+
|
|
230
|
+
# add any custom policies
|
|
231
|
+
self._add_custom_task_policies(task_role)
|
|
244
232
|
|
|
245
233
|
# Create task definition based on launch type
|
|
246
234
|
if self.ecs_config.launch_type == "EC2":
|
|
@@ -253,6 +241,7 @@ class EcsServiceStack(IStack, EnhancedSsmParameterMixin):
|
|
|
253
241
|
network_mode=ecs.NetworkMode(network_mode.upper()) if network_mode else ecs.NetworkMode.BRIDGE,
|
|
254
242
|
execution_role=execution_role,
|
|
255
243
|
task_role=task_role,
|
|
244
|
+
inference_accelerators=None
|
|
256
245
|
)
|
|
257
246
|
else:
|
|
258
247
|
# Fargate task definition
|
|
@@ -264,6 +253,7 @@ class EcsServiceStack(IStack, EnhancedSsmParameterMixin):
|
|
|
264
253
|
memory_limit_mib=int(self.ecs_config.memory),
|
|
265
254
|
execution_role=execution_role,
|
|
266
255
|
task_role=task_role,
|
|
256
|
+
inference_accelerators=None
|
|
267
257
|
)
|
|
268
258
|
|
|
269
259
|
# Add volumes to task definition
|
|
@@ -272,6 +262,37 @@ class EcsServiceStack(IStack, EnhancedSsmParameterMixin):
|
|
|
272
262
|
# Add containers
|
|
273
263
|
self._add_containers_to_task()
|
|
274
264
|
|
|
265
|
+
def _add_custom_task_policies(self, task_role: iam.Role) -> None:
|
|
266
|
+
"""
|
|
267
|
+
Add custom task policies to the task definition.
|
|
268
|
+
"""
|
|
269
|
+
for policy in self.ecs_config.task_definition.get("policies", []):
|
|
270
|
+
|
|
271
|
+
effect = policy.get("effect", "Allow")
|
|
272
|
+
action = policy.get("action", None)
|
|
273
|
+
actions = policy.get("actions", [])
|
|
274
|
+
if action:
|
|
275
|
+
actions.append(action)
|
|
276
|
+
resources = policy.get("resources", [])
|
|
277
|
+
resource = policy.get("resource", None)
|
|
278
|
+
if resource:
|
|
279
|
+
resources.append(resource)
|
|
280
|
+
|
|
281
|
+
if effect == "Allow" and actions:
|
|
282
|
+
effect = iam.Effect.ALLOW
|
|
283
|
+
if effect == "Deny" and actions:
|
|
284
|
+
effect = iam.Effect.DENY
|
|
285
|
+
|
|
286
|
+
sid = policy.get("sid", None)
|
|
287
|
+
task_role.add_to_policy(
|
|
288
|
+
iam.PolicyStatement(
|
|
289
|
+
effect=effect,
|
|
290
|
+
actions=actions,
|
|
291
|
+
resources=resources,
|
|
292
|
+
sid=sid,
|
|
293
|
+
)
|
|
294
|
+
)
|
|
295
|
+
|
|
275
296
|
def _add_volumes_to_task(self) -> None:
|
|
276
297
|
"""
|
|
277
298
|
Add volumes to the task definition.
|
|
@@ -556,6 +577,14 @@ class EcsServiceStack(IStack, EnhancedSsmParameterMixin):
|
|
|
556
577
|
"""Attach service to load balancer target groups"""
|
|
557
578
|
target_group_arns = self.ecs_config.target_group_arns
|
|
558
579
|
|
|
580
|
+
if target_group_arns:
|
|
581
|
+
tmp = []
|
|
582
|
+
for tg_arn in target_group_arns:
|
|
583
|
+
import hashlib
|
|
584
|
+
unique_id = hashlib.md5(tg_arn.encode()).hexdigest()
|
|
585
|
+
tmp.append(self.resolve_ssm_value(self, tg_arn, unique_id))
|
|
586
|
+
target_group_arns = tmp
|
|
587
|
+
|
|
559
588
|
if not target_group_arns:
|
|
560
589
|
# Try to load from SSM if configured
|
|
561
590
|
target_group_arns = self._load_target_groups_from_ssm()
|
|
@@ -579,7 +608,7 @@ class EcsServiceStack(IStack, EnhancedSsmParameterMixin):
|
|
|
579
608
|
for param_key, param_name in ssm_imports.items():
|
|
580
609
|
if 'target_group' in param_key.lower() or 'tg' in param_key.lower():
|
|
581
610
|
try:
|
|
582
|
-
param_value = self.
|
|
611
|
+
param_value = self.get_ssm_imported_value(param_name)
|
|
583
612
|
if param_value and param_value.startswith('arn:'):
|
|
584
613
|
target_group_arns.append(param_value)
|
|
585
614
|
except Exception as e:
|
|
@@ -611,35 +640,13 @@ class EcsServiceStack(IStack, EnhancedSsmParameterMixin):
|
|
|
611
640
|
scale_out_cooldown=cdk.Duration.seconds(60),
|
|
612
641
|
)
|
|
613
642
|
|
|
643
|
+
|
|
644
|
+
|
|
614
645
|
def _add_outputs(self, service_name: str) -> None:
|
|
615
646
|
"""Add CloudFormation outputs"""
|
|
647
|
+
return
|
|
616
648
|
|
|
617
|
-
# Service name output
|
|
618
|
-
cdk.CfnOutput(
|
|
619
|
-
self,
|
|
620
|
-
"ServiceName",
|
|
621
|
-
value=self.service.service_name,
|
|
622
|
-
description=f"ECS Service Name: {service_name}",
|
|
623
|
-
)
|
|
624
649
|
|
|
625
|
-
# Service ARN output
|
|
626
|
-
cdk.CfnOutput(
|
|
627
|
-
self,
|
|
628
|
-
"ServiceArn",
|
|
629
|
-
value=self.service.service_arn,
|
|
630
|
-
description=f"ECS Service ARN: {service_name}",
|
|
631
|
-
)
|
|
632
|
-
|
|
633
|
-
# Cluster name output
|
|
634
|
-
cdk.CfnOutput(
|
|
635
|
-
self,
|
|
636
|
-
"ClusterName",
|
|
637
|
-
value=self.cluster.cluster_name,
|
|
638
|
-
description=f"ECS Cluster Name for {service_name}",
|
|
639
|
-
)
|
|
640
|
-
|
|
641
|
-
# Export to SSM if configured
|
|
642
|
-
self._export_to_ssm(service_name)
|
|
643
650
|
|
|
644
651
|
def _export_to_ssm(self, service_name: str) -> None:
|
|
645
652
|
"""Export resource ARNs and names to SSM Parameter Store"""
|