2026-04-09 01:38:06 -03:00
|
|
|
#!/usr/bin/env bash
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
|
|
|
|
|
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
2026-04-20 08:17:18 -03:00
|
|
|
BUILD_DIR="${REPO_DIR}/build"
|
|
|
|
|
COVERAGE_PROFILE="${BUILD_DIR}/coverage.out"
|
|
|
|
|
COVERAGE_PERCENT_FILE="${BUILD_DIR}/coverage-percent.txt"
|
2026-04-09 01:38:06 -03:00
|
|
|
QUALITY_METRICS_ENABLED="${ANANKE_QUALITY_METRICS_ENABLED:-1}"
|
|
|
|
|
QUALITY_METRICS_FILE="${ANANKE_QUALITY_METRICS_FILE:-/var/lib/ananke/quality-gate.prom}"
|
|
|
|
|
QUALITY_STATE_FILE="${ANANKE_QUALITY_STATE_FILE:-/var/lib/ananke/quality-gate.state}"
|
2026-04-10 13:53:42 -03:00
|
|
|
QUALITY_PUSHGATEWAY_ENABLED="${ANANKE_QUALITY_PUSHGATEWAY_ENABLED:-1}"
|
|
|
|
|
QUALITY_PUSHGATEWAY_URL="${ANANKE_QUALITY_PUSHGATEWAY_URL:-${PUSHGATEWAY_URL:-http://platform-quality-gateway.monitoring.svc.cluster.local:9091}}"
|
|
|
|
|
QUALITY_PUSHGATEWAY_JOB="${ANANKE_QUALITY_PUSHGATEWAY_JOB:-platform-quality-ci}"
|
|
|
|
|
QUALITY_PUSHGATEWAY_TRIGGER="${ANANKE_QUALITY_PUSHGATEWAY_TRIGGER:-host}"
|
|
|
|
|
|
|
|
|
|
QUALITY_LAST_OK=0
|
|
|
|
|
QUALITY_LAST_FAILED=0
|
|
|
|
|
QUALITY_LAST_SUCCESS=0
|
|
|
|
|
QUALITY_LAST_RUN_TS=0
|
|
|
|
|
QUALITY_SUCCESS_PERCENT="0.00"
|
2026-04-09 01:38:06 -03:00
|
|
|
|
2026-04-20 10:49:24 -03:00
|
|
|
run_with_retry() {
|
|
|
|
|
local attempts="$1"
|
|
|
|
|
shift
|
|
|
|
|
local try=1
|
|
|
|
|
local delay=3
|
|
|
|
|
local rc=0
|
|
|
|
|
while true; do
|
|
|
|
|
"$@" && return 0
|
|
|
|
|
rc=$?
|
|
|
|
|
if [[ "${try}" -ge "${attempts}" ]]; then
|
|
|
|
|
return "${rc}"
|
|
|
|
|
fi
|
|
|
|
|
echo "[quality] retry ${try}/${attempts} after rc=${rc}: $*" >&2
|
|
|
|
|
sleep "${delay}"
|
|
|
|
|
delay=$((delay * 2))
|
|
|
|
|
try=$((try + 1))
|
|
|
|
|
done
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-09 01:38:06 -03:00
|
|
|
read_quality_counter() {
|
|
|
|
|
local key="$1"
|
|
|
|
|
if [[ ! -f "${QUALITY_STATE_FILE}" ]]; then
|
|
|
|
|
echo 0
|
|
|
|
|
return 0
|
|
|
|
|
fi
|
|
|
|
|
local value
|
|
|
|
|
value="$(awk -F= -v key="${key}" '$1==key {print $2}' "${QUALITY_STATE_FILE}" | tail -n1)"
|
|
|
|
|
if [[ ! "${value}" =~ ^[0-9]+$ ]]; then
|
|
|
|
|
echo 0
|
|
|
|
|
return 0
|
|
|
|
|
fi
|
|
|
|
|
echo "${value}"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
write_quality_metrics() {
|
|
|
|
|
local exit_code="$1"
|
|
|
|
|
|
|
|
|
|
local metrics_dir state_dir
|
|
|
|
|
metrics_dir="$(dirname "${QUALITY_METRICS_FILE}")"
|
|
|
|
|
state_dir="$(dirname "${QUALITY_STATE_FILE}")"
|
2026-04-10 13:53:42 -03:00
|
|
|
mkdir -p "${state_dir}" >/dev/null 2>&1 || return 0
|
|
|
|
|
if [[ "${QUALITY_METRICS_ENABLED}" == "1" ]]; then
|
|
|
|
|
mkdir -p "${metrics_dir}" >/dev/null 2>&1 || return 0
|
|
|
|
|
fi
|
2026-04-09 01:38:06 -03:00
|
|
|
|
|
|
|
|
local ok failed total last_success now success_percent
|
|
|
|
|
ok="$(read_quality_counter ok)"
|
|
|
|
|
failed="$(read_quality_counter failed)"
|
|
|
|
|
last_success=0
|
|
|
|
|
if [[ "${exit_code}" -eq 0 ]]; then
|
|
|
|
|
ok=$((ok + 1))
|
|
|
|
|
last_success=1
|
|
|
|
|
else
|
|
|
|
|
failed=$((failed + 1))
|
|
|
|
|
fi
|
|
|
|
|
total=$((ok + failed))
|
|
|
|
|
now="$(date +%s)"
|
|
|
|
|
success_percent="$(awk -v ok="${ok}" -v total="${total}" 'BEGIN { if (total <= 0) { print "0.00" } else { printf "%.2f", (ok * 100.0) / total } }')"
|
2026-04-10 13:53:42 -03:00
|
|
|
QUALITY_LAST_OK="${ok}"
|
|
|
|
|
QUALITY_LAST_FAILED="${failed}"
|
|
|
|
|
QUALITY_LAST_SUCCESS="${last_success}"
|
|
|
|
|
QUALITY_LAST_RUN_TS="${now}"
|
|
|
|
|
QUALITY_SUCCESS_PERCENT="${success_percent}"
|
|
|
|
|
|
|
|
|
|
local tmp_metrics="" tmp_state
|
|
|
|
|
if [[ "${QUALITY_METRICS_ENABLED}" == "1" ]]; then
|
|
|
|
|
tmp_metrics="$(mktemp "${metrics_dir}/quality-gate.prom.XXXXXX")"
|
|
|
|
|
fi
|
2026-04-09 01:38:06 -03:00
|
|
|
tmp_state="$(mktemp "${state_dir}/quality-gate.state.XXXXXX")"
|
|
|
|
|
|
2026-04-10 13:53:42 -03:00
|
|
|
if [[ "${QUALITY_METRICS_ENABLED}" == "1" ]]; then
|
|
|
|
|
cat > "${tmp_metrics}" <<EOF
|
2026-04-09 01:38:06 -03:00
|
|
|
# HELP ananke_quality_gate_runs_total Total Ananke quality gate runs by status.
|
|
|
|
|
# TYPE ananke_quality_gate_runs_total counter
|
|
|
|
|
ananke_quality_gate_runs_total{suite="ananke",status="ok"} ${ok}
|
|
|
|
|
ananke_quality_gate_runs_total{suite="ananke",status="failed"} ${failed}
|
|
|
|
|
# HELP ananke_quality_gate_last_run_success Whether the latest quality gate run succeeded.
|
|
|
|
|
# TYPE ananke_quality_gate_last_run_success gauge
|
|
|
|
|
ananke_quality_gate_last_run_success{suite="ananke"} ${last_success}
|
|
|
|
|
# HELP ananke_quality_gate_last_run_timestamp_seconds Unix timestamp of the latest quality gate run.
|
|
|
|
|
# TYPE ananke_quality_gate_last_run_timestamp_seconds gauge
|
|
|
|
|
ananke_quality_gate_last_run_timestamp_seconds{suite="ananke"} ${now}
|
|
|
|
|
# HELP ananke_quality_gate_success_percent Running quality gate success percentage for Ananke.
|
|
|
|
|
# TYPE ananke_quality_gate_success_percent gauge
|
|
|
|
|
ananke_quality_gate_success_percent{suite="ananke"} ${success_percent}
|
|
|
|
|
EOF
|
2026-04-10 13:53:42 -03:00
|
|
|
fi
|
2026-04-09 01:38:06 -03:00
|
|
|
|
|
|
|
|
cat > "${tmp_state}" <<EOF
|
|
|
|
|
ok=${ok}
|
|
|
|
|
failed=${failed}
|
|
|
|
|
last_success=${last_success}
|
|
|
|
|
last_run=${now}
|
|
|
|
|
EOF
|
|
|
|
|
|
|
|
|
|
mv -f "${tmp_state}" "${QUALITY_STATE_FILE}"
|
2026-04-10 13:53:42 -03:00
|
|
|
if [[ "${QUALITY_METRICS_ENABLED}" == "1" ]]; then
|
|
|
|
|
mv -f "${tmp_metrics}" "${QUALITY_METRICS_FILE}"
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
publish_quality_metrics() {
|
|
|
|
|
if [[ "${QUALITY_PUSHGATEWAY_ENABLED}" != "1" ]]; then
|
|
|
|
|
return 0
|
|
|
|
|
fi
|
|
|
|
|
if [[ -z "${QUALITY_PUSHGATEWAY_URL}" ]]; then
|
|
|
|
|
return 0
|
|
|
|
|
fi
|
|
|
|
|
if ! command -v python3 >/dev/null 2>&1; then
|
|
|
|
|
echo "[quality] warning: python3 not found; skipping Pushgateway publish" >&2
|
|
|
|
|
return 0
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
if ! python3 "${REPO_DIR}/scripts/publish_quality_metrics.py" \
|
|
|
|
|
--pushgateway-url "${QUALITY_PUSHGATEWAY_URL}" \
|
|
|
|
|
--job-name "${QUALITY_PUSHGATEWAY_JOB}" \
|
|
|
|
|
--suite "ananke" \
|
|
|
|
|
--trigger "${QUALITY_PUSHGATEWAY_TRIGGER}" \
|
|
|
|
|
--local-ok "${QUALITY_LAST_OK}" \
|
|
|
|
|
--local-failed "${QUALITY_LAST_FAILED}"; then
|
|
|
|
|
echo "[quality] warning: Pushgateway publish failed for suite=ananke url=${QUALITY_PUSHGATEWAY_URL}" >&2
|
|
|
|
|
fi
|
2026-04-09 01:38:06 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
quality_gate_finalize() {
|
|
|
|
|
local exit_code="$1"
|
|
|
|
|
set +e
|
|
|
|
|
write_quality_metrics "${exit_code}" || true
|
2026-04-10 13:53:42 -03:00
|
|
|
publish_quality_metrics || true
|
2026-04-09 01:38:06 -03:00
|
|
|
exit "${exit_code}"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
trap 'quality_gate_finalize $?' EXIT
|
|
|
|
|
|
|
|
|
|
cd "${REPO_DIR}"
|
2026-04-20 08:17:18 -03:00
|
|
|
mkdir -p "${BUILD_DIR}"
|
|
|
|
|
rm -f "${COVERAGE_PROFILE}" "${COVERAGE_PERCENT_FILE}"
|
|
|
|
|
printf 'failed\n' > "${BUILD_DIR}/docs-naming.status"
|
|
|
|
|
|
2026-04-22 01:24:25 -03:00
|
|
|
echo "[quality] dependency download"
|
2026-04-20 10:49:24 -03:00
|
|
|
export GOPROXY="${GOPROXY:-https://proxy.golang.org,direct}"
|
|
|
|
|
run_with_retry 4 go mod download
|
2026-04-09 01:38:06 -03:00
|
|
|
|
|
|
|
|
echo "[quality] hygiene: doc contracts"
|
|
|
|
|
cd testing
|
|
|
|
|
go test ./hygiene -run TestHygieneContracts/doc_contract -count=1
|
|
|
|
|
|
|
|
|
|
echo "[quality] hygiene: naming contracts"
|
|
|
|
|
go test ./hygiene -run TestHygieneContracts/naming_contract -count=1
|
2026-04-20 08:17:18 -03:00
|
|
|
printf 'ok\n' > "${BUILD_DIR}/docs-naming.status"
|
2026-04-09 01:38:06 -03:00
|
|
|
|
|
|
|
|
echo "[quality] hygiene: LOC limits"
|
|
|
|
|
go test ./hygiene -run TestHygieneContracts/loc_limit -count=1
|
2026-04-10 16:55:27 -03:00
|
|
|
|
|
|
|
|
echo "[quality] hygiene: split test-module contract"
|
|
|
|
|
go test ./hygiene -run TestHygieneContracts/split_module_contract -count=1
|
2026-04-09 01:38:06 -03:00
|
|
|
cd "${REPO_DIR}"
|
|
|
|
|
|
|
|
|
|
echo "[quality] lint"
|
|
|
|
|
./scripts/lint.sh
|
|
|
|
|
|
2026-04-09 11:46:15 -03:00
|
|
|
echo "[quality] installer template contracts"
|
|
|
|
|
./scripts/verify_install_templates.sh
|
|
|
|
|
|
2026-04-22 01:24:25 -03:00
|
|
|
echo "[quality] unit tests + workspace coverage profile"
|
|
|
|
|
run_with_retry 3 go test -coverprofile="${COVERAGE_PROFILE}" ./...
|
|
|
|
|
coverage_percent="$(go tool cover -func="${COVERAGE_PROFILE}" | awk '/^total:/ {gsub("%","",$3); print $3}')"
|
|
|
|
|
if [[ -z "${coverage_percent}" ]]; then
|
|
|
|
|
coverage_percent="0"
|
|
|
|
|
fi
|
|
|
|
|
printf '%s\n' "${coverage_percent}" > "${COVERAGE_PERCENT_FILE}"
|
|
|
|
|
|
2026-04-09 01:38:06 -03:00
|
|
|
echo "[quality] per-file coverage gate (95%)"
|
|
|
|
|
cd testing
|
|
|
|
|
ANANKE_ENFORCE_COVERAGE=1 ANANKE_PER_FILE_COVERAGE_TARGET=95 go test ./coverage -run TestPerFileCoverageReport -count=1 -v
|