feat: add ops center and node onboarding flow
This commit is contained in:
373
domain-api/deploy/multi-region/check_release_hub.sh
Normal file
373
domain-api/deploy/multi-region/check_release_hub.sh
Normal file
@@ -0,0 +1,373 @@
|
||||
#!/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
|
||||
Reference in New Issue
Block a user