cdk-factory 0.18.0__py3-none-any.whl → 0.18.4__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/ecs_cluster.py +5 -0
- cdk_factory/stack_library/auto_scaling/auto_scaling_old.py +721 -0
- cdk_factory/stack_library/auto_scaling/auto_scaling_stack_standardized.py +12 -8
- cdk_factory/stack_library/ecs/ecs_cluster_stack_standardized.py +33 -5
- cdk_factory/version.py +1 -1
- {cdk_factory-0.18.0.dist-info → cdk_factory-0.18.4.dist-info}/METADATA +1 -1
- {cdk_factory-0.18.0.dist-info → cdk_factory-0.18.4.dist-info}/RECORD +10 -9
- {cdk_factory-0.18.0.dist-info → cdk_factory-0.18.4.dist-info}/WHEEL +0 -0
- {cdk_factory-0.18.0.dist-info → cdk_factory-0.18.4.dist-info}/entry_points.txt +0 -0
- {cdk_factory-0.18.0.dist-info → cdk_factory-0.18.4.dist-info}/licenses/LICENSE +0 -0
|
@@ -18,6 +18,11 @@ class EcsClusterConfig:
|
|
|
18
18
|
def __init__(self, config: Dict[str, Any]) -> None:
|
|
19
19
|
self._config = config or {}
|
|
20
20
|
|
|
21
|
+
@property
|
|
22
|
+
def dictionary(self) -> Dict[str, Any]:
|
|
23
|
+
"""Access to the underlying configuration dictionary (for compatibility with SSM mixin)"""
|
|
24
|
+
return self._config
|
|
25
|
+
|
|
21
26
|
@property
|
|
22
27
|
def name(self) -> str:
|
|
23
28
|
"""Name of the ECS cluster. Supports template variables like {{WORKLOAD_NAME}}-{{ENVIRONMENT}}-cluster"""
|
|
@@ -0,0 +1,721 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Auto Scaling Group Stack Pattern for CDK-Factory
|
|
3
|
+
Maintainers: Eric Wilson
|
|
4
|
+
MIT License. See Project Root for the license information.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Dict, Any, List, Optional
|
|
8
|
+
|
|
9
|
+
import aws_cdk as cdk
|
|
10
|
+
from aws_cdk import aws_ec2 as ec2
|
|
11
|
+
from aws_cdk import aws_autoscaling as autoscaling
|
|
12
|
+
from aws_cdk import aws_cloudwatch as cloudwatch
|
|
13
|
+
from aws_cdk import aws_iam as iam
|
|
14
|
+
from aws_cdk import aws_ssm as ssm
|
|
15
|
+
from aws_cdk import aws_ecs as ecs
|
|
16
|
+
from aws_cdk import Duration, Stack
|
|
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.auto_scaling import AutoScalingConfig
|
|
23
|
+
from cdk_factory.interfaces.istack import IStack
|
|
24
|
+
from cdk_factory.interfaces.vpc_provider_mixin import VPCProviderMixin
|
|
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="AutoScalingStack")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@register_stack("auto_scaling_library_module_old")
|
|
32
|
+
@register_stack("auto_scaling_stack_old")
|
|
33
|
+
class AutoScalingStack(IStack, VPCProviderMixin):
|
|
34
|
+
"""
|
|
35
|
+
Reusable stack for AWS Auto Scaling Groups.
|
|
36
|
+
Supports creating EC2 Auto Scaling Groups with customizable configurations.
|
|
37
|
+
|
|
38
|
+
Uses enhanced SsmParameterMixin (via IStack) to eliminate SSM code duplication.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __init__(self, scope: Construct, id: str, **kwargs) -> None:
|
|
42
|
+
# Initialize parent class properly - IStack inherits from enhanced SsmParameterMixin
|
|
43
|
+
super().__init__(scope, id, **kwargs)
|
|
44
|
+
|
|
45
|
+
# Initialize VPC cache from mixin
|
|
46
|
+
self._initialize_vpc_cache()
|
|
47
|
+
|
|
48
|
+
self.asg_config = None
|
|
49
|
+
self.stack_config = None
|
|
50
|
+
self.deployment = None
|
|
51
|
+
self.workload = None
|
|
52
|
+
self.security_groups = []
|
|
53
|
+
self.auto_scaling_group = None
|
|
54
|
+
self.launch_template = None
|
|
55
|
+
self.instance_role = None
|
|
56
|
+
self.user_data = None
|
|
57
|
+
self.user_data_commands = [] # Store raw commands for ECS cluster detection
|
|
58
|
+
self.ecs_cluster = None
|
|
59
|
+
|
|
60
|
+
# SSM imports storage is now handled by the enhanced SsmParameterMixin via IStack
|
|
61
|
+
# VPC caching is now handled by VPCProviderMixin
|
|
62
|
+
|
|
63
|
+
def build(
|
|
64
|
+
self,
|
|
65
|
+
stack_config: StackConfig,
|
|
66
|
+
deployment: DeploymentConfig,
|
|
67
|
+
workload: WorkloadConfig,
|
|
68
|
+
) -> None:
|
|
69
|
+
"""Build the Auto Scaling Group stack"""
|
|
70
|
+
self._build(stack_config, deployment, workload)
|
|
71
|
+
|
|
72
|
+
def _build(
|
|
73
|
+
self,
|
|
74
|
+
stack_config: StackConfig,
|
|
75
|
+
deployment: DeploymentConfig,
|
|
76
|
+
workload: WorkloadConfig,
|
|
77
|
+
) -> None:
|
|
78
|
+
"""Internal build method for the Auto Scaling Group stack"""
|
|
79
|
+
self.stack_config = stack_config
|
|
80
|
+
self.deployment = deployment
|
|
81
|
+
self.workload = workload
|
|
82
|
+
|
|
83
|
+
self.asg_config = AutoScalingConfig(
|
|
84
|
+
stack_config.dictionary.get("auto_scaling", {}), deployment
|
|
85
|
+
)
|
|
86
|
+
asg_name = deployment.build_resource_name(self.asg_config.name)
|
|
87
|
+
|
|
88
|
+
# Process SSM imports using enhanced SsmParameterMixin
|
|
89
|
+
self.process_ssm_imports(self.asg_config, deployment, "Auto Scaling Group")
|
|
90
|
+
|
|
91
|
+
# Get security groups
|
|
92
|
+
self.security_groups = self._get_security_groups()
|
|
93
|
+
|
|
94
|
+
# Create IAM role for instances
|
|
95
|
+
self.instance_role = self._create_instance_role(asg_name)
|
|
96
|
+
|
|
97
|
+
# Create user data
|
|
98
|
+
self.user_data = self._create_user_data()
|
|
99
|
+
|
|
100
|
+
# Create ECS cluster if ECS configuration is detected
|
|
101
|
+
# This must happen before launch template creation so user data can be updated
|
|
102
|
+
self._create_ecs_cluster_if_needed(asg_name)
|
|
103
|
+
|
|
104
|
+
# Create launch template
|
|
105
|
+
self.launch_template = self._create_launch_template(asg_name)
|
|
106
|
+
|
|
107
|
+
# Create Auto Scaling Group
|
|
108
|
+
self.auto_scaling_group = self._create_auto_scaling_group(asg_name)
|
|
109
|
+
|
|
110
|
+
# Add scaling policies
|
|
111
|
+
self._add_scaling_policies()
|
|
112
|
+
|
|
113
|
+
# Add scheduled actions
|
|
114
|
+
self._add_scheduled_actions()
|
|
115
|
+
|
|
116
|
+
# Export resources
|
|
117
|
+
self._export_resources(asg_name)
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
def vpc(self) -> ec2.IVpc:
|
|
121
|
+
"""Get the VPC for the Auto Scaling Group using VPCProviderMixin"""
|
|
122
|
+
if not self.asg_config:
|
|
123
|
+
raise AttributeError("AutoScalingStack not properly initialized. Call build() first.")
|
|
124
|
+
|
|
125
|
+
# Use VPCProviderMixin to resolve VPC with proper subnet handling
|
|
126
|
+
return self.resolve_vpc(
|
|
127
|
+
config=self.asg_config,
|
|
128
|
+
deployment=self.deployment,
|
|
129
|
+
workload=self.workload
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
def _get_target_group_arns(self) -> List[str]:
|
|
133
|
+
"""Get target group ARNs from SSM imports using enhanced SsmParameterMixin"""
|
|
134
|
+
target_group_arns = []
|
|
135
|
+
|
|
136
|
+
# Check if we have SSM imports for target groups using enhanced mixin
|
|
137
|
+
if self.has_ssm_import("target_group_arns"):
|
|
138
|
+
imported_tg_arns = self.get_ssm_imported_value("target_group_arns", [])
|
|
139
|
+
if isinstance(imported_tg_arns, list):
|
|
140
|
+
target_group_arns.extend(imported_tg_arns)
|
|
141
|
+
else:
|
|
142
|
+
target_group_arns.append(imported_tg_arns)
|
|
143
|
+
|
|
144
|
+
# see if we have any directly defined in the config
|
|
145
|
+
if self.asg_config.target_group_arns:
|
|
146
|
+
for arn in self.asg_config.target_group_arns:
|
|
147
|
+
logger.info(f"Adding target group ARN: {arn}")
|
|
148
|
+
target_group_arns.append(arn)
|
|
149
|
+
|
|
150
|
+
return target_group_arns
|
|
151
|
+
|
|
152
|
+
def _attach_target_groups(self, asg: autoscaling.AutoScalingGroup) -> None:
|
|
153
|
+
"""Attach the Auto Scaling Group to target groups"""
|
|
154
|
+
target_group_arns = self._get_target_group_arns()
|
|
155
|
+
|
|
156
|
+
if not target_group_arns:
|
|
157
|
+
logger.warning("No target group ARNs found for Auto Scaling Group")
|
|
158
|
+
print(
|
|
159
|
+
"⚠️ No target group ARNs found for Auto Scaling Group. Nothing will be attached."
|
|
160
|
+
)
|
|
161
|
+
return
|
|
162
|
+
|
|
163
|
+
# Get the underlying CloudFormation resource to add target group ARNs
|
|
164
|
+
cfn_asg = asg.node.default_child
|
|
165
|
+
cfn_asg.add_property_override("TargetGroupARNs", target_group_arns)
|
|
166
|
+
|
|
167
|
+
def _get_security_groups(self) -> List[ec2.ISecurityGroup]:
|
|
168
|
+
"""Get security groups for the Auto Scaling Group"""
|
|
169
|
+
security_groups = []
|
|
170
|
+
for sg_id in self.asg_config.security_group_ids:
|
|
171
|
+
# if the security group id contains a comma, it is a list of security group ids
|
|
172
|
+
if "," in sg_id:
|
|
173
|
+
blocks = sg_id.split(",")
|
|
174
|
+
for block in blocks:
|
|
175
|
+
security_groups.append(
|
|
176
|
+
ec2.SecurityGroup.from_security_group_id(
|
|
177
|
+
self, f"SecurityGroup-{block}", block
|
|
178
|
+
)
|
|
179
|
+
)
|
|
180
|
+
else:
|
|
181
|
+
# TODO: add some additional checks to make it more robust
|
|
182
|
+
security_groups.append(
|
|
183
|
+
ec2.SecurityGroup.from_security_group_id(
|
|
184
|
+
self, f"SecurityGroup-{sg_id}", sg_id
|
|
185
|
+
)
|
|
186
|
+
)
|
|
187
|
+
return security_groups
|
|
188
|
+
|
|
189
|
+
def _create_instance_role(self, asg_name: str) -> iam.Role:
|
|
190
|
+
"""Create IAM role for EC2 instances"""
|
|
191
|
+
role = iam.Role(
|
|
192
|
+
self,
|
|
193
|
+
f"{asg_name}-InstanceRole",
|
|
194
|
+
assumed_by=iam.ServicePrincipal("ec2.amazonaws.com"),
|
|
195
|
+
role_name=f"{asg_name}-role",
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
# Add managed policies
|
|
199
|
+
for policy_name in self.asg_config.managed_policies:
|
|
200
|
+
role.add_managed_policy(
|
|
201
|
+
iam.ManagedPolicy.from_aws_managed_policy_name(policy_name)
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
# Add inline policies (for custom permissions like S3 bucket access)
|
|
205
|
+
for policy_config in self.asg_config.iam_inline_policies:
|
|
206
|
+
policy_name = policy_config.get("name", "CustomPolicy")
|
|
207
|
+
statements = policy_config.get("statements", [])
|
|
208
|
+
|
|
209
|
+
if not statements:
|
|
210
|
+
logger.warning(f"No statements found for inline policy {policy_name}, skipping")
|
|
211
|
+
continue
|
|
212
|
+
|
|
213
|
+
# Build policy statements
|
|
214
|
+
policy_statements = []
|
|
215
|
+
for stmt in statements:
|
|
216
|
+
effect = iam.Effect.ALLOW if stmt.get("effect", "Allow") == "Allow" else iam.Effect.DENY
|
|
217
|
+
actions = stmt.get("actions", [])
|
|
218
|
+
resources = stmt.get("resources", [])
|
|
219
|
+
|
|
220
|
+
if not actions or not resources:
|
|
221
|
+
logger.warning(f"Incomplete statement in policy {policy_name}, skipping")
|
|
222
|
+
continue
|
|
223
|
+
|
|
224
|
+
policy_statements.append(
|
|
225
|
+
iam.PolicyStatement(
|
|
226
|
+
effect=effect,
|
|
227
|
+
actions=actions,
|
|
228
|
+
resources=resources
|
|
229
|
+
)
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
if policy_statements:
|
|
233
|
+
role.add_to_principal_policy(policy_statements[0])
|
|
234
|
+
for stmt in policy_statements[1:]:
|
|
235
|
+
role.add_to_principal_policy(stmt)
|
|
236
|
+
|
|
237
|
+
logger.info(f"Added inline policy {policy_name} with {len(policy_statements)} statements")
|
|
238
|
+
|
|
239
|
+
return role
|
|
240
|
+
|
|
241
|
+
def _create_user_data(self) -> ec2.UserData:
|
|
242
|
+
"""Create user data for EC2 instances"""
|
|
243
|
+
user_data = ec2.UserData.for_linux()
|
|
244
|
+
|
|
245
|
+
# Store raw commands for ECS cluster detection
|
|
246
|
+
self.user_data_commands = ["set -euxo pipefail"]
|
|
247
|
+
|
|
248
|
+
# Add base commands
|
|
249
|
+
user_data.add_commands("set -euxo pipefail")
|
|
250
|
+
|
|
251
|
+
# Add custom commands from config (with variable substitution)
|
|
252
|
+
for command in self.asg_config.user_data_commands:
|
|
253
|
+
# Perform variable substitution on the command
|
|
254
|
+
substituted_command = self._substitute_variables(command)
|
|
255
|
+
user_data.add_commands(substituted_command)
|
|
256
|
+
self.user_data_commands.append(substituted_command)
|
|
257
|
+
|
|
258
|
+
# Add user data scripts from files (with variable substitution)
|
|
259
|
+
if self.asg_config.user_data_scripts:
|
|
260
|
+
self._add_user_data_scripts_from_files(user_data)
|
|
261
|
+
|
|
262
|
+
# Add container configuration if specified
|
|
263
|
+
container_config = self.asg_config.container_config
|
|
264
|
+
if container_config:
|
|
265
|
+
self._add_container_user_data(user_data, container_config)
|
|
266
|
+
|
|
267
|
+
return user_data
|
|
268
|
+
|
|
269
|
+
def _add_user_data_scripts_from_files(self, user_data: ec2.UserData) -> None:
|
|
270
|
+
"""
|
|
271
|
+
Add user data scripts from external files with variable substitution.
|
|
272
|
+
Supports loading shell scripts and injecting them into user data with
|
|
273
|
+
placeholder replacement.
|
|
274
|
+
"""
|
|
275
|
+
from pathlib import Path
|
|
276
|
+
|
|
277
|
+
for script_config in self.asg_config.user_data_scripts:
|
|
278
|
+
script_type = script_config.get("type", "file")
|
|
279
|
+
|
|
280
|
+
if script_type == "file":
|
|
281
|
+
# Load script from file
|
|
282
|
+
script_path = script_config.get("path")
|
|
283
|
+
if not script_path:
|
|
284
|
+
logger.warning("Script path not specified, skipping")
|
|
285
|
+
continue
|
|
286
|
+
|
|
287
|
+
# Resolve path (relative to project root or absolute)
|
|
288
|
+
path = Path(script_path)
|
|
289
|
+
if not path.is_absolute():
|
|
290
|
+
# Try relative to current working directory
|
|
291
|
+
path = Path.cwd() / script_path
|
|
292
|
+
|
|
293
|
+
if not path.exists():
|
|
294
|
+
logger.warning(f"Script file not found: {path}, skipping")
|
|
295
|
+
continue
|
|
296
|
+
|
|
297
|
+
# Read script content
|
|
298
|
+
try:
|
|
299
|
+
with open(path, 'r') as f:
|
|
300
|
+
script_content = f.read()
|
|
301
|
+
except Exception as e:
|
|
302
|
+
logger.error(f"Failed to read script file {path}: {e}")
|
|
303
|
+
continue
|
|
304
|
+
|
|
305
|
+
elif script_type == "inline":
|
|
306
|
+
# Use inline script content
|
|
307
|
+
script_content = script_config.get("content", "")
|
|
308
|
+
if not script_content:
|
|
309
|
+
logger.warning("Inline script content is empty, skipping")
|
|
310
|
+
continue
|
|
311
|
+
else:
|
|
312
|
+
logger.warning(f"Unknown script type: {script_type}, skipping")
|
|
313
|
+
continue
|
|
314
|
+
|
|
315
|
+
# Perform variable substitution
|
|
316
|
+
variables = script_config.get("variables", {})
|
|
317
|
+
for var_name, var_value in variables.items():
|
|
318
|
+
placeholder = f"{{{{{var_name}}}}}" # {{VAR_NAME}}
|
|
319
|
+
script_content = script_content.replace(placeholder, str(var_value))
|
|
320
|
+
|
|
321
|
+
# Add script to user data
|
|
322
|
+
# Split by lines and add each line as a command
|
|
323
|
+
for line in script_content.split('\n'):
|
|
324
|
+
if line.strip(): # Skip empty lines
|
|
325
|
+
user_data.add_commands(line)
|
|
326
|
+
|
|
327
|
+
logger.info(f"Added user data script from {script_type}: {script_config.get('path', 'inline')}")
|
|
328
|
+
|
|
329
|
+
def _substitute_variables(self, command: str) -> str:
|
|
330
|
+
"""
|
|
331
|
+
Perform variable substitution on a user data command.
|
|
332
|
+
Uses workload and deployment configuration for substitution.
|
|
333
|
+
"""
|
|
334
|
+
if not command:
|
|
335
|
+
return command
|
|
336
|
+
|
|
337
|
+
# Start with the original command
|
|
338
|
+
substituted_command = command
|
|
339
|
+
|
|
340
|
+
# Define available variables for substitution
|
|
341
|
+
variables = {}
|
|
342
|
+
|
|
343
|
+
# Add workload variables
|
|
344
|
+
if self.workload:
|
|
345
|
+
variables.update({
|
|
346
|
+
"WORKLOAD_NAME": getattr(self.workload, 'name', ''),
|
|
347
|
+
"ENVIRONMENT": getattr(self.workload, 'environment', ''),
|
|
348
|
+
"WORKLOAD": getattr(self.workload, 'name', ''),
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
# Add deployment variables
|
|
352
|
+
if self.deployment:
|
|
353
|
+
variables.update({
|
|
354
|
+
"DEPLOYMENT_NAME": getattr(self.deployment, 'name', ''),
|
|
355
|
+
"REGION": getattr(self.deployment, 'region', ''),
|
|
356
|
+
"ACCOUNT": getattr(self.deployment, 'account', ''),
|
|
357
|
+
})
|
|
358
|
+
|
|
359
|
+
# Add stack-level variables
|
|
360
|
+
variables.update({
|
|
361
|
+
"STACK_NAME": self.stack_name,
|
|
362
|
+
})
|
|
363
|
+
|
|
364
|
+
# Perform substitution
|
|
365
|
+
for var_name, var_value in variables.items():
|
|
366
|
+
if var_value is not None:
|
|
367
|
+
placeholder = f"{{{{{var_name}}}}}" # {{VAR_NAME}}
|
|
368
|
+
substituted_command = substituted_command.replace(placeholder, str(var_value))
|
|
369
|
+
|
|
370
|
+
return substituted_command
|
|
371
|
+
|
|
372
|
+
def _add_container_user_data(
|
|
373
|
+
self, user_data: ec2.UserData, container_config: Dict[str, Any]
|
|
374
|
+
) -> None:
|
|
375
|
+
"""Add container-specific user data commands"""
|
|
376
|
+
# Install Docker
|
|
377
|
+
user_data.add_commands(
|
|
378
|
+
"dnf -y update", "dnf -y install docker jq", "systemctl enable --now docker"
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
# ECR configuration
|
|
382
|
+
if "ecr" in container_config:
|
|
383
|
+
ecr_config = container_config["ecr"]
|
|
384
|
+
user_data.add_commands(
|
|
385
|
+
f"ACCOUNT_ID={ecr_config.get('account_id', self.account)}",
|
|
386
|
+
f"REGION={ecr_config.get('region', self.region)}",
|
|
387
|
+
f"REPO={ecr_config.get('repo', 'app')}",
|
|
388
|
+
f"TAG={ecr_config.get('tag', 'latest')}",
|
|
389
|
+
"aws ecr get-login-password --region $REGION | docker login --username AWS --password-stdin ${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com",
|
|
390
|
+
"docker pull ${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/${REPO}:${TAG}",
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
# Database configuration
|
|
394
|
+
if "database" in container_config:
|
|
395
|
+
db_config = container_config["database"]
|
|
396
|
+
secret_arn = db_config.get("secret_arn", "")
|
|
397
|
+
if secret_arn:
|
|
398
|
+
user_data.add_commands(
|
|
399
|
+
f"DB_SECRET_ARN={secret_arn}",
|
|
400
|
+
'if [ -n "$DB_SECRET_ARN" ]; then DB_JSON=$(aws secretsmanager get-secret-value --secret-id $DB_SECRET_ARN --query SecretString --output text --region $REGION); fi',
|
|
401
|
+
'if [ -n "$DB_SECRET_ARN" ]; then DB_HOST=$(echo $DB_JSON | jq -r .host); DB_USER=$(echo $DB_JSON | jq -r .username); DB_PASS=$(echo $DB_JSON | jq -r .password); DB_NAME=$(echo $DB_JSON | jq -r .dbname); fi',
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
# Run container
|
|
405
|
+
if "run_command" in container_config:
|
|
406
|
+
user_data.add_commands(container_config["run_command"])
|
|
407
|
+
elif "ecr" in container_config:
|
|
408
|
+
port = container_config.get("port", 8080)
|
|
409
|
+
user_data.add_commands(
|
|
410
|
+
f"docker run -d --name app -p {port}:{port} "
|
|
411
|
+
'-e DB_HOST="$DB_HOST" -e DB_USER="$DB_USER" -e DB_PASS="$DB_PASS" -e DB_NAME="$DB_NAME" '
|
|
412
|
+
"--restart=always ${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/${REPO}:${TAG}"
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
def _create_launch_template(self, asg_name: str) -> ec2.LaunchTemplate:
|
|
416
|
+
"""Create launch template for the Auto Scaling Group"""
|
|
417
|
+
# Get AMI
|
|
418
|
+
ami = None
|
|
419
|
+
if self.asg_config.ami_id:
|
|
420
|
+
ami = ec2.MachineImage.generic_linux({self.region: self.asg_config.ami_id})
|
|
421
|
+
else:
|
|
422
|
+
if self.asg_config.ami_type == "amazon-linux-2023":
|
|
423
|
+
ami = ec2.MachineImage.latest_amazon_linux2023()
|
|
424
|
+
elif self.asg_config.ami_type == "amazon-linux-2":
|
|
425
|
+
ami = ec2.MachineImage.latest_amazon_linux2()
|
|
426
|
+
else:
|
|
427
|
+
ami = ec2.MachineImage.latest_amazon_linux2023()
|
|
428
|
+
|
|
429
|
+
# Parse instance type
|
|
430
|
+
instance_type_str = self.asg_config.instance_type
|
|
431
|
+
instance_type = None
|
|
432
|
+
|
|
433
|
+
if "." in instance_type_str:
|
|
434
|
+
parts = instance_type_str.split(".")
|
|
435
|
+
if len(parts) == 2:
|
|
436
|
+
try:
|
|
437
|
+
instance_class = ec2.InstanceClass[parts[0].upper()]
|
|
438
|
+
instance_size = ec2.InstanceSize[parts[1].upper()]
|
|
439
|
+
instance_type = ec2.InstanceType.of(instance_class, instance_size)
|
|
440
|
+
except (KeyError, ValueError):
|
|
441
|
+
instance_type = ec2.InstanceType(instance_type_str)
|
|
442
|
+
else:
|
|
443
|
+
instance_type = ec2.InstanceType(instance_type_str)
|
|
444
|
+
else:
|
|
445
|
+
instance_type = ec2.InstanceType(instance_type_str)
|
|
446
|
+
|
|
447
|
+
# Create block device mappings
|
|
448
|
+
block_devices = []
|
|
449
|
+
for device in self.asg_config.block_devices:
|
|
450
|
+
block_devices.append(
|
|
451
|
+
ec2.BlockDevice(
|
|
452
|
+
device_name=device.get("device_name", "/dev/xvda"),
|
|
453
|
+
volume=ec2.BlockDeviceVolume.ebs(
|
|
454
|
+
volume_size=device.get("volume_size", 8),
|
|
455
|
+
volume_type=ec2.EbsDeviceVolumeType(
|
|
456
|
+
str(device.get("volume_type", "gp3")).upper()
|
|
457
|
+
),
|
|
458
|
+
delete_on_termination=device.get("delete_on_termination", True),
|
|
459
|
+
encrypted=device.get("encrypted", True),
|
|
460
|
+
),
|
|
461
|
+
)
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
# Create launch template
|
|
465
|
+
launch_template = ec2.LaunchTemplate(
|
|
466
|
+
self,
|
|
467
|
+
f"{asg_name}-LaunchTemplate",
|
|
468
|
+
machine_image=ami,
|
|
469
|
+
instance_type=instance_type,
|
|
470
|
+
role=self.instance_role,
|
|
471
|
+
security_group=self.security_groups[0] if self.security_groups else None,
|
|
472
|
+
user_data=self.user_data,
|
|
473
|
+
detailed_monitoring=self.asg_config.detailed_monitoring,
|
|
474
|
+
block_devices=block_devices if block_devices else None,
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
return launch_template
|
|
478
|
+
|
|
479
|
+
def _create_auto_scaling_group(self, asg_name: str) -> autoscaling.AutoScalingGroup:
|
|
480
|
+
"""Create the Auto Scaling Group"""
|
|
481
|
+
# Configure subnet selection
|
|
482
|
+
subnet_group_name = self.asg_config.subnet_group_name
|
|
483
|
+
subnets = ec2.SubnetSelection(subnet_group_name=subnet_group_name)
|
|
484
|
+
|
|
485
|
+
# Configure health check
|
|
486
|
+
health_check_type = autoscaling.HealthCheck.ec2()
|
|
487
|
+
if self.asg_config.health_check_type.upper() == "ELB":
|
|
488
|
+
health_check_type = autoscaling.HealthCheck.elb(
|
|
489
|
+
grace=Duration.seconds(self.asg_config.health_check_grace_period)
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
# Create Auto Scaling Group
|
|
493
|
+
asg = autoscaling.AutoScalingGroup(
|
|
494
|
+
self,
|
|
495
|
+
asg_name,
|
|
496
|
+
vpc=self.vpc,
|
|
497
|
+
vpc_subnets=subnets,
|
|
498
|
+
min_capacity=self.asg_config.min_capacity,
|
|
499
|
+
max_capacity=self.asg_config.max_capacity,
|
|
500
|
+
desired_capacity=self.asg_config.desired_capacity,
|
|
501
|
+
launch_template=self.launch_template,
|
|
502
|
+
health_check=health_check_type,
|
|
503
|
+
cooldown=Duration.seconds(self.asg_config.cooldown),
|
|
504
|
+
termination_policies=[
|
|
505
|
+
autoscaling.TerminationPolicy(policy)
|
|
506
|
+
for policy in self.asg_config.termination_policies
|
|
507
|
+
],
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
# Attach to target groups after ASG creation
|
|
511
|
+
self._attach_target_groups(asg)
|
|
512
|
+
|
|
513
|
+
# Configure update policy
|
|
514
|
+
# Only apply update policy if it was explicitly configured
|
|
515
|
+
if "update_policy" in self.stack_config.dictionary.get("auto_scaling", {}):
|
|
516
|
+
update_policy = self.asg_config.update_policy
|
|
517
|
+
# Apply the update policy to the ASG's CloudFormation resource
|
|
518
|
+
cfn_asg = asg.node.default_child
|
|
519
|
+
cfn_asg.add_override(
|
|
520
|
+
"UpdatePolicy",
|
|
521
|
+
{
|
|
522
|
+
"AutoScalingRollingUpdate": {
|
|
523
|
+
"MinInstancesInService": update_policy.get(
|
|
524
|
+
"min_instances_in_service", 1
|
|
525
|
+
),
|
|
526
|
+
"MaxBatchSize": update_policy.get("max_batch_size", 1),
|
|
527
|
+
"PauseTime": f"PT{update_policy.get('pause_time', 300) // 60}M",
|
|
528
|
+
}
|
|
529
|
+
},
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
# Add tags
|
|
533
|
+
for key, value in self.asg_config.tags.items():
|
|
534
|
+
cdk.Tags.of(asg).add(key, value)
|
|
535
|
+
|
|
536
|
+
return asg
|
|
537
|
+
|
|
538
|
+
def _configure_scaling_policies(self) -> None:
|
|
539
|
+
"""Configure scaling policies for the Auto Scaling Group"""
|
|
540
|
+
for policy in self.asg_config.scaling_policies:
|
|
541
|
+
policy_type = policy.get("type", "target_tracking")
|
|
542
|
+
|
|
543
|
+
if policy_type == "target_tracking":
|
|
544
|
+
self.auto_scaling_group.scale_on_metric(
|
|
545
|
+
f"{self.asg_config.name}-{policy.get('name', 'scaling-policy')}",
|
|
546
|
+
metric=self._get_metric(policy),
|
|
547
|
+
scaling_steps=self._get_scaling_steps(policy),
|
|
548
|
+
adjustment_type=autoscaling.AdjustmentType.CHANGE_IN_CAPACITY,
|
|
549
|
+
)
|
|
550
|
+
elif policy_type == "step":
|
|
551
|
+
self.auto_scaling_group.scale_on_metric(
|
|
552
|
+
f"{self.asg_config.name}-{policy.get('name', 'scaling-policy')}",
|
|
553
|
+
metric=self._get_metric(policy),
|
|
554
|
+
scaling_steps=self._get_scaling_steps(policy),
|
|
555
|
+
adjustment_type=autoscaling.AdjustmentType.CHANGE_IN_CAPACITY,
|
|
556
|
+
)
|
|
557
|
+
|
|
558
|
+
def _get_metric(self, policy: Dict[str, Any]) -> cloudwatch.Metric:
|
|
559
|
+
"""Get metric for scaling policy"""
|
|
560
|
+
# This is a simplified implementation
|
|
561
|
+
# In a real-world scenario, you would use CloudWatch metrics
|
|
562
|
+
return cloudwatch.Metric(
|
|
563
|
+
namespace="AWS/EC2",
|
|
564
|
+
metric_name=policy.get("metric_name", "CPUUtilization"),
|
|
565
|
+
dimensions_map={
|
|
566
|
+
"AutoScalingGroupName": self.auto_scaling_group.auto_scaling_group_name
|
|
567
|
+
},
|
|
568
|
+
statistic=policy.get("statistic", "Average"),
|
|
569
|
+
period=Duration.seconds(policy.get("period", 60)),
|
|
570
|
+
)
|
|
571
|
+
|
|
572
|
+
def _get_scaling_steps(
|
|
573
|
+
self, policy: Dict[str, Any]
|
|
574
|
+
) -> List[autoscaling.ScalingInterval]:
|
|
575
|
+
"""Get scaling steps for scaling policy"""
|
|
576
|
+
steps = policy.get("steps", [])
|
|
577
|
+
scaling_intervals = []
|
|
578
|
+
|
|
579
|
+
for step in steps:
|
|
580
|
+
# Handle upper bound - if not specified, don't set it (let CDK handle it)
|
|
581
|
+
interval_kwargs = {
|
|
582
|
+
"lower": step.get("lower", 0),
|
|
583
|
+
"change": step.get("change", 1),
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
# Only set upper if it's explicitly provided
|
|
587
|
+
if "upper" in step:
|
|
588
|
+
interval_kwargs["upper"] = step["upper"]
|
|
589
|
+
|
|
590
|
+
scaling_intervals.append(autoscaling.ScalingInterval(**interval_kwargs))
|
|
591
|
+
|
|
592
|
+
return scaling_intervals
|
|
593
|
+
|
|
594
|
+
def _add_outputs(self, asg_name: str) -> None:
|
|
595
|
+
"""Add CloudFormation outputs for the Auto Scaling Group"""
|
|
596
|
+
if self.auto_scaling_group:
|
|
597
|
+
# Auto Scaling Group Name
|
|
598
|
+
cdk.CfnOutput(
|
|
599
|
+
self,
|
|
600
|
+
f"{asg_name}-name",
|
|
601
|
+
value=self.auto_scaling_group.auto_scaling_group_name,
|
|
602
|
+
export_name=f"{self.deployment.build_resource_name(asg_name)}-name",
|
|
603
|
+
)
|
|
604
|
+
|
|
605
|
+
# Auto Scaling Group ARN
|
|
606
|
+
cdk.CfnOutput(
|
|
607
|
+
self,
|
|
608
|
+
f"{asg_name}-arn",
|
|
609
|
+
value=self.auto_scaling_group.auto_scaling_group_arn,
|
|
610
|
+
export_name=f"{self.deployment.build_resource_name(asg_name)}-arn",
|
|
611
|
+
)
|
|
612
|
+
|
|
613
|
+
# Launch Template ID
|
|
614
|
+
if self.launch_template:
|
|
615
|
+
cdk.CfnOutput(
|
|
616
|
+
self,
|
|
617
|
+
f"{asg_name}-launch-template-id",
|
|
618
|
+
value=self.launch_template.launch_template_id,
|
|
619
|
+
export_name=f"{self.deployment.build_resource_name(asg_name)}-launch-template-id",
|
|
620
|
+
)
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
def _add_scaling_policies(self) -> None:
|
|
624
|
+
"""Add scaling policies to the Auto Scaling Group"""
|
|
625
|
+
for policy_config in self.asg_config.scaling_policies:
|
|
626
|
+
# Scaling policy implementation would go here
|
|
627
|
+
pass
|
|
628
|
+
|
|
629
|
+
def _add_scheduled_actions(self) -> None:
|
|
630
|
+
"""Add scheduled actions to the Auto Scaling Group"""
|
|
631
|
+
for action_config in self.asg_config.scheduled_actions:
|
|
632
|
+
# Scheduled action implementation would go here
|
|
633
|
+
pass
|
|
634
|
+
|
|
635
|
+
def _create_ecs_cluster_if_needed(self, asg_name: str):
|
|
636
|
+
"""
|
|
637
|
+
ECS cluster creation should be handled by the dedicated EcsClusterStack module.
|
|
638
|
+
This method only handles SSM imports for cluster name injection.
|
|
639
|
+
"""
|
|
640
|
+
# Check if ECS cluster name is available via SSM imports
|
|
641
|
+
if self.has_ssm_import("ecs_cluster_name"):
|
|
642
|
+
logger.info(f"ECS cluster name available via SSM imports")
|
|
643
|
+
# Inject cluster name into user data if available
|
|
644
|
+
if self.user_data and self.user_data_commands:
|
|
645
|
+
self._inject_cluster_name_into_user_data()
|
|
646
|
+
return
|
|
647
|
+
|
|
648
|
+
logger.warning(
|
|
649
|
+
"No ECS cluster name found in SSM imports. "
|
|
650
|
+
"Use the dedicated EcsClusterStack module to create ECS clusters."
|
|
651
|
+
)
|
|
652
|
+
|
|
653
|
+
def _inject_cluster_name_into_user_data(self) -> None:
|
|
654
|
+
"""Inject the ECS cluster name into user data commands using SSM imports"""
|
|
655
|
+
# Check if ECS cluster name is available via SSM imports
|
|
656
|
+
if self.has_ssm_import("ecs_cluster_name"):
|
|
657
|
+
cluster_name = self.get_ssm_imported_value("ecs_cluster_name")
|
|
658
|
+
logger.info(f"Using ECS cluster name from SSM: {cluster_name}")
|
|
659
|
+
else:
|
|
660
|
+
logger.warning("No ECS cluster name found in SSM imports, skipping cluster name injection")
|
|
661
|
+
return
|
|
662
|
+
|
|
663
|
+
injected_commands = []
|
|
664
|
+
cluster_name_injected = False
|
|
665
|
+
|
|
666
|
+
for command in self.user_data_commands:
|
|
667
|
+
# If this command already sets ECS_CLUSTER, replace it
|
|
668
|
+
if 'ECS_CLUSTER=' in command:
|
|
669
|
+
# Replace existing ECS_CLUSTER setting with our cluster name
|
|
670
|
+
parts = command.split('ECS_CLUSTER=')
|
|
671
|
+
if len(parts) > 1:
|
|
672
|
+
# Keep everything before ECS_CLUSTER=, add our cluster name, then add the rest
|
|
673
|
+
before = parts[0]
|
|
674
|
+
after_parts = parts[1].split(None, 1) # Split on first whitespace
|
|
675
|
+
after = after_parts[1] if len(after_parts) > 1 else ''
|
|
676
|
+
new_command = f"{before}ECS_CLUSTER={cluster_name} {after}".strip()
|
|
677
|
+
injected_commands.append(new_command)
|
|
678
|
+
cluster_name_injected = True
|
|
679
|
+
else:
|
|
680
|
+
injected_commands.append(f"{command}ECS_CLUSTER={cluster_name}")
|
|
681
|
+
cluster_name_injected = True
|
|
682
|
+
else:
|
|
683
|
+
injected_commands.append(command)
|
|
684
|
+
|
|
685
|
+
# If no ECS_CLUSTER was found in existing commands, add it
|
|
686
|
+
if not cluster_name_injected:
|
|
687
|
+
injected_commands.append(f"echo ECS_CLUSTER={cluster_name} >> /etc/ecs/ecs.config")
|
|
688
|
+
|
|
689
|
+
# Update the user data with the injected commands
|
|
690
|
+
self.user_data_commands = injected_commands
|
|
691
|
+
|
|
692
|
+
# If user data object exists, we need to recreate it with the updated commands
|
|
693
|
+
if hasattr(self, 'user_data') and self.user_data:
|
|
694
|
+
self.user_data = self._recreate_user_data_with_commands(injected_commands)
|
|
695
|
+
|
|
696
|
+
def _recreate_user_data_with_commands(self, commands: List[str]) -> ec2.UserData:
|
|
697
|
+
"""Recreate user data with updated commands"""
|
|
698
|
+
user_data = ec2.UserData.for_linux()
|
|
699
|
+
|
|
700
|
+
for command in commands:
|
|
701
|
+
user_data.add_commands(command)
|
|
702
|
+
|
|
703
|
+
return user_data
|
|
704
|
+
|
|
705
|
+
def _export_resources(self, asg_name: str) -> None:
|
|
706
|
+
"""Export stack resources to SSM and CloudFormation outputs"""
|
|
707
|
+
# Export ASG name
|
|
708
|
+
cdk.CfnOutput(
|
|
709
|
+
self,
|
|
710
|
+
f"{asg_name}-name",
|
|
711
|
+
value=self.auto_scaling_group.auto_scaling_group_name,
|
|
712
|
+
export_name=f"{self.deployment.build_resource_name(asg_name)}-name",
|
|
713
|
+
)
|
|
714
|
+
|
|
715
|
+
# Export ASG ARN
|
|
716
|
+
cdk.CfnOutput(
|
|
717
|
+
self,
|
|
718
|
+
f"{asg_name}-arn",
|
|
719
|
+
value=self.auto_scaling_group.auto_scaling_group_arn,
|
|
720
|
+
export_name=f"{self.deployment.build_resource_name(asg_name)}-arn",
|
|
721
|
+
)
|
|
@@ -497,14 +497,18 @@ class AutoScalingStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
|
|
|
497
497
|
# Get the underlying CloudFormation resource to add update policy
|
|
498
498
|
cfn_asg = self.auto_scaling_group.node.default_child
|
|
499
499
|
|
|
500
|
-
#
|
|
501
|
-
cfn_asg
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
500
|
+
# Get existing update policy or create new one
|
|
501
|
+
existing_policy = getattr(cfn_asg, 'update_policy', {})
|
|
502
|
+
|
|
503
|
+
# Merge rolling update policy with existing policy
|
|
504
|
+
existing_policy["AutoScalingRollingUpdate"] = {
|
|
505
|
+
"MinInstancesInService": update_policy.get("min_instances_in_service", 1),
|
|
506
|
+
"MaxBatchSize": update_policy.get("max_batch_size", 1),
|
|
507
|
+
"PauseTime": f"PT{update_policy.get('pause_time', 300)}S"
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
# Set the merged policy
|
|
511
|
+
cfn_asg.update_policy = existing_policy
|
|
508
512
|
|
|
509
513
|
logger.info("Added rolling update policy to Auto Scaling Group")
|
|
510
514
|
|
|
@@ -118,7 +118,9 @@ class EcsClusterStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
|
|
|
118
118
|
self._export_cluster_info()
|
|
119
119
|
|
|
120
120
|
# Export SSM parameters
|
|
121
|
+
logger.info("Starting SSM parameter export for ECS cluster")
|
|
121
122
|
self._export_ssm_parameters()
|
|
123
|
+
logger.info("Completed SSM parameter export for ECS cluster")
|
|
122
124
|
|
|
123
125
|
logger.info(f"ECS Cluster stack created: {cluster_name}")
|
|
124
126
|
|
|
@@ -174,6 +176,8 @@ class EcsClusterStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
|
|
|
174
176
|
|
|
175
177
|
def _create_iam_roles(self):
|
|
176
178
|
"""Create IAM roles for the ECS cluster if configured."""
|
|
179
|
+
logger.info(f"create_instance_role setting: {self.ecs_config.create_instance_role}")
|
|
180
|
+
|
|
177
181
|
if not self.ecs_config.create_instance_role:
|
|
178
182
|
logger.info("Skipping instance role creation (disabled in config)")
|
|
179
183
|
return
|
|
@@ -193,14 +197,18 @@ class EcsClusterStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
|
|
|
193
197
|
role_name=f"{self.ecs_config.name}-ecs-instance-role",
|
|
194
198
|
)
|
|
195
199
|
|
|
200
|
+
logger.info(f"Created ECS instance role: {self.instance_role.role_name}")
|
|
201
|
+
|
|
196
202
|
# Create instance profile
|
|
197
203
|
self.instance_profile = iam.CfnInstanceProfile(
|
|
198
204
|
self,
|
|
199
205
|
"ECSInstanceProfile",
|
|
200
|
-
roles=[self.instance_role.role_name],
|
|
201
206
|
instance_profile_name=f"{self.ecs_config.name}-ecs-instance-profile",
|
|
207
|
+
roles=[self.instance_role.role_name],
|
|
202
208
|
)
|
|
203
209
|
|
|
210
|
+
logger.info(f"Created ECS instance profile: {self.instance_profile.instance_profile_name}")
|
|
211
|
+
|
|
204
212
|
logger.info("ECS instance role and profile created")
|
|
205
213
|
|
|
206
214
|
def _export_cluster_info(self):
|
|
@@ -254,31 +262,51 @@ class EcsClusterStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
|
|
|
254
262
|
|
|
255
263
|
def _export_ssm_parameters(self) -> None:
|
|
256
264
|
"""Export SSM parameters using standardized approach"""
|
|
265
|
+
logger.info("=== Starting SSM Parameter Export ===")
|
|
266
|
+
|
|
257
267
|
if not self.ecs_cluster:
|
|
258
268
|
logger.warning("No ECS cluster to export")
|
|
259
269
|
return
|
|
260
270
|
|
|
271
|
+
logger.info(f"ECS cluster found: {self.ecs_cluster.cluster_name}")
|
|
272
|
+
logger.info(f"SSM exports configured: {self.ssm_config.get('exports', {})}")
|
|
273
|
+
|
|
261
274
|
# Prepare resource values for export
|
|
262
275
|
resource_values = {
|
|
263
276
|
"cluster_name": self.ecs_cluster.cluster_name,
|
|
264
277
|
"cluster_arn": self.ecs_cluster.cluster_arn,
|
|
265
|
-
"instance_role_arn": self.instance_role.role_arn,
|
|
266
278
|
}
|
|
267
279
|
|
|
280
|
+
# Add instance role ARN if created
|
|
281
|
+
if self.instance_role:
|
|
282
|
+
resource_values["instance_role_arn"] = self.instance_role.role_arn
|
|
283
|
+
logger.info(f"Instance role ARN added: {self.instance_role.role_name}")
|
|
284
|
+
else:
|
|
285
|
+
logger.info("No instance role to export")
|
|
286
|
+
|
|
268
287
|
# Add security group ID if available
|
|
269
288
|
if hasattr(self.ecs_cluster, 'connections') and self.ecs_cluster.connections:
|
|
270
289
|
security_groups = self.ecs_cluster.connections.security_groups
|
|
271
290
|
if security_groups:
|
|
272
291
|
resource_values["security_group_id"] = security_groups[0].security_group_id
|
|
292
|
+
logger.info(f"Security group ID added: {security_groups[0].security_group_id}")
|
|
273
293
|
|
|
274
294
|
# Add instance profile ARN if created
|
|
275
295
|
if self.instance_profile:
|
|
276
296
|
resource_values["instance_profile_arn"] = self.instance_profile.attr_arn
|
|
297
|
+
logger.info(f"Instance profile ARN added: {self.instance_profile.instance_profile_name}")
|
|
277
298
|
|
|
278
299
|
# Export using standardized SSM mixin
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
300
|
+
logger.info(f"Resource values available for export: {list(resource_values.keys())}")
|
|
301
|
+
for key, value in resource_values.items():
|
|
302
|
+
logger.info(f" {key}: {value}")
|
|
303
|
+
|
|
304
|
+
try:
|
|
305
|
+
exported_params = self.export_standardized_ssm_parameters(resource_values)
|
|
306
|
+
logger.info(f"Successfully exported SSM parameters: {exported_params}")
|
|
307
|
+
except Exception as e:
|
|
308
|
+
logger.error(f"Failed to export SSM parameters: {str(e)}")
|
|
309
|
+
raise
|
|
282
310
|
|
|
283
311
|
# Backward compatibility methods
|
|
284
312
|
def process_ssm_imports(self, config: Any, deployment: DeploymentConfig, resource_type: str = "resource") -> None:
|
cdk_factory/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.18.
|
|
1
|
+
__version__ = "0.18.4"
|
|
@@ -2,7 +2,7 @@ cdk_factory/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
2
2
|
cdk_factory/app.py,sha256=RnX0-pwdTAPAdKJK_j13Zl8anf9zYKBwboR0KA8K8xM,10346
|
|
3
3
|
cdk_factory/cdk.json,sha256=SKZKhJ2PBpFH78j-F8S3VDYW-lf76--Q2I3ON-ZIQfw,3106
|
|
4
4
|
cdk_factory/cli.py,sha256=FGbCTS5dYCNsfp-etshzvFlGDCjC28r6rtzYbe7KoHI,6407
|
|
5
|
-
cdk_factory/version.py,sha256=
|
|
5
|
+
cdk_factory/version.py,sha256=eFcwd1MYukWcWS9_p4d0HWJGW8jgPe27mOGuSygcjsY,23
|
|
6
6
|
cdk_factory/builds/README.md,sha256=9BBWd7bXpyKdMU_g2UljhQwrC9i5O_Tvkb6oPvndoZk,90
|
|
7
7
|
cdk_factory/commands/command_loader.py,sha256=QbLquuP_AdxtlxlDy-2IWCQ6D-7qa58aphnDPtp_uTs,3744
|
|
8
8
|
cdk_factory/configurations/base_config.py,sha256=eJ3Pl3GWk1jVr_bYQaaWlw4_-ZiFGaiXllI_fOOX1i0,9323
|
|
@@ -31,7 +31,7 @@ cdk_factory/configurations/resources/cognito.py,sha256=udX2AJ1ITLhy4f1XiJQwrva6F
|
|
|
31
31
|
cdk_factory/configurations/resources/docker.py,sha256=hUbuxkuhcQu9LnLX7I8_57eTmHefEAGVnOHO37MkqC4,2166
|
|
32
32
|
cdk_factory/configurations/resources/dynamodb.py,sha256=HsZMOaRwfuNPwKIzokeeE3f5zAQLTB5hRb_GzYq2ibg,2903
|
|
33
33
|
cdk_factory/configurations/resources/ecr.py,sha256=iJEtKqBT7vQU0LU4urIglraIR7cPZqp3HQBs_2An7kA,8303
|
|
34
|
-
cdk_factory/configurations/resources/ecs_cluster.py,sha256=
|
|
34
|
+
cdk_factory/configurations/resources/ecs_cluster.py,sha256=mQYJu7SUPDl5E4dMR6HCPFoWvFA3RGIb0iMNn-K7LX8,3635
|
|
35
35
|
cdk_factory/configurations/resources/ecs_service.py,sha256=iwUVewUy9fQSLpkfRTNuIaWy7eQYWTHFiiD1jIYt9zU,5452
|
|
36
36
|
cdk_factory/configurations/resources/exisiting.py,sha256=EVOLnkB-DGfTlmDgyQ5DD5k2zYfpFxqI3gugDR7mifI,478
|
|
37
37
|
cdk_factory/configurations/resources/lambda_edge.py,sha256=MjmiwDkys4aoRvDQhH3MT6BgeShzJXNWL7761HJrLtQ,3404
|
|
@@ -86,7 +86,8 @@ cdk_factory/stack_library/acm/__init__.py,sha256=4FNRLykblcKZvq_wieYwvv9N_jgrZnJ
|
|
|
86
86
|
cdk_factory/stack_library/acm/acm_stack.py,sha256=QJ3GkT17PmWoGkfO5Um02hvrfyJ9HbiPMnclwDP7IbA,5846
|
|
87
87
|
cdk_factory/stack_library/api_gateway/api_gateway_stack.py,sha256=_wbPBsgh7FHq9cnL44CiuffBj3XCO5ErQx_yclxFsVY,39669
|
|
88
88
|
cdk_factory/stack_library/auto_scaling/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
89
|
-
cdk_factory/stack_library/auto_scaling/
|
|
89
|
+
cdk_factory/stack_library/auto_scaling/auto_scaling_old.py,sha256=WvEwIao4KPOLa8e3zaQjxlTydCIK0GfgjZn4-CNyuF0,30135
|
|
90
|
+
cdk_factory/stack_library/auto_scaling/auto_scaling_stack_standardized.py,sha256=y208T0Qxbkhc7RRgWtQWmuN01Rt-ABxvcW20LoOYevI,22089
|
|
90
91
|
cdk_factory/stack_library/aws_lambdas/lambda_stack.py,sha256=SFbBPvvCopbyiuYtq-O5sQkFCf94Wzua6aDUXiFDSB4,26161
|
|
91
92
|
cdk_factory/stack_library/buckets/README.md,sha256=XkK3UNVtRLE7NtUvbhCOBBYUYi8hlrrSaI1s3GJVrqI,78
|
|
92
93
|
cdk_factory/stack_library/buckets/bucket_stack.py,sha256=SLoZqSffAqmeBBEVUQg54D_8Ad5UKdkjEAmKAVgAqQo,1778
|
|
@@ -98,7 +99,7 @@ cdk_factory/stack_library/dynamodb/dynamodb_stack.py,sha256=3_8lQP91GnBY77-61mtn
|
|
|
98
99
|
cdk_factory/stack_library/ecr/README.md,sha256=xw2wPx9WN03Y4BBwqvbi9lAFGNyaD1FUNpqxVJX14Oo,179
|
|
99
100
|
cdk_factory/stack_library/ecr/ecr_stack.py,sha256=KLbd5WN5-ZiojsS5wJ4PX-tIL0cCylCSvXjO6sVrgWY,2102
|
|
100
101
|
cdk_factory/stack_library/ecs/__init__.py,sha256=ebM8vVboNBplK0ua6bMSpNjewcFFkdvJ5wvVYEL1ONQ,302
|
|
101
|
-
cdk_factory/stack_library/ecs/ecs_cluster_stack_standardized.py,sha256
|
|
102
|
+
cdk_factory/stack_library/ecs/ecs_cluster_stack_standardized.py,sha256=-NK1U9vCFad8CcW6dOTguQgr0IBL_bhtg5cxyU2MR6U,12832
|
|
102
103
|
cdk_factory/stack_library/ecs/ecs_service_stack.py,sha256=3en447kWBOqd0d_i2C8mRRBscO2GqN9-B2l_PW7kZuM,27409
|
|
103
104
|
cdk_factory/stack_library/lambda_edge/__init__.py,sha256=ByBJ_CWdc4UtTmFBZH-6pzBMNkjkdtE65AmnB0Fs6lM,156
|
|
104
105
|
cdk_factory/stack_library/lambda_edge/lambda_edge_stack.py,sha256=ft5AxHy8__F90ZYDaoJwTjACGIfrn2Sd9Zr2CdHO7GE,16398
|
|
@@ -136,8 +137,8 @@ cdk_factory/utilities/os_execute.py,sha256=5Op0LY_8Y-pUm04y1k8MTpNrmQvcLmQHPQITE
|
|
|
136
137
|
cdk_factory/utils/api_gateway_utilities.py,sha256=If7Xu5s_UxmuV-kL3JkXxPLBdSVUKoLtohm0IUFoiV8,4378
|
|
137
138
|
cdk_factory/validation/config_validator.py,sha256=Pb0TkLiPFzUplBOgMorhRCVm08vEzZhRU5xXCDTa5CA,17602
|
|
138
139
|
cdk_factory/workload/workload_factory.py,sha256=yDI3cRhVI5ELNDcJPLpk9UY54Uind1xQoV3spzT4z7E,6068
|
|
139
|
-
cdk_factory-0.18.
|
|
140
|
-
cdk_factory-0.18.
|
|
141
|
-
cdk_factory-0.18.
|
|
142
|
-
cdk_factory-0.18.
|
|
143
|
-
cdk_factory-0.18.
|
|
140
|
+
cdk_factory-0.18.4.dist-info/METADATA,sha256=ugv0E7xmz7lqhLPyUTOHkFFpLn3F65OwhAcvnzPuIu8,2451
|
|
141
|
+
cdk_factory-0.18.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
142
|
+
cdk_factory-0.18.4.dist-info/entry_points.txt,sha256=S1DPe0ORcdiwEALMN_WIo3UQrW_g4YdQCLEsc_b0Swg,53
|
|
143
|
+
cdk_factory-0.18.4.dist-info/licenses/LICENSE,sha256=NOtdOeLwg2il_XBJdXUPFPX8JlV4dqTdDGAd2-khxT8,1066
|
|
144
|
+
cdk_factory-0.18.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|