cdk-factory 0.8.7__py3-none-any.whl → 0.9.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.
- cdk_factory/app.py +132 -2
- cdk_factory/cli.py +200 -0
- cdk_factory/configurations/resources/ecr.py +47 -2
- cdk_factory/configurations/resources/ecs_service.py +144 -0
- cdk_factory/constructs/ecr/ecr_construct.py +125 -53
- cdk_factory/pipeline/pipeline_factory.py +23 -2
- cdk_factory/stack_library/ecs/__init__.py +0 -0
- cdk_factory/stack_library/ecs/ecs_service_stack.py +525 -0
- cdk_factory/templates/README.md +99 -0
- cdk_factory/templates/app.py.template +36 -0
- cdk_factory/templates/cdk.json.template +73 -0
- cdk_factory/version.py +1 -1
- {cdk_factory-0.8.7.dist-info → cdk_factory-0.9.0.dist-info}/METADATA +1 -1
- {cdk_factory-0.8.7.dist-info → cdk_factory-0.9.0.dist-info}/RECORD +17 -9
- cdk_factory-0.9.0.dist-info/entry_points.txt +2 -0
- {cdk_factory-0.8.7.dist-info → cdk_factory-0.9.0.dist-info}/WHEEL +0 -0
- {cdk_factory-0.8.7.dist-info → cdk_factory-0.9.0.dist-info}/licenses/LICENSE +0 -0
cdk_factory/app.py
CHANGED
|
@@ -25,14 +25,52 @@ class CdkAppFactory:
|
|
|
25
25
|
config_path: str | None = None,
|
|
26
26
|
outdir: str | None = None,
|
|
27
27
|
add_env_context: bool = True,
|
|
28
|
+
auto_detect_project_root: bool = True,
|
|
29
|
+
is_pipeline: bool = False,
|
|
28
30
|
) -> None:
|
|
29
31
|
|
|
30
32
|
self.args = args or CommandlineArgs()
|
|
31
|
-
self.outdir = outdir or self.args.outdir
|
|
32
|
-
self.app: aws_cdk.App = aws_cdk.App()
|
|
33
33
|
self.runtime_directory = runtime_directory or str(Path(__file__).parent)
|
|
34
34
|
self.config_path: str | None = config_path
|
|
35
35
|
self.add_env_context = add_env_context
|
|
36
|
+
self._is_pipeline = is_pipeline
|
|
37
|
+
|
|
38
|
+
# Auto-detect outdir for CodeBuild compatibility
|
|
39
|
+
if outdir is None and self.args.outdir is None and auto_detect_project_root:
|
|
40
|
+
# Always detect project root first
|
|
41
|
+
project_root = self._detect_project_root()
|
|
42
|
+
|
|
43
|
+
# Check if we're in CodeBuild or building a pipeline
|
|
44
|
+
in_codebuild = bool(os.getenv('CODEBUILD_SRC_DIR'))
|
|
45
|
+
|
|
46
|
+
# Auto-detect if this is a pipeline deployment by checking config
|
|
47
|
+
is_pipeline_deployment = is_pipeline or self._check_if_pipeline_deployment(config_path)
|
|
48
|
+
|
|
49
|
+
if in_codebuild or is_pipeline_deployment:
|
|
50
|
+
# For pipelines, calculate relative path from runtime_directory to project_root/cdk.out
|
|
51
|
+
# This ensures cdk.out is always at project root, even when app.py is in subdirectory
|
|
52
|
+
runtime_path = Path(self.runtime_directory).resolve()
|
|
53
|
+
project_path = Path(project_root).resolve()
|
|
54
|
+
cdk_out_path = project_path / 'cdk.out'
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
# Calculate relative path from runtime directory to project_root/cdk.out
|
|
58
|
+
relative_path = os.path.relpath(cdk_out_path, runtime_path)
|
|
59
|
+
self.outdir = relative_path
|
|
60
|
+
if in_codebuild:
|
|
61
|
+
print(f"📦 CodeBuild detected: using relative path '{relative_path}'")
|
|
62
|
+
else:
|
|
63
|
+
print(f"📦 Pipeline deployment detected: using relative path '{relative_path}'")
|
|
64
|
+
except ValueError:
|
|
65
|
+
# If paths are on different drives (Windows), fallback to absolute
|
|
66
|
+
self.outdir = str(cdk_out_path)
|
|
67
|
+
else:
|
|
68
|
+
# For local dev, use absolute path at project root
|
|
69
|
+
self.outdir = os.path.join(project_root, 'cdk.out')
|
|
70
|
+
else:
|
|
71
|
+
self.outdir = outdir or self.args.outdir
|
|
72
|
+
|
|
73
|
+
self.app: aws_cdk.App = aws_cdk.App(outdir=self.outdir)
|
|
36
74
|
|
|
37
75
|
def synth(
|
|
38
76
|
self,
|
|
@@ -84,6 +122,98 @@ class CdkAppFactory:
|
|
|
84
122
|
|
|
85
123
|
return assembly
|
|
86
124
|
|
|
125
|
+
def _detect_project_root(self) -> str:
|
|
126
|
+
"""
|
|
127
|
+
Detect project root directory for proper cdk.out placement
|
|
128
|
+
|
|
129
|
+
Priority:
|
|
130
|
+
1. CODEBUILD_SRC_DIR (CodeBuild environment)
|
|
131
|
+
2. Find project markers (pyproject.toml, package.json, .git, etc.)
|
|
132
|
+
3. Assume devops/cdk-iac structure (go up 2 levels)
|
|
133
|
+
4. Fallback to runtime_directory
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
str: Absolute path to project root
|
|
137
|
+
"""
|
|
138
|
+
# Priority 1: CodeBuild environment (most reliable)
|
|
139
|
+
codebuild_src = os.getenv("CODEBUILD_SRC_DIR")
|
|
140
|
+
if codebuild_src:
|
|
141
|
+
return str(Path(codebuild_src).resolve())
|
|
142
|
+
|
|
143
|
+
# Priority 2: Look for project root markers
|
|
144
|
+
# CodeBuild often gets zip without .git, so check multiple markers
|
|
145
|
+
current = Path(self.runtime_directory).resolve()
|
|
146
|
+
|
|
147
|
+
# Walk up the directory tree looking for root markers
|
|
148
|
+
for parent in [current] + list(current.parents):
|
|
149
|
+
# Check for common project root indicators
|
|
150
|
+
root_markers = [
|
|
151
|
+
".git", # Git repo (local dev)
|
|
152
|
+
"pyproject.toml", # Python project root
|
|
153
|
+
"package.json", # Node project root
|
|
154
|
+
"Cargo.toml", # Rust project root
|
|
155
|
+
".gitignore", # Often at root
|
|
156
|
+
"README.md", # Often at root
|
|
157
|
+
"requirements.txt", # Python dependencies
|
|
158
|
+
]
|
|
159
|
+
|
|
160
|
+
# If we find multiple markers at this level, it's likely the root
|
|
161
|
+
markers_found = sum(
|
|
162
|
+
1 for marker in root_markers if (parent / marker).exists()
|
|
163
|
+
)
|
|
164
|
+
if markers_found >= 2 and parent != current:
|
|
165
|
+
return str(parent)
|
|
166
|
+
|
|
167
|
+
# Priority 3: Assume devops/cdk-iac structure
|
|
168
|
+
# If runtime_directory ends with devops/cdk-iac, go up 2 levels
|
|
169
|
+
parts = current.parts
|
|
170
|
+
if len(parts) >= 2 and parts[-2:] == ("devops", "cdk-iac"):
|
|
171
|
+
return str(current.parent.parent)
|
|
172
|
+
|
|
173
|
+
# Also try just 'cdk-iac' or 'devops'
|
|
174
|
+
if len(parts) >= 1 and parts[-1] in (
|
|
175
|
+
"cdk-iac",
|
|
176
|
+
"devops",
|
|
177
|
+
"infrastructure",
|
|
178
|
+
"iac",
|
|
179
|
+
):
|
|
180
|
+
# Go up until we're not in these directories
|
|
181
|
+
potential_root = current.parent
|
|
182
|
+
while potential_root.name in ("devops", "cdk-iac", "infrastructure", "iac"):
|
|
183
|
+
potential_root = potential_root.parent
|
|
184
|
+
return str(potential_root)
|
|
185
|
+
|
|
186
|
+
# Priority 4: Fallback to runtime_directory
|
|
187
|
+
return str(current)
|
|
188
|
+
|
|
189
|
+
def _check_if_pipeline_deployment(self, config_path: str | None) -> bool:
|
|
190
|
+
"""
|
|
191
|
+
Check if the configuration includes pipeline deployments with CI/CD enabled.
|
|
192
|
+
Returns True if pipelines are detected, False otherwise.
|
|
193
|
+
"""
|
|
194
|
+
if not config_path or not os.path.exists(config_path):
|
|
195
|
+
return False
|
|
196
|
+
|
|
197
|
+
try:
|
|
198
|
+
import json
|
|
199
|
+
with open(config_path, 'r') as f:
|
|
200
|
+
config = json.load(f)
|
|
201
|
+
|
|
202
|
+
# Check for workload.deployments with CI/CD enabled
|
|
203
|
+
workload = config.get('workload', {})
|
|
204
|
+
deployments = workload.get('deployments', [])
|
|
205
|
+
|
|
206
|
+
for deployment in deployments:
|
|
207
|
+
devops = deployment.get('devops', {})
|
|
208
|
+
ci_cd = devops.get('ci_cd', {})
|
|
209
|
+
if ci_cd.get('enabled', False):
|
|
210
|
+
return True
|
|
211
|
+
|
|
212
|
+
return False
|
|
213
|
+
except:
|
|
214
|
+
# If we can't read/parse the config, assume not a pipeline
|
|
215
|
+
return False
|
|
216
|
+
|
|
87
217
|
|
|
88
218
|
if __name__ == "__main__":
|
|
89
219
|
# deploy_test()
|
cdk_factory/cli.py
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
CDK Factory CLI
|
|
4
|
+
|
|
5
|
+
Provides convenience commands for initializing and managing cdk-factory projects.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import argparse
|
|
9
|
+
import os
|
|
10
|
+
import shutil
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Optional
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class CdkFactoryCLI:
|
|
16
|
+
"""CLI for cdk-factory project management"""
|
|
17
|
+
|
|
18
|
+
def __init__(self):
|
|
19
|
+
self.package_root = Path(__file__).parent.resolve()
|
|
20
|
+
self.templates_dir = self.package_root / "templates"
|
|
21
|
+
|
|
22
|
+
# Verify templates directory exists
|
|
23
|
+
if not self.templates_dir.exists():
|
|
24
|
+
raise RuntimeError(
|
|
25
|
+
f"Templates directory not found at {self.templates_dir}. "
|
|
26
|
+
"Please ensure cdk-factory is properly installed."
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
def init_project(
|
|
30
|
+
self,
|
|
31
|
+
target_dir: str,
|
|
32
|
+
workload_name: Optional[str] = None,
|
|
33
|
+
environment: Optional[str] = None,
|
|
34
|
+
) -> None:
|
|
35
|
+
"""
|
|
36
|
+
Initialize a new cdk-factory project
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
target_dir: Directory to initialize (e.g., devops/cdk-iac)
|
|
40
|
+
workload_name: Name of the workload (optional)
|
|
41
|
+
environment: Environment name (optional)
|
|
42
|
+
"""
|
|
43
|
+
target_path = Path(target_dir).resolve()
|
|
44
|
+
|
|
45
|
+
if not target_path.exists():
|
|
46
|
+
target_path.mkdir(parents=True, exist_ok=True)
|
|
47
|
+
print(f"✅ Created directory: {target_path}")
|
|
48
|
+
|
|
49
|
+
# Copy app.py template
|
|
50
|
+
app_template = self.templates_dir / "app.py.template"
|
|
51
|
+
app_dest = target_path / "app.py"
|
|
52
|
+
|
|
53
|
+
if app_dest.exists():
|
|
54
|
+
response = input(f"⚠️ {app_dest} already exists. Overwrite? (y/N): ")
|
|
55
|
+
if response.lower() != 'y':
|
|
56
|
+
print("Skipped app.py")
|
|
57
|
+
else:
|
|
58
|
+
shutil.copy(app_template, app_dest)
|
|
59
|
+
print(f"✅ Created {app_dest}")
|
|
60
|
+
else:
|
|
61
|
+
shutil.copy(app_template, app_dest)
|
|
62
|
+
print(f"✅ Created {app_dest}")
|
|
63
|
+
|
|
64
|
+
# Copy cdk.json template
|
|
65
|
+
cdk_json_template = self.templates_dir / "cdk.json.template"
|
|
66
|
+
cdk_json_dest = target_path / "cdk.json"
|
|
67
|
+
|
|
68
|
+
if cdk_json_dest.exists():
|
|
69
|
+
print(f"⚠️ {cdk_json_dest} already exists. Skipping.")
|
|
70
|
+
else:
|
|
71
|
+
shutil.copy(cdk_json_template, cdk_json_dest)
|
|
72
|
+
print(f"✅ Created {cdk_json_dest}")
|
|
73
|
+
|
|
74
|
+
# Create minimal config.json
|
|
75
|
+
config_dest = target_path / "config.json"
|
|
76
|
+
if config_dest.exists():
|
|
77
|
+
print(f"⚠️ {config_dest} already exists. Skipping.")
|
|
78
|
+
else:
|
|
79
|
+
self._create_minimal_config(
|
|
80
|
+
config_dest,
|
|
81
|
+
workload_name=workload_name,
|
|
82
|
+
environment=environment
|
|
83
|
+
)
|
|
84
|
+
print(f"✅ Created {config_dest}")
|
|
85
|
+
|
|
86
|
+
# Create .gitignore
|
|
87
|
+
gitignore_dest = target_path / ".gitignore"
|
|
88
|
+
if not gitignore_dest.exists():
|
|
89
|
+
gitignore_dest.write_text("cdk.out/\n*.swp\n.DS_Store\n__pycache__/\n")
|
|
90
|
+
print(f"✅ Created {gitignore_dest}")
|
|
91
|
+
|
|
92
|
+
print("\n✨ Project initialized successfully!")
|
|
93
|
+
print(f"\nNext steps:")
|
|
94
|
+
print(f"1. cd {target_path}")
|
|
95
|
+
print(f"2. Edit config.json to configure your infrastructure")
|
|
96
|
+
print(f"3. Run: cdk synth")
|
|
97
|
+
print(f"4. Run: cdk deploy")
|
|
98
|
+
|
|
99
|
+
def _create_minimal_config(
|
|
100
|
+
self,
|
|
101
|
+
path: Path,
|
|
102
|
+
workload_name: Optional[str] = None,
|
|
103
|
+
environment: Optional[str] = None
|
|
104
|
+
) -> None:
|
|
105
|
+
"""Create a minimal config.json template"""
|
|
106
|
+
config = {
|
|
107
|
+
"cdk": {
|
|
108
|
+
"parameters": [
|
|
109
|
+
{
|
|
110
|
+
"placeholder": "{{ENVIRONMENT}}",
|
|
111
|
+
"env_var_name": "ENVIRONMENT",
|
|
112
|
+
"cdk_parameter_name": "Environment"
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"placeholder": "{{WORKLOAD_NAME}}",
|
|
116
|
+
"env_var_name": "WORKLOAD_NAME",
|
|
117
|
+
"cdk_parameter_name": "WorkloadName"
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"placeholder": "{{AWS_ACCOUNT}}",
|
|
121
|
+
"env_var_name": "AWS_ACCOUNT",
|
|
122
|
+
"cdk_parameter_name": "AccountNumber"
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"placeholder": "{{AWS_REGION}}",
|
|
126
|
+
"env_var_name": "AWS_REGION",
|
|
127
|
+
"cdk_parameter_name": "AccountRegion"
|
|
128
|
+
}
|
|
129
|
+
]
|
|
130
|
+
},
|
|
131
|
+
"workload": {
|
|
132
|
+
"name": workload_name or "{{WORKLOAD_NAME}}",
|
|
133
|
+
"environment": environment or "{{ENVIRONMENT}}",
|
|
134
|
+
"deployments": []
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
import json
|
|
139
|
+
path.write_text(json.dumps(config, indent=2))
|
|
140
|
+
|
|
141
|
+
def list_templates(self) -> None:
|
|
142
|
+
"""List available templates"""
|
|
143
|
+
print("Available templates:")
|
|
144
|
+
if self.templates_dir.exists():
|
|
145
|
+
for template in self.templates_dir.glob("*.template"):
|
|
146
|
+
print(f" - {template.name}")
|
|
147
|
+
else:
|
|
148
|
+
print(" No templates found")
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def main():
|
|
152
|
+
"""CLI entry point"""
|
|
153
|
+
parser = argparse.ArgumentParser(
|
|
154
|
+
description="CDK Factory CLI - Initialize and manage cdk-factory projects"
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
|
158
|
+
|
|
159
|
+
# Init command
|
|
160
|
+
init_parser = subparsers.add_parser(
|
|
161
|
+
"init",
|
|
162
|
+
help="Initialize a new cdk-factory project"
|
|
163
|
+
)
|
|
164
|
+
init_parser.add_argument(
|
|
165
|
+
"directory",
|
|
166
|
+
help="Target directory (e.g., devops/cdk-iac)"
|
|
167
|
+
)
|
|
168
|
+
init_parser.add_argument(
|
|
169
|
+
"--workload-name",
|
|
170
|
+
help="Workload name"
|
|
171
|
+
)
|
|
172
|
+
init_parser.add_argument(
|
|
173
|
+
"--environment",
|
|
174
|
+
help="Environment (dev, prod, etc.)"
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
# List templates command
|
|
178
|
+
subparsers.add_parser(
|
|
179
|
+
"list-templates",
|
|
180
|
+
help="List available templates"
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
args = parser.parse_args()
|
|
184
|
+
|
|
185
|
+
cli = CdkFactoryCLI()
|
|
186
|
+
|
|
187
|
+
if args.command == "init":
|
|
188
|
+
cli.init_project(
|
|
189
|
+
args.directory,
|
|
190
|
+
workload_name=args.workload_name,
|
|
191
|
+
environment=args.environment
|
|
192
|
+
)
|
|
193
|
+
elif args.command == "list-templates":
|
|
194
|
+
cli.list_templates()
|
|
195
|
+
else:
|
|
196
|
+
parser.print_help()
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
if __name__ == "__main__":
|
|
200
|
+
main()
|
|
@@ -16,7 +16,7 @@ class ECRConfig(EnhancedBaseConfig):
|
|
|
16
16
|
) -> None:
|
|
17
17
|
super().__init__(config, resource_type="ecr", resource_name=config.get("name", "ecr") if config else "ecr")
|
|
18
18
|
self.__config = config
|
|
19
|
-
self.__deployment =
|
|
19
|
+
self.__deployment = deployment
|
|
20
20
|
self.__ssm_prefix_template = config.get("ssm_prefix_template", None)
|
|
21
21
|
|
|
22
22
|
@property
|
|
@@ -74,12 +74,13 @@ class ECRConfig(EnhancedBaseConfig):
|
|
|
74
74
|
Clear out untagged images after x days. This helps save costs.
|
|
75
75
|
Untagged images will stay forever if you don't clean them out.
|
|
76
76
|
"""
|
|
77
|
+
days = None
|
|
77
78
|
if self.__config and isinstance(self.__config, dict):
|
|
78
79
|
days = self.__config.get("auto_delete_untagged_images_in_days")
|
|
79
80
|
if days:
|
|
80
81
|
days = int(days)
|
|
81
82
|
|
|
82
|
-
return
|
|
83
|
+
return days
|
|
83
84
|
|
|
84
85
|
@property
|
|
85
86
|
def use_existing(self) -> bool:
|
|
@@ -118,6 +119,50 @@ class ECRConfig(EnhancedBaseConfig):
|
|
|
118
119
|
if not value:
|
|
119
120
|
raise RuntimeError("Region is not defined")
|
|
120
121
|
return value
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def cross_account_access(self) -> dict:
|
|
125
|
+
"""
|
|
126
|
+
Cross-account access configuration.
|
|
127
|
+
|
|
128
|
+
Example:
|
|
129
|
+
{
|
|
130
|
+
"enabled": true,
|
|
131
|
+
"accounts": ["123456789012", "987654321098"],
|
|
132
|
+
"services": [
|
|
133
|
+
{
|
|
134
|
+
"name": "lambda",
|
|
135
|
+
"actions": ["ecr:BatchGetImage", "ecr:GetDownloadUrlForLayer"],
|
|
136
|
+
"condition": {
|
|
137
|
+
"StringLike": {
|
|
138
|
+
"aws:sourceArn": "arn:aws:lambda:*:*:function:*"
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
"name": "ecs-tasks",
|
|
144
|
+
"service_principal": "ecs-tasks.amazonaws.com",
|
|
145
|
+
"actions": ["ecr:BatchGetImage", "ecr:GetDownloadUrlForLayer"]
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
"name": "codebuild",
|
|
149
|
+
"service_principal": "codebuild.amazonaws.com",
|
|
150
|
+
"actions": ["ecr:BatchGetImage", "ecr:GetDownloadUrlForLayer", "ecr:BatchCheckLayerAvailability"]
|
|
151
|
+
}
|
|
152
|
+
]
|
|
153
|
+
}
|
|
154
|
+
"""
|
|
155
|
+
if self.__config and isinstance(self.__config, dict):
|
|
156
|
+
return self.__config.get("cross_account_access", {})
|
|
157
|
+
return {}
|
|
158
|
+
|
|
159
|
+
@property
|
|
160
|
+
def cross_account_enabled(self) -> bool:
|
|
161
|
+
"""Whether cross-account access is explicitly enabled"""
|
|
162
|
+
access_config = self.cross_account_access
|
|
163
|
+
if access_config:
|
|
164
|
+
return str(access_config.get("enabled", "true")).lower() == "true"
|
|
165
|
+
return True # Default to enabled for backward compatibility
|
|
121
166
|
|
|
122
167
|
# SSM properties are now inherited from EnhancedBaseConfig
|
|
123
168
|
# Keeping these for any direct access patterns in existing code
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ECS Service Configuration
|
|
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
|
+
|
|
10
|
+
class EcsServiceConfig:
|
|
11
|
+
"""ECS Service Configuration"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, config: Dict[str, Any]) -> None:
|
|
14
|
+
self._config = config
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def name(self) -> str:
|
|
18
|
+
"""Service name"""
|
|
19
|
+
return self._config.get("name", "")
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def cluster_name(self) -> Optional[str]:
|
|
23
|
+
"""ECS Cluster name"""
|
|
24
|
+
return self._config.get("cluster_name")
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def task_definition(self) -> Dict[str, Any]:
|
|
28
|
+
"""Task definition configuration"""
|
|
29
|
+
return self._config.get("task_definition", {})
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def container_definitions(self) -> List[Dict[str, Any]]:
|
|
33
|
+
"""Container definitions"""
|
|
34
|
+
return self.task_definition.get("containers", [])
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def cpu(self) -> str:
|
|
38
|
+
"""Task CPU units"""
|
|
39
|
+
return self.task_definition.get("cpu", "256")
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def memory(self) -> str:
|
|
43
|
+
"""Task memory (MB)"""
|
|
44
|
+
return self.task_definition.get("memory", "512")
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def launch_type(self) -> str:
|
|
48
|
+
"""Launch type: FARGATE or EC2"""
|
|
49
|
+
return self._config.get("launch_type", "FARGATE")
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def desired_count(self) -> int:
|
|
53
|
+
"""Desired number of tasks"""
|
|
54
|
+
return self._config.get("desired_count", 2)
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def min_capacity(self) -> int:
|
|
58
|
+
"""Minimum number of tasks"""
|
|
59
|
+
return self._config.get("min_capacity", 1)
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def max_capacity(self) -> int:
|
|
63
|
+
"""Maximum number of tasks"""
|
|
64
|
+
return self._config.get("max_capacity", 4)
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def vpc_id(self) -> Optional[str]:
|
|
68
|
+
"""VPC ID"""
|
|
69
|
+
return self._config.get("vpc_id")
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def subnet_group_name(self) -> Optional[str]:
|
|
73
|
+
"""Subnet group name for service placement"""
|
|
74
|
+
return self._config.get("subnet_group_name")
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def security_group_ids(self) -> List[str]:
|
|
78
|
+
"""Security group IDs"""
|
|
79
|
+
return self._config.get("security_group_ids", [])
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def assign_public_ip(self) -> bool:
|
|
83
|
+
"""Whether to assign public IP addresses"""
|
|
84
|
+
return self._config.get("assign_public_ip", False)
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def target_group_arns(self) -> List[str]:
|
|
88
|
+
"""Target group ARNs for load balancing"""
|
|
89
|
+
return self._config.get("target_group_arns", [])
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def container_port(self) -> int:
|
|
93
|
+
"""Container port for load balancer"""
|
|
94
|
+
return self._config.get("container_port", 80)
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def health_check_grace_period(self) -> int:
|
|
98
|
+
"""Health check grace period in seconds"""
|
|
99
|
+
return self._config.get("health_check_grace_period", 60)
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def enable_execute_command(self) -> bool:
|
|
103
|
+
"""Enable ECS Exec for debugging"""
|
|
104
|
+
return self._config.get("enable_execute_command", False)
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def enable_auto_scaling(self) -> bool:
|
|
108
|
+
"""Enable auto-scaling"""
|
|
109
|
+
return self._config.get("enable_auto_scaling", True)
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def auto_scaling_target_cpu(self) -> int:
|
|
113
|
+
"""Target CPU utilization percentage for auto-scaling"""
|
|
114
|
+
return self._config.get("auto_scaling_target_cpu", 70)
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def auto_scaling_target_memory(self) -> int:
|
|
118
|
+
"""Target memory utilization percentage for auto-scaling"""
|
|
119
|
+
return self._config.get("auto_scaling_target_memory", 80)
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def tags(self) -> Dict[str, str]:
|
|
123
|
+
"""Resource tags"""
|
|
124
|
+
return self._config.get("tags", {})
|
|
125
|
+
|
|
126
|
+
@property
|
|
127
|
+
def ssm_exports(self) -> Dict[str, str]:
|
|
128
|
+
"""SSM parameter exports"""
|
|
129
|
+
return self._config.get("ssm_exports", {})
|
|
130
|
+
|
|
131
|
+
@property
|
|
132
|
+
def ssm_imports(self) -> Dict[str, str]:
|
|
133
|
+
"""SSM parameter imports"""
|
|
134
|
+
return self._config.get("ssm_imports", {})
|
|
135
|
+
|
|
136
|
+
@property
|
|
137
|
+
def deployment_type(self) -> str:
|
|
138
|
+
"""Deployment type: production, maintenance, or blue-green"""
|
|
139
|
+
return self._config.get("deployment_type", "production")
|
|
140
|
+
|
|
141
|
+
@property
|
|
142
|
+
def is_maintenance_mode(self) -> bool:
|
|
143
|
+
"""Whether this is a maintenance mode deployment"""
|
|
144
|
+
return self.deployment_type == "maintenance"
|