#!/usr/bin/env bash # # run.sh — End-to-end IEC 62443-3-3 SL2 compliance test runner # # Orchestrates three phases: # 1. Run ansible-playbook against the inventory (all FR suites) # 2. Find the latest JSON report in reports/ # 3. Render it with Python (or Go, or fallback Python one-liner) # # Usage: # ./run.sh # runs against all hosts # ./run.sh --limit plc-rack01 -K # single host, ask sudo password # ./run.sh --limit localhost -K # test locally # # Environment: # INVENTORY — path to inventory file (default: ./inventory.ini) # LIMIT — ansible --limit pattern (default: all) # # All extra arguments are forwarded to ansible-playbook: # ./run.sh -vvv --limit localhost # # Output: # reports/-.json — raw JSON test data # stdout — formatted report (terminal or md) set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" INVENTORY="${INVENTORY:-$SCRIPT_DIR/inventory.ini}" LIMIT="${LIMIT:-all}" echo "=== IEC 62443-3-3 SL2 Compliance Validation ===" echo "" # ── Phase 1: Run Ansible tests ────────────────────────────────── echo "[1/3] Running compliance tests..." ansible-playbook -i "$INVENTORY" "$SCRIPT_DIR/playbooks/site.yml" \ --limit "$LIMIT" \ "$@" # ── Phase 2: Find latest report ───────────────────────────────── REPORT_DIR="$SCRIPT_DIR/reports" LATEST_JSON=$(ls -t "$REPORT_DIR"/*.json 2>/dev/null | head -1) if [ -z "$LATEST_JSON" ]; then echo "" echo "✗ No JSON report generated. Check Ansible output above." exit 1 fi echo "" echo "[2/3] Latest report: $(basename "$LATEST_JSON")" # ── Phase 3: Render with Go template ──────────────────────────── echo "[3/3] Rendering report with Go template..." echo "" cd "$REPORT_DIR" if ! go run render.go "$LATEST_JSON" report.gohtml 2>/dev/null; then # Fallback: if Go isn't available, just cat the JSON echo "---" echo "(Go not available; showing raw JSON summary)" python3 -c " import json, sys with open('$(basename "$LATEST_JSON")') as f: r = json.load(f) s = r['summary'] print(f'Total: {s[\"total\"]} | Passed: {s[\"passed\"]} | Failed: {s[\"failed\"]} | Rate: {s[\"passed\"]/s[\"total\"]*100:.1f}%') print() for t in r['results']: icon = '✅' if t['passed'] == True else ('❌' if t['passed'] == False else '🔍') print(f' {icon} [{t[\"test_id\"]}] {t[\"description\"]}') print() print('Failures:') for f in r['failures']: print(f' ❌ {f[\"test_id\"]}: {f[\"description\"]} (Severity: {f[\"severity\"]})') print(f' Expected: {f[\"expected\"]}') print(f' Actual: {f[\"actual\"]}') print(f' Fix: {f[\"remediation\"]}') " 2>/dev/null || cat "$LATEST_JSON" fi echo "" echo "=== Done ==="