[#801] CMDB drift reconcile (C3): nightly report-mode script + timer
ci / audit (push) Successful in 31s
ci / audit (push) Successful in 31s
reconcile-report.sh diffs seed/systems.csv (canonical seed) vs live GLPI computers; report-only, never writes GLPI. First run: 77 seed CIs all present, 3 GLPI ghosts (tsys-cloudron, ultix-field, ultix-highside). Timer ukrrs-glpi-reconcile.timer 04:17 CT on ultix-streaming; exit 3 on drift = alert surface. --from-inventory mode experimental (s1-s4 parser). https://projects.knownelement.com/issues/801
This commit is contained in:
Executable
+87
@@ -0,0 +1,87 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# reconcile-report.sh — nightly CMDB drift report [#801 C3]
|
||||||
|
#
|
||||||
|
# Compares the CANONICAL seed CSV (seed/systems.csv, the rows that created
|
||||||
|
# the GLPI computers) against the live GLPI CMDB and reports drift BOTH
|
||||||
|
# ways. REPORT ONLY — this script never writes to GLPI. Apply-mode stays
|
||||||
|
# manual (glpi-change.sh / to-glpi.sh) before CRs.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# reconcile-report.sh # report to stdout
|
||||||
|
# reconcile-report.sh --out FILE # also write the report
|
||||||
|
# reconcile-report.sh --from-inventory # re-derive seed from t/307 post
|
||||||
|
# # (EXPERIMENTAL: parser covers
|
||||||
|
# # sections s1-s4 only today)
|
||||||
|
# Env: discourse.env (DISCOURSE_*, only for --from-inventory), mglpi.env.
|
||||||
|
# Exit: 0 no drift | 3 drift found | 1 error.
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
POST_ID="${INV307_POST_ID:-385}"
|
||||||
|
FROM_INVENTORY=0
|
||||||
|
OUT=""
|
||||||
|
while [ $# -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
--out) OUT="$2"; shift 2 ;;
|
||||||
|
--from-inventory) FROM_INVENTORY=1; shift ;;
|
||||||
|
*) echo "unknown arg: $1" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
[ -f "$HOME/.creds/discourse.env" ] && . "$HOME/.creds/discourse.env"
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
[ -f "$HOME/.creds/mglpi.env" ] && . "$HOME/.creds/mglpi.env"
|
||||||
|
: "${DISCOURSE_URL:?}"; : "${DISCOURSE_API_KEY:?}"; : "${DISCOURSE_API_USERNAME:?}"
|
||||||
|
: "${MGLPI_URL:?}"; : "${MGLPI_APP_TOKEN:?}"; : "${MGLPI_USER_TOKEN:?}"
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
TMP=$(mktemp -d); trap 'rm -rf "$TMP"' EXIT
|
||||||
|
|
||||||
|
# 1. inventory side: canonical seed CSV (or re-derived from t/307)
|
||||||
|
if [ "$FROM_INVENTORY" = 1 ]; then
|
||||||
|
raw=$(curl -sS --max-time 30 -H "Api-Key: ${DISCOURSE_API_KEY}" \
|
||||||
|
-H "Api-Username: ${DISCOURSE_API_USERNAME}" "${DISCOURSE_URL}/posts/${POST_ID}.json" \
|
||||||
|
| jq -r '.raw // empty')
|
||||||
|
[ -n "$raw" ] || { echo "FAIL: could not fetch t/307 post ${POST_ID}" >&2; exit 1; }
|
||||||
|
printf '%s' "$raw" > "$TMP/inv.md"
|
||||||
|
bash "$SCRIPT_DIR/../seed/from-inventory.sh" "$TMP/inv.md" > "$TMP/systems.csv" || exit 1
|
||||||
|
else
|
||||||
|
cp "$SCRIPT_DIR/../seed/systems.csv" "$TMP/systems.csv"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2. GLPI computers (read-only)
|
||||||
|
S=$(curl -sS --max-time 30 -H "Content-Type: application/json" \
|
||||||
|
-H "App-Token: ${MGLPI_APP_TOKEN}" -H "Authorization: user_token ${MGLPI_USER_TOKEN}" \
|
||||||
|
"${MGLPI_URL}/initSession" | jq -r '.session_token // empty')
|
||||||
|
[ -n "$S" ] || { echo "FAIL: GLPI session init" >&2; exit 1; }
|
||||||
|
curl -sS --max-time 60 -H "App-Token: ${MGLPI_APP_TOKEN}" -H "Session-Token: $S" \
|
||||||
|
"${MGLPI_URL}/Computer?range=0-499&is_deleted=0" > "$TMP/glpi.json"
|
||||||
|
curl -sS --max-time 30 -X DELETE -H "App-Token: ${MGLPI_APP_TOKEN}" \
|
||||||
|
-H "Session-Token: $S" "${MGLPI_URL}/killSession" >/dev/null
|
||||||
|
|
||||||
|
# 3. name sets (lowercased)
|
||||||
|
jq -r '.[] | select(type == "object") | (.name // "") | ascii_downcase' "$TMP/glpi.json" \
|
||||||
|
| sort -u > "$TMP/glpi.names"
|
||||||
|
# ci_name is the 2nd field: rows are `category,"ci_name",...` — capture
|
||||||
|
# between the first `,"` and the closing `"` (names never contain quotes)
|
||||||
|
tail -n +2 "$TMP/systems.csv" | sed -n 's/^[^,]*,"\([^"]*\)".*/\1/p' \
|
||||||
|
| tr '[:upper:]' '[:lower:]' | sort -u > "$TMP/inv.names"
|
||||||
|
|
||||||
|
comm -23 "$TMP/inv.names" "$TMP/glpi.names" > "$TMP/missing_in_glpi"
|
||||||
|
comm -13 "$TMP/inv.names" "$TMP/glpi.names" > "$TMP/ghost_in_glpi"
|
||||||
|
|
||||||
|
miss=$(grep -c . "$TMP/missing_in_glpi" || true)
|
||||||
|
ghost=$(grep -c . "$TMP/ghost_in_glpi" || true)
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "# CMDB drift report $(date -Is) (inventory t/307 post $POST_ID vs GLPI computers)"
|
||||||
|
echo "inventory CIs: $(grep -c . "$TMP/inv.names") | GLPI computers: $(grep -c . "$TMP/glpi.names")"
|
||||||
|
echo
|
||||||
|
echo "## in inventory, MISSING in GLPI ($miss)"
|
||||||
|
cat "$TMP/missing_in_glpi"
|
||||||
|
echo
|
||||||
|
echo "## in GLPI, not in inventory — GHOSTS ($ghost)"
|
||||||
|
cat "$TMP/ghost_in_glpi"
|
||||||
|
} | tee "${OUT:-/dev/null}"
|
||||||
|
|
||||||
|
[ "$miss" -eq 0 ] && [ "$ghost" -eq 0 ] && exit 0 || exit 3
|
||||||
Reference in New Issue
Block a user