supervaizer 0.9.8__py3-none-any.whl → 0.10.1__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.
- supervaizer/__init__.py +11 -2
- supervaizer/__version__.py +1 -1
- supervaizer/account.py +4 -0
- supervaizer/account_service.py +7 -1
- supervaizer/admin/routes.py +24 -8
- supervaizer/admin/templates/agents.html +74 -0
- supervaizer/admin/templates/agents_grid.html +5 -3
- supervaizer/admin/templates/navigation.html +11 -1
- supervaizer/admin/templates/supervaize_instructions.html +212 -0
- supervaizer/agent.py +28 -6
- supervaizer/case.py +46 -14
- supervaizer/cli.py +247 -7
- supervaizer/common.py +45 -4
- supervaizer/deploy/__init__.py +16 -0
- supervaizer/deploy/cli.py +296 -0
- supervaizer/deploy/commands/__init__.py +9 -0
- supervaizer/deploy/commands/clean.py +294 -0
- supervaizer/deploy/commands/down.py +119 -0
- supervaizer/deploy/commands/local.py +460 -0
- supervaizer/deploy/commands/plan.py +167 -0
- supervaizer/deploy/commands/status.py +169 -0
- supervaizer/deploy/commands/up.py +281 -0
- supervaizer/deploy/docker.py +378 -0
- supervaizer/deploy/driver_factory.py +42 -0
- supervaizer/deploy/drivers/__init__.py +39 -0
- supervaizer/deploy/drivers/aws_app_runner.py +607 -0
- supervaizer/deploy/drivers/base.py +196 -0
- supervaizer/deploy/drivers/cloud_run.py +570 -0
- supervaizer/deploy/drivers/do_app_platform.py +504 -0
- supervaizer/deploy/health.py +404 -0
- supervaizer/deploy/state.py +210 -0
- supervaizer/deploy/templates/Dockerfile.template +44 -0
- supervaizer/deploy/templates/debug_env.py +69 -0
- supervaizer/deploy/templates/docker-compose.yml.template +37 -0
- supervaizer/deploy/templates/dockerignore.template +66 -0
- supervaizer/deploy/templates/entrypoint.sh +20 -0
- supervaizer/deploy/utils.py +52 -0
- supervaizer/examples/controller_template.py +1 -1
- supervaizer/job.py +18 -5
- supervaizer/job_service.py +6 -5
- supervaizer/parameter.py +13 -1
- supervaizer/protocol/__init__.py +2 -2
- supervaizer/protocol/a2a/routes.py +1 -1
- supervaizer/routes.py +141 -17
- supervaizer/server.py +5 -11
- supervaizer/utils/__init__.py +16 -0
- supervaizer/utils/version_check.py +56 -0
- {supervaizer-0.9.8.dist-info → supervaizer-0.10.1.dist-info}/METADATA +105 -34
- supervaizer-0.10.1.dist-info/RECORD +76 -0
- {supervaizer-0.9.8.dist-info → supervaizer-0.10.1.dist-info}/WHEEL +1 -1
- supervaizer/protocol/acp/__init__.py +0 -21
- supervaizer/protocol/acp/model.py +0 -198
- supervaizer/protocol/acp/routes.py +0 -74
- supervaizer-0.9.8.dist-info/RECORD +0 -52
- {supervaizer-0.9.8.dist-info → supervaizer-0.10.1.dist-info}/entry_points.txt +0 -0
- {supervaizer-0.9.8.dist-info → supervaizer-0.10.1.dist-info}/licenses/LICENSE.md +0 -0
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
# Copyright (c) 2024-2025 Alain Prasquier - Supervaize.com. All rights reserved.
|
|
2
|
+
#
|
|
3
|
+
# This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
|
|
4
|
+
# If a copy of the MPL was not distributed with this file, you can obtain one at
|
|
5
|
+
# https://mozilla.org/MPL/2.0/.
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
Docker Operations
|
|
9
|
+
|
|
10
|
+
This module handles Docker-related operations for deployment.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
import subprocess
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Optional
|
|
17
|
+
|
|
18
|
+
from rich.console import Console
|
|
19
|
+
|
|
20
|
+
from supervaizer.common import log
|
|
21
|
+
|
|
22
|
+
console = Console()
|
|
23
|
+
|
|
24
|
+
TEMPLATE_DIR = Path(__file__).parent / "templates"
|
|
25
|
+
|
|
26
|
+
# List of environment variables to include in Dockerfile
|
|
27
|
+
DOCKER_ENV_VARS = [
|
|
28
|
+
"SUPERVAIZE_API_KEY",
|
|
29
|
+
"SUPERVAIZE_WORKSPACE_ID",
|
|
30
|
+
"SUPERVAIZE_API_URL",
|
|
31
|
+
"SUPERVAIZER_PORT",
|
|
32
|
+
"SUPERVAIZER_PUBLIC_URL",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def get_docker_env_vars(port: int = 8000) -> dict[str, str]:
|
|
37
|
+
"""Get environment variables for Docker deployment.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
port: The application port to use for SUPERVAIZER_PORT
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
Dictionary mapping environment variable names to their values
|
|
44
|
+
"""
|
|
45
|
+
env_vars = {}
|
|
46
|
+
|
|
47
|
+
for var_name in DOCKER_ENV_VARS:
|
|
48
|
+
if var_name == "SUPERVAIZER_PORT":
|
|
49
|
+
env_vars[var_name] = str(port)
|
|
50
|
+
else:
|
|
51
|
+
env_vars[var_name] = os.getenv(var_name, "")
|
|
52
|
+
|
|
53
|
+
return env_vars
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def get_docker_build_args(port: int = 8000) -> dict[str, str]:
|
|
57
|
+
"""Get build arguments for Docker deployment.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
port: The application port to use for SUPERVAIZER_PORT
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
Dictionary mapping build argument names to their values
|
|
64
|
+
"""
|
|
65
|
+
build_args = {}
|
|
66
|
+
|
|
67
|
+
for var_name in DOCKER_ENV_VARS:
|
|
68
|
+
if var_name == "SUPERVAIZER_PORT":
|
|
69
|
+
build_args[var_name] = str(port)
|
|
70
|
+
else:
|
|
71
|
+
# Only include build args for variables that are set
|
|
72
|
+
value = os.getenv(var_name)
|
|
73
|
+
if value:
|
|
74
|
+
build_args[var_name] = value
|
|
75
|
+
|
|
76
|
+
return build_args
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class DockerManager:
|
|
80
|
+
"""Manages Docker operations for deployment."""
|
|
81
|
+
|
|
82
|
+
def __init__(self, require_docker: bool = True) -> None:
|
|
83
|
+
"""Initialize Docker manager."""
|
|
84
|
+
self.client = None
|
|
85
|
+
if require_docker:
|
|
86
|
+
try:
|
|
87
|
+
from docker import DockerClient
|
|
88
|
+
from docker.errors import DockerException
|
|
89
|
+
except ImportError:
|
|
90
|
+
raise RuntimeError(
|
|
91
|
+
"Docker package not installed. Install with: pip install supervaizer[deploy]"
|
|
92
|
+
) from None
|
|
93
|
+
|
|
94
|
+
try:
|
|
95
|
+
self.client = DockerClient.from_env()
|
|
96
|
+
self.client.ping() # Test connection
|
|
97
|
+
except DockerException as e:
|
|
98
|
+
log.error(f"Failed to connect to Docker: {e}")
|
|
99
|
+
raise RuntimeError("Docker is not running or not accessible") from e
|
|
100
|
+
|
|
101
|
+
def generate_dockerfile(
|
|
102
|
+
self,
|
|
103
|
+
output_path: Optional[Path] = None,
|
|
104
|
+
python_version: str = "3.12",
|
|
105
|
+
app_port: int = 8000,
|
|
106
|
+
controller_file: str = "supervaizer_control.py",
|
|
107
|
+
) -> None:
|
|
108
|
+
"""Generate a Dockerfile for Supervaizer deployment."""
|
|
109
|
+
if output_path is None:
|
|
110
|
+
output_path = Path(".deployment/Dockerfile")
|
|
111
|
+
|
|
112
|
+
# Ensure output directory exists
|
|
113
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
114
|
+
|
|
115
|
+
# Read template and customize it
|
|
116
|
+
template_path = TEMPLATE_DIR / "Dockerfile.template"
|
|
117
|
+
dockerfile_content = template_path.read_text()
|
|
118
|
+
|
|
119
|
+
# Replace template placeholders with actual values
|
|
120
|
+
dockerfile_content = dockerfile_content.replace(
|
|
121
|
+
"{{PYTHON_VERSION}}", python_version
|
|
122
|
+
)
|
|
123
|
+
dockerfile_content = dockerfile_content.replace("{{APP_PORT}}", str(app_port))
|
|
124
|
+
dockerfile_content = dockerfile_content.replace(
|
|
125
|
+
"{{CONTROLLER_FILE}}", controller_file
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
# Replace environment variables placeholder
|
|
129
|
+
env_vars = get_docker_env_vars(app_port)
|
|
130
|
+
env_lines = []
|
|
131
|
+
for var_name in env_vars.keys():
|
|
132
|
+
env_lines.append(f"ARG {var_name}")
|
|
133
|
+
env_lines.append(f"ENV {var_name}=${{{var_name}}}")
|
|
134
|
+
env_vars_section = "\n".join(env_lines)
|
|
135
|
+
dockerfile_content = dockerfile_content.replace(
|
|
136
|
+
"{{ENV_VARS}}", env_vars_section
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
output_path.write_text(dockerfile_content)
|
|
140
|
+
log.info(f"Generated Dockerfile at {output_path}")
|
|
141
|
+
|
|
142
|
+
# Copy entrypoint script to deployment directory
|
|
143
|
+
entrypoint_script_path = output_path.parent / "entrypoint.sh"
|
|
144
|
+
entrypoint_template_path = TEMPLATE_DIR / "entrypoint.sh"
|
|
145
|
+
if entrypoint_template_path.exists():
|
|
146
|
+
entrypoint_script_path.write_text(entrypoint_template_path.read_text())
|
|
147
|
+
# Make it executable
|
|
148
|
+
entrypoint_script_path.chmod(0o755)
|
|
149
|
+
log.info(f"Generated entrypoint script at {entrypoint_script_path}")
|
|
150
|
+
|
|
151
|
+
# Copy debug script to deployment directory
|
|
152
|
+
debug_script_path = output_path.parent / "debug_env.py"
|
|
153
|
+
debug_template_path = TEMPLATE_DIR / "debug_env.py"
|
|
154
|
+
if debug_template_path.exists():
|
|
155
|
+
debug_script_path.write_text(debug_template_path.read_text())
|
|
156
|
+
log.info(f"Generated debug script at {debug_script_path}")
|
|
157
|
+
|
|
158
|
+
def generate_dockerignore(self, output_path: Optional[Path] = None) -> None:
|
|
159
|
+
"""Generate a .dockerignore file."""
|
|
160
|
+
if output_path is None:
|
|
161
|
+
output_path = Path(".deployment/.dockerignore")
|
|
162
|
+
|
|
163
|
+
# Ensure output directory exists
|
|
164
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
165
|
+
|
|
166
|
+
# Read template
|
|
167
|
+
template_path = TEMPLATE_DIR / "dockerignore.template"
|
|
168
|
+
dockerignore_content = template_path.read_text()
|
|
169
|
+
|
|
170
|
+
output_path.write_text(dockerignore_content)
|
|
171
|
+
log.info(f"Generated .dockerignore at {output_path}")
|
|
172
|
+
|
|
173
|
+
def generate_docker_compose(
|
|
174
|
+
self,
|
|
175
|
+
output_path: Optional[Path] = None,
|
|
176
|
+
port: int = 8000,
|
|
177
|
+
service_name: str = "supervaizer-dev",
|
|
178
|
+
environment: str = "dev",
|
|
179
|
+
api_key: str = "test-api-key",
|
|
180
|
+
rsa_key: str = "test-rsa-key",
|
|
181
|
+
) -> None:
|
|
182
|
+
"""Generate a docker-compose.yml for local testing."""
|
|
183
|
+
if output_path is None:
|
|
184
|
+
output_path = Path(".deployment/docker-compose.yml")
|
|
185
|
+
|
|
186
|
+
# Ensure output directory exists
|
|
187
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
188
|
+
|
|
189
|
+
# Read template and customize it
|
|
190
|
+
template_path = TEMPLATE_DIR / "docker-compose.yml.template"
|
|
191
|
+
compose_content = template_path.read_text()
|
|
192
|
+
|
|
193
|
+
# Get environment variables for build args
|
|
194
|
+
env_vars = get_docker_env_vars(port)
|
|
195
|
+
|
|
196
|
+
# Replace template placeholders with actual values
|
|
197
|
+
compose_content = compose_content.replace("{{PORT}}", str(port))
|
|
198
|
+
compose_content = compose_content.replace("{{SERVICE_NAME}}", service_name)
|
|
199
|
+
compose_content = compose_content.replace("{{ENVIRONMENT}}", environment)
|
|
200
|
+
compose_content = compose_content.replace("{{API_KEY}}", api_key)
|
|
201
|
+
compose_content = compose_content.replace("{{RSA_KEY}}", rsa_key)
|
|
202
|
+
compose_content = compose_content.replace(
|
|
203
|
+
"{{ env.SV_LOG_LEVEL | default('INFO') }}", "INFO"
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
# Replace environment variable placeholders for build args
|
|
207
|
+
compose_content = compose_content.replace(
|
|
208
|
+
"{{WORKSPACE_ID}}", env_vars.get("SUPERVAIZE_WORKSPACE_ID", "")
|
|
209
|
+
)
|
|
210
|
+
compose_content = compose_content.replace(
|
|
211
|
+
"{{API_URL}}", env_vars.get("SUPERVAIZE_API_URL", "")
|
|
212
|
+
)
|
|
213
|
+
compose_content = compose_content.replace(
|
|
214
|
+
"{{PUBLIC_URL}}", env_vars.get("SUPERVAIZER_PUBLIC_URL", "")
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
output_path.write_text(compose_content)
|
|
218
|
+
log.info(f"Generated docker-compose.yml at {output_path}")
|
|
219
|
+
|
|
220
|
+
def build_image(
|
|
221
|
+
self,
|
|
222
|
+
tag: str,
|
|
223
|
+
context_path: Optional[Path] = None,
|
|
224
|
+
dockerfile_path: Optional[Path] = None,
|
|
225
|
+
verbose: bool = False,
|
|
226
|
+
build_args: Optional[dict] = None,
|
|
227
|
+
) -> str:
|
|
228
|
+
"""Build Docker image and return the image ID."""
|
|
229
|
+
from docker.errors import APIError, BuildError, DockerException
|
|
230
|
+
|
|
231
|
+
if self.client is None:
|
|
232
|
+
raise RuntimeError(
|
|
233
|
+
"Docker client not available. Initialize DockerManager with require_docker=True"
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
if context_path is None:
|
|
237
|
+
context_path = Path(".")
|
|
238
|
+
if dockerfile_path is None:
|
|
239
|
+
dockerfile_path = Path(".deployment/Dockerfile")
|
|
240
|
+
|
|
241
|
+
try:
|
|
242
|
+
log.info(f"Building Docker image with tag: {tag}")
|
|
243
|
+
|
|
244
|
+
# Use low-level API to get logs even when build fails
|
|
245
|
+
# Access the low-level API through the existing client
|
|
246
|
+
build_kwargs = {
|
|
247
|
+
"path": str(context_path),
|
|
248
|
+
"dockerfile": str(dockerfile_path),
|
|
249
|
+
"tag": tag,
|
|
250
|
+
"rm": True,
|
|
251
|
+
"forcerm": True,
|
|
252
|
+
"buildargs": build_args or {},
|
|
253
|
+
"decode": True,
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
response = [line for line in self.client.api.build(**build_kwargs)]
|
|
257
|
+
|
|
258
|
+
if not response:
|
|
259
|
+
raise RuntimeError("Failed to get a response from docker client")
|
|
260
|
+
|
|
261
|
+
# Process and display logs (always show output, especially errors)
|
|
262
|
+
image_id = None
|
|
263
|
+
last_error = None
|
|
264
|
+
for result in response:
|
|
265
|
+
if isinstance(result, dict):
|
|
266
|
+
# Check for errors first
|
|
267
|
+
if "error" in result or "errorDetail" in result:
|
|
268
|
+
error_msg = result.get("error") or result.get(
|
|
269
|
+
"errorDetail", {}
|
|
270
|
+
).get("message", "Unknown error")
|
|
271
|
+
last_error = error_msg
|
|
272
|
+
|
|
273
|
+
for key, value in result.items():
|
|
274
|
+
if isinstance(value, str):
|
|
275
|
+
# Always print string values (logs, errors, etc.)
|
|
276
|
+
print(value, end="", flush=True)
|
|
277
|
+
elif key == "aux" and isinstance(value, dict):
|
|
278
|
+
# Extract image ID from aux data
|
|
279
|
+
image_id = value.get("ID")
|
|
280
|
+
|
|
281
|
+
# If we found an error, raise it
|
|
282
|
+
if last_error:
|
|
283
|
+
raise RuntimeError(f"Docker build failed: {last_error}")
|
|
284
|
+
|
|
285
|
+
# If we didn't get image ID from aux, try to get it from the tag
|
|
286
|
+
if image_id is None:
|
|
287
|
+
try:
|
|
288
|
+
image = self.client.images.get(tag)
|
|
289
|
+
image_id = image.id
|
|
290
|
+
except DockerException:
|
|
291
|
+
raise RuntimeError(
|
|
292
|
+
"Docker build completed but no image was returned"
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
log.info(f"Successfully built image: {image_id}")
|
|
296
|
+
return image_id
|
|
297
|
+
|
|
298
|
+
except (BuildError, APIError) as e:
|
|
299
|
+
log.error(f"Failed to build Docker image: {e}")
|
|
300
|
+
raise RuntimeError(f"Docker build failed: {e}") from e
|
|
301
|
+
except DockerException as e:
|
|
302
|
+
log.error(f"Failed to build Docker image: {e}")
|
|
303
|
+
raise RuntimeError(f"Docker build failed: {e}") from e
|
|
304
|
+
|
|
305
|
+
def tag_image(self, source_tag: str, target_tag: str) -> None:
|
|
306
|
+
"""Tag a Docker image."""
|
|
307
|
+
from docker.errors import DockerException
|
|
308
|
+
|
|
309
|
+
if self.client is None:
|
|
310
|
+
raise RuntimeError(
|
|
311
|
+
"Docker client not available. Initialize DockerManager with require_docker=True"
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
try:
|
|
315
|
+
image = self.client.images.get(source_tag)
|
|
316
|
+
image.tag(target_tag)
|
|
317
|
+
log.info(f"Tagged {source_tag} as {target_tag}")
|
|
318
|
+
except DockerException as e:
|
|
319
|
+
log.error(f"Failed to tag image: {e}")
|
|
320
|
+
raise RuntimeError(f"Failed to tag image: {e}") from e
|
|
321
|
+
|
|
322
|
+
def push_image(self, tag: str) -> None:
|
|
323
|
+
"""Push Docker image to registry."""
|
|
324
|
+
from docker.errors import DockerException
|
|
325
|
+
|
|
326
|
+
if self.client is None:
|
|
327
|
+
raise RuntimeError(
|
|
328
|
+
"Docker client not available. Initialize DockerManager with require_docker=True"
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
try:
|
|
332
|
+
log.info(f"Pushing image: {tag}")
|
|
333
|
+
push_logs = self.client.images.push(tag, stream=True, decode=True)
|
|
334
|
+
|
|
335
|
+
for log_line in push_logs:
|
|
336
|
+
if "error" in log_line:
|
|
337
|
+
log.error(f"Push error: {log_line}")
|
|
338
|
+
raise RuntimeError(f"Push failed: {log_line}")
|
|
339
|
+
elif "status" in log_line:
|
|
340
|
+
log.debug(f"Push status: {log_line['status']}")
|
|
341
|
+
|
|
342
|
+
log.info(f"Successfully pushed image: {tag}")
|
|
343
|
+
|
|
344
|
+
except DockerException as e:
|
|
345
|
+
log.error(f"Failed to push image: {e}")
|
|
346
|
+
raise RuntimeError(f"Failed to push image: {e}") from e
|
|
347
|
+
|
|
348
|
+
def get_image_digest(self, tag: str) -> Optional[str]:
|
|
349
|
+
"""Get the digest of a Docker image."""
|
|
350
|
+
from docker.errors import DockerException
|
|
351
|
+
|
|
352
|
+
if self.client is None:
|
|
353
|
+
raise RuntimeError(
|
|
354
|
+
"Docker client not available. Initialize DockerManager with require_docker=True"
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
try:
|
|
358
|
+
image = self.client.images.get(tag)
|
|
359
|
+
repo_digests = image.attrs.get("RepoDigests", [])
|
|
360
|
+
return repo_digests[0] if repo_digests else None
|
|
361
|
+
except DockerException as e:
|
|
362
|
+
log.error(f"Failed to get image digest: {e}")
|
|
363
|
+
return None
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def ensure_docker_running() -> bool:
|
|
367
|
+
"""Check if Docker is running and accessible."""
|
|
368
|
+
try:
|
|
369
|
+
from docker import DockerClient
|
|
370
|
+
from docker.errors import DockerException
|
|
371
|
+
|
|
372
|
+
client = DockerClient.from_env()
|
|
373
|
+
client.ping()
|
|
374
|
+
return True
|
|
375
|
+
except ImportError:
|
|
376
|
+
return False
|
|
377
|
+
except DockerException:
|
|
378
|
+
return False
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Copyright (c) 2024-2025 Alain Prasquier - Supervaize.com. All rights reserved.
|
|
2
|
+
#
|
|
3
|
+
# This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
|
|
4
|
+
# If a copy of the MPL was not distributed with this file, you can obtain one at
|
|
5
|
+
# https://mozilla.org/MPL/2.0/.
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
Driver Factory
|
|
9
|
+
|
|
10
|
+
This module provides a factory for creating deployment drivers.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from typing import Optional
|
|
14
|
+
|
|
15
|
+
from supervaizer.deploy.drivers.base import BaseDriver
|
|
16
|
+
from supervaizer.deploy.drivers.cloud_run import CloudRunDriver
|
|
17
|
+
from supervaizer.deploy.drivers.aws_app_runner import AWSAppRunnerDriver
|
|
18
|
+
from supervaizer.deploy.drivers.do_app_platform import DOAppPlatformDriver
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def create_driver(
|
|
22
|
+
platform: str, region: str, project_id: Optional[str] = None
|
|
23
|
+
) -> BaseDriver:
|
|
24
|
+
"""Create a deployment driver for the specified platform."""
|
|
25
|
+
platform = platform.lower()
|
|
26
|
+
|
|
27
|
+
match platform:
|
|
28
|
+
case "cloud-run":
|
|
29
|
+
if not project_id:
|
|
30
|
+
raise ValueError("project_id is required for Cloud Run")
|
|
31
|
+
return CloudRunDriver(region, project_id)
|
|
32
|
+
case "aws-app-runner":
|
|
33
|
+
return AWSAppRunnerDriver(region, project_id)
|
|
34
|
+
case "do-app-platform":
|
|
35
|
+
return DOAppPlatformDriver(region, project_id)
|
|
36
|
+
case _:
|
|
37
|
+
raise ValueError(f"Unsupported platform: {platform}")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get_supported_platforms() -> list[str]:
|
|
41
|
+
"""Get list of supported platforms."""
|
|
42
|
+
return ["cloud-run", "aws-app-runner", "do-app-platform"]
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Copyright (c) 2024-2025 Alain Prasquier - Supervaize.com. All rights reserved.
|
|
2
|
+
#
|
|
3
|
+
# This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
|
|
4
|
+
# If a copy of the MPL was not distributed with this file, you can obtain one at
|
|
5
|
+
# https://mozilla.org/MPL/2.0/.
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
Deployment Drivers
|
|
9
|
+
|
|
10
|
+
This module contains platform-specific deployment drivers.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from .base import BaseDriver, DeploymentPlan, DeploymentResult
|
|
14
|
+
|
|
15
|
+
# Conditional imports for platform-specific drivers
|
|
16
|
+
try:
|
|
17
|
+
from .cloud_run import CloudRunDriver
|
|
18
|
+
|
|
19
|
+
CLOUD_RUN_AVAILABLE = True
|
|
20
|
+
except ImportError:
|
|
21
|
+
CLOUD_RUN_AVAILABLE = False
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
from .aws_app_runner import AWSAppRunnerDriver
|
|
25
|
+
|
|
26
|
+
AWS_APP_RUNNER_AVAILABLE = True
|
|
27
|
+
except ImportError:
|
|
28
|
+
AWS_APP_RUNNER_AVAILABLE = False
|
|
29
|
+
|
|
30
|
+
from .do_app_platform import DOAppPlatformDriver
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
"BaseDriver",
|
|
34
|
+
"DeploymentPlan",
|
|
35
|
+
"DeploymentResult",
|
|
36
|
+
"CloudRunDriver",
|
|
37
|
+
"AWSAppRunnerDriver",
|
|
38
|
+
"DOAppPlatformDriver",
|
|
39
|
+
]
|