Newer
Older
openstack-caracal-dc-dc / tests / opentofu-validate / run-tests.sh
#!/usr/bin/env bash
# tests/opentofu-validate/run-tests.sh -- offline harness for
# scripts/opentofu-validate.sh.
#
# UPDATED 2026-07-13: a tofu binary IS now present on the jumphost (OpenTofu
# v1.12.3), so the fmt/init/validate path is no longer untestable. T8-T10 exercise
# S3 (module-standalone validation) for real; they SKIP if no tofu is on PATH.
# The old header claimed "no tofu binary is available anywhere this repo has been
# worked in" -- that was true when written and is now false.
# Exit: 0 all pass | 1 any case failed.  ASCII + LF.
set -uo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SCRIPT="$HERE/../../scripts/opentofu-validate.sh"
PASS=0; FAIL=0

run() { # run <want_rc> <regex> <label> -- args...
  local want="$1" rx="$2" label="$3"; shift 3
  local out rc
  out="$(bash "$SCRIPT" "$@" 2>&1)"; rc=$?
  if [[ "$rc" == "$want" ]] && grep -qE "$rx" <<<"$out"; then
    echo "  PASS  $label"; PASS=$((PASS+1))
  else
    echo "  FAIL  $label (rc=$rc want=$want)"; echo "$out" | sed 's/^/        /'; FAIL=$((FAIL+1))
  fi
}

run 1 'no such directory' "T1 nonexistent tofu-dir FAILS (rc 1)" "$HERE/does-not-exist"

if command -v tofu >/dev/null 2>&1; then
  echo "  SKIP  T2 tofu-binary-missing case (a tofu binary IS present in this environment -- can't exercise the missing-binary guard here)"
else
  TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
  run 2 'tofu binary not found' "T2 tofu binary missing FAILS (rc 2)" "$TMP"
fi

# --- S1/S2: libvirt_domain static guards (both halves of the 2026-07-12 boot incident) ---
# --static-only runs the static scans only: no tofu binary, no network.

run 1 'sets memory with no memory_unit' \
  "T3 S1 REJECTS a libvirt_domain with memory and no memory_unit (rc 1)" \
  --static-only "$HERE/fixtures/s1-bad"

run 0 'every libvirt_domain that sets memory also sets memory_unit' \
  "T4 S1+S2 ACCEPT memory+memory_unit+acpi, and a domain with no memory at all (rc 0)" \
  --static-only "$HERE/fixtures/s1-good"

run 1 'does not enable ACPI' \
  "T5 S2 REJECTS a libvirt_domain with no ACPI (rc 1) -- and it PASSES S1, proving S2 is independent" \
  --static-only "$HERE/fixtures/s2-bad"

run 0 'every libvirt_domain enables ACPI' \
  "T6 S1+S2 PASS against the real opentofu/ tree (all VM modules carry memory_unit + acpi)" \
  --static-only "$HERE/../../opentofu"

run 0 'every libvirt_domain that sets memory also sets memory_unit' \
  "T7 back-compat: the old --check-memory-unit flag still works" \
  --check-memory-unit "$HERE/../../opentofu"

# --- S3: the root-only blind spot (DOCFIX-194) -------------------------------
#
# `tofu validate` on the root module NEVER PARSES a module the root does not
# instantiate. On 2026-07-13 this gate printed PASS while node-vm (a bogus
# top-level `format` on libvirt_volume) and netem-link (a destroy provisioner
# referencing var.*) were both broken -- neither is called from root's main.tf.
# Both would have detonated at first use, mid-deploy.
#
# T8's fixture is the load-bearing one: its ROOT IS VALID and does NOT call the
# broken module. A gate that only validates root reports PASS on it. If T8 ever
# goes green-when-it-should-be-red, the blind spot is back.
#
# The fixtures use `terraform_data` (a builtin), so no provider download is
# needed -- these cases stay fast and need no registry access.

if command -v tofu >/dev/null 2>&1; then
  run 1 '\[FAIL\] modules/broken' \
    "T8 S3 REJECTS a broken module that root does NOT call (rc 1) -- THE blind-spot regression test" \
    "$HERE/fixtures/s3-bad"

  run 0 '\[PASS\] modules/fine' \
    "T9 S3 ACCEPTS a correct standalone module (rc 0) -- proves T8 fails on the defect, not on the fixture shape" \
    "$HERE/fixtures/s3-good"

  run 0 '\[PASS\] modules/node-vm' \
    "T10 S3 PASSES against the real opentofu/ tree (all 10 modules validate standalone)" \
    "$HERE/../../opentofu"
