agentscope-runtime 1.0.1__py3-none-any.whl → 1.0.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.
- agentscope_runtime/adapters/agentscope/message.py +32 -7
- agentscope_runtime/adapters/agentscope/stream.py +121 -91
- agentscope_runtime/adapters/agno/__init__.py +0 -0
- agentscope_runtime/adapters/agno/message.py +30 -0
- agentscope_runtime/adapters/agno/stream.py +122 -0
- agentscope_runtime/adapters/langgraph/__init__.py +12 -0
- agentscope_runtime/adapters/langgraph/message.py +257 -0
- agentscope_runtime/adapters/langgraph/stream.py +205 -0
- agentscope_runtime/cli/__init__.py +7 -0
- agentscope_runtime/cli/cli.py +63 -0
- agentscope_runtime/cli/commands/__init__.py +2 -0
- agentscope_runtime/cli/commands/chat.py +815 -0
- agentscope_runtime/cli/commands/deploy.py +1062 -0
- agentscope_runtime/cli/commands/invoke.py +58 -0
- agentscope_runtime/cli/commands/list_cmd.py +103 -0
- agentscope_runtime/cli/commands/run.py +176 -0
- agentscope_runtime/cli/commands/sandbox.py +128 -0
- agentscope_runtime/cli/commands/status.py +60 -0
- agentscope_runtime/cli/commands/stop.py +185 -0
- agentscope_runtime/cli/commands/web.py +166 -0
- agentscope_runtime/cli/loaders/__init__.py +6 -0
- agentscope_runtime/cli/loaders/agent_loader.py +295 -0
- agentscope_runtime/cli/state/__init__.py +10 -0
- agentscope_runtime/cli/utils/__init__.py +18 -0
- agentscope_runtime/cli/utils/console.py +378 -0
- agentscope_runtime/cli/utils/validators.py +118 -0
- agentscope_runtime/engine/app/agent_app.py +7 -4
- agentscope_runtime/engine/deployers/__init__.py +1 -0
- agentscope_runtime/engine/deployers/agentrun_deployer.py +152 -22
- agentscope_runtime/engine/deployers/base.py +27 -2
- agentscope_runtime/engine/deployers/kubernetes_deployer.py +158 -31
- agentscope_runtime/engine/deployers/local_deployer.py +188 -25
- agentscope_runtime/engine/deployers/modelstudio_deployer.py +109 -18
- agentscope_runtime/engine/deployers/state/__init__.py +9 -0
- agentscope_runtime/engine/deployers/state/manager.py +388 -0
- agentscope_runtime/engine/deployers/state/schema.py +96 -0
- agentscope_runtime/engine/deployers/utils/build_cache.py +736 -0
- agentscope_runtime/engine/deployers/utils/detached_app.py +105 -30
- agentscope_runtime/engine/deployers/utils/docker_image_utils/docker_image_builder.py +31 -10
- agentscope_runtime/engine/deployers/utils/docker_image_utils/dockerfile_generator.py +15 -8
- agentscope_runtime/engine/deployers/utils/docker_image_utils/image_factory.py +30 -2
- agentscope_runtime/engine/deployers/utils/k8s_utils.py +241 -0
- agentscope_runtime/engine/deployers/utils/package.py +56 -6
- agentscope_runtime/engine/deployers/utils/service_utils/fastapi_factory.py +16 -2
- agentscope_runtime/engine/deployers/utils/service_utils/process_manager.py +155 -5
- agentscope_runtime/engine/deployers/utils/wheel_packager.py +107 -123
- agentscope_runtime/engine/runner.py +25 -6
- agentscope_runtime/engine/schemas/exception.py +580 -0
- agentscope_runtime/sandbox/box/mobile/mobile_sandbox.py +113 -39
- agentscope_runtime/sandbox/box/shared/routers/mcp_utils.py +20 -4
- agentscope_runtime/sandbox/utils.py +2 -0
- agentscope_runtime/version.py +1 -1
- {agentscope_runtime-1.0.1.dist-info → agentscope_runtime-1.0.2.dist-info}/METADATA +24 -7
- {agentscope_runtime-1.0.1.dist-info → agentscope_runtime-1.0.2.dist-info}/RECORD +58 -28
- {agentscope_runtime-1.0.1.dist-info → agentscope_runtime-1.0.2.dist-info}/entry_points.txt +1 -0
- {agentscope_runtime-1.0.1.dist-info → agentscope_runtime-1.0.2.dist-info}/WHEEL +0 -0
- {agentscope_runtime-1.0.1.dist-info → agentscope_runtime-1.0.2.dist-info}/licenses/LICENSE +0 -0
- {agentscope_runtime-1.0.1.dist-info → agentscope_runtime-1.0.2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,1062 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""agentscope deploy command - Deploy agents to various platforms."""
|
|
3
|
+
# pylint: disable=too-many-statements, too-many-branches
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
import click
|
|
11
|
+
import yaml
|
|
12
|
+
|
|
13
|
+
from agentscope_runtime.cli.utils.console import (
|
|
14
|
+
echo_error,
|
|
15
|
+
echo_info,
|
|
16
|
+
echo_success,
|
|
17
|
+
echo_warning,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
# Only import LocalDeployManager directly (needs app object, will use loader
|
|
21
|
+
# internally)
|
|
22
|
+
from agentscope_runtime.engine.deployers.local_deployer import (
|
|
23
|
+
LocalDeployManager,
|
|
24
|
+
)
|
|
25
|
+
from agentscope_runtime.engine.deployers.utils.deployment_modes import (
|
|
26
|
+
DeploymentMode,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
# Optional imports for cloud deployers
|
|
30
|
+
try:
|
|
31
|
+
from agentscope_runtime.engine.deployers.modelstudio_deployer import (
|
|
32
|
+
ModelstudioDeployManager,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
MODELSTUDIO_AVAILABLE = True
|
|
36
|
+
except ImportError:
|
|
37
|
+
MODELSTUDIO_AVAILABLE = False
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
from agentscope_runtime.engine.deployers.agentrun_deployer import (
|
|
41
|
+
AgentRunDeployManager,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
AGENTRUN_AVAILABLE = True
|
|
45
|
+
except ImportError:
|
|
46
|
+
AGENTRUN_AVAILABLE = False
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
from agentscope_runtime.engine.deployers.kubernetes_deployer import (
|
|
50
|
+
KubernetesDeployManager,
|
|
51
|
+
K8sConfig,
|
|
52
|
+
RegistryConfig,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
K8S_AVAILABLE = True
|
|
56
|
+
except ImportError:
|
|
57
|
+
K8S_AVAILABLE = False
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _validate_source(source: str) -> tuple[str, str]:
|
|
61
|
+
"""
|
|
62
|
+
Validate source path and determine its type.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
Tuple of (absolute_path, source_type) where source_type
|
|
66
|
+
is 'file' or 'directory'
|
|
67
|
+
|
|
68
|
+
Raises:
|
|
69
|
+
ValueError: If source doesn't exist
|
|
70
|
+
"""
|
|
71
|
+
abs_source = os.path.abspath(source)
|
|
72
|
+
|
|
73
|
+
if not os.path.exists(abs_source):
|
|
74
|
+
raise ValueError(f"Source not found: {abs_source}")
|
|
75
|
+
|
|
76
|
+
if os.path.isdir(abs_source):
|
|
77
|
+
return abs_source, "directory"
|
|
78
|
+
elif os.path.isfile(abs_source):
|
|
79
|
+
return abs_source, "file"
|
|
80
|
+
else:
|
|
81
|
+
raise ValueError(f"Source must be a file or directory: {abs_source}")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _find_entrypoint(project_dir: str, entrypoint: str = None) -> str:
|
|
85
|
+
"""
|
|
86
|
+
Find or validate entrypoint file in project directory.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
project_dir: Project directory path
|
|
90
|
+
entrypoint: Optional user-specified entrypoint file name
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
Entrypoint file name (relative to project_dir)
|
|
94
|
+
|
|
95
|
+
Raises:
|
|
96
|
+
ValueError: If entrypoint not found
|
|
97
|
+
"""
|
|
98
|
+
if entrypoint:
|
|
99
|
+
entry_path = os.path.join(project_dir, entrypoint)
|
|
100
|
+
if not os.path.isfile(entry_path):
|
|
101
|
+
raise ValueError(f"Entrypoint file not found: {entry_path}")
|
|
102
|
+
return entrypoint
|
|
103
|
+
|
|
104
|
+
# Try default entry files
|
|
105
|
+
for candidate in ["app.py", "agent.py", "main.py"]:
|
|
106
|
+
candidate_path = os.path.join(project_dir, candidate)
|
|
107
|
+
if os.path.isfile(candidate_path):
|
|
108
|
+
return candidate
|
|
109
|
+
|
|
110
|
+
raise ValueError(
|
|
111
|
+
f"No entry point found in {project_dir}. "
|
|
112
|
+
f"Use --entrypoint to specify one.",
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _load_config_file(config_path: str) -> dict:
|
|
117
|
+
"""
|
|
118
|
+
Load deployment configuration from JSON or YAML file.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
config_path: Path to config file (.json, .yaml, or .yml)
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
Dictionary of configuration parameters
|
|
125
|
+
|
|
126
|
+
Raises:
|
|
127
|
+
ValueError: If file format is unsupported or parsing fails
|
|
128
|
+
"""
|
|
129
|
+
if not os.path.isfile(config_path):
|
|
130
|
+
raise ValueError(f"Config file not found: {config_path}")
|
|
131
|
+
|
|
132
|
+
file_ext = os.path.splitext(config_path)[1].lower()
|
|
133
|
+
|
|
134
|
+
try:
|
|
135
|
+
with open(config_path, "r", encoding="utf-8") as f:
|
|
136
|
+
if file_ext == ".json":
|
|
137
|
+
return json.load(f)
|
|
138
|
+
elif file_ext in [".yaml", ".yml"]:
|
|
139
|
+
return yaml.safe_load(f)
|
|
140
|
+
else:
|
|
141
|
+
raise ValueError(
|
|
142
|
+
f"Unsupported config file format: {file_ext}. "
|
|
143
|
+
f"Use .json, .yaml, or .yml",
|
|
144
|
+
)
|
|
145
|
+
except (json.JSONDecodeError, yaml.YAMLError) as e:
|
|
146
|
+
raise ValueError(
|
|
147
|
+
f"Failed to parse config file {config_path}: {e}",
|
|
148
|
+
) from e
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _merge_config(config_dict: dict, cli_params: dict) -> dict:
|
|
152
|
+
"""
|
|
153
|
+
Merge config file with CLI parameters. CLI parameters take precedence.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
config_dict: Configuration from file
|
|
157
|
+
cli_params: Parameters from CLI options
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
Merged configuration dictionary
|
|
161
|
+
"""
|
|
162
|
+
merged = config_dict.copy()
|
|
163
|
+
|
|
164
|
+
# Override with non-None CLI parameters
|
|
165
|
+
for key, value in cli_params.items():
|
|
166
|
+
if value is not None:
|
|
167
|
+
# Special handling for tuples (like env)
|
|
168
|
+
if key == "env" and value:
|
|
169
|
+
# Merge environment variables
|
|
170
|
+
if "environment" not in merged:
|
|
171
|
+
merged["environment"] = {}
|
|
172
|
+
# CLI env overrides config environment
|
|
173
|
+
continue # Will be handled by _parse_environment
|
|
174
|
+
merged[key] = value
|
|
175
|
+
|
|
176
|
+
return merged
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _parse_environment(env_tuples: tuple, env_file: str = None) -> dict:
|
|
180
|
+
"""
|
|
181
|
+
Parse environment variables from --env options and --env-file.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
env_tuples: Tuple of KEY=VALUE strings from --env options
|
|
185
|
+
env_file: Optional path to .env file
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
Dictionary of environment variables
|
|
189
|
+
|
|
190
|
+
Raises:
|
|
191
|
+
ValueError: If env format is invalid
|
|
192
|
+
"""
|
|
193
|
+
environment = {}
|
|
194
|
+
|
|
195
|
+
# 1. Load from env file first (if provided)
|
|
196
|
+
if env_file:
|
|
197
|
+
if not os.path.isfile(env_file):
|
|
198
|
+
raise ValueError(f"Environment file not found: {env_file}")
|
|
199
|
+
|
|
200
|
+
with open(env_file, "r", encoding="utf-8") as f:
|
|
201
|
+
for line_num, line in enumerate(f, 1):
|
|
202
|
+
line = line.strip()
|
|
203
|
+
# Skip empty lines and comments
|
|
204
|
+
if not line or line.startswith("#"):
|
|
205
|
+
continue
|
|
206
|
+
|
|
207
|
+
if "=" not in line:
|
|
208
|
+
echo_warning(
|
|
209
|
+
f"Skipping invalid line {line_num} in {env_file}: "
|
|
210
|
+
f"{line}",
|
|
211
|
+
)
|
|
212
|
+
continue
|
|
213
|
+
|
|
214
|
+
key, value = line.split("=", 1)
|
|
215
|
+
key = key.strip()
|
|
216
|
+
value = value.strip()
|
|
217
|
+
|
|
218
|
+
# Remove quotes if present
|
|
219
|
+
if value.startswith('"') and value.endswith('"'):
|
|
220
|
+
value = value[1:-1]
|
|
221
|
+
elif value.startswith("'") and value.endswith("'"):
|
|
222
|
+
value = value[1:-1]
|
|
223
|
+
|
|
224
|
+
environment[key] = value
|
|
225
|
+
|
|
226
|
+
# 2. Override with --env options (command line takes precedence)
|
|
227
|
+
for env_pair in env_tuples:
|
|
228
|
+
if "=" not in env_pair:
|
|
229
|
+
raise ValueError(
|
|
230
|
+
f"Invalid env format: '{env_pair}'. Use KEY=VALUE format",
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
key, value = env_pair.split("=", 1)
|
|
234
|
+
environment[key.strip()] = value.strip()
|
|
235
|
+
|
|
236
|
+
return environment
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
@click.group()
|
|
240
|
+
def deploy():
|
|
241
|
+
"""
|
|
242
|
+
Deploy agents to various platforms.
|
|
243
|
+
|
|
244
|
+
Supported platforms:
|
|
245
|
+
\b
|
|
246
|
+
- modelstudio: Alibaba Cloud ModelStudio
|
|
247
|
+
- agentrun: Alibaba Cloud AgentRun
|
|
248
|
+
- k8s: Kubernetes/ACK
|
|
249
|
+
- local: Local deployment (detached mode)
|
|
250
|
+
|
|
251
|
+
Use 'agentscope deploy <platform> --help' for platform-specific options.
|
|
252
|
+
"""
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
@deploy.command()
|
|
256
|
+
@click.argument("source", required=True)
|
|
257
|
+
@click.option("--name", help="Deployment name", default=None)
|
|
258
|
+
@click.option("--host", help="Host to bind to", default=None)
|
|
259
|
+
@click.option(
|
|
260
|
+
"--port",
|
|
261
|
+
help="Port to expose",
|
|
262
|
+
default=None,
|
|
263
|
+
type=int,
|
|
264
|
+
)
|
|
265
|
+
@click.option(
|
|
266
|
+
"--entrypoint",
|
|
267
|
+
"-e",
|
|
268
|
+
help="Entrypoint file name for directory sources (e.g., 'app.py', "
|
|
269
|
+
"'main.py')",
|
|
270
|
+
default=None,
|
|
271
|
+
)
|
|
272
|
+
@click.option(
|
|
273
|
+
"--env",
|
|
274
|
+
"-E",
|
|
275
|
+
multiple=True,
|
|
276
|
+
help="Environment variable in KEY=VALUE format (can be repeated)",
|
|
277
|
+
)
|
|
278
|
+
@click.option(
|
|
279
|
+
"--env-file",
|
|
280
|
+
type=click.Path(exists=True),
|
|
281
|
+
help="Path to .env file with environment variables",
|
|
282
|
+
)
|
|
283
|
+
@click.option(
|
|
284
|
+
"--config",
|
|
285
|
+
"-c",
|
|
286
|
+
type=click.Path(exists=True),
|
|
287
|
+
help="Path to deployment config file (.json, .yaml, or .yml)",
|
|
288
|
+
)
|
|
289
|
+
def local(
|
|
290
|
+
source: str,
|
|
291
|
+
name: str,
|
|
292
|
+
host: str,
|
|
293
|
+
port: int,
|
|
294
|
+
entrypoint: str,
|
|
295
|
+
env: tuple,
|
|
296
|
+
env_file: str,
|
|
297
|
+
config: str,
|
|
298
|
+
):
|
|
299
|
+
"""
|
|
300
|
+
Deploy locally in detached mode.
|
|
301
|
+
|
|
302
|
+
SOURCE can be a Python file or project directory containing an agent.
|
|
303
|
+
"""
|
|
304
|
+
try:
|
|
305
|
+
echo_info(f"Preparing deployment from {source}...")
|
|
306
|
+
|
|
307
|
+
# Load config file if provided
|
|
308
|
+
config_dict = {}
|
|
309
|
+
if config:
|
|
310
|
+
echo_info(f"Loading configuration from {config}...")
|
|
311
|
+
config_dict = _load_config_file(config)
|
|
312
|
+
|
|
313
|
+
# Merge CLI parameters with config (CLI takes precedence)
|
|
314
|
+
cli_params = {
|
|
315
|
+
"name": name,
|
|
316
|
+
"host": host,
|
|
317
|
+
"port": port,
|
|
318
|
+
"entrypoint": entrypoint,
|
|
319
|
+
}
|
|
320
|
+
merged_config = _merge_config(config_dict, cli_params)
|
|
321
|
+
|
|
322
|
+
# Extract parameters with defaults
|
|
323
|
+
host = merged_config.get("host", "127.0.0.1")
|
|
324
|
+
port = merged_config.get("port", 8090)
|
|
325
|
+
entrypoint = merged_config.get("entrypoint")
|
|
326
|
+
|
|
327
|
+
# Validate source
|
|
328
|
+
abs_source, source_type = _validate_source(source)
|
|
329
|
+
|
|
330
|
+
# Parse environment variables (from config, env_file, and CLI)
|
|
331
|
+
environment = merged_config.get("environment", {}).copy()
|
|
332
|
+
cli_env = _parse_environment(env, env_file)
|
|
333
|
+
environment.update(cli_env) # CLI env overrides config env
|
|
334
|
+
|
|
335
|
+
if environment:
|
|
336
|
+
echo_info(f"Using {len(environment)} environment variable(s)")
|
|
337
|
+
|
|
338
|
+
# Create deployer
|
|
339
|
+
deployer = LocalDeployManager(host=host, port=port)
|
|
340
|
+
|
|
341
|
+
# Prepare entrypoint specification
|
|
342
|
+
if source_type == "directory":
|
|
343
|
+
# For directory: find entrypoint and create path
|
|
344
|
+
project_dir = abs_source
|
|
345
|
+
entry_script = _find_entrypoint(project_dir, entrypoint)
|
|
346
|
+
entrypoint_spec = os.path.join(project_dir, entry_script)
|
|
347
|
+
|
|
348
|
+
echo_info(f"Using project directory: {project_dir}")
|
|
349
|
+
echo_info(f"Entry script: {entry_script}")
|
|
350
|
+
else:
|
|
351
|
+
# For single file: use file path directly
|
|
352
|
+
entrypoint_spec = abs_source
|
|
353
|
+
|
|
354
|
+
echo_info(f"Using file: {abs_source}")
|
|
355
|
+
|
|
356
|
+
# Deploy locally using entrypoint
|
|
357
|
+
echo_info(f"Deploying agent to {host}:{port} in detached mode...")
|
|
358
|
+
result = asyncio.run(
|
|
359
|
+
deployer.deploy(
|
|
360
|
+
entrypoint=entrypoint_spec,
|
|
361
|
+
mode=DeploymentMode.DETACHED_PROCESS,
|
|
362
|
+
environment=environment if environment else None,
|
|
363
|
+
agent_source=abs_source, # Pass source for state saving
|
|
364
|
+
),
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
deploy_id = result.get("deploy_id")
|
|
368
|
+
url = result.get("url")
|
|
369
|
+
|
|
370
|
+
echo_success("Deployment successful!")
|
|
371
|
+
echo_info(f"Deployment ID: {deploy_id}")
|
|
372
|
+
echo_info(f"URL: {url}")
|
|
373
|
+
echo_info(f"Use 'agentscope stop {deploy_id}' to stop the deployment")
|
|
374
|
+
|
|
375
|
+
except Exception as e:
|
|
376
|
+
# Error details (including process logs) are already logged by the
|
|
377
|
+
# deployer
|
|
378
|
+
# Just show a simple error message here without the full traceback
|
|
379
|
+
echo_error(f"Deployment failed: {e}")
|
|
380
|
+
sys.exit(1)
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
@deploy.command()
|
|
384
|
+
@click.argument("source", required=True)
|
|
385
|
+
@click.option("--name", help="Deployment name", default=None)
|
|
386
|
+
@click.option(
|
|
387
|
+
"--entrypoint",
|
|
388
|
+
"-e",
|
|
389
|
+
help="Entrypoint file name for directory sources (e.g., 'app.py', "
|
|
390
|
+
"'main.py')",
|
|
391
|
+
default=None,
|
|
392
|
+
)
|
|
393
|
+
@click.option(
|
|
394
|
+
"--skip-upload",
|
|
395
|
+
is_flag=True,
|
|
396
|
+
help="Build package without uploading",
|
|
397
|
+
)
|
|
398
|
+
@click.option(
|
|
399
|
+
"--env",
|
|
400
|
+
"-E",
|
|
401
|
+
multiple=True,
|
|
402
|
+
help="Environment variable in KEY=VALUE format (can be repeated)",
|
|
403
|
+
)
|
|
404
|
+
@click.option(
|
|
405
|
+
"--env-file",
|
|
406
|
+
type=click.Path(exists=True),
|
|
407
|
+
help="Path to .env file with environment variables",
|
|
408
|
+
)
|
|
409
|
+
@click.option(
|
|
410
|
+
"--config",
|
|
411
|
+
"-c",
|
|
412
|
+
type=click.Path(exists=True),
|
|
413
|
+
help="Path to deployment config file (.json, .yaml, or .yml)",
|
|
414
|
+
)
|
|
415
|
+
def modelstudio(
|
|
416
|
+
source: str,
|
|
417
|
+
name: str,
|
|
418
|
+
entrypoint: str,
|
|
419
|
+
skip_upload: bool,
|
|
420
|
+
env: tuple,
|
|
421
|
+
env_file: str,
|
|
422
|
+
config: str,
|
|
423
|
+
):
|
|
424
|
+
"""
|
|
425
|
+
Deploy to Alibaba Cloud ModelStudio.
|
|
426
|
+
|
|
427
|
+
SOURCE can be a Python file or project directory containing an agent.
|
|
428
|
+
|
|
429
|
+
Required environment variables:
|
|
430
|
+
- ALIBABA_CLOUD_ACCESS_KEY_ID
|
|
431
|
+
- ALIBABA_CLOUD_ACCESS_KEY_SECRET
|
|
432
|
+
- MODELSTUDIO_WORKSPACE_ID
|
|
433
|
+
"""
|
|
434
|
+
if not MODELSTUDIO_AVAILABLE:
|
|
435
|
+
echo_error("ModelStudio deployer is not available")
|
|
436
|
+
echo_info(
|
|
437
|
+
"Please install required dependencies: alibabacloud-oss-v2 "
|
|
438
|
+
"alibabacloud-bailian20231229",
|
|
439
|
+
)
|
|
440
|
+
sys.exit(1)
|
|
441
|
+
|
|
442
|
+
try:
|
|
443
|
+
echo_info(f"Preparing deployment from {source}...")
|
|
444
|
+
|
|
445
|
+
# Load config file if provided
|
|
446
|
+
config_dict = {}
|
|
447
|
+
if config:
|
|
448
|
+
echo_info(f"Loading configuration from {config}...")
|
|
449
|
+
config_dict = _load_config_file(config)
|
|
450
|
+
|
|
451
|
+
# Merge CLI parameters with config (CLI takes precedence)
|
|
452
|
+
cli_params = {
|
|
453
|
+
"name": name,
|
|
454
|
+
"entrypoint": entrypoint,
|
|
455
|
+
"skip_upload": skip_upload if skip_upload else None,
|
|
456
|
+
}
|
|
457
|
+
merged_config = _merge_config(config_dict, cli_params)
|
|
458
|
+
|
|
459
|
+
# Extract parameters
|
|
460
|
+
name = merged_config.get("name") or merged_config.get("deploy_name")
|
|
461
|
+
entrypoint = merged_config.get("entrypoint")
|
|
462
|
+
skip_upload = merged_config.get("skip_upload", False)
|
|
463
|
+
|
|
464
|
+
# Validate source
|
|
465
|
+
abs_source, source_type = _validate_source(source)
|
|
466
|
+
|
|
467
|
+
# Parse environment variables (from config, env_file, and CLI)
|
|
468
|
+
environment = merged_config.get("environment", {}).copy()
|
|
469
|
+
cli_env = _parse_environment(env, env_file)
|
|
470
|
+
environment.update(cli_env) # CLI env overrides config env
|
|
471
|
+
|
|
472
|
+
if environment:
|
|
473
|
+
echo_info(f"Using {len(environment)} environment variable(s)")
|
|
474
|
+
|
|
475
|
+
# Create deployer
|
|
476
|
+
deployer = ModelstudioDeployManager()
|
|
477
|
+
|
|
478
|
+
# Prepare deployment parameters - ModelStudio always needs
|
|
479
|
+
# project_dir + cmd
|
|
480
|
+
if source_type == "directory":
|
|
481
|
+
# For directory: use directory as project_dir
|
|
482
|
+
project_dir = abs_source
|
|
483
|
+
entry_script = _find_entrypoint(project_dir, entrypoint)
|
|
484
|
+
cmd = f"python {entry_script}"
|
|
485
|
+
|
|
486
|
+
echo_info(f"Using project directory: {project_dir}")
|
|
487
|
+
echo_info(f"Entry script: {entry_script}")
|
|
488
|
+
else:
|
|
489
|
+
# For single file: use parent directory as project_dir
|
|
490
|
+
file_path = abs_source
|
|
491
|
+
project_dir = os.path.dirname(file_path)
|
|
492
|
+
entry_filename = os.path.basename(file_path)
|
|
493
|
+
cmd = f"python {entry_filename}"
|
|
494
|
+
|
|
495
|
+
echo_info(f"Using file: {file_path}")
|
|
496
|
+
echo_info(f"Project directory: {project_dir}")
|
|
497
|
+
|
|
498
|
+
# Deploy to ModelStudio using project_dir + cmd
|
|
499
|
+
echo_info("Deploying to ModelStudio...")
|
|
500
|
+
result = asyncio.run(
|
|
501
|
+
deployer.deploy(
|
|
502
|
+
project_dir=project_dir,
|
|
503
|
+
cmd=cmd,
|
|
504
|
+
deploy_name=name,
|
|
505
|
+
skip_upload=skip_upload,
|
|
506
|
+
environment=environment if environment else None,
|
|
507
|
+
agent_source=abs_source, # Pass source for state saving
|
|
508
|
+
),
|
|
509
|
+
)
|
|
510
|
+
|
|
511
|
+
if skip_upload:
|
|
512
|
+
echo_success("Package built successfully")
|
|
513
|
+
echo_info(f"Wheel path: {result.get('wheel_path')}")
|
|
514
|
+
else:
|
|
515
|
+
deploy_id = result.get("deploy_id")
|
|
516
|
+
url = result.get("url")
|
|
517
|
+
workspace_id = result.get("workspace_id")
|
|
518
|
+
|
|
519
|
+
echo_success("Deployment successful!")
|
|
520
|
+
echo_info(f"Deployment ID: {deploy_id}")
|
|
521
|
+
echo_info(f"Console URL: {url}")
|
|
522
|
+
echo_info(f"Workspace ID: {workspace_id}")
|
|
523
|
+
|
|
524
|
+
except Exception as e:
|
|
525
|
+
echo_error(f"Deployment failed: {e}")
|
|
526
|
+
import traceback
|
|
527
|
+
|
|
528
|
+
echo_error(traceback.format_exc())
|
|
529
|
+
sys.exit(1)
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
@deploy.command()
|
|
533
|
+
@click.argument("source", required=True)
|
|
534
|
+
@click.option("--name", help="Deployment name", default=None)
|
|
535
|
+
@click.option(
|
|
536
|
+
"--entrypoint",
|
|
537
|
+
"-e",
|
|
538
|
+
help="Entrypoint file name for directory sources (e.g., 'app.py', "
|
|
539
|
+
"'main.py')",
|
|
540
|
+
default=None,
|
|
541
|
+
)
|
|
542
|
+
@click.option(
|
|
543
|
+
"--skip-upload",
|
|
544
|
+
is_flag=True,
|
|
545
|
+
help="Build package without uploading",
|
|
546
|
+
)
|
|
547
|
+
@click.option("--region", help="Alibaba Cloud region", default=None)
|
|
548
|
+
@click.option("--cpu", help="CPU allocation (cores)", type=float, default=None)
|
|
549
|
+
@click.option(
|
|
550
|
+
"--memory",
|
|
551
|
+
help="Memory allocation (MB)",
|
|
552
|
+
type=int,
|
|
553
|
+
default=None,
|
|
554
|
+
)
|
|
555
|
+
@click.option(
|
|
556
|
+
"--env",
|
|
557
|
+
"-E",
|
|
558
|
+
multiple=True,
|
|
559
|
+
help="Environment variable in KEY=VALUE format (can be repeated)",
|
|
560
|
+
)
|
|
561
|
+
@click.option(
|
|
562
|
+
"--env-file",
|
|
563
|
+
type=click.Path(exists=True),
|
|
564
|
+
help="Path to .env file with environment variables",
|
|
565
|
+
)
|
|
566
|
+
@click.option(
|
|
567
|
+
"--config",
|
|
568
|
+
"-c",
|
|
569
|
+
type=click.Path(exists=True),
|
|
570
|
+
help="Path to deployment config file (.json, .yaml, or .yml)",
|
|
571
|
+
)
|
|
572
|
+
def agentrun(
|
|
573
|
+
source: str,
|
|
574
|
+
name: str,
|
|
575
|
+
entrypoint: str,
|
|
576
|
+
skip_upload: bool,
|
|
577
|
+
region: str,
|
|
578
|
+
cpu: float,
|
|
579
|
+
memory: int,
|
|
580
|
+
env: tuple,
|
|
581
|
+
env_file: str,
|
|
582
|
+
config: str,
|
|
583
|
+
):
|
|
584
|
+
"""
|
|
585
|
+
Deploy to Alibaba Cloud AgentRun.
|
|
586
|
+
|
|
587
|
+
SOURCE can be a Python file or project directory containing an agent.
|
|
588
|
+
|
|
589
|
+
Required environment variables:
|
|
590
|
+
- ALIBABA_CLOUD_ACCESS_KEY_ID
|
|
591
|
+
- ALIBABA_CLOUD_ACCESS_KEY_SECRET
|
|
592
|
+
"""
|
|
593
|
+
if not AGENTRUN_AVAILABLE:
|
|
594
|
+
echo_error("AgentRun deployer is not available")
|
|
595
|
+
echo_info(
|
|
596
|
+
"Please install required dependencies: "
|
|
597
|
+
"alibabacloud-agentrun20250910",
|
|
598
|
+
)
|
|
599
|
+
sys.exit(1)
|
|
600
|
+
|
|
601
|
+
try:
|
|
602
|
+
echo_info(f"Preparing deployment from {source}...")
|
|
603
|
+
|
|
604
|
+
# Load config file if provided
|
|
605
|
+
config_dict = {}
|
|
606
|
+
if config:
|
|
607
|
+
echo_info(f"Loading configuration from {config}...")
|
|
608
|
+
config_dict = _load_config_file(config)
|
|
609
|
+
|
|
610
|
+
# Merge CLI parameters with config (CLI takes precedence)
|
|
611
|
+
cli_params = {
|
|
612
|
+
"name": name,
|
|
613
|
+
"entrypoint": entrypoint,
|
|
614
|
+
"skip_upload": skip_upload if skip_upload else None,
|
|
615
|
+
"region": region,
|
|
616
|
+
"cpu": cpu,
|
|
617
|
+
"memory": memory,
|
|
618
|
+
}
|
|
619
|
+
merged_config = _merge_config(config_dict, cli_params)
|
|
620
|
+
|
|
621
|
+
# Extract parameters with defaults
|
|
622
|
+
name = merged_config.get("name") or merged_config.get("deploy_name")
|
|
623
|
+
entrypoint = merged_config.get("entrypoint")
|
|
624
|
+
skip_upload = merged_config.get("skip_upload", False)
|
|
625
|
+
region = merged_config.get("region", "cn-hangzhou")
|
|
626
|
+
cpu = merged_config.get("cpu", 2.0)
|
|
627
|
+
memory = merged_config.get("memory", 2048)
|
|
628
|
+
|
|
629
|
+
# Validate source
|
|
630
|
+
abs_source, source_type = _validate_source(source)
|
|
631
|
+
|
|
632
|
+
# Parse environment variables (from config, env_file, and CLI)
|
|
633
|
+
environment = merged_config.get("environment", {}).copy()
|
|
634
|
+
cli_env = _parse_environment(env, env_file)
|
|
635
|
+
environment.update(cli_env) # CLI env overrides config env
|
|
636
|
+
|
|
637
|
+
if environment:
|
|
638
|
+
echo_info(f"Using {len(environment)} environment variable(s)")
|
|
639
|
+
|
|
640
|
+
# Set region and resource config
|
|
641
|
+
if region:
|
|
642
|
+
os.environ["AGENT_RUN_REGION_ID"] = region
|
|
643
|
+
if cpu:
|
|
644
|
+
os.environ["AGENT_RUN_CPU"] = str(cpu)
|
|
645
|
+
if memory:
|
|
646
|
+
os.environ["AGENT_RUN_MEMORY"] = str(memory)
|
|
647
|
+
|
|
648
|
+
# Create deployer
|
|
649
|
+
deployer = AgentRunDeployManager()
|
|
650
|
+
|
|
651
|
+
# Prepare deployment - AgentRun always needs project_dir + cmd
|
|
652
|
+
if source_type == "directory":
|
|
653
|
+
# For directory: use directory as project_dir
|
|
654
|
+
project_dir = abs_source
|
|
655
|
+
entry_script = _find_entrypoint(project_dir, entrypoint)
|
|
656
|
+
cmd = f"python {entry_script}"
|
|
657
|
+
|
|
658
|
+
echo_info(f"Using project directory: {project_dir}")
|
|
659
|
+
echo_info(f"Entry script: {entry_script}")
|
|
660
|
+
else:
|
|
661
|
+
# For single file: use parent directory as project_dir
|
|
662
|
+
file_path = abs_source
|
|
663
|
+
project_dir = os.path.dirname(file_path)
|
|
664
|
+
entry_filename = os.path.basename(file_path)
|
|
665
|
+
cmd = f"python {entry_filename}"
|
|
666
|
+
|
|
667
|
+
echo_info(f"Using file: {file_path}")
|
|
668
|
+
echo_info(f"Project directory: {project_dir}")
|
|
669
|
+
|
|
670
|
+
# Deploy to AgentRun using project_dir + cmd
|
|
671
|
+
echo_info("Deploying to AgentRun...")
|
|
672
|
+
result = asyncio.run(
|
|
673
|
+
deployer.deploy(
|
|
674
|
+
project_dir=project_dir,
|
|
675
|
+
cmd=cmd,
|
|
676
|
+
deploy_name=name,
|
|
677
|
+
skip_upload=skip_upload,
|
|
678
|
+
environment=environment if environment else None,
|
|
679
|
+
agent_source=abs_source, # Pass source for state saving
|
|
680
|
+
),
|
|
681
|
+
)
|
|
682
|
+
|
|
683
|
+
if skip_upload:
|
|
684
|
+
echo_success("Package built successfully")
|
|
685
|
+
echo_info(f"Wheel path: {result.get('wheel_path')}")
|
|
686
|
+
else:
|
|
687
|
+
deploy_id = result.get("agentrun_id") or result.get("deploy_id")
|
|
688
|
+
url = result.get("url")
|
|
689
|
+
endpoint_url = result.get("agentrun_endpoint_url")
|
|
690
|
+
|
|
691
|
+
echo_success("Deployment successful!")
|
|
692
|
+
echo_info(f"Deployment ID: {deploy_id}")
|
|
693
|
+
echo_info(f"Endpoint URL: {endpoint_url}")
|
|
694
|
+
echo_info(f"Console URL: {url}")
|
|
695
|
+
|
|
696
|
+
except Exception as e:
|
|
697
|
+
echo_error(f"Deployment failed: {e}")
|
|
698
|
+
import traceback
|
|
699
|
+
|
|
700
|
+
echo_error(traceback.format_exc())
|
|
701
|
+
sys.exit(1)
|
|
702
|
+
|
|
703
|
+
|
|
704
|
+
@deploy.command()
|
|
705
|
+
@click.argument("source", required=True)
|
|
706
|
+
@click.option("--name", help="Deployment name", default=None)
|
|
707
|
+
@click.option(
|
|
708
|
+
"--namespace",
|
|
709
|
+
help="Kubernetes namespace",
|
|
710
|
+
default="agentscope-runtime",
|
|
711
|
+
)
|
|
712
|
+
@click.option(
|
|
713
|
+
"--kube-config-path",
|
|
714
|
+
"-c",
|
|
715
|
+
type=click.Path(exists=True),
|
|
716
|
+
help="Path to deployment config file (.json, .yaml, or .yml)",
|
|
717
|
+
)
|
|
718
|
+
@click.option(
|
|
719
|
+
"--replicas",
|
|
720
|
+
help="Number of replicas",
|
|
721
|
+
type=int,
|
|
722
|
+
default=1,
|
|
723
|
+
)
|
|
724
|
+
@click.option(
|
|
725
|
+
"--port",
|
|
726
|
+
help="Container port",
|
|
727
|
+
type=int,
|
|
728
|
+
default=8080,
|
|
729
|
+
)
|
|
730
|
+
@click.option(
|
|
731
|
+
"--image-name",
|
|
732
|
+
help="Docker image name",
|
|
733
|
+
default="agent_app",
|
|
734
|
+
)
|
|
735
|
+
@click.option(
|
|
736
|
+
"--image-tag",
|
|
737
|
+
help="Docker image tag",
|
|
738
|
+
default="linux-amd64",
|
|
739
|
+
)
|
|
740
|
+
@click.option(
|
|
741
|
+
"--registry-url",
|
|
742
|
+
help="Remote registry url",
|
|
743
|
+
default="localhost",
|
|
744
|
+
)
|
|
745
|
+
@click.option(
|
|
746
|
+
"--registry-namespace",
|
|
747
|
+
help="Remote registry namespace",
|
|
748
|
+
default="agentscope-runtime",
|
|
749
|
+
)
|
|
750
|
+
@click.option(
|
|
751
|
+
"--push",
|
|
752
|
+
is_flag=True,
|
|
753
|
+
help="Push image to registry",
|
|
754
|
+
)
|
|
755
|
+
@click.option(
|
|
756
|
+
"--entrypoint",
|
|
757
|
+
"-e",
|
|
758
|
+
help="Entrypoint file name for directory sources (e.g., 'app.py', "
|
|
759
|
+
"'main.py')",
|
|
760
|
+
default=None,
|
|
761
|
+
)
|
|
762
|
+
@click.option(
|
|
763
|
+
"--env",
|
|
764
|
+
"-E",
|
|
765
|
+
multiple=True,
|
|
766
|
+
help="Environment variable in KEY=VALUE format (can be repeated)",
|
|
767
|
+
)
|
|
768
|
+
@click.option(
|
|
769
|
+
"--env-file",
|
|
770
|
+
type=click.Path(exists=True),
|
|
771
|
+
help="Path to .env file with environment variables",
|
|
772
|
+
)
|
|
773
|
+
@click.option(
|
|
774
|
+
"--config",
|
|
775
|
+
"-c",
|
|
776
|
+
type=click.Path(exists=True),
|
|
777
|
+
help="Path to deployment config file (.json, .yaml, or .yml)",
|
|
778
|
+
)
|
|
779
|
+
@click.option(
|
|
780
|
+
"--base-image",
|
|
781
|
+
help="Base Docker image",
|
|
782
|
+
default="python:3.10-slim-bookworm",
|
|
783
|
+
)
|
|
784
|
+
@click.option(
|
|
785
|
+
"--requirements",
|
|
786
|
+
help="Python requirements (comma-separated or file path)",
|
|
787
|
+
default=None,
|
|
788
|
+
)
|
|
789
|
+
@click.option(
|
|
790
|
+
"--cpu-request",
|
|
791
|
+
help="CPU resource request (e.g., '200m', '1')",
|
|
792
|
+
default="200m",
|
|
793
|
+
)
|
|
794
|
+
@click.option(
|
|
795
|
+
"--cpu-limit",
|
|
796
|
+
help="CPU resource limit (e.g., '1000m', '2')",
|
|
797
|
+
default="1000m",
|
|
798
|
+
)
|
|
799
|
+
@click.option(
|
|
800
|
+
"--memory-request",
|
|
801
|
+
help="Memory resource request (e.g., '512Mi', '1Gi')",
|
|
802
|
+
default="512Mi",
|
|
803
|
+
)
|
|
804
|
+
@click.option(
|
|
805
|
+
"--memory-limit",
|
|
806
|
+
help="Memory resource limit (e.g., '2Gi', '4Gi')",
|
|
807
|
+
default="2Gi",
|
|
808
|
+
)
|
|
809
|
+
@click.option(
|
|
810
|
+
"--image-pull-policy",
|
|
811
|
+
help="Image pull policy",
|
|
812
|
+
type=click.Choice(["Always", "IfNotPresent", "Never"]),
|
|
813
|
+
default="IfNotPresent",
|
|
814
|
+
)
|
|
815
|
+
@click.option(
|
|
816
|
+
"--deploy-timeout",
|
|
817
|
+
help="Deployment timeout in seconds",
|
|
818
|
+
type=int,
|
|
819
|
+
default=300,
|
|
820
|
+
)
|
|
821
|
+
@click.option(
|
|
822
|
+
"--health-check",
|
|
823
|
+
is_flag=True,
|
|
824
|
+
help="Enable/disable health check",
|
|
825
|
+
)
|
|
826
|
+
@click.option(
|
|
827
|
+
"--platform",
|
|
828
|
+
help="Target platform (e.g., 'linux/amd64', 'linux/arm64')",
|
|
829
|
+
default="linux/amd64",
|
|
830
|
+
)
|
|
831
|
+
def k8s(
|
|
832
|
+
source: str,
|
|
833
|
+
name: str,
|
|
834
|
+
namespace: str,
|
|
835
|
+
kube_config_path: str,
|
|
836
|
+
replicas: int,
|
|
837
|
+
port: int,
|
|
838
|
+
image_name: str,
|
|
839
|
+
image_tag: str,
|
|
840
|
+
registry_url: str,
|
|
841
|
+
registry_namespace: str,
|
|
842
|
+
push: bool,
|
|
843
|
+
entrypoint: str,
|
|
844
|
+
env: tuple,
|
|
845
|
+
env_file: str,
|
|
846
|
+
config: str,
|
|
847
|
+
base_image: str,
|
|
848
|
+
requirements: str,
|
|
849
|
+
cpu_request: str,
|
|
850
|
+
cpu_limit: str,
|
|
851
|
+
memory_request: str,
|
|
852
|
+
memory_limit: str,
|
|
853
|
+
image_pull_policy: str,
|
|
854
|
+
deploy_timeout: int,
|
|
855
|
+
health_check: bool,
|
|
856
|
+
platform: str,
|
|
857
|
+
):
|
|
858
|
+
"""
|
|
859
|
+
Deploy to Kubernetes/ACK.
|
|
860
|
+
|
|
861
|
+
SOURCE can be a Python file or project directory containing an agent.
|
|
862
|
+
|
|
863
|
+
This will build a Docker image and deploy it to your Kubernetes cluster.
|
|
864
|
+
"""
|
|
865
|
+
if not K8S_AVAILABLE:
|
|
866
|
+
echo_error("Kubernetes deployer is not available")
|
|
867
|
+
echo_info("Please ensure Docker and Kubernetes client are available")
|
|
868
|
+
sys.exit(1)
|
|
869
|
+
|
|
870
|
+
try:
|
|
871
|
+
echo_info(f"Preparing deployment from {source}...")
|
|
872
|
+
|
|
873
|
+
# Load config file if provided
|
|
874
|
+
config_dict = {}
|
|
875
|
+
if config:
|
|
876
|
+
echo_info(f"Loading configuration from {config}...")
|
|
877
|
+
config_dict = _load_config_file(config)
|
|
878
|
+
|
|
879
|
+
# make sure not to push if use local registry_url
|
|
880
|
+
if registry_url == "localhost":
|
|
881
|
+
push = False
|
|
882
|
+
|
|
883
|
+
# Merge CLI parameters with config (CLI takes precedence)
|
|
884
|
+
cli_params = {
|
|
885
|
+
"name": name,
|
|
886
|
+
"namespace": namespace,
|
|
887
|
+
"replicas": replicas,
|
|
888
|
+
"port": port,
|
|
889
|
+
"image_name": image_name,
|
|
890
|
+
"image_tag": image_tag,
|
|
891
|
+
"registry_url": registry_url,
|
|
892
|
+
"registry_namespace": registry_namespace,
|
|
893
|
+
"push_to_registry": push if push else None,
|
|
894
|
+
"entrypoint": entrypoint,
|
|
895
|
+
"base_image": base_image,
|
|
896
|
+
"requirements": requirements,
|
|
897
|
+
"image_pull_policy": image_pull_policy,
|
|
898
|
+
"deploy_timeout": deploy_timeout,
|
|
899
|
+
"health_check": health_check,
|
|
900
|
+
"platform": platform,
|
|
901
|
+
}
|
|
902
|
+
merged_config = _merge_config(config_dict, cli_params)
|
|
903
|
+
|
|
904
|
+
# Extract parameters with defaults
|
|
905
|
+
namespace = merged_config.get("namespace", "agentscope-runtime")
|
|
906
|
+
replicas = merged_config.get("replicas", 1)
|
|
907
|
+
port = merged_config.get("port", 8090)
|
|
908
|
+
image_name = merged_config.get("image_name", "agent_llm")
|
|
909
|
+
image_tag = merged_config.get("image_tag", "latest")
|
|
910
|
+
registry_url = merged_config.get("registry_url", "localhost")
|
|
911
|
+
registry_namespace = merged_config.get(
|
|
912
|
+
"registry_namespace",
|
|
913
|
+
"agentscope-runtime",
|
|
914
|
+
)
|
|
915
|
+
push_to_registry = merged_config.get("push_to_registry", False)
|
|
916
|
+
entrypoint = merged_config.get("entrypoint")
|
|
917
|
+
base_image = merged_config.get("base_image")
|
|
918
|
+
deploy_timeout = merged_config.get("deploy_timeout", 300)
|
|
919
|
+
health_check = merged_config.get("health_check", True)
|
|
920
|
+
platform = merged_config.get("platform")
|
|
921
|
+
|
|
922
|
+
# Handle requirements (can be comma-separated string, list, or file
|
|
923
|
+
# path)
|
|
924
|
+
requirements = merged_config.get("requirements")
|
|
925
|
+
if requirements:
|
|
926
|
+
if isinstance(requirements, str):
|
|
927
|
+
# Check if it's a file path
|
|
928
|
+
if os.path.isfile(requirements):
|
|
929
|
+
with open(requirements, "r", encoding="utf-8") as f:
|
|
930
|
+
requirements = [
|
|
931
|
+
line.strip()
|
|
932
|
+
for line in f
|
|
933
|
+
if line.strip() and not line.startswith("#")
|
|
934
|
+
]
|
|
935
|
+
else:
|
|
936
|
+
# Treat as comma-separated string
|
|
937
|
+
requirements = [r.strip() for r in requirements.split(",")]
|
|
938
|
+
|
|
939
|
+
# Handle extra_packages
|
|
940
|
+
extra_packages = merged_config.get("extra_packages", [])
|
|
941
|
+
|
|
942
|
+
# Handle image_pull_policy
|
|
943
|
+
image_pull_policy = merged_config.get("image_pull_policy")
|
|
944
|
+
|
|
945
|
+
# Build runtime_config from resource parameters
|
|
946
|
+
runtime_config = merged_config.get("runtime_config", {})
|
|
947
|
+
if not runtime_config.get("resources"):
|
|
948
|
+
resources = {}
|
|
949
|
+
if cpu_request or memory_request:
|
|
950
|
+
resources["requests"] = {}
|
|
951
|
+
if cpu_request:
|
|
952
|
+
resources["requests"]["cpu"] = cpu_request
|
|
953
|
+
if memory_request:
|
|
954
|
+
resources["requests"]["memory"] = memory_request
|
|
955
|
+
if cpu_limit or memory_limit:
|
|
956
|
+
resources["limits"] = {}
|
|
957
|
+
if cpu_limit:
|
|
958
|
+
resources["limits"]["cpu"] = cpu_limit
|
|
959
|
+
if memory_limit:
|
|
960
|
+
resources["limits"]["memory"] = memory_limit
|
|
961
|
+
if resources:
|
|
962
|
+
runtime_config["resources"] = resources
|
|
963
|
+
|
|
964
|
+
if image_pull_policy and "image_pull_policy" not in runtime_config:
|
|
965
|
+
runtime_config["image_pull_policy"] = image_pull_policy
|
|
966
|
+
|
|
967
|
+
# Validate source
|
|
968
|
+
abs_source, source_type = _validate_source(source)
|
|
969
|
+
|
|
970
|
+
# Parse environment variables (from config, env_file, and CLI)
|
|
971
|
+
environment = merged_config.get("environment", {}).copy()
|
|
972
|
+
cli_env = _parse_environment(env, env_file)
|
|
973
|
+
environment.update(cli_env) # CLI env overrides config env
|
|
974
|
+
|
|
975
|
+
if environment:
|
|
976
|
+
echo_info(f"Using {len(environment)} environment variable(s)")
|
|
977
|
+
|
|
978
|
+
# Create deployer
|
|
979
|
+
k8s_config = K8sConfig(
|
|
980
|
+
k8s_namespace=namespace,
|
|
981
|
+
kubeconfig_path=kube_config_path,
|
|
982
|
+
)
|
|
983
|
+
registry_config = RegistryConfig(
|
|
984
|
+
registry_url=registry_url,
|
|
985
|
+
namespace=registry_namespace,
|
|
986
|
+
)
|
|
987
|
+
deployer = KubernetesDeployManager(
|
|
988
|
+
kube_config=k8s_config,
|
|
989
|
+
registry_config=registry_config,
|
|
990
|
+
)
|
|
991
|
+
|
|
992
|
+
# Prepare entrypoint specification
|
|
993
|
+
if source_type == "directory":
|
|
994
|
+
# For directory: find entrypoint and create path
|
|
995
|
+
project_dir = abs_source
|
|
996
|
+
entry_script = _find_entrypoint(project_dir, entrypoint)
|
|
997
|
+
entrypoint_spec = os.path.join(project_dir, entry_script)
|
|
998
|
+
|
|
999
|
+
echo_info(f"Using project directory: {project_dir}")
|
|
1000
|
+
echo_info(f"Entry script: {entry_script}")
|
|
1001
|
+
else:
|
|
1002
|
+
# For single file: use file path directly
|
|
1003
|
+
entrypoint_spec = abs_source
|
|
1004
|
+
|
|
1005
|
+
echo_info(f"Using file: {abs_source}")
|
|
1006
|
+
|
|
1007
|
+
# Deploy to Kubernetes using entrypoint
|
|
1008
|
+
echo_info("Deploying to Kubernetes...")
|
|
1009
|
+
|
|
1010
|
+
# Build deploy parameters
|
|
1011
|
+
deploy_params = {
|
|
1012
|
+
"entrypoint": entrypoint_spec,
|
|
1013
|
+
"port": port,
|
|
1014
|
+
"replicas": replicas,
|
|
1015
|
+
"image_name": image_name,
|
|
1016
|
+
"image_tag": image_tag,
|
|
1017
|
+
"push_to_registry": push_to_registry,
|
|
1018
|
+
"environment": environment if environment else None,
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
# Add optional parameters if provided
|
|
1022
|
+
if base_image:
|
|
1023
|
+
deploy_params["base_image"] = base_image
|
|
1024
|
+
if requirements:
|
|
1025
|
+
deploy_params["requirements"] = requirements
|
|
1026
|
+
if extra_packages:
|
|
1027
|
+
deploy_params["extra_packages"] = extra_packages
|
|
1028
|
+
if runtime_config:
|
|
1029
|
+
deploy_params["runtime_config"] = runtime_config
|
|
1030
|
+
if deploy_timeout:
|
|
1031
|
+
deploy_params["deploy_timeout"] = deploy_timeout
|
|
1032
|
+
if health_check is not None:
|
|
1033
|
+
deploy_params["health_check"] = health_check
|
|
1034
|
+
if platform:
|
|
1035
|
+
deploy_params["platform"] = platform
|
|
1036
|
+
|
|
1037
|
+
# Add agent_source for state saving
|
|
1038
|
+
deploy_params["agent_source"] = abs_source
|
|
1039
|
+
|
|
1040
|
+
result = asyncio.run(deployer.deploy(**deploy_params))
|
|
1041
|
+
|
|
1042
|
+
deploy_id = result.get("deploy_id")
|
|
1043
|
+
url = result.get("url")
|
|
1044
|
+
resource_name = result.get("resource_name")
|
|
1045
|
+
|
|
1046
|
+
echo_success("Deployment successful!")
|
|
1047
|
+
echo_info(f"Deployment ID: {deploy_id}")
|
|
1048
|
+
echo_info(f"Resource Name: {resource_name}")
|
|
1049
|
+
echo_info(f"URL: {url}")
|
|
1050
|
+
echo_info(f"Namespace: {namespace}")
|
|
1051
|
+
echo_info(f"Replicas: {replicas}")
|
|
1052
|
+
|
|
1053
|
+
except Exception as e:
|
|
1054
|
+
echo_error(f"Deployment failed: {e}")
|
|
1055
|
+
import traceback
|
|
1056
|
+
|
|
1057
|
+
echo_error(traceback.format_exc())
|
|
1058
|
+
sys.exit(1)
|
|
1059
|
+
|
|
1060
|
+
|
|
1061
|
+
if __name__ == "__main__":
|
|
1062
|
+
deploy()
|