hy-app-monitor/ops/deploy_via_tat.py
2026-04-22 00:15:58 +08:00

148 lines
5.1 KiB
Python
Executable File

#!/usr/bin/env python3
from __future__ import annotations
import base64
import json
import os
import time
from typing import Callable, TypeVar
from tencentcloud.common import credential
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
from tencentcloud.common.profile.client_profile import ClientProfile
from tencentcloud.common.profile.http_profile import HttpProfile
from tencentcloud.tat.v20201028 import models as tat_models, tat_client
DEFAULT_INSTANCE_ID = os.environ.get("DEPLOY_INSTANCE_ID", "ins-ntmpcd3a")
DEFAULT_REGION = os.environ.get("DEPLOY_REGION", "me-saudi-arabia")
DEFAULT_REPO_URL = os.environ.get("MONITOR_REPO_URL", "git@gitea.haiyihy.com:hy/hy-app-monitor.git")
DEFAULT_REPO_REF = os.environ.get("MONITOR_REPO_REF", "main")
DEFAULT_REMOTE_DIR = os.environ.get("MONITOR_REMOTE_DIR", "/opt/hy-app-monitor")
DEFAULT_APP_PORT = os.environ.get("APP_PORT", "6666")
T = TypeVar("T")
def env_required(name: str) -> str:
value = os.environ.get(name, "").strip()
if not value:
raise SystemExit(f"missing env: {name}")
return value
def build_tat(secret_id: str, secret_key: str, region: str) -> tat_client.TatClient:
cred = credential.Credential(secret_id, secret_key)
return tat_client.TatClient(
cred,
region,
ClientProfile(httpProfile=HttpProfile(endpoint="tat.tencentcloudapi.com")),
)
def invoke(action: Callable[[], T], label: str, retries: int = 4, delay_seconds: int = 2) -> T:
last_error: Exception | None = None
for attempt in range(1, retries + 1):
try:
return action()
except TencentCloudSDKException as exc:
last_error = exc
if attempt == retries:
break
time.sleep(delay_seconds * attempt)
if last_error is not None:
raise last_error
raise RuntimeError(f"{label} failed without error")
def remote_script(repo_url: str, repo_ref: str, remote_dir: str, app_port: str) -> str:
return f"""set -euo pipefail
REPO_URL={json.dumps(repo_url)}
REPO_REF={json.dumps(repo_ref)}
REMOTE_DIR={json.dumps(remote_dir)}
APP_PORT={json.dumps(app_port)}
export GIT_SSH_COMMAND='ssh -o StrictHostKeyChecking=accept-new'
mkdir -p "$(dirname "$REMOTE_DIR")"
if [ -d "$REMOTE_DIR/.git" ]; then
git -C "$REMOTE_DIR" fetch --all --prune
git -C "$REMOTE_DIR" checkout "$REPO_REF"
git -C "$REMOTE_DIR" pull --ff-only origin "$REPO_REF"
else
rm -rf "$REMOTE_DIR"
git clone --branch "$REPO_REF" "$REPO_URL" "$REMOTE_DIR"
fi
cd "$REMOTE_DIR"
chmod +x scripts/*.sh
APP_PORT="$APP_PORT" bash scripts/restart.sh
sleep 1
ss -lntp | grep ":$APP_PORT" || true
"""
def run_tat_script(client: tat_client.TatClient, instance_id: str, script: str, timeout_seconds: int = 600) -> str:
req = tat_models.RunCommandRequest()
req.from_json_string(
json.dumps(
{
"CommandName": "hy-app-monitor-deploy",
"CommandType": "SHELL",
"Content": base64.b64encode(script.encode("utf-8")).decode("ascii"),
"InstanceIds": [instance_id],
"Timeout": timeout_seconds,
"WorkingDirectory": "/root",
}
)
)
resp = json.loads(invoke(lambda: client.RunCommand(req), "RunCommand").to_json_string())
invocation_id = resp["InvocationId"]
deadline = time.time() + timeout_seconds + 120
while time.time() < deadline:
time.sleep(3)
query = tat_models.DescribeInvocationTasksRequest()
query.from_json_string(
json.dumps(
{
"HideOutput": False,
"Limit": 20,
"Filters": [{"Name": "invocation-id", "Values": [invocation_id]}],
}
)
)
data = json.loads(invoke(lambda: client.DescribeInvocationTasks(query), "DescribeInvocationTasks").to_json_string())
tasks = data.get("InvocationTaskSet") or []
if not tasks:
continue
task = tasks[0]
status = task["TaskStatus"]
if status in {"SUCCESS", "FAILED", "TIMEOUT", "CANCELLED", "START_FAILED"}:
output = base64.b64decode(task.get("TaskResult", {}).get("Output", "")).decode("utf-8", errors="replace")
if status != "SUCCESS":
raise SystemExit(output or f"TAT task failed: {status}")
return output
raise SystemExit("TAT task timed out")
def main() -> None:
secret_id = env_required("TENCENT_SECRET_ID")
secret_key = env_required("TENCENT_SECRET_KEY")
instance_id = os.environ.get("DEPLOY_INSTANCE_ID", DEFAULT_INSTANCE_ID)
region = os.environ.get("DEPLOY_REGION", DEFAULT_REGION)
repo_url = os.environ.get("MONITOR_REPO_URL", DEFAULT_REPO_URL)
repo_ref = os.environ.get("MONITOR_REPO_REF", DEFAULT_REPO_REF)
remote_dir = os.environ.get("MONITOR_REMOTE_DIR", DEFAULT_REMOTE_DIR)
app_port = os.environ.get("APP_PORT", DEFAULT_APP_PORT)
tat = build_tat(secret_id, secret_key, region)
output = run_tat_script(tat, instance_id, remote_script(repo_url, repo_ref, remote_dir, app_port))
print(output.strip())
if __name__ == "__main__":
main()