Skip to content

Commit cb48410

Browse files
committed
feat(vefaas): yaml deploy
1 parent efdec24 commit cb48410

File tree

2 files changed

+500
-121
lines changed

2 files changed

+500
-121
lines changed

veadk/cli/cli_deploy.py

Lines changed: 236 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -34,109 +34,245 @@
3434
@click.option(
3535
"--deploy-config-file", default="./deploy.yaml", help="Deploy config file path"
3636
)
37+
@click.option(
38+
"--local_test", is_flag=True, default=False, help="Run local test before deploy"
39+
)
3740
def deploy(
3841
volcengine_access_key: str,
3942
volcengine_secret_key: str,
4043
project_path: str,
4144
deploy_config_file: str,
45+
local_test: bool,
4246
) -> None:
43-
# """Deploy a user project to Volcengine FaaS application."""
44-
# import asyncio
45-
# import shutil
46-
# from pathlib import Path
47-
48-
# from cookiecutter.main import cookiecutter
49-
50-
# import veadk.integrations.ve_faas as vefaas
51-
# from veadk.config import getenv
52-
# from veadk.utils.logger import get_logger
53-
# from veadk.utils.misc import formatted_timestamp, load_module_from_file
54-
55-
# logger = get_logger(__name__)
56-
57-
# if not volcengine_access_key:
58-
# access_key = getenv("VOLCENGINE_ACCESS_KEY")
59-
# if not volcengine_secret_key:
60-
# secret_key = getenv("VOLCENGINE_SECRET_KEY")
61-
62-
# user_proj_abs_path = Path(path).resolve()
63-
# template_dir_path = Path(vefaas.__file__).parent / "template"
64-
65-
# tmp_dir_name = f"{user_proj_abs_path.name}_{formatted_timestamp()}"
66-
67-
# settings = {
68-
# "local_dir_name": tmp_dir_name.replace("-", "_"),
69-
# "app_name": user_proj_abs_path.name.replace("-", "_"),
70-
# "agent_module_name": user_proj_abs_path.name,
71-
# "short_term_memory_backend": short_term_memory_backend,
72-
# "vefaas_application_name": vefaas_app_name,
73-
# "veapig_instance_name": veapig_instance_name,
74-
# "veapig_service_name": veapig_service_name,
75-
# "veapig_upstream_name": veapig_upstream_name,
76-
# "use_adk_web": use_adk_web,
77-
# "veadk_version": VERSION,
78-
# }
79-
80-
# cookiecutter(
81-
# template=str(template_dir_path),
82-
# output_dir=TEMP_PATH,
83-
# no_input=True,
84-
# extra_context=settings,
85-
# )
86-
# logger.debug(f"Create a template project at {TEMP_PATH}/{tmp_dir_name}")
87-
88-
# agent_dir = (
89-
# Path(TEMP_PATH)
90-
# / tmp_dir_name
91-
# / "src"
92-
# / user_proj_abs_path.name.replace("-", "_")
93-
# )
94-
95-
# # remove /tmp/tmp_dir_name/src/user_proj_abs_path.name
96-
# shutil.rmtree(agent_dir)
97-
# agent_dir.mkdir(parents=True, exist_ok=True)
98-
99-
# # copy
100-
# shutil.copytree(user_proj_abs_path, agent_dir, dirs_exist_ok=True)
101-
# logger.debug(f"Remove agent module from {user_proj_abs_path} to {agent_dir}")
102-
103-
# # copy requirements.txt
104-
# if (user_proj_abs_path / "requirements.txt").exists():
105-
# logger.debug(
106-
# f"Find a requirements.txt in {user_proj_abs_path}/requirements.txt, copy it to temp project."
107-
# )
108-
# shutil.copy(
109-
# user_proj_abs_path / "requirements.txt",
110-
# Path(TEMP_PATH) / tmp_dir_name / "src" / "requirements.txt",
111-
# )
112-
# else:
113-
# logger.warning(
114-
# "No requirements.txt found in the user project, we will use a default one."
115-
# )
116-
117-
# # avoid upload user's config.yaml
118-
# if (user_proj_abs_path / "config.yaml").exists():
119-
# logger.warning(
120-
# f"Find a config.yaml in {user_proj_abs_path}/config.yaml, we will not upload it by default."
121-
# )
122-
# shutil.move(agent_dir / "config.yaml", Path(TEMP_PATH) / tmp_dir_name)
123-
# else:
124-
# logger.info(
125-
# "No config.yaml found in the user project. Some environment variables may not be set."
126-
# )
127-
128-
# # load
129-
# logger.debug(
130-
# f"Load deploy module from {Path(TEMP_PATH) / tmp_dir_name / 'deploy.py'}"
131-
# )
132-
# deploy_module = load_module_from_file(
133-
# module_name="deploy_module",
134-
# file_path=str(Path(TEMP_PATH) / tmp_dir_name / "deploy.py"),
135-
# )
136-
# logger.info(f"Begin deploy from {Path(TEMP_PATH) / tmp_dir_name / 'src'}")
137-
# asyncio.run(deploy_module.main())
138-
139-
# # remove tmp file
140-
# logger.info("Deploy done. Delete temp dir.")
141-
# shutil.rmtree(Path(TEMP_PATH) / tmp_dir_name)
142-
pass
47+
"""Deploy a user project to Volcengine FaaS application."""
48+
import asyncio
49+
import shutil
50+
import yaml
51+
import os
52+
import sys
53+
import subprocess
54+
from pathlib import Path
55+
from cookiecutter.main import cookiecutter
56+
import veadk.integrations.ve_faas as vefaas
57+
from veadk.config import veadk_environments
58+
from veadk.utils.logger import get_logger
59+
from veadk.utils.misc import formatted_timestamp
60+
from veadk.version import VERSION
61+
from veadk.cloud.cloud_agent_engine import CloudAgentEngine
62+
63+
logger = get_logger(__name__)
64+
65+
# if not volcengine_access_key:
66+
# access_key = getenv("VOLCENGINE_ACCESS_KEY")
67+
# if not volcengine_secret_key:
68+
# secret_key = getenv("VOLCENGINE_SECRET_KEY")
69+
70+
# Get deploy.yaml
71+
if deploy_config_file:
72+
deploy_config_path = Path(deploy_config_file).resolve()
73+
else:
74+
deploy_config_path = Path.cwd() / "deploy.yaml"
75+
76+
if not deploy_config_path.exists():
77+
raise click.ClickException(
78+
f"Deploy config file not found: {deploy_config_path}"
79+
)
80+
81+
with open(deploy_config_path, "r") as f:
82+
deploy_config = yaml.safe_load(f)
83+
84+
vefaas_config = deploy_config.get("vefaas", {})
85+
veapig_config = deploy_config.get("veapig", {})
86+
veadk_config = deploy_config.get("veadk", {})
87+
88+
# Set environment variables
89+
os.environ["VEADK_ENTRYPOINT_AGENT"] = veadk_config.get("entrypoint_agent", "")
90+
os.environ["APP_NAME"] = vefaas_config.get("application_name", "veadk-app")
91+
os.environ["VOLCENGINE_ACCESS_KEY"] = volcengine_access_key or ""
92+
os.environ["VOLCENGINE_SECRET_KEY"] = volcengine_secret_key or ""
93+
os.environ["USE_ADK_WEB"] = (
94+
"True" if veadk_config.get("deploy_mode", "A2A/MCP") == "WEB" else "False"
95+
)
96+
97+
yaml_envs = {
98+
"VEADK_ENTRYPOINT_AGENT": veadk_config.get("entrypoint_agent", ""),
99+
"APP_NAME": vefaas_config.get("application_name", "veadk-app"),
100+
"VOLCENGINE_ACCESS_KEY": volcengine_access_key or "",
101+
"VOLCENGINE_SECRET_KEY": volcengine_secret_key or "",
102+
"USE_ADK_WEB": "True"
103+
if veadk_config.get("deploy_mode", "A2A/MCP") == "WEB"
104+
else "False",
105+
}
106+
107+
for key, value in yaml_envs.items():
108+
os.environ[key] = value
109+
veadk_environments[key] = value
110+
111+
# Get user project path
112+
user_proj_abs_path = Path(project_path).resolve()
113+
114+
if not user_proj_abs_path.exists():
115+
raise click.ClickException(f"Project path not found: {project_path}")
116+
117+
""" Local test mode """
118+
if local_test:
119+
logger.info("Running in local test mode")
120+
# Add project path to PYTHONPATH for local test
121+
current_pythonpath = os.environ.get("PYTHONPATH", "")
122+
os.environ["PYTHONPATH"] = (
123+
f"{user_proj_abs_path}:{current_pythonpath}"
124+
if current_pythonpath
125+
else str(user_proj_abs_path)
126+
)
127+
128+
cmd = [
129+
sys.executable,
130+
"-m",
131+
"uvicorn",
132+
"veadk.cloud.app:app",
133+
"--host",
134+
"0.0.0.0",
135+
"--port",
136+
"8000",
137+
"--reload",
138+
]
139+
140+
try:
141+
subprocess.run(cmd, check=True)
142+
except KeyboardInterrupt:
143+
logger.info("Server stopped")
144+
return
145+
146+
""" Deploy mode """
147+
template_dir_path = Path(vefaas.__file__).parent / "template"
148+
tmp_dir_name = f"{user_proj_abs_path.name}_{formatted_timestamp()}"
149+
150+
agent_temp_module_name = user_proj_abs_path.name.replace("-", "_")
151+
152+
settings = {
153+
"local_dir_name": tmp_dir_name.replace("-", "_"),
154+
"app_name": agent_temp_module_name,
155+
"agent_module_name": user_proj_abs_path.name,
156+
"short_term_memory_backend": veadk_config.get(
157+
"short_term_memory_backend", "InMemorySTM"
158+
),
159+
"vefaas_application_name": vefaas_config.get(
160+
"application_name", "veadk-cloud-agent"
161+
),
162+
"veapig_instance_name": veapig_config.get("instance_name", ""),
163+
"veapig_service_name": veapig_config.get("service_name", ""),
164+
"veapig_upstream_name": veapig_config.get("upstream_name", ""),
165+
"use_adk_web": veadk_config.get("deploy_mode", "A2A/MCP") == "WEB",
166+
"veadk_version": VERSION,
167+
}
168+
169+
cookiecutter(
170+
template=str(template_dir_path),
171+
output_dir=TEMP_PATH,
172+
no_input=True,
173+
extra_context=settings,
174+
)
175+
logger.debug(f"Create a template project at {TEMP_PATH}/{tmp_dir_name}")
176+
177+
# remove template agent dir. /tmp/tmp_dir_name/user_proj_abs_path.name
178+
temp_proj_dir = Path(TEMP_PATH) / tmp_dir_name / agent_temp_module_name
179+
180+
if temp_proj_dir.exists():
181+
shutil.rmtree(temp_proj_dir)
182+
temp_proj_dir.mkdir(parents=True, exist_ok=True)
183+
184+
# Copy user project files to template agent dir
185+
shutil.copytree(user_proj_abs_path, temp_proj_dir, dirs_exist_ok=True)
186+
logger.debug(f"Copy agent module from {user_proj_abs_path} to {temp_proj_dir}")
187+
188+
# copy requirements.txt
189+
if (user_proj_abs_path / agent_temp_module_name / "requirements.txt").exists():
190+
logger.debug(
191+
f"Find a requirements.txt in {user_proj_abs_path}/requirements.txt, copy it to temp project."
192+
)
193+
shutil.copy(
194+
user_proj_abs_path / agent_temp_module_name / "requirements.txt",
195+
Path(TEMP_PATH) / tmp_dir_name / "requirements.txt",
196+
)
197+
else:
198+
logger.warning(
199+
"No requirements.txt found in the user project, we will use a default one."
200+
)
201+
202+
# avoid upload user's config.yaml
203+
if (user_proj_abs_path / "config.yaml").exists():
204+
logger.warning(
205+
f"Find a config.yaml in {user_proj_abs_path}/config.yaml, we will not upload it by default."
206+
)
207+
shutil.move(temp_proj_dir / "config.yaml", Path(TEMP_PATH) / tmp_dir_name)
208+
else:
209+
logger.info(
210+
"No config.yaml found in the user project. Some environment variables may not be set."
211+
)
212+
213+
# Create run.sh
214+
run_sh_content = f"""#!/bin/bash
215+
set -ex
216+
217+
cd `dirname $0`
218+
219+
# A special check for CLI users (run.sh should be located at the 'root' dir)
220+
if [ -d "output" ]; then
221+
cd ./output/
222+
fi
223+
224+
# Default values for host and port
225+
HOST="0.0.0.0"
226+
PORT=${{_FAAS_RUNTIME_PORT:-8000}}
227+
TIMEOUT=${{_FAAS_FUNC_TIMEOUT:-900}}
228+
229+
export SERVER_HOST=$HOST
230+
export SERVER_PORT=$PORT
231+
232+
# Install requirements
233+
pip install -r requirements.txt
234+
235+
# Set PYTHONPATH
236+
export PYTHONPATH=$PYTHONPATH:./site-packages
237+
export PYTHONPATH=$PYTHONPATH:$(pwd)/{agent_temp_module_name}
238+
239+
# Run server based on mode
240+
if [ "$USE_ADK_WEB" = "True" ]; then
241+
echo "Running VeADK Web mode"
242+
exec python3 -m veadk.cli.cli web --host $HOST
243+
else
244+
echo "Running A2A/MCP mode"
245+
exec python3 -m uvicorn veadk.cloud.app:app --host $HOST --port $PORT --timeout-graceful-shutdown $TIMEOUT --loop asyncio
246+
fi
247+
"""
248+
# Write run.sh to the tmp project root
249+
run_sh_path = Path(TEMP_PATH) / tmp_dir_name / agent_temp_module_name / "run.sh"
250+
with open(run_sh_path, "w") as f:
251+
f.write(run_sh_content)
252+
logger.debug(f"Created run.sh at {run_sh_path}")
253+
254+
# Deploy using CloudAgentEngine
255+
async def _deploy():
256+
engine = CloudAgentEngine()
257+
cloud_app = engine.deploy(
258+
path=str(Path(TEMP_PATH) / tmp_dir_name / agent_temp_module_name),
259+
application_name=vefaas_config.get("application_name"),
260+
gateway_name=veapig_config.get("instance_name", ""),
261+
gateway_service_name=veapig_config.get("service_name", ""),
262+
gateway_upstream_name=veapig_config.get("upstream_name", ""),
263+
use_adk_web=settings["use_adk_web"],
264+
)
265+
return cloud_app
266+
267+
# Run deployment
268+
cloud_app = asyncio.run(_deploy())
269+
logger.info(f"VeFaaS application ID: {cloud_app.vefaas_application_id}")
270+
if settings["use_adk_web"]:
271+
logger.info(f"Web is running at: {cloud_app.vefaas_endpoint}")
272+
else:
273+
logger.info(f"A2A endpoint: {cloud_app.vefaas_endpoint}")
274+
logger.info(f"MCP endpoint: {cloud_app.vefaas_endpoint}/mcp")
275+
276+
# remove tmp file
277+
logger.info(f"Deploy done. Delete temp dir {Path(TEMP_PATH) / tmp_dir_name}.")
278+
shutil.rmtree(Path(TEMP_PATH) / tmp_dir_name)

0 commit comments

Comments
 (0)