cdk-factory 0.17.6__py3-none-any.whl → 0.20.2__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/deployment.py +12 -0
- cdk_factory/configurations/resources/acm.py +9 -2
- cdk_factory/configurations/resources/auto_scaling.py +7 -5
- cdk_factory/configurations/resources/ecs_cluster.py +5 -0
- cdk_factory/configurations/resources/ecs_service.py +24 -2
- cdk_factory/configurations/resources/lambda_edge.py +18 -4
- cdk_factory/configurations/resources/rds.py +1 -1
- cdk_factory/configurations/resources/route53.py +5 -0
- cdk_factory/configurations/resources/s3.py +9 -1
- cdk_factory/constructs/cloudfront/cloudfront_distribution_construct.py +1 -1
- cdk_factory/constructs/lambdas/policies/policy_docs.py +1 -1
- cdk_factory/interfaces/networked_stack_mixin.py +1 -1
- cdk_factory/interfaces/standardized_ssm_mixin.py +82 -10
- cdk_factory/stack_library/acm/acm_stack.py +5 -15
- cdk_factory/stack_library/api_gateway/api_gateway_stack.py +2 -2
- cdk_factory/stack_library/auto_scaling/{auto_scaling_stack_standardized.py → auto_scaling_stack.py} +213 -105
- cdk_factory/stack_library/cloudfront/cloudfront_stack.py +76 -22
- cdk_factory/stack_library/code_artifact/code_artifact_stack.py +3 -25
- cdk_factory/stack_library/cognito/cognito_stack.py +2 -2
- cdk_factory/stack_library/dynamodb/dynamodb_stack.py +2 -2
- cdk_factory/stack_library/ecs/__init__.py +2 -4
- cdk_factory/stack_library/ecs/{ecs_cluster_stack_standardized.py → ecs_cluster_stack.py} +52 -41
- cdk_factory/stack_library/ecs/ecs_service_stack.py +49 -26
- 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 +241 -81
- cdk_factory/stack_library/load_balancer/load_balancer_stack.py +128 -177
- cdk_factory/stack_library/rds/rds_stack.py +65 -72
- cdk_factory/stack_library/route53/route53_stack.py +244 -38
- cdk_factory/stack_library/rum/rum_stack.py +3 -3
- cdk_factory/stack_library/security_group/security_group_full_stack.py +1 -31
- cdk_factory/stack_library/security_group/security_group_stack.py +1 -8
- 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_standardized.py → vpc_stack.py} +6 -109
- cdk_factory/stack_library/websites/static_website_stack.py +7 -3
- cdk_factory/utilities/api_gateway_integration_utility.py +2 -2
- cdk_factory/utilities/environment_services.py +2 -2
- cdk_factory/version.py +1 -1
- {cdk_factory-0.17.6.dist-info → cdk_factory-0.20.2.dist-info}/METADATA +1 -1
- {cdk_factory-0.17.6.dist-info → cdk_factory-0.20.2.dist-info}/RECORD +44 -42
- {cdk_factory-0.17.6.dist-info → cdk_factory-0.20.2.dist-info}/WHEEL +0 -0
- {cdk_factory-0.17.6.dist-info → cdk_factory-0.20.2.dist-info}/entry_points.txt +0 -0
- {cdk_factory-0.17.6.dist-info → cdk_factory-0.20.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -13,6 +13,8 @@ from aws_cdk import (
|
|
|
13
13
|
aws_cloudfront as cloudfront,
|
|
14
14
|
aws_cloudfront_origins as origins,
|
|
15
15
|
aws_certificatemanager as acm,
|
|
16
|
+
aws_route53 as route53,
|
|
17
|
+
aws_s3 as s3,
|
|
16
18
|
aws_lambda as _lambda,
|
|
17
19
|
aws_ssm as ssm,
|
|
18
20
|
CfnOutput,
|
|
@@ -143,7 +145,7 @@ class CloudFrontStack(IStack):
|
|
|
143
145
|
return
|
|
144
146
|
|
|
145
147
|
# Check if certificate ARN is provided
|
|
146
|
-
cert_arn = cert_config.get("arn")
|
|
148
|
+
cert_arn = self.resolve_ssm_value(self, cert_config.get("arn"), "CertificateARN")
|
|
147
149
|
if cert_arn:
|
|
148
150
|
self.certificate = acm.Certificate.from_certificate_arn(
|
|
149
151
|
self, "Certificate", certificate_arn=cert_arn
|
|
@@ -161,8 +163,36 @@ class CloudFrontStack(IStack):
|
|
|
161
163
|
logger.info(f"Using certificate from SSM: {ssm_param}")
|
|
162
164
|
return
|
|
163
165
|
|
|
166
|
+
# Create new certificate from domain name
|
|
167
|
+
domain_name = cert_config.get("domain_name")
|
|
168
|
+
if domain_name and self.cf_config.aliases:
|
|
169
|
+
# CloudFront certificates must be in us-east-1
|
|
170
|
+
if self.region != "us-east-1":
|
|
171
|
+
logger.warning(
|
|
172
|
+
f"Certificate creation requested but stack is in {self.region}. "
|
|
173
|
+
"CloudFront certificates must be created in us-east-1"
|
|
174
|
+
)
|
|
175
|
+
return
|
|
176
|
+
|
|
177
|
+
# Create the certificate
|
|
178
|
+
# Get hosted zone from SSM imports
|
|
179
|
+
hosted_zone_id = cert_config.get("hosted_zone_id")
|
|
180
|
+
hosted_zone = route53.HostedZone.from_hosted_zone_id(
|
|
181
|
+
self, "HostedZone", hosted_zone_id
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
self.certificate = acm.Certificate(
|
|
185
|
+
self,
|
|
186
|
+
"Certificate",
|
|
187
|
+
domain_name=domain_name,
|
|
188
|
+
subject_alternative_names=self.cf_config.aliases,
|
|
189
|
+
validation=acm.CertificateValidation.from_dns(hosted_zone=hosted_zone),
|
|
190
|
+
)
|
|
191
|
+
logger.info(f"Created new ACM certificate for domain: {domain_name}")
|
|
192
|
+
return
|
|
193
|
+
|
|
164
194
|
logger.warning(
|
|
165
|
-
"No certificate ARN provided - CloudFront will use default certificate"
|
|
195
|
+
"No certificate ARN or domain name provided - CloudFront will use default certificate"
|
|
166
196
|
)
|
|
167
197
|
|
|
168
198
|
def _create_origins(self) -> None:
|
|
@@ -193,27 +223,29 @@ class CloudFrontStack(IStack):
|
|
|
193
223
|
|
|
194
224
|
def _create_custom_origin(self, config: Dict[str, Any]) -> cloudfront.IOrigin:
|
|
195
225
|
"""Create custom origin (ALB, API Gateway, etc.)"""
|
|
196
|
-
domain_name =
|
|
226
|
+
domain_name = self.resolve_ssm_value(
|
|
227
|
+
self, config.get("domain_name"), config.get("domain_name")
|
|
228
|
+
)
|
|
197
229
|
origin_id = config.get("id")
|
|
198
230
|
|
|
199
231
|
if not domain_name:
|
|
200
232
|
raise ValueError("domain_name is required for custom origin")
|
|
201
233
|
|
|
202
|
-
# Check if domain name is a placeholder from ssm_imports
|
|
203
|
-
if domain_name.startswith("{{") and domain_name.endswith("}}"):
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
# Legacy support: Check if domain name is an SSM parameter reference
|
|
212
|
-
elif domain_name.startswith("{{ssm:") and domain_name.endswith("}}"):
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
234
|
+
# # Check if domain name is a placeholder from ssm_imports
|
|
235
|
+
# if domain_name.startswith("{{") and domain_name.endswith("}}"):
|
|
236
|
+
# placeholder_key = domain_name[2:-2] # Remove {{ and }}
|
|
237
|
+
# if placeholder_key in self.ssm_imported_values:
|
|
238
|
+
# domain_name = self.ssm_imported_values[placeholder_key]
|
|
239
|
+
# logger.info(f"Resolved domain from SSM import: {placeholder_key}")
|
|
240
|
+
# else:
|
|
241
|
+
# logger.warning(f"Placeholder {domain_name} not found in SSM imports")
|
|
242
|
+
|
|
243
|
+
# # Legacy support: Check if domain name is an SSM parameter reference
|
|
244
|
+
# elif domain_name.startswith("{{ssm:") and domain_name.endswith("}}"):
|
|
245
|
+
# # Extract SSM parameter name
|
|
246
|
+
# ssm_param = domain_name[6:-2] # Remove {{ssm: and }}
|
|
247
|
+
# domain_name = ssm.StringParameter.value_from_lookup(self, ssm_param)
|
|
248
|
+
# logger.info(f"Resolved domain from SSM lookup {ssm_param}: {domain_name}")
|
|
217
249
|
|
|
218
250
|
# Build custom headers (e.g., X-Origin-Secret)
|
|
219
251
|
custom_headers = {}
|
|
@@ -267,12 +299,34 @@ class CloudFrontStack(IStack):
|
|
|
267
299
|
|
|
268
300
|
def _create_s3_origin(self, config: Dict[str, Any]) -> cloudfront.IOrigin:
|
|
269
301
|
"""Create S3 origin"""
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
302
|
+
bucket_name = self.resolve_ssm_value(
|
|
303
|
+
self, config.get("bucket_name"), config.get("bucket_name")
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
origin_path = config.get("origin_path", "")
|
|
307
|
+
|
|
308
|
+
if not bucket_name:
|
|
309
|
+
raise ValueError("S3 origin requires 'bucket_name' configuration")
|
|
310
|
+
|
|
311
|
+
# For S3 origins, we need to import the bucket by name
|
|
312
|
+
bucket = s3.Bucket.from_bucket_name(
|
|
313
|
+
self,
|
|
314
|
+
id=f"S3OriginBucket-{config.get('id', 'unknown')}",
|
|
315
|
+
bucket_name=bucket_name,
|
|
274
316
|
)
|
|
275
317
|
|
|
318
|
+
# Create S3 origin with OAC (Origin Access Control) for security
|
|
319
|
+
origin = origins.S3BucketOrigin.with_origin_access_control(
|
|
320
|
+
bucket,
|
|
321
|
+
origin_path=origin_path,
|
|
322
|
+
origin_access_levels=[
|
|
323
|
+
cloudfront.AccessLevel.READ,
|
|
324
|
+
cloudfront.AccessLevel.LIST,
|
|
325
|
+
],
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
return origin
|
|
329
|
+
|
|
276
330
|
def _create_distribution(self) -> None:
|
|
277
331
|
"""Create CloudFront distribution"""
|
|
278
332
|
|
|
@@ -140,32 +140,10 @@ class CodeArtifactStack(IStack, StandardizedSsmMixin):
|
|
|
140
140
|
|
|
141
141
|
def _add_outputs(self) -> None:
|
|
142
142
|
"""Add CloudFormation outputs for the CodeArtifact resources"""
|
|
143
|
+
|
|
144
|
+
|
|
143
145
|
# Domain outputs
|
|
144
146
|
if self.domain:
|
|
145
147
|
domain_name = self.code_artifact_config.domain_name
|
|
146
148
|
|
|
147
|
-
|
|
148
|
-
cdk.CfnOutput(
|
|
149
|
-
self,
|
|
150
|
-
f"{domain_name}-domain-arn",
|
|
151
|
-
value=self.domain.attr_arn,
|
|
152
|
-
export_name=f"{self.deployment.build_resource_name(domain_name)}-domain-arn"
|
|
153
|
-
)
|
|
154
|
-
|
|
155
|
-
# Domain URL
|
|
156
|
-
cdk.CfnOutput(
|
|
157
|
-
self,
|
|
158
|
-
f"{domain_name}-domain-url",
|
|
159
|
-
value=f"https://{self.code_artifact_config.account}.d.codeartifact.{self.code_artifact_config.region}.amazonaws.com/",
|
|
160
|
-
export_name=f"{self.deployment.build_resource_name(domain_name)}-domain-url"
|
|
161
|
-
)
|
|
162
|
-
|
|
163
|
-
# Repository outputs
|
|
164
|
-
for repo_name, repo in self.repositories.items():
|
|
165
|
-
# Repository ARN
|
|
166
|
-
cdk.CfnOutput(
|
|
167
|
-
self,
|
|
168
|
-
f"{repo_name}-repo-arn",
|
|
169
|
-
value=repo.attr_arn,
|
|
170
|
-
export_name=f"{self.deployment.build_resource_name(repo_name)}-repo-arn"
|
|
171
|
-
)
|
|
149
|
+
|
|
@@ -564,7 +564,7 @@ class CognitoStack(IStack, StandardizedSsmMixin):
|
|
|
564
564
|
# Setup enhanced SSM integration with proper resource type and name
|
|
565
565
|
# Use "user-pool" as resource identifier for SSM paths, not the full pool name
|
|
566
566
|
|
|
567
|
-
self.
|
|
567
|
+
self.setup_ssm_integration(
|
|
568
568
|
scope=self,
|
|
569
569
|
config=self.stack_config.dictionary.get("cognito", {}),
|
|
570
570
|
resource_type="cognito",
|
|
@@ -591,7 +591,7 @@ class CognitoStack(IStack, StandardizedSsmMixin):
|
|
|
591
591
|
# or retrieve via AWS Console/CLI if needed.
|
|
592
592
|
|
|
593
593
|
# Use enhanced SSM parameter export
|
|
594
|
-
exported_params = self.
|
|
594
|
+
exported_params = self.export_ssm_parameters(resource_values)
|
|
595
595
|
|
|
596
596
|
if exported_params:
|
|
597
597
|
logger.info(f"Exported {len(exported_params)} Cognito parameters to SSM")
|
|
@@ -152,7 +152,7 @@ class DynamoDBStack(IStack, StandardizedSsmMixin):
|
|
|
152
152
|
# Setup enhanced SSM integration with proper resource type and name
|
|
153
153
|
# Use "app-table" as resource identifier for SSM paths, not the full table name
|
|
154
154
|
|
|
155
|
-
self.
|
|
155
|
+
self.setup_ssm_integration(
|
|
156
156
|
scope=self,
|
|
157
157
|
config=self.stack_config.dictionary.get("dynamodb", {}),
|
|
158
158
|
resource_type="dynamodb",
|
|
@@ -178,7 +178,7 @@ class DynamoDBStack(IStack, StandardizedSsmMixin):
|
|
|
178
178
|
resource_values = {k: v for k, v in resource_values.items() if v is not None}
|
|
179
179
|
|
|
180
180
|
# Use enhanced SSM parameter export
|
|
181
|
-
exported_params = self.
|
|
181
|
+
exported_params = self.export_ssm_parameters(resource_values)
|
|
182
182
|
|
|
183
183
|
if exported_params:
|
|
184
184
|
logger.info(f"Exported {len(exported_params)} DynamoDB parameters to SSM")
|
|
@@ -5,10 +5,8 @@ Contains ECS-related stack modules for creating and managing
|
|
|
5
5
|
ECS clusters, services, and related resources.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
from .
|
|
9
|
-
from .ecs_service_stack import EcsServiceStack
|
|
8
|
+
from .ecs_cluster_stack import EcsClusterStack
|
|
10
9
|
|
|
11
10
|
__all__ = [
|
|
12
|
-
"EcsClusterStack"
|
|
13
|
-
"EcsServiceStack"
|
|
11
|
+
"EcsClusterStack"
|
|
14
12
|
]
|
|
@@ -86,17 +86,20 @@ class EcsClusterStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
|
|
|
86
86
|
# Initialize VPC cache from mixin
|
|
87
87
|
self._initialize_vpc_cache()
|
|
88
88
|
|
|
89
|
-
# Load ECS cluster configuration
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
+
|
|
95
|
+
self.ecs_config: EcsClusterConfig = EcsClusterConfig(ecs_cluster_dict)
|
|
93
96
|
|
|
94
97
|
cluster_name = deployment.build_resource_name(self.ecs_config.name)
|
|
95
98
|
|
|
96
99
|
logger.info(f"Creating ECS Cluster stack: {cluster_name}")
|
|
97
100
|
|
|
98
101
|
# Setup standardized SSM integration
|
|
99
|
-
self.
|
|
102
|
+
self.setup_ssm_integration(
|
|
100
103
|
scope=self,
|
|
101
104
|
config=self.ecs_config,
|
|
102
105
|
resource_type="ecs_cluster",
|
|
@@ -106,7 +109,7 @@ class EcsClusterStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
|
|
|
106
109
|
)
|
|
107
110
|
|
|
108
111
|
# Process SSM imports using standardized method
|
|
109
|
-
self.
|
|
112
|
+
self.process_ssm_imports()
|
|
110
113
|
|
|
111
114
|
# Create the ECS cluster
|
|
112
115
|
self._create_ecs_cluster()
|
|
@@ -118,7 +121,9 @@ class EcsClusterStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
|
|
|
118
121
|
self._export_cluster_info()
|
|
119
122
|
|
|
120
123
|
# Export SSM parameters
|
|
124
|
+
logger.info("Starting SSM parameter export for ECS cluster")
|
|
121
125
|
self._export_ssm_parameters()
|
|
126
|
+
logger.info("Completed SSM parameter export for ECS cluster")
|
|
122
127
|
|
|
123
128
|
logger.info(f"ECS Cluster stack created: {cluster_name}")
|
|
124
129
|
|
|
@@ -131,7 +136,7 @@ class EcsClusterStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
|
|
|
131
136
|
|
|
132
137
|
# Add container insights if enabled
|
|
133
138
|
if self.ecs_config.container_insights:
|
|
134
|
-
cluster_settings.append({"name": "
|
|
139
|
+
cluster_settings.append({"name": "containerInsightsV2", "value": "enabled"})
|
|
135
140
|
|
|
136
141
|
# Add custom cluster settings
|
|
137
142
|
if self.ecs_config.cluster_settings:
|
|
@@ -146,7 +151,7 @@ class EcsClusterStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
|
|
|
146
151
|
"ECSCluster",
|
|
147
152
|
cluster_name=self.ecs_config.name,
|
|
148
153
|
vpc=self.vpc,
|
|
149
|
-
|
|
154
|
+
container_insights_v2=ecs.ContainerInsights.ENABLED if self.ecs_config.container_insights else ecs.ContainerInsights.DISABLED,
|
|
150
155
|
default_cloud_map_namespace=(
|
|
151
156
|
self.ecs_config.cloud_map_namespace
|
|
152
157
|
if self.ecs_config.cloud_map_namespace
|
|
@@ -165,7 +170,8 @@ class EcsClusterStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
|
|
|
165
170
|
"""
|
|
166
171
|
Get VPC using the centralized VPC provider mixin.
|
|
167
172
|
"""
|
|
168
|
-
|
|
173
|
+
|
|
174
|
+
# Use the stack_config (not ecs_config) to ensure SSM imports are available
|
|
169
175
|
return self.resolve_vpc(
|
|
170
176
|
config=self.ecs_config,
|
|
171
177
|
deployment=self.deployment,
|
|
@@ -174,6 +180,8 @@ class EcsClusterStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
|
|
|
174
180
|
|
|
175
181
|
def _create_iam_roles(self):
|
|
176
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
|
+
|
|
177
185
|
if not self.ecs_config.create_instance_role:
|
|
178
186
|
logger.info("Skipping instance role creation (disabled in config)")
|
|
179
187
|
return
|
|
@@ -186,21 +194,25 @@ class EcsClusterStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
|
|
|
186
194
|
"ECSInstanceRole",
|
|
187
195
|
assumed_by=iam.ServicePrincipal("ec2.amazonaws.com"),
|
|
188
196
|
managed_policies=[
|
|
189
|
-
iam.ManagedPolicy.from_aws_managed_policy_name("AmazonEC2ContainerServiceforEC2Role"),
|
|
197
|
+
iam.ManagedPolicy.from_aws_managed_policy_name("service-role/AmazonEC2ContainerServiceforEC2Role"),
|
|
190
198
|
iam.ManagedPolicy.from_aws_managed_policy_name("AmazonEC2ContainerRegistryReadOnly"),
|
|
191
199
|
iam.ManagedPolicy.from_aws_managed_policy_name("AmazonSSMManagedInstanceCore"),
|
|
192
200
|
],
|
|
193
201
|
role_name=f"{self.ecs_config.name}-ecs-instance-role",
|
|
194
202
|
)
|
|
195
203
|
|
|
204
|
+
logger.info(f"Created ECS instance role: {self.instance_role.role_name}")
|
|
205
|
+
|
|
196
206
|
# Create instance profile
|
|
197
207
|
self.instance_profile = iam.CfnInstanceProfile(
|
|
198
208
|
self,
|
|
199
209
|
"ECSInstanceProfile",
|
|
200
|
-
roles=[self.instance_role.role_name],
|
|
201
210
|
instance_profile_name=f"{self.ecs_config.name}-ecs-instance-profile",
|
|
211
|
+
roles=[self.instance_role.role_name],
|
|
202
212
|
)
|
|
203
213
|
|
|
214
|
+
logger.info(f"Created ECS instance profile: {self.instance_profile.instance_profile_name}")
|
|
215
|
+
|
|
204
216
|
logger.info("ECS instance role and profile created")
|
|
205
217
|
|
|
206
218
|
def _export_cluster_info(self):
|
|
@@ -254,52 +266,51 @@ class EcsClusterStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
|
|
|
254
266
|
|
|
255
267
|
def _export_ssm_parameters(self) -> None:
|
|
256
268
|
"""Export SSM parameters using standardized approach"""
|
|
269
|
+
logger.info("=== Starting SSM Parameter Export ===")
|
|
270
|
+
|
|
257
271
|
if not self.ecs_cluster:
|
|
258
272
|
logger.warning("No ECS cluster to export")
|
|
259
273
|
return
|
|
260
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
|
+
|
|
261
278
|
# Prepare resource values for export
|
|
262
279
|
resource_values = {
|
|
263
|
-
"
|
|
264
|
-
"
|
|
280
|
+
"cluster_name": self.ecs_cluster.cluster_name,
|
|
281
|
+
"cluster_arn": self.ecs_cluster.cluster_arn,
|
|
265
282
|
}
|
|
266
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
|
+
|
|
267
291
|
# Add security group ID if available
|
|
268
292
|
if hasattr(self.ecs_cluster, 'connections') and self.ecs_cluster.connections:
|
|
269
293
|
security_groups = self.ecs_cluster.connections.security_groups
|
|
270
294
|
if security_groups:
|
|
271
|
-
resource_values["
|
|
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}")
|
|
272
297
|
|
|
273
298
|
# Add instance profile ARN if created
|
|
274
299
|
if self.instance_profile:
|
|
275
|
-
resource_values["
|
|
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}")
|
|
276
302
|
|
|
277
303
|
# Export using standardized SSM mixin
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
# Backward compatibility methods
|
|
283
|
-
def process_ssm_imports(self, config: Any, deployment: DeploymentConfig, resource_type: str = "resource") -> None:
|
|
284
|
-
"""Backward compatibility method for existing modules."""
|
|
285
|
-
# Extract SSM configuration from old format
|
|
286
|
-
if hasattr(config, 'ssm_imports'):
|
|
287
|
-
# Convert old ssm_imports format to new format
|
|
288
|
-
old_imports = config.ssm_imports
|
|
289
|
-
new_imports = {}
|
|
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}")
|
|
290
307
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
# Process imports using standardized method
|
|
301
|
-
self.process_standardized_ssm_imports()
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
# Backward compatibility alias
|
|
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
|
|
305
316
|
EcsClusterStackStandardized = EcsClusterStack
|
|
@@ -101,6 +101,7 @@ class EcsServiceStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
|
|
|
101
101
|
|
|
102
102
|
# Add outputs
|
|
103
103
|
self._add_outputs(service_name)
|
|
104
|
+
self._export_to_ssm(service_name)
|
|
104
105
|
|
|
105
106
|
def _load_vpc(self) -> None:
|
|
106
107
|
"""Load VPC using the centralized VPC provider mixin."""
|
|
@@ -225,6 +226,9 @@ class EcsServiceStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
|
|
|
225
226
|
"CloudWatchAgentServerPolicy"
|
|
226
227
|
)
|
|
227
228
|
)
|
|
229
|
+
|
|
230
|
+
# add any custom policies
|
|
231
|
+
self._add_custom_task_policies(task_role)
|
|
228
232
|
|
|
229
233
|
# Create task definition based on launch type
|
|
230
234
|
if self.ecs_config.launch_type == "EC2":
|
|
@@ -237,6 +241,7 @@ class EcsServiceStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
|
|
|
237
241
|
network_mode=ecs.NetworkMode(network_mode.upper()) if network_mode else ecs.NetworkMode.BRIDGE,
|
|
238
242
|
execution_role=execution_role,
|
|
239
243
|
task_role=task_role,
|
|
244
|
+
inference_accelerators=None
|
|
240
245
|
)
|
|
241
246
|
else:
|
|
242
247
|
# Fargate task definition
|
|
@@ -248,6 +253,7 @@ class EcsServiceStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
|
|
|
248
253
|
memory_limit_mib=int(self.ecs_config.memory),
|
|
249
254
|
execution_role=execution_role,
|
|
250
255
|
task_role=task_role,
|
|
256
|
+
inference_accelerators=None
|
|
251
257
|
)
|
|
252
258
|
|
|
253
259
|
# Add volumes to task definition
|
|
@@ -256,6 +262,37 @@ class EcsServiceStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
|
|
|
256
262
|
# Add containers
|
|
257
263
|
self._add_containers_to_task()
|
|
258
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
|
+
|
|
259
296
|
def _add_volumes_to_task(self) -> None:
|
|
260
297
|
"""
|
|
261
298
|
Add volumes to the task definition.
|
|
@@ -540,6 +577,14 @@ class EcsServiceStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
|
|
|
540
577
|
"""Attach service to load balancer target groups"""
|
|
541
578
|
target_group_arns = self.ecs_config.target_group_arns
|
|
542
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
|
+
|
|
543
588
|
if not target_group_arns:
|
|
544
589
|
# Try to load from SSM if configured
|
|
545
590
|
target_group_arns = self._load_target_groups_from_ssm()
|
|
@@ -563,7 +608,7 @@ class EcsServiceStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
|
|
|
563
608
|
for param_key, param_name in ssm_imports.items():
|
|
564
609
|
if 'target_group' in param_key.lower() or 'tg' in param_key.lower():
|
|
565
610
|
try:
|
|
566
|
-
param_value = self.
|
|
611
|
+
param_value = self.get_ssm_imported_value(param_name)
|
|
567
612
|
if param_value and param_value.startswith('arn:'):
|
|
568
613
|
target_group_arns.append(param_value)
|
|
569
614
|
except Exception as e:
|
|
@@ -595,35 +640,13 @@ class EcsServiceStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
|
|
|
595
640
|
scale_out_cooldown=cdk.Duration.seconds(60),
|
|
596
641
|
)
|
|
597
642
|
|
|
643
|
+
|
|
644
|
+
|
|
598
645
|
def _add_outputs(self, service_name: str) -> None:
|
|
599
646
|
"""Add CloudFormation outputs"""
|
|
647
|
+
return
|
|
600
648
|
|
|
601
|
-
# Service name output
|
|
602
|
-
cdk.CfnOutput(
|
|
603
|
-
self,
|
|
604
|
-
"ServiceName",
|
|
605
|
-
value=self.service.service_name,
|
|
606
|
-
description=f"ECS Service Name: {service_name}",
|
|
607
|
-
)
|
|
608
649
|
|
|
609
|
-
# Service ARN output
|
|
610
|
-
cdk.CfnOutput(
|
|
611
|
-
self,
|
|
612
|
-
"ServiceArn",
|
|
613
|
-
value=self.service.service_arn,
|
|
614
|
-
description=f"ECS Service ARN: {service_name}",
|
|
615
|
-
)
|
|
616
|
-
|
|
617
|
-
# Cluster name output
|
|
618
|
-
cdk.CfnOutput(
|
|
619
|
-
self,
|
|
620
|
-
"ClusterName",
|
|
621
|
-
value=self.cluster.cluster_name,
|
|
622
|
-
description=f"ECS Cluster Name for {service_name}",
|
|
623
|
-
)
|
|
624
|
-
|
|
625
|
-
# Export to SSM if configured
|
|
626
|
-
self._export_to_ssm(service_name)
|
|
627
650
|
|
|
628
651
|
def _export_to_ssm(self, service_name: str) -> None:
|
|
629
652
|
"""Export resource ARNs and names to SSM Parameter Store"""
|