Compare commits
No commits in common. "6a1a8d639be616bcef75eff8c9618e53a3d4785f" and "fa54989deecf6fd73c0868d907d615af8317cfb7" have entirely different histories.
6a1a8d639b
...
fa54989dee
7
jeannie
7
jeannie
|
|
@ -4344,7 +4344,7 @@ cluster_worker_targets() {
|
|||
if [[ -s "${var_file}" ]] && command -v python3 >/dev/null 2>&1; then
|
||||
while IFS= read -r target; do
|
||||
[[ -n "${target}" ]] || continue
|
||||
if [[ ! " ${CLUSTER_WORKER_TARGETS[*]-} " =~ [[:space:]]${target}[[:space:]] ]]; then
|
||||
if [[ ! " ${CLUSTER_WORKER_TARGETS[*]} " =~ [[:space:]]${target}[[:space:]] ]]; then
|
||||
CLUSTER_WORKER_TARGETS+=("${target}")
|
||||
fi
|
||||
done < <(python3 - "${var_file}" <<'PY'
|
||||
|
|
@ -6585,11 +6585,6 @@ Destructive
|
|||
EOF
|
||||
}
|
||||
|
||||
if [[ "${JEANNIE_LIBRARY_MODE:-false}" == "true" ]]; then
|
||||
# shellcheck disable=SC2317
|
||||
return 0 2>/dev/null || exit 0
|
||||
fi
|
||||
|
||||
case "${1:-}" in
|
||||
"" | help | -h | --help)
|
||||
print_usage
|
||||
|
|
|
|||
|
|
@ -87,14 +87,9 @@ validate_security_static() {
|
|||
fi
|
||||
}
|
||||
|
||||
validate_unit_tests() {
|
||||
"${REPO_ROOT}/tests/jeannie-unit"
|
||||
}
|
||||
|
||||
main() {
|
||||
cd "${REPO_ROOT}"
|
||||
run_step "Inventory" "${REPO_ROOT}/scripts/validate-homelab-inventory" "${HOMELAB_INVENTORY_FILE:-${REPO_ROOT}/homelab.yml}"
|
||||
run_step "Unit tests" validate_unit_tests
|
||||
run_step "Generated docs" "${REPO_ROOT}/scripts/render-docs" --check
|
||||
run_step "Bash syntax" validate_bash_syntax
|
||||
run_step "Shellcheck" validate_shellcheck
|
||||
|
|
|
|||
|
|
@ -1,289 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
LAB_DIR="${REPO_ROOT}/.lab"
|
||||
TMP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/jeannie-unit.XXXXXX")"
|
||||
BACKUP_DIR="${TMP_DIR}/backup"
|
||||
FAILURES=0
|
||||
LAB_STATE_FILES="pimox-workers.tsv cluster-workers.auto.tfvars.json manual-workers.tsv"
|
||||
|
||||
mkdir -p "${BACKUP_DIR}" "${LAB_DIR}" "${TMP_DIR}/bin"
|
||||
|
||||
cleanup() {
|
||||
for file in ${LAB_STATE_FILES}; do
|
||||
rm -f "${LAB_DIR}/${file}"
|
||||
if [[ -f "${BACKUP_DIR}/${file}" ]]; then
|
||||
cp "${BACKUP_DIR}/${file}" "${LAB_DIR}/${file}"
|
||||
fi
|
||||
done
|
||||
rm -rf "${TMP_DIR}"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
for file in ${LAB_STATE_FILES}; do
|
||||
if [[ -f "${LAB_DIR}/${file}" ]]; then
|
||||
cp "${LAB_DIR}/${file}" "${BACKUP_DIR}/${file}"
|
||||
fi
|
||||
done
|
||||
|
||||
cat >"${TMP_DIR}/bin/tofu" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
exit 1
|
||||
EOF
|
||||
chmod +x "${TMP_DIR}/bin/tofu"
|
||||
export PATH="${TMP_DIR}/bin:${PATH}"
|
||||
|
||||
cat >"${LAB_DIR}/pimox-workers.tsv" <<'EOF'
|
||||
pimox01 192.168.100.66 jv pimox-worker-01 /home/jv/.ssh/id_ed25519
|
||||
pimox02 192.168.100.67 jv pimox-worker-02 /home/jv/.ssh/id_ed25519
|
||||
pimox03 192.168.100.76 jv pimox-worker-03 /home/jv/.ssh/id_ed25519
|
||||
EOF
|
||||
|
||||
cat >"${LAB_DIR}/manual-workers.tsv" <<'EOF'
|
||||
manual01 192.168.100.99 jv manual-worker-01 /home/jv/.ssh/id_ed25519
|
||||
EOF
|
||||
|
||||
cat >"${LAB_DIR}/cluster-workers.auto.tfvars.json" <<'EOF'
|
||||
{
|
||||
"worker_nodes": {
|
||||
"pimox01": {
|
||||
"host": "192.168.100.66",
|
||||
"user": "jv",
|
||||
"node_name": "pimox-worker-01",
|
||||
"ssh_private_key_path": "/home/jv/.ssh/id_ed25519"
|
||||
},
|
||||
"pimox02": {
|
||||
"host": "192.168.100.67",
|
||||
"user": "jv",
|
||||
"node_name": "pimox-worker-02",
|
||||
"ssh_private_key_path": "/home/jv/.ssh/id_ed25519"
|
||||
},
|
||||
"pimox03": {
|
||||
"host": "192.168.100.76",
|
||||
"user": "jv",
|
||||
"node_name": "pimox-worker-03",
|
||||
"ssh_private_key_path": "/home/jv/.ssh/id_ed25519"
|
||||
},
|
||||
"manual01": {
|
||||
"host": "192.168.100.99",
|
||||
"user": "jv",
|
||||
"node_name": "manual-worker-01",
|
||||
"ssh_private_key_path": "/home/jv/.ssh/id_ed25519"
|
||||
}
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
export JEANNIE_LIBRARY_MODE=true
|
||||
# shellcheck disable=SC1091
|
||||
source "${REPO_ROOT}/jeannie"
|
||||
|
||||
pass() {
|
||||
printf 'ok %s\n' "$1"
|
||||
}
|
||||
|
||||
fail() {
|
||||
printf 'fail %s\n' "$1"
|
||||
FAILURES=$((FAILURES + 1))
|
||||
}
|
||||
|
||||
assert_eq() {
|
||||
local label="$1"
|
||||
local expected="$2"
|
||||
local actual="$3"
|
||||
|
||||
if [[ "${actual}" == "${expected}" ]]; then
|
||||
pass "${label}"
|
||||
else
|
||||
fail "${label}: expected '${expected}', got '${actual}'"
|
||||
fi
|
||||
}
|
||||
|
||||
assert_contains() {
|
||||
local label="$1"
|
||||
local needle="$2"
|
||||
local haystack="$3"
|
||||
|
||||
if grep -Fq "${needle}" <<<"${haystack}"; then
|
||||
pass "${label}"
|
||||
else
|
||||
fail "${label}: expected output to contain '${needle}'"
|
||||
fi
|
||||
}
|
||||
|
||||
assert_command_success() {
|
||||
local label="$1"
|
||||
shift
|
||||
|
||||
if "$@" >/dev/null 2>&1; then
|
||||
pass "${label}"
|
||||
else
|
||||
fail "${label}: command failed"
|
||||
fi
|
||||
}
|
||||
|
||||
assert_command_failure_contains() {
|
||||
local label="$1"
|
||||
local needle="$2"
|
||||
shift 2
|
||||
local output
|
||||
local status
|
||||
|
||||
set +e
|
||||
output="$("$@" 2>&1)"
|
||||
status=$?
|
||||
set -e
|
||||
if [[ "${status}" -eq 0 ]]; then
|
||||
fail "${label}: command unexpectedly passed"
|
||||
return 0
|
||||
fi
|
||||
assert_contains "${label}" "${needle}" "${output}"
|
||||
}
|
||||
|
||||
test_pimox_worker_count_detects_generated_topology() {
|
||||
unset LAB_PIMOX_WORKER_COUNT
|
||||
assert_eq "detects 3 generated Pimox workers" "3" "$(pimox_worker_count_default)"
|
||||
}
|
||||
|
||||
test_configured_worker_count_cannot_hide_existing_workers() {
|
||||
LAB_PIMOX_WORKER_COUNT=1
|
||||
assert_eq "effective count keeps detected 3 workers" "3" "$(pimox_worker_count_effective)"
|
||||
unset LAB_PIMOX_WORKER_COUNT
|
||||
}
|
||||
|
||||
test_configured_worker_count_can_expand_target_set() {
|
||||
LAB_PIMOX_WORKER_COUNT=4
|
||||
assert_eq "effective count allows explicit expansion" "4" "$(pimox_worker_count_effective)"
|
||||
unset LAB_PIMOX_WORKER_COUNT
|
||||
}
|
||||
|
||||
test_cluster_worker_targets_include_pimox_and_manual_workers() {
|
||||
CLUSTER_WORKER_TARGETS=()
|
||||
cluster_worker_targets
|
||||
local targets
|
||||
targets="$(printf '%s\n' "${CLUSTER_WORKER_TARGETS[@]}" | sort | paste -sd ',' -)"
|
||||
assert_eq "cluster targets include generated Pimox workers" "jv@192.168.100.66,jv@192.168.100.67,jv@192.168.100.76,jv@192.168.100.99" "${targets}"
|
||||
}
|
||||
|
||||
test_prepare_cluster_worker_var_file_merges_manual_and_pimox_workers() {
|
||||
unset LAB_INCLUDE_RASPBERRY_WORKER
|
||||
LAB_PIMOX_WORKER_NODE_LABELS_JSON='{"homelab.dev/node-role":"app","homelab.dev/storage":"ssd"}'
|
||||
LAB_MANUAL_WORKER_NODE_LABELS_JSON='{"homelab.dev/node-role":"manual","homelab.dev/storage":"custom"}'
|
||||
prepare_cluster_worker_var_file false
|
||||
python3 - "${LAB_DIR}/cluster-workers.auto.tfvars.json" <<'PY'
|
||||
import json
|
||||
import sys
|
||||
|
||||
with open(sys.argv[1], encoding="utf-8") as handle:
|
||||
document = json.load(handle)
|
||||
nodes = document["worker_nodes"]
|
||||
labels = document["worker_node_labels"]
|
||||
expected = {"pimox01", "pimox02", "pimox03", "manual01"}
|
||||
if set(nodes) != expected:
|
||||
raise SystemExit(f"worker keys mismatch: {sorted(nodes)}")
|
||||
if nodes["manual01"]["node_name"] != "manual-worker-01":
|
||||
raise SystemExit("manual worker node name was not preserved")
|
||||
if labels["pimox01"]["homelab.dev/storage"] != "ssd":
|
||||
raise SystemExit("pimox labels were not applied")
|
||||
if labels["manual01"]["homelab.dev/node-role"] != "manual":
|
||||
raise SystemExit("manual labels were not applied")
|
||||
PY
|
||||
pass "worker var file merges manual and Pimox workers"
|
||||
unset LAB_PIMOX_WORKER_NODE_LABELS_JSON LAB_MANUAL_WORKER_NODE_LABELS_JSON LAB_CLUSTER_VAR_FILE LAB_INCLUDE_RASPBERRY_WORKER
|
||||
}
|
||||
|
||||
test_prepare_cluster_worker_var_file_can_include_raspberry_worker() {
|
||||
LAB_INCLUDE_RASPBERRY_WORKER=true
|
||||
LAB_RASPBERRY_HOST=192.168.100.89
|
||||
LAB_RASPBERRY_NODE_NAME=raspberry
|
||||
prepare_cluster_worker_var_file false
|
||||
python3 - "${LAB_DIR}/cluster-workers.auto.tfvars.json" <<'PY'
|
||||
import json
|
||||
import sys
|
||||
|
||||
with open(sys.argv[1], encoding="utf-8") as handle:
|
||||
document = json.load(handle)
|
||||
node = document["worker_nodes"].get("raspberrypi")
|
||||
if not node:
|
||||
raise SystemExit("raspberrypi worker was not included")
|
||||
if node["host"] != "192.168.100.89" or node["node_name"] != "raspberry":
|
||||
raise SystemExit(f"unexpected raspberry worker: {node}")
|
||||
PY
|
||||
pass "worker var file can include Raspberry Pi worker"
|
||||
unset LAB_INCLUDE_RASPBERRY_WORKER LAB_RASPBERRY_HOST LAB_RASPBERRY_NODE_NAME LAB_CLUSTER_VAR_FILE
|
||||
}
|
||||
|
||||
test_report_renderer_hides_ok_links() {
|
||||
local output
|
||||
output="$(
|
||||
printf 'ok\tArea\tHealthy\tok\tdetail\tfix\tgraph\tquery\nwarn\tArea\tWarning\tneeds attention\tdetail\tfix\tgraph\tquery\n' |
|
||||
"${REPO_ROOT}/scripts/report-render" --title "Unit Report" --only all
|
||||
)"
|
||||
if grep -A4 'Healthy' <<<"${output}" | grep -Eq 'graph:|query:'; then
|
||||
fail "report renderer hides graph/query for ok rows"
|
||||
else
|
||||
pass "report renderer hides graph/query for ok rows"
|
||||
fi
|
||||
}
|
||||
|
||||
test_report_renderer_problems_only_hides_ok_rows() {
|
||||
local output
|
||||
output="$(
|
||||
printf 'ok\tArea\tHealthy\tok\t\t\t\t\nwarn\tArea\tWarning\tneeds attention\tdetail\tfix\tgraph\tquery\n' |
|
||||
"${REPO_ROOT}/scripts/report-render" --title "Unit Report" --only problems
|
||||
)"
|
||||
if grep -Fq 'Healthy' <<<"${output}"; then
|
||||
fail "report renderer problems-only hides ok rows"
|
||||
else
|
||||
pass "report renderer problems-only hides ok rows"
|
||||
fi
|
||||
assert_contains "report renderer problems-only keeps warning" "Warning" "${output}"
|
||||
assert_contains "report renderer problems-only prints next fix" "Next Fix" "${output}"
|
||||
}
|
||||
|
||||
test_report_renderer_empty_problems_message() {
|
||||
local output
|
||||
output="$(
|
||||
printf 'ok\tArea\tHealthy\tok\t\t\t\t\n' |
|
||||
"${REPO_ROOT}/scripts/report-render" --title "Unit Report" --only problems
|
||||
)"
|
||||
assert_contains "report renderer empty problems message" "No failing or warning items." "${output}"
|
||||
}
|
||||
|
||||
test_inventory_validator_accepts_current_inventory() {
|
||||
assert_command_success "inventory validator accepts repo inventory" "${REPO_ROOT}/scripts/validate-homelab-inventory" "${REPO_ROOT}/homelab.yml"
|
||||
}
|
||||
|
||||
test_inventory_validator_rejects_ip_drift() {
|
||||
local inventory="${TMP_DIR}/invalid-homelab.yml"
|
||||
python3 - "${REPO_ROOT}/homelab.yml" "${inventory}" <<'PY'
|
||||
import sys
|
||||
|
||||
source, target = sys.argv[1:3]
|
||||
text = open(source, encoding="utf-8").read()
|
||||
text = text.replace("lan_ip: 192.168.100.73", "lan_ip: not-an-ip", 1)
|
||||
with open(target, "w", encoding="utf-8") as handle:
|
||||
handle.write(text)
|
||||
PY
|
||||
assert_command_failure_contains "inventory validator rejects invalid Debian LAN IP" "hosts.debian.lan_ip is not a valid IP" "${REPO_ROOT}/scripts/validate-homelab-inventory" "${inventory}"
|
||||
}
|
||||
|
||||
test_pimox_worker_count_detects_generated_topology
|
||||
test_configured_worker_count_cannot_hide_existing_workers
|
||||
test_configured_worker_count_can_expand_target_set
|
||||
test_cluster_worker_targets_include_pimox_and_manual_workers
|
||||
test_prepare_cluster_worker_var_file_merges_manual_and_pimox_workers
|
||||
test_prepare_cluster_worker_var_file_can_include_raspberry_worker
|
||||
test_report_renderer_hides_ok_links
|
||||
test_report_renderer_problems_only_hides_ok_rows
|
||||
test_report_renderer_empty_problems_message
|
||||
test_inventory_validator_accepts_current_inventory
|
||||
test_inventory_validator_rejects_ip_drift
|
||||
|
||||
if ((FAILURES > 0)); then
|
||||
printf '\n%s unit test(s) failed.\n' "${FAILURES}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf '\nAll jeannie unit tests passed.\n'
|
||||
Loading…
Reference in New Issue