feat: initial OpenHands Agent Canvas stack with Docker-in-Docker and automatic Komodo deployment
Deploy OpenHands Agent Canvas / Deploy to Komodo (push) Failing after 14s
Deploy OpenHands Agent Canvas / Deploy to Komodo (push) Failing after 14s
This commit is contained in:
commit
5dc53d1a66
6 files changed
+270
No files matched your search
Executable
+137
@@ -0,0 +1,137 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Deploy script for OpenHands Agent Canvas on Komodo Core.
|
||||
Reads docker-compose.yml, ensures the Komodo Stack exists on server-tsj08,
|
||||
updates the Compose configuration, and triggers deployment via Komodo API.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
|
||||
STACK_NAME = "openhands-agent-canvas"
|
||||
DEFAULT_SERVER = "server-tsj08"
|
||||
|
||||
|
||||
def get_komodo_config():
|
||||
host = os.getenv("KOMODO_HOST", "").strip()
|
||||
key = os.getenv("KOMODO_API_KEY", "").strip()
|
||||
secret = os.getenv("KOMODO_API_SECRET", "").strip()
|
||||
|
||||
if not (host and key and secret):
|
||||
cfg_path = os.path.expanduser("~/.config/komodo/komodo.cli.toml")
|
||||
if os.path.exists(cfg_path):
|
||||
try:
|
||||
import tomllib
|
||||
with open(cfg_path, "rb") as f:
|
||||
cfg = tomllib.load(f)
|
||||
host = host or cfg.get("host", "http://localhost:9120")
|
||||
key = key or cfg.get("cli_key", "")
|
||||
secret = secret or cfg.get("cli_secret", "")
|
||||
except Exception as e:
|
||||
print(f"[WARN] Failed to parse {cfg_path}: {e}")
|
||||
|
||||
host = host or "http://localhost:9120"
|
||||
if not (key and secret):
|
||||
print("::error::KOMODO_API_KEY and KOMODO_API_SECRET must be set.")
|
||||
sys.exit(1)
|
||||
|
||||
return host.rstrip("/"), key, secret
|
||||
|
||||
|
||||
def api_call(host, key, secret, endpoint, payload):
|
||||
url = f"{host}{endpoint}"
|
||||
req = urllib.request.Request(
|
||||
url,
|
||||
data=json.dumps(payload).encode("utf-8"),
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"x-api-key": key,
|
||||
"x-api-secret": secret,
|
||||
},
|
||||
method="POST",
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=120) as resp:
|
||||
data = resp.read().decode("utf-8")
|
||||
return json.loads(data) if data else {}
|
||||
except urllib.error.HTTPError as e:
|
||||
err = e.read().decode("utf-8", errors="replace")
|
||||
raise RuntimeError(f"Komodo API {endpoint} error {e.code}: {err}") from e
|
||||
|
||||
|
||||
def main():
|
||||
host, key, secret = get_komodo_config()
|
||||
print(f"=== Komodo Target: {host} ===")
|
||||
|
||||
# 1. Resolve server ID
|
||||
servers = api_call(host, key, secret, "/read/ListServers", {})
|
||||
server_id = None
|
||||
for s in servers:
|
||||
if s.get("name") == DEFAULT_SERVER or s.get("id") == DEFAULT_SERVER:
|
||||
server_id = s.get("id")
|
||||
break
|
||||
|
||||
if not server_id and servers:
|
||||
server_id = servers[0].get("id")
|
||||
print(f"[INFO] Server '{DEFAULT_SERVER}' not found by name, using '{servers[0].get('name')}' ({server_id})")
|
||||
elif server_id:
|
||||
print(f"[INFO] Target server '{DEFAULT_SERVER}' resolved to id: {server_id}")
|
||||
else:
|
||||
print(f"::error::No active servers found in Komodo.")
|
||||
sys.exit(1)
|
||||
|
||||
# 2. Read docker-compose.yml
|
||||
compose_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "docker-compose.yml")
|
||||
if not os.path.exists(compose_path):
|
||||
compose_path = "docker-compose.yml"
|
||||
with open(compose_path, "r", encoding="utf-8") as f:
|
||||
compose_content = f.read()
|
||||
|
||||
# 3. Check or Create Stack
|
||||
stacks = api_call(host, key, secret, "/read/ListStacks", {})
|
||||
existing_stack = None
|
||||
for stk in stacks:
|
||||
if stk.get("name") == STACK_NAME:
|
||||
existing_stack = stk
|
||||
break
|
||||
|
||||
stack_config = {
|
||||
"server_id": server_id,
|
||||
"auto_pull": True,
|
||||
"run_build": False,
|
||||
"auto_update": True,
|
||||
"auto_update_all_services": True,
|
||||
"file_contents": compose_content,
|
||||
"environment": os.getenv("STACK_ENVIRONMENT", ""),
|
||||
}
|
||||
|
||||
if not existing_stack:
|
||||
print(f"[INFO] Creating Komodo Stack '{STACK_NAME}'...")
|
||||
res = api_call(
|
||||
host, key, secret, "/write/CreateStack",
|
||||
{"name": STACK_NAME, "config": stack_config}
|
||||
)
|
||||
print(f"[INFO] Created Stack successfully: {res.get('name', STACK_NAME)}")
|
||||
else:
|
||||
print(f"[INFO] Updating Komodo Stack '{STACK_NAME}' compose definition...")
|
||||
api_call(
|
||||
host, key, secret, "/write/UpdateStack",
|
||||
{"id": existing_stack["id"], "config": stack_config}
|
||||
)
|
||||
print(f"[INFO] Updated Stack configuration.")
|
||||
|
||||
# 4. Trigger Deploy
|
||||
print(f"[INFO] Executing DeployStack for '{STACK_NAME}'...")
|
||||
deploy_res = api_call(
|
||||
host, key, secret, "/execute/DeployStack",
|
||||
{"stack": STACK_NAME, "services": [], "stop_time": None}
|
||||
)
|
||||
print(f"[SUCCESS] Deploy execution dispatched: {deploy_res}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user