#!/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