PSA/ee/appliance/tests/local-utm-smoke.sh
Hermes 284313f908
Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00

221 lines
7.9 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<'USAGE'
Usage:
local-utm-smoke.sh monitor \
--status-url http://<node-ip>:8080/api/status \
--app-url http://<node-ip>:3000 \
--token <status-token> [--timeout-seconds 3600] [--interval-seconds 5]
local-utm-smoke.sh verify \
--kubeconfig <path> \
--node-ip <ip> \
--status-token <status-token>
Subtests:
monitor mode validates T020 by proving status API becomes reachable before app URL is ready.
verify mode validates T021-T023 using live cluster checks.
USAGE
}
require_cmd() {
command -v "$1" >/dev/null 2>&1 || {
echo "missing required command: $1" >&2
exit 1
}
}
http_code() {
local url="$1"
shift || true
curl -sS -o /dev/null -w '%{http_code}' "$@" "$url" || true
}
# Establish an authenticated management session and echo a curl cookie-jar path.
# On a fresh appliance this redeems the one-time token and sets the password; if
# a password is already configured it logs in with the same smoke password.
establish_session() {
local base="$1" token="$2" password="$3"
local jar; jar="$(mktemp)"
if curl -fsS -c "$jar" -X POST "${base}/api/auth/redeem-token" \
-H 'content-type: application/json' --data "{\"token\":\"${token}\"}" >/dev/null 2>&1; then
curl -fsS -c "$jar" -b "$jar" -X POST "${base}/api/auth/set-password" \
-H 'content-type: application/json' --data "{\"token\":\"${token}\",\"password\":\"${password}\"}" >/dev/null
else
curl -fsS -c "$jar" -X POST "${base}/api/auth/login" \
-H 'content-type: application/json' --data "{\"password\":\"${password}\"}" >/dev/null
fi
printf '%s' "$jar"
}
monitor_t020() {
local status_url=""
local app_url=""
local token=""
local timeout_seconds=3600
local interval_seconds=5
while [ "$#" -gt 0 ]; do
case "$1" in
--status-url) status_url="$2"; shift 2 ;;
--app-url) app_url="$2"; shift 2 ;;
--token) token="$2"; shift 2 ;;
--timeout-seconds) timeout_seconds="$2"; shift 2 ;;
--interval-seconds) interval_seconds="$2"; shift 2 ;;
*) echo "unknown arg: $1" >&2; usage; exit 1 ;;
esac
done
[ -n "$status_url" ] || { echo "--status-url is required" >&2; exit 1; }
[ -n "$app_url" ] || { echo "--app-url is required" >&2; exit 1; }
[ -n "$token" ] || { echo "--token is required" >&2; exit 1; }
require_cmd curl
local start_ts now elapsed
start_ts="$(date +%s)"
local status_first_ts=""
local app_first_ts=""
echo "[T020] Monitoring bootstrap timing"
echo "[T020] status_url=$status_url"
echo "[T020] app_url=$app_url"
while true; do
now="$(date +%s)"
elapsed=$((now - start_ts))
if [ "$elapsed" -gt "$timeout_seconds" ]; then
echo "[T020] FAIL: timed out after ${timeout_seconds}s" >&2
exit 1
fi
if [ -z "$status_first_ts" ]; then
local status_code
status_code="$(http_code "$status_url")"
# The control-plane is "reachable" once it answers at all; an
# unauthenticated /api/status now returns 401 (still a live response),
# which is a valid readiness signal for ordering vs. the app URL.
if [ "$status_code" = "200" ] || [ "$status_code" = "401" ]; then
status_first_ts="$now"
echo "[T020] status API first reachable at +$elapsed s"
fi
fi
if [ -z "$app_first_ts" ]; then
local app_code
app_code="$(http_code "$app_url")"
if [ "$app_code" = "200" ] || [ "$app_code" = "301" ] || [ "$app_code" = "302" ] || [ "$app_code" = "307" ] || [ "$app_code" = "308" ]; then
app_first_ts="$now"
echo "[T020] app URL first reachable at +$elapsed s (HTTP $app_code)"
fi
fi
if [ -n "$status_first_ts" ] && [ -n "$app_first_ts" ]; then
if [ "$status_first_ts" -lt "$app_first_ts" ]; then
echo "[T020] PASS: status UI became reachable before app URL"
return 0
fi
echo "[T020] FAIL: app URL became reachable before status UI" >&2
exit 1
fi
sleep "$interval_seconds"
done
}
verify_t021_t023() {
local kubeconfig=""
local node_ip=""
local status_token=""
while [ "$#" -gt 0 ]; do
case "$1" in
--kubeconfig) kubeconfig="$2"; shift 2 ;;
--node-ip) node_ip="$2"; shift 2 ;;
--status-token) status_token="$2"; shift 2 ;;
*) echo "unknown arg: $1" >&2; usage; exit 1 ;;
esac
done
[ -n "$kubeconfig" ] || { echo "--kubeconfig is required" >&2; exit 1; }
[ -n "$node_ip" ] || { echo "--node-ip is required" >&2; exit 1; }
[ -n "$status_token" ] || { echo "--status-token is required" >&2; exit 1; }
require_cmd kubectl
require_cmd curl
local kube=(kubectl --kubeconfig "$kubeconfig")
echo "[T021] Checking LOGIN_READY conditions"
local app_code
app_code="$(http_code "http://${node_ip}:3000")"
case "$app_code" in
200|301|302|307|308) ;;
*) echo "[T021] FAIL: app URL returned unexpected HTTP code $app_code" >&2; exit 1 ;;
esac
local users_count
users_count="$(${kube[@]} -n msp exec db-0 -- sh -c "PGPASSWORD=\$POSTGRES_PASSWORD psql -U postgres -d server -tAc 'select count(*) from users;'" | tr -d '[:space:]')"
if [ -z "$users_count" ] || [ "$users_count" -le 0 ] 2>/dev/null; then
echo "[T021] FAIL: expected seeded users > 0, got '${users_count:-<empty>}'" >&2
exit 1
fi
echo "[T021] PASS: app reachable and seeded users=$users_count"
echo "[T022] Checking background-degraded status classification"
local base="http://${node_ip}:8080"
local cookie_jar; cookie_jar="$(establish_session "$base" "$status_token" "Str0ng!Pass")"
local status_json
status_json="$(curl -fsS -b "$cookie_jar" "${base}/api/status")"
printf '%s\n' "$status_json" | jq -e '.rollup.state == "ready_with_background_issues"' >/dev/null
printf '%s\n' "$status_json" | jq -e '.topBlockers[]? | select(.component == "workflow-worker") | select(.loginBlocking == false)' >/dev/null
printf '%s\n' "$status_json" | jq -e '.topBlockers[]? | select((.reason // "") | test("not found"; "i"))' >/dev/null
echo "[T022] PASS: status API reports non-login-blocking workflow-worker missing-tag blocker"
echo "[T023] Checking Temporal autosetup + service-links hardening"
local temporal_command temporal_enable temporal_ui_enable temporal_ready temporal_ui_ready
temporal_command="$(${kube[@]} -n msp get deploy temporal -o jsonpath='{.spec.template.spec.containers[0].command}' 2>/dev/null || true)"
temporal_enable="$(${kube[@]} -n msp get deploy temporal -o jsonpath='{.spec.template.spec.enableServiceLinks}' 2>/dev/null || true)"
temporal_ui_enable="$(${kube[@]} -n msp get deploy temporal-ui -o jsonpath='{.spec.template.spec.enableServiceLinks}' 2>/dev/null || true)"
temporal_ready="$(${kube[@]} -n msp get deploy temporal -o jsonpath='{.status.readyReplicas}' 2>/dev/null || true)"
temporal_ui_ready="$(${kube[@]} -n msp get deploy temporal-ui -o jsonpath='{.status.readyReplicas}' 2>/dev/null || true)"
printf '%s' "$temporal_command" | grep -q 'autosetup' || { echo "[T023] FAIL: temporal command does not include autosetup" >&2; exit 1; }
[ "$temporal_enable" = "false" ] || { echo "[T023] FAIL: temporal enableServiceLinks expected false, got '$temporal_enable'" >&2; exit 1; }
[ "$temporal_ui_enable" = "false" ] || { echo "[T023] FAIL: temporal-ui enableServiceLinks expected false, got '$temporal_ui_enable'" >&2; exit 1; }
[ -n "$temporal_ready" ] && [ "$temporal_ready" -ge 1 ] 2>/dev/null || { echo "[T023] FAIL: temporal deployment not ready" >&2; exit 1; }
[ -n "$temporal_ui_ready" ] && [ "$temporal_ui_ready" -ge 1 ] 2>/dev/null || { echo "[T023] FAIL: temporal-ui deployment not ready" >&2; exit 1; }
echo "[T023] PASS: Temporal and Temporal UI are ready with autosetup and service links disabled"
}
main() {
if [ "$#" -lt 1 ]; then
usage
exit 1
fi
case "$1" in
-h|--help)
usage
exit 0
;;
monitor)
shift
monitor_t020 "$@"
;;
verify)
shift
verify_t021_t023 "$@"
;;
*)
echo "unknown mode: $1" >&2
usage
exit 1
;;
esac
}
main "$@"