else
  echo "  SKIP  T8-T10 S3 module-standalone cases (no tofu binary on PATH)"
fi

# --- D-127 autostart boot matrix -- pin per-instance values against drift ---
# Foundational service VMs + edges autostart; DC containment + node VMs MANUAL.
# Flipping vvr1-dc0/nodes to true (auto-booting the 416 GiB nested container /
# MAAS-controlled nodes on host boot) is exactly the defect this guards.
pin() { # pin <label> <repo-relative-file> <ERE>
  if grep -qE "$3" "$HERE/../../$2" 2>/dev/null; then echo "  PASS  $1"; PASS=$((PASS+1))
  else echo "  FAIL  $1 (missing autostart pin: $3 in $2)"; FAIL=$((FAIL+1)); fi
}
pin "T11 D-127 voffice1 autostart=true"      opentofu/main.tf 'autostart[[:space:]]+= true # D-127: foundational Office1 host'
pin "T12 D-127 office1-opnsense autostart=true" opentofu/main.tf 'autostart[[:space:]]+= true # D-127: foundational edge'
pin "T13 D-127 vvr1-dc0 autostart=FALSE"     opentofu/main.tf 'autostart[[:space:]]+= false[[:space:]]+# D-127: DC containment VM'
pin "T14 D-127 DC edge autostart=true"       opentofu/vr1-dc0-substrate/main.tf 'autostart[[:space:]]+= true # D-127: the DC edge'
pin "T15 D-127 node VMs autostart=FALSE"     opentofu/vr1-dc0-substrate/main.tf 'autostart[[:space:]]+= false # D-127: node VMs'

# T16/T17 -- the fmt check must be scoped to REPO CONTENT. Measured 2026-07-30: this gate read
# ALL GREEN on the jumphost and FAILED on the Office1 headend, and the only flagged file was a
# GITIGNORED per-DC `*.auto.tfvars` that exists solely on the host which runs the applies. So the
# gate was RED ON THE ONLY HOST THAT DEPLOYS -- the same host-dependent-verdict class as the
# LC_ALL manifest sort. Both directions are pinned, because scoping a check is exactly the kind
# of change that can quietly turn it into a no-op.
_probe_tracked="$HERE/../../opentofu/modules/base-image/_fmtprobe.tf"
_probe_ignored="$HERE/../../opentofu/vr1-dc0-substrate/_fmtprobe.auto.tfvars"
printf 'variable   "x"  {\n  type=string\n}\n' > "$_probe_tracked"
# CAPTURE FIRST, then grep. `bash "$SCRIPT" | grep -q ...` is wrong here: the gate correctly
# exits 1 on the planted defect, and `set -o pipefail` makes the PIPELINE inherit that 1 even
# though grep matched -- so a passing assertion reads as a failure. Same strict-bash family as
# the IFS and inherit_errexit traps this repo has hit repeatedly.
_out16="$(bash "$SCRIPT" 2>&1 || true)"
if printf '%s' "$_out16" | grep -q "\[FAIL\] formatting drift in repo-tracked"; then
  PASS=$((PASS+1)); echo "  PASS  T16 a badly-formatted REPO-TRACKED file still FAILS the fmt gate"
else
  FAIL=$((FAIL+1)); echo "  FAIL  T16 scoping the fmt check must not turn it into a no-op"
fi
rm -f "$_probe_tracked"
# A gitignored local tfvars must be SKIPPED, not failed -- and must say so rather than go silent.
printf 'foo   =  "bar"\n' > "$_probe_ignored"
if git -C "$HERE/../.." check-ignore -q "$_probe_ignored" 2>/dev/null; then
  _out="$(bash "$SCRIPT" 2>&1 || true)"
  if printf '%s' "$_out" | grep -q "is gitignored (local input, not repo content)" \
     && ! printf '%s' "$_out" | grep -q "\[FAIL\] formatting drift"; then
    PASS=$((PASS+1)); echo "  PASS  T17 a gitignored local tfvars is SKIPPED with the reason, not failed"
  else
    FAIL=$((FAIL+1)); echo "  FAIL  T17 a gitignored local tfvars must not fail the gate"
  fi
else
  # If the ignore rule ever stops covering per-DC tfvars, this case cannot test what it claims.
  FAIL=$((FAIL+1)); echo "  FAIL  T17 fixture is not gitignored -- the ignore rule for per-DC tfvars changed"
fi
rm -f "$_probe_ignored"

echo; echo "RESULT: PASS=$PASS FAIL=$FAIL"
[[ "$FAIL" -eq 0 ]] && { echo "ALL PASS"; exit 0; } || exit 1