374 lines
13 KiB
Bash
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
|