#!/usr/bin/env bash
# scripts/validate.sh -- D-011 validation orchestrator (modular check runner).
#
# Runs a PROFILE (named set) or an explicit list of check scripts from
# scripts/checks/, applies the standard exit contract (see lib-validate.sh),
# and prints ONE structured report + a single overall verdict. Each check is
# ALSO runnable standalone; this composes them for a full acceptance run.
#
# Usage:
# validate.sh [--profile NAME | --checks id,id,...] [options]
# Options:
# --profile NAME run a named profile (default: full-d011)
# --checks a,b,c run an explicit comma-list of check ids (overrides profile)
# --include-disruptive permit checks' destructive paths (amphora failover, etc.)
# --stop-on-fail halt after the first FAIL/HOLD (default: continue, report all)
# --list list profiles + discovered checks, then exit 0
# -h|--help this help
#
# Profiles (data, not code -- extend freely):
# full-d011 the complete amended D-011 bar (1-6; item 4 needs --include-disruptive)
# post-restart quick confidence after a restart (charms, VIP jumphost, VIP tenant)
# lb-health octavia LB pattern only (non-disruptive unless --include-disruptive)
# isolation tenant e2e incl. cross-tenant isolation (item 5)
#
# Exit (overall verdict, worst-wins with MANUAL as a distinct tier):
# 0 PASS every check PASS (or SKIPPED); nothing outstanding
# 1 FAIL at least one check FAIL
# 2 HOLD no FAIL, but at least one HOLD (undetermined)
# 3 PASS_PENDING_MANUAL no FAIL/HOLD, but a manual item outstanding
# ASCII + LF.
set -uo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=scripts/lib-validate.sh
. "$HERE/lib-validate.sh"
CHECKDIR="${VR_CHECKDIR:-$HERE/checks}"
PROFILE="full-d011"; EXPLICIT=""; STOP=0; LIST=0
export VR_DISRUPTIVE="${VR_DISRUPTIVE:-0}"
while [ $# -gt 0 ]; do
case "$1" in
--profile) PROFILE="${2:-}"; shift 2 ;;
--checks) EXPLICIT="${2:-}"; shift 2 ;;
--include-disruptive) VR_DISRUPTIVE=1; shift ;;
--stop-on-fail) STOP=1; shift ;;
--list) LIST=1; shift ;;
-h|--help) sed -n '2,40p' "$HERE/$(basename "${BASH_SOURCE[0]}")" | sed 's/^# \{0,1\}//'; exit 0 ;;
*) echo "unknown arg: $1" >&2; exit 2 ;;
esac
done
# --- profiles: name -> ordered check ids -------------------------------------
profile_checks() {
case "$1" in
full-d011) echo "d011-01-charms d011-02-vip-jumphost d011-03-vip-tenant d011-04-octavia-lb d011-05-magnum-e2e d011-06-vault-unseal" ;;
post-restart) echo "d011-01-charms d011-02-vip-jumphost d011-03-vip-tenant" ;;
lb-health) echo "d011-04-octavia-lb" ;;
isolation) echo "d011-05-magnum-e2e" ;;
*) return 1 ;;
esac
}
if [ "$LIST" = 1 ]; then
echo "profiles:"; for p in full-d011 post-restart lb-health isolation; do printf ' %-14s %s\n' "$p" "$(profile_checks "$p")"; done
echo "discovered checks in $CHECKDIR:"
shopt -s nullglob; for c in "$CHECKDIR"/*.sh; do echo " $(basename "$c" .sh)"; done
exit 0
fi
# resolve the ordered list
if [ -n "$EXPLICIT" ]; then
IFS=',' read -r -a CHECKS <<< "$EXPLICIT"
else
read -r -a CHECKS <<< "$(profile_checks "$PROFILE")" || { echo "unknown profile: $PROFILE (try --list)" >&2; exit 2; }
[ "${#CHECKS[@]}" -gt 0 ] || { echo "unknown profile: $PROFILE (try --list)" >&2; exit 2; }
fi
# --- run loop ----------------------------------------------------------------
declare -i n_pass=0 n_fail=0 n_hold=0 n_manual=0 n_skip=0
REPORT=()
worst=$VR_PASS
note_worst() { # keep the worst by a custom precedence: FAIL > HOLD > MANUAL > PASS/SKIP
local c="$1"
case "$c" in
"$VR_FAIL") worst=$VR_FAIL ;;
"$VR_HOLD") [ "$worst" = "$VR_FAIL" ] || worst=$VR_HOLD ;;
"$VR_MANUAL") { [ "$worst" = "$VR_FAIL" ] || [ "$worst" = "$VR_HOLD" ]; } || worst=$VR_MANUAL ;;
esac
}
for id in "${CHECKS[@]}"; do
script="$CHECKDIR/$id.sh"
if [ ! -f "$script" ]; then
line="RESULT $id HOLD 2 - check script not found: $script"; rc=$VR_HOLD
else
out="$(VR_DISRUPTIVE="$VR_DISRUPTIVE" bash "$script" 2>&1)"; rc=$?
# the check's own RESULT line is authoritative for the message; the EXIT CODE
# is authoritative for the verdict. Prefer the emitted line but trust rc.
line="$(printf '%s\n' "$out" | grep -E '^RESULT ' | tail -1)"
[ -n "$line" ] || line="RESULT $id ${VR_WORD[$rc]:-UNKNOWN} $rc - (no RESULT line emitted)"
# print the check's human output above its result line
printf '%s\n' "$out" | grep -vE '^RESULT ' | sed 's/^/ /'
fi
REPORT+=("$line")
case "$rc" in
"$VR_PASS") n_pass+=1 ;;
"$VR_FAIL") n_fail+=1 ;;
"$VR_HOLD") n_hold+=1 ;;
"$VR_MANUAL") n_manual+=1 ;;
"$VR_SKIP") n_skip+=1 ;;
*) n_hold+=1; line="${line/ $rc / $rc(nonstd) }" ;;
esac
note_worst "$rc"
if [ "$STOP" = 1 ] && { [ "$rc" = "$VR_FAIL" ] || [ "$rc" = "$VR_HOLD" ]; }; then
echo " [stop-on-fail: halting after $id]"; break
fi
done
echo
echo "================ VALIDATION REPORT (profile: ${EXPLICIT:+explicit}${EXPLICIT:-$PROFILE}) ================"
printf '%s\n' "${REPORT[@]}"
echo "----------------------------------------------------------------"
printf 'totals: PASS=%d FAIL=%d HOLD=%d MANUAL=%d SKIP=%d\n' "$n_pass" "$n_fail" "$n_hold" "$n_manual" "$n_skip"
echo "OVERALL: ${VR_WORD[$worst]} (exit $worst)"
exit "$worst"