Files
getDomain/domain-api/deploy/multi-region/check_release_hub.sh
2026-04-18 23:52:51 +08:00

374 lines
13 KiB
Bash

#!/usr/bin/env bash
set -euo pipefail
BASE_URL="${1:-http://127.0.0.1:8100}"
INPUT_RELEASE_ID="${2:-}"
CURL_CONNECT_TIMEOUT="${CURL_CONNECT_TIMEOUT:-3}"
CURL_MAX_TIME="${CURL_MAX_TIME:-10}"
curl_json() {
curl -fsS \
--connect-timeout "${CURL_CONNECT_TIMEOUT}" \
--max-time "${CURL_MAX_TIME}" \
"$1"
}
curl_post_json() {
local url="$1"
local body="$2"
curl -fsS \
--connect-timeout "${CURL_CONNECT_TIMEOUT}" \
--max-time "${CURL_MAX_TIME}" \
-X POST "${url}" \
-H 'Content-Type: application/json' \
-d "${body}"
}
launchpad="$(curl_json "${BASE_URL}/api/v1/ops/releases/launchpad")"
latest_release="$(curl_json "${BASE_URL}/api/v1/ops/releases/latest")"
action_templates="$(curl_json "${BASE_URL}/api/v1/ops/action-templates")"
cluster_json="$(curl_json "${BASE_URL}/api/v1/runtime/cluster")"
release_id="${INPUT_RELEASE_ID}"
if [[ -z "${release_id}" ]]; then
release_id="$(python3 -c '
import json
import sys
payload = json.loads(sys.argv[1])
data = payload.get("data") or {}
value = data.get("id") or 0
print(value)
' "${latest_release}")"
fi
selected_release="${latest_release}"
if [[ -n "${release_id}" && "${release_id}" != "0" ]]; then
selected_release="$(curl_json "${BASE_URL}/api/v1/ops/releases/${release_id}")"
fi
build_release_preview_payload() {
local preview_mode="$1"
python3 - <<'PY' "${selected_release}" "${cluster_json}" "${preview_mode}"
import json
import sys
release_payload = json.loads(sys.argv[1]).get("data") or {}
cluster_payload = json.loads(sys.argv[2]).get("data") or {}
preview_mode = str(sys.argv[3] or "").strip().lower()
nodes = list(cluster_payload.get("nodes") or [])
def is_online(node):
return str(node.get("status") or "").strip() in {"online", "busy"}
control_nodes = [
str(node.get("node_code") or "").strip()
for node in nodes
if is_online(node) and str(node.get("role") or "").strip() == "control"
]
worker_nodes = [
str(node.get("node_code") or "").strip()
for node in nodes
if is_online(node)
and str(node.get("role") or "").strip() == "worker"
and bool(node.get("is_effective_worker", False))
]
online_nodes = [
str(node.get("node_code") or "").strip()
for node in nodes
if is_online(node)
]
payload = {
"release_version": str(release_payload.get("release_version") or "").strip(),
"artifact_url": str(release_payload.get("artifact_url") or "").strip(),
"checksum": str(release_payload.get("checksum") or "").strip(),
"install_root": "/opt/domaincheck",
"health_check_timeout_seconds": 10,
"health_check_retries": 2,
"health_check_interval_seconds": 2,
"rollback_on_failure": True,
"switch_current": True,
}
if preview_mode == "control":
template_key = "deploy.release.control"
target_node_codes = control_nodes
payload.update(
{
"restart_services": ["domaincheck-api", "domaincheck-worker", "domaincheck-sync-agent"],
"health_check_urls": ["http://127.0.0.1:8100/health"],
"health_check_services": ["domaincheck-api", "domaincheck-worker", "domaincheck-sync-agent"],
}
)
elif preview_mode == "worker":
template_key = "deploy.release.worker"
target_node_codes = worker_nodes
payload.update(
{
"restart_services": ["domaincheck-worker"],
"health_check_urls": [],
"health_check_services": ["domaincheck-worker"],
}
)
else:
template_key = "deploy.release.custom"
target_node_codes = (control_nodes[:1] + worker_nodes[:2]) or online_nodes[:3]
payload.update(
{
"restart_services": [],
"health_check_urls": [],
"health_check_services": [],
}
)
request_payload = {
"template_key": template_key,
"action": "deploy.release",
"target_type": "batch",
"execution_mode": "remote-agent",
"target_node_codes": [item for item in target_node_codes if item],
"payload": payload,
}
print(json.dumps(request_payload, ensure_ascii=False))
PY
}
print_preview_request_summary() {
local preview_payload="$1"
python3 - <<'PY' "${preview_payload}"
import json
import sys
payload = json.loads(sys.argv[1])
body = payload.get("payload") or {}
print(
json.dumps(
{
"template_key": payload.get("template_key") or "",
"action": payload.get("action") or "",
"execution_mode": payload.get("execution_mode") or "",
"target_node_codes": payload.get("target_node_codes") or [],
"release_version": body.get("release_version") or "",
"artifact_url_present": bool(str(body.get("artifact_url") or "").strip()),
"checksum_present": bool(str(body.get("checksum") or "").strip()),
"restart_services": body.get("restart_services") or [],
"health_check_urls": body.get("health_check_urls") or [],
"health_check_services": body.get("health_check_services") or [],
},
ensure_ascii=False,
indent=2,
)
)
PY
}
run_release_preview() {
local step_no="$1"
local preview_mode="$2"
local section_title="$3"
local preview_payload
preview_payload="$(build_release_preview_payload "${preview_mode}")"
echo "[${step_no}/10] ${section_title}"
print_preview_request_summary "${preview_payload}"
echo
curl_post_json "${BASE_URL}/api/v1/ops/policy/preview" "${preview_payload}"
echo
echo
}
echo "[1/11] release launchpad"
printf '%s\n\n' "${launchpad}"
echo "[2/11] latest release"
printf '%s\n\n' "${selected_release}"
echo "[3/11] recent releases"
curl_json "${BASE_URL}/api/v1/ops/releases?limit=8"
echo
echo
echo "[4/11] release deploy templates"
python3 - <<'PY' "${action_templates}"
import json
import sys
payload = json.loads(sys.argv[1])
groups = (payload.get("data") or {}).get("groups") or []
release_group = next((item for item in groups if str(item.get("group_key") or "") == "release"), {})
items = list(release_group.get("items") or [])
summary = {
"group_key": release_group.get("group_key") or "",
"group_title": release_group.get("group_title") or "",
"template_keys": [str(item.get("key") or "") for item in items],
"field_types": sorted(
{
str(field.get("type") or "")
for item in items
for field in list(item.get("fields") or [])
if str(field.get("type") or "")
}
),
}
print(json.dumps(summary, ensure_ascii=False, indent=2))
PY
echo
echo "[5/11] online release target summary"
python3 - <<'PY' "${cluster_json}"
import json
import sys
payload = json.loads(sys.argv[1]).get("data") or {}
nodes = list(payload.get("nodes") or [])
def is_online(node):
return str(node.get("status") or "").strip() in {"online", "busy"}
summary = {
"online_control_nodes": [
str(node.get("node_code") or "").strip()
for node in nodes
if is_online(node) and str(node.get("role") or "").strip() == "control"
],
"online_worker_nodes": [
str(node.get("node_code") or "").strip()
for node in nodes
if is_online(node) and str(node.get("role") or "").strip() == "worker" and bool(node.get("is_effective_worker", False))
],
"all_online_nodes": [
{
"node_code": str(node.get("node_code") or "").strip(),
"role": str(node.get("role") or "").strip(),
"status": str(node.get("status") or "").strip(),
"is_effective_worker": bool(node.get("is_effective_worker", False)),
}
for node in nodes
if is_online(node)
],
}
print(json.dumps(summary, ensure_ascii=False, indent=2))
PY
echo
echo
run_release_preview "6" "control" "deploy.release.control preview"
run_release_preview "7" "worker" "deploy.release.worker preview"
run_release_preview "8" "custom" "deploy.release.custom preview"
rollout_list='{"code":0,"message":"no release selected","data":{"rollouts":[]}}'
rollout_id="0"
if [[ -n "${release_id}" && "${release_id}" != "0" ]]; then
rollout_list="$(curl_json "${BASE_URL}/api/v1/ops/releases/${release_id}/rollouts?limit=10")"
rollout_id="$(python3 -c '
import json
import sys
payload = json.loads(sys.argv[1])
rollouts = (payload.get("data") or {}).get("rollouts") or []
print((rollouts[0] or {}).get("id") or 0 if rollouts else 0)
' "${rollout_list}")"
fi
echo "[9/11] rollout list for release ${release_id:-0}"
printf '%s\n\n' "${rollout_list}"
echo "[10/11] primary rollout detail"
if [[ "${rollout_id}" != "0" ]]; then
curl_json "${BASE_URL}/api/v1/ops/rollouts/${rollout_id}"
else
echo '{"code":0,"message":"no rollout","data":{}}'
fi
echo
echo
echo "[11/12] primary rollout jobs"
if [[ "${rollout_id}" != "0" ]]; then
curl_json "${BASE_URL}/api/v1/ops/rollouts/${rollout_id}/jobs?limit=50"
else
echo '{"code":0,"message":"no rollout","data":{"jobs":[]}}'
fi
echo
echo
echo "[12/12] condensed summary"
python3 - <<'PY' "${launchpad}" "${selected_release}" "${cluster_json}" "${rollout_list}" "${rollout_id}"
import json
import sys
launchpad_payload = json.loads(sys.argv[1] or "{}").get("data") or {}
selected_release = json.loads(sys.argv[2] or "{}").get("data") or {}
cluster_payload = json.loads(sys.argv[3] or "{}").get("data") or {}
rollout_list_payload = json.loads(sys.argv[4] or "{}").get("data") or {}
rollout_id = int(sys.argv[5] or 0)
launchpad_status = dict(launchpad_payload.get("launchpad_status") or {})
worker_preview = dict(launchpad_payload.get("worker_rollout_preview") or {})
worker_smart_rollout = dict(worker_preview.get("smart_rollout") or {})
control_preview = dict(launchpad_payload.get("control_rollout_preview") or {})
control_smart_rollout = dict(control_preview.get("smart_rollout") or {})
nodes = list(cluster_payload.get("nodes") or [])
rollouts = list(rollout_list_payload.get("rollouts") or [])
def is_online(node):
return str(node.get("status") or "").strip() in {"online", "busy"}
summary = {
"release": {
"id": int(selected_release.get("id") or 0),
"release_version": str(selected_release.get("release_version") or ""),
"channel": str(selected_release.get("channel") or ""),
"status": str(selected_release.get("status") or ""),
},
"launchpad": {
"status": str(launchpad_status.get("status") or ""),
"status_label": str(launchpad_status.get("status_label") or ""),
"summary": str(launchpad_status.get("summary") or ""),
"recommended_action_code": str(launchpad_status.get("recommended_action_code") or ""),
"recommended_target_node_code": str(launchpad_status.get("recommended_target_node_code") or ""),
"recommended_recovery_label": str(launchpad_status.get("recommended_recovery_label") or ""),
"recommended_recovery_summary": str(launchpad_status.get("recommended_recovery_summary") or ""),
"onboarding_bootstrap_pending_nodes": int(launchpad_status.get("onboarding_bootstrap_pending_nodes", 0) or 0),
"onboarding_acceptance_ready_nodes": int(launchpad_status.get("onboarding_acceptance_ready_nodes", 0) or 0),
"recommended_execution_mode": str(launchpad_status.get("recommended_execution_mode") or ""),
"recommended_execution_mode_label": str(launchpad_status.get("recommended_execution_mode_label") or ""),
"focus_ref": dict(launchpad_status.get("focus_ref") or {}),
},
"worker_rollout_preview": {
"available": bool(worker_smart_rollout.get("available", False)),
"blocked": bool(worker_preview.get("blocked", False)),
"requires_confirmation": bool(worker_preview.get("requires_confirmation", False)),
"execution_mode": str(worker_smart_rollout.get("execution_mode") or ""),
"target_node_codes": list(worker_smart_rollout.get("target_node_codes") or []),
},
"control_rollout_preview": {
"available": bool(control_smart_rollout.get("available", False)),
"blocked": bool(control_preview.get("blocked", False)),
"requires_confirmation": bool(control_preview.get("requires_confirmation", False)),
"execution_mode": str(control_smart_rollout.get("execution_mode") or ""),
"target_node_codes": list(control_smart_rollout.get("target_node_codes") or []),
},
"cluster": {
"online_control_nodes": [
str(node.get("node_code") or "").strip()
for node in nodes
if is_online(node) and str(node.get("role") or "").strip() == "control"
],
"online_worker_nodes": [
str(node.get("node_code") or "").strip()
for node in nodes
if is_online(node) and str(node.get("role") or "").strip() == "worker" and bool(node.get("is_effective_worker", False))
],
},
"rollouts": {
"count": len(rollouts),
"selected_rollout_id": rollout_id,
},
}
print(json.dumps(summary, ensure_ascii=False, indent=2))
PY
echo