titan-iac/services/monitoring/grafana-user-dedupe-job.yaml

144 lines
5.7 KiB
YAML

# services/monitoring/grafana-user-dedupe-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: grafana-user-dedupe-api-v7
namespace: monitoring
spec:
backoffLimit: 1
template:
metadata:
annotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/agent-pre-populate-only: "true"
vault.hashicorp.com/role: "monitoring"
vault.hashicorp.com/agent-inject-secret-grafana-env.sh: "kv/data/atlas/monitoring/grafana-admin"
vault.hashicorp.com/agent-inject-template-grafana-env.sh: |
{{ with secret "kv/data/atlas/monitoring/grafana-admin" }}
export GRAFANA_USER="{{ index .Data.data "admin-user" }}"
export GRAFANA_PASSWORD="{{ index .Data.data "admin-password" }}"
{{ end }}
spec:
serviceAccountName: monitoring-vault-sync
automountServiceAccountToken: true
restartPolicy: Never
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node-role.kubernetes.io/worker
operator: Exists
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
preference:
matchExpressions:
- key: kubernetes.io/arch
operator: In
values: ["arm64"]
containers:
- name: dedupe
image: python:3.12-slim
command:
- /bin/sh
- -c
args:
- |
set -euo pipefail
for _ in $(seq 1 30); do
if [ -f /vault/secrets/grafana-env.sh ]; then
break
fi
sleep 1
done
if [ ! -f /vault/secrets/grafana-env.sh ]; then
echo "Vault secret not available"
exit 1
fi
. /vault/secrets/grafana-env.sh
grafana_url="${GRAFANA_URL}"
if [ -z "${grafana_url}" ]; then
echo "GRAFANA_URL is required"
exit 1
fi
if [ -z "${GRAFANA_USER}" ] || [ -z "${GRAFANA_PASSWORD}" ]; then
echo "Grafana admin credentials missing"
exit 1
fi
if [ -z "${GRAFANA_DEDUPE_EMAILS}" ]; then
echo "GRAFANA_DEDUPE_EMAILS is required"
exit 1
fi
python - <<'PY'
import base64
import json
import os
import urllib.parse
import urllib.error
import urllib.request
grafana_url = os.environ["GRAFANA_URL"].rstrip("/")
user = os.environ["GRAFANA_USER"]
password = os.environ["GRAFANA_PASSWORD"]
lookups = [e.strip() for e in os.environ["GRAFANA_DEDUPE_EMAILS"].split(",") if e.strip()]
token = base64.b64encode(f"{user}:{password}".encode("utf-8")).decode("utf-8")
headers = {"Authorization": f"Basic {token}"}
def request(method: str, url: str):
req = urllib.request.Request(url, headers=headers, method=method)
try:
with urllib.request.urlopen(req, timeout=10) as resp:
return resp.status, resp.read()
except urllib.error.HTTPError as err:
body = err.read()
return err.code, body
for _ in range(60):
status, _ = request("GET", f"{grafana_url}/api/health")
if status == 200:
break
else:
raise SystemExit("Grafana API did not become ready in time")
for lookup in lookups:
search_url = f"{grafana_url}/api/users/search?query={urllib.parse.quote(lookup)}"
status, body = request("GET", search_url)
if status != 200:
print(f"search failed for {lookup}: status={status} body={body.decode('utf-8', errors='ignore')}")
continue
payload = json.loads(body)
users = payload.get("users", [])
matches = [
user
for user in users
if user.get("email", "").lower() == lookup.lower()
or user.get("login", "").lower() == lookup.lower()
]
if not matches:
print(f"no grafana user found for {lookup}")
continue
for user in matches:
user_id = user.get("id")
if not user_id:
continue
print(f"deleting grafana user {user_id} ({user.get('email')})")
delete_url = f"{grafana_url}/api/admin/users/{user_id}"
del_status, del_body = request("DELETE", delete_url)
if del_status not in (200, 202, 204):
print(
"delete failed for",
user_id,
"status",
del_status,
"body",
del_body.decode("utf-8", errors="ignore"),
)
PY
echo "done"
env:
- name: GRAFANA_URL
value: http://grafana.monitoring.svc.cluster.local
- name: GRAFANA_DEDUPE_EMAILS
value: brad.stein@gmail.com,brad@bstein.dev