From f898d65983c9af4c5bb3ccd8f1dbdd27617646a4 Mon Sep 17 00:00:00 2001 From: Prathamesh Musale Date: Mon, 11 May 2026 10:50:13 +0000 Subject: [PATCH] host-metrics: entrypoint + offline tests Add telegraf-entrypoint.sh to render telegraf.conf from the template (replacing @@HOST_TAG_BLOCK@@ and @@ZFS_BLOCK@@ markers via awk) and exec telegraf. Add test-telegraf-entrypoint.sh with 8 offline tests (10 assertions) covering marker substitution and required-env validation. Fix run() stderr redirect from >/dev/null 2>&1 to >/dev/null so that entrypoint error output reaches the T6-T8 assertion captures. Co-Authored-By: Claude Sonnet 4.6 --- .../scripts/telegraf-entrypoint.sh | 68 ++++++++++ .../scripts/test-telegraf-entrypoint.sh | 121 ++++++++++++++++++ 2 files changed, 189 insertions(+) create mode 100755 stack_orchestrator/data/config/host-metrics/scripts/telegraf-entrypoint.sh create mode 100755 stack_orchestrator/data/config/host-metrics/scripts/test-telegraf-entrypoint.sh diff --git a/stack_orchestrator/data/config/host-metrics/scripts/telegraf-entrypoint.sh b/stack_orchestrator/data/config/host-metrics/scripts/telegraf-entrypoint.sh new file mode 100755 index 00000000..fbc145f1 --- /dev/null +++ b/stack_orchestrator/data/config/host-metrics/scripts/telegraf-entrypoint.sh @@ -0,0 +1,68 @@ +#!/bin/sh +# host-metrics telegraf-entrypoint.sh +# Render telegraf.conf from telegraf.conf.tpl, then exec telegraf. +# +# Substitutions performed here (by awk): +# @@HOST_TAG_BLOCK@@ -> "[global_tags]\n host = \"$HOST_TAG\"" if set, else empty. +# @@ZFS_BLOCK@@ -> "[[inputs.zfs]]\n poolMetrics = true" if COLLECT_ZFS=true, else empty. +# +# Variables of the form ${VAR} in the template (INFLUXDB_URL, INFLUXDB_DB, +# INFLUXDB_USER, INFLUXDB_PASSWORD, COLLECT_INTERVAL) are resolved by +# telegraf's own env-var substitution at config-load time and are NOT +# touched by this script. +# +# TELEGRAF_CONF_DIR overrides the conf directory for tests; defaults to +# /etc/telegraf which is the standard path inside the official image. + +set -eu + +CONF_DIR="${TELEGRAF_CONF_DIR:-/etc/telegraf}" +TPL="$CONF_DIR/telegraf.conf.tpl" +OUT="$CONF_DIR/telegraf.conf" + +# Fail-fast required env. Empty string counts as missing -- a half-rendered +# conf or a noisy telegraf auth error is worse than a clear startup failure. +for v in INFLUXDB_URL INFLUXDB_USER INFLUXDB_PASSWORD; do + eval val=\${$v:-} + if [ -z "$val" ]; then + echo "FATAL: $v is required but empty" >&2 + exit 1 + fi +done + +# Apply defaults for optional vars. +: "${INFLUXDB_DB:=host_metrics}" +: "${COLLECT_INTERVAL:=10s}" +: "${HOST_TAG:=}" +: "${COLLECT_ZFS:=false}" + +# Build the marker substitutions. Use printf for the newline so the +# rendered block lands on its own line. +if [ -n "$HOST_TAG" ]; then + HOST_TAG_BLOCK=$(printf '[global_tags]\n host = "%s"' "$HOST_TAG") +else + HOST_TAG_BLOCK="" +fi + +if [ "$COLLECT_ZFS" = "true" ]; then + ZFS_BLOCK=$(printf '[[inputs.zfs]]\n poolMetrics = true') +else + ZFS_BLOCK="" +fi + +# Export telegraf hostfs envs so /proc, /sys, and root come from the +# bind-mount under /hostfs (set in compose). +export HOST_PROC=/hostfs/proc +export HOST_SYS=/hostfs/sys +export HOST_MOUNT_PREFIX=/hostfs + +# Render with awk: handles multi-line replacement values cleanly, +# avoids sed's newline-in-replacement portability quirks across BusyBox / +# GNU / BSD sed. +awk -v ht="$HOST_TAG_BLOCK" -v zb="$ZFS_BLOCK" ' + { gsub(/@@HOST_TAG_BLOCK@@/, ht); + gsub(/@@ZFS_BLOCK@@/, zb); + print } +' "$TPL" > "$OUT" + +exec telegraf --config "$OUT" diff --git a/stack_orchestrator/data/config/host-metrics/scripts/test-telegraf-entrypoint.sh b/stack_orchestrator/data/config/host-metrics/scripts/test-telegraf-entrypoint.sh new file mode 100755 index 00000000..caef229e --- /dev/null +++ b/stack_orchestrator/data/config/host-metrics/scripts/test-telegraf-entrypoint.sh @@ -0,0 +1,121 @@ +#!/bin/sh +# Offline tests for host-metrics telegraf-entrypoint.sh. +# Stubs telegraf and envsubst's downstream consumer; no telegraf binary needed. +set -eu + +SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) +ENTRYPOINT="$SCRIPT_DIR/telegraf-entrypoint.sh" + +[ -x "$ENTRYPOINT" ] || { echo "FATAL: $ENTRYPOINT not executable"; exit 2; } + +TMP=$(mktemp -d) +trap 'rm -rf "$TMP"' EXIT +mkdir -p "$TMP/bin" "$TMP/etc/telegraf" + +# Stub telegraf so `exec telegraf` is a no-op. +cat > "$TMP/bin/telegraf" <<'EOF' +#!/bin/sh +exit 0 +EOF +chmod +x "$TMP/bin/telegraf" + +# Minimal template that exercises both markers. +cat > "$TMP/etc/telegraf/telegraf.conf.tpl" <<'EOF' +@@HOST_TAG_BLOCK@@ + +[agent] + interval = "${COLLECT_INTERVAL}" + +[[outputs.influxdb]] + urls = ["${INFLUXDB_URL}"] + +@@ZFS_BLOCK@@ +EOF + +PASS=0 +FAIL=0 + +# run sets required env defaults, then layers caller env on top. +run() { + env PATH="$TMP/bin:$PATH" \ + TELEGRAF_CONF_DIR="$TMP/etc/telegraf" \ + INFLUXDB_URL="${INFLUXDB_URL-http://example/}" \ + INFLUXDB_USER="${INFLUXDB_USER-writer}" \ + INFLUXDB_PASSWORD="${INFLUXDB_PASSWORD-secret}" \ + INFLUXDB_DB="${INFLUXDB_DB-host_metrics}" \ + COLLECT_INTERVAL="${COLLECT_INTERVAL-10s}" \ + HOST_TAG="${HOST_TAG-}" \ + COLLECT_ZFS="${COLLECT_ZFS-false}" \ + sh "$ENTRYPOINT" >/dev/null + rc=$? + [ -f "$TMP/etc/telegraf/telegraf.conf" ] && cat "$TMP/etc/telegraf/telegraf.conf" + return $rc +} + +assert_grep() { + name=$1; actual=$2; pattern=$3 + if printf '%s' "$actual" | grep -qE "$pattern"; then + echo "PASS: $name"; PASS=$((PASS + 1)) + else + echo "FAIL: $name" + echo " expected pattern: $pattern" + echo " actual: $actual" + FAIL=$((FAIL + 1)) + fi +} + +assert_not_grep() { + name=$1; actual=$2; pattern=$3 + if printf '%s' "$actual" | grep -qE "$pattern"; then + echo "FAIL: $name (matched pattern $pattern)"; FAIL=$((FAIL + 1)) + else + echo "PASS: $name"; PASS=$((PASS + 1)) + fi +} + +# T1: HOST_TAG unset -> no [global_tags] block emitted +out=$(HOST_TAG="" run) +assert_not_grep "T1: HOST_TAG empty -> no global_tags" "$out" '^\[global_tags\]' + +# T2: HOST_TAG set -> [global_tags] block with host = "" +out=$(HOST_TAG="validator-1" run) +assert_grep "T2: HOST_TAG set -> [global_tags] block" "$out" '^\[global_tags\]' +assert_grep "T2: HOST_TAG set -> host = \"validator-1\"" "$out" 'host = "validator-1"' + +# T3: COLLECT_ZFS=true -> [[inputs.zfs]] block present +out=$(COLLECT_ZFS="true" run) +assert_grep "T3: COLLECT_ZFS true -> inputs.zfs block" "$out" '\[\[inputs\.zfs\]\]' + +# T4: COLLECT_ZFS=false -> no inputs.zfs block +out=$(COLLECT_ZFS="false" run) +assert_not_grep "T4: COLLECT_ZFS false -> no inputs.zfs" "$out" '\[\[inputs\.zfs\]\]' + +# T5: markers fully removed even when block bodies are empty +out=$(HOST_TAG="" COLLECT_ZFS="false" run) +assert_not_grep "T5: no leftover @@HOST_TAG_BLOCK@@" "$out" '@@HOST_TAG_BLOCK@@' +assert_not_grep "T5: no leftover @@ZFS_BLOCK@@" "$out" '@@ZFS_BLOCK@@' + +# T6: missing INFLUXDB_URL -> exit non-zero, error on stderr +rc=0 +INFLUXDB_URL="" run 2>"$TMP/err" || rc=$? +[ "$rc" -ne 0 ] && grep -q INFLUXDB_URL "$TMP/err" \ + && { echo "PASS: T6: missing INFLUXDB_URL -> error"; PASS=$((PASS + 1)); } \ + || { echo "FAIL: T6: missing INFLUXDB_URL handling (rc=$rc)"; FAIL=$((FAIL + 1)); } + +# T7: missing INFLUXDB_USER -> exit non-zero +rc=0 +INFLUXDB_USER="" run 2>"$TMP/err" || rc=$? +[ "$rc" -ne 0 ] && grep -q INFLUXDB_USER "$TMP/err" \ + && { echo "PASS: T7: missing INFLUXDB_USER -> error"; PASS=$((PASS + 1)); } \ + || { echo "FAIL: T7: missing INFLUXDB_USER handling (rc=$rc)"; FAIL=$((FAIL + 1)); } + +# T8: missing INFLUXDB_PASSWORD -> exit non-zero +rc=0 +INFLUXDB_PASSWORD="" run 2>"$TMP/err" || rc=$? +[ "$rc" -ne 0 ] && grep -q INFLUXDB_PASSWORD "$TMP/err" \ + && { echo "PASS: T8: missing INFLUXDB_PASSWORD -> error"; PASS=$((PASS + 1)); } \ + || { echo "FAIL: T8: missing INFLUXDB_PASSWORD handling (rc=$rc)"; FAIL=$((FAIL + 1)); } + +echo +echo "Results: $PASS passed, $FAIL failed" +[ "$FAIL" = "0" ]