2026-01-28 01:48:32 -03:00
|
|
|
# services/monitoring/oneoffs/grafana-user-dedupe-job.yaml
|
|
|
|
|
# One-off job for monitoring/grafana-user-dedupe-api-v7.
|
|
|
|
|
# Purpose: grafana user dedupe api v7 (see container args/env in this file).
|
|
|
|
|
# Run by setting spec.suspend to false, reconcile, then set it back to true.
|
|
|
|
|
# Safe to delete the finished Job/pod; it should not run continuously.
|
2026-01-21 12:08:23 -03:00
|
|
|
apiVersion: batch/v1
|
|
|
|
|
kind: Job
|
|
|
|
|
metadata:
|
2026-01-21 12:31:54 -03:00
|
|
|
name: grafana-user-dedupe-api-v7
|
2026-01-21 12:08:23 -03:00
|
|
|
namespace: monitoring
|
|
|
|
|
spec:
|
2026-01-28 01:48:32 -03:00
|
|
|
suspend: true
|
2026-01-21 12:08:23 -03:00
|
|
|
backoffLimit: 1
|
|
|
|
|
template:
|
2026-01-21 12:18:57 -03:00
|
|
|
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 }}
|
2026-01-21 12:08:23 -03:00
|
|
|
spec:
|
2026-01-21 12:16:26 -03:00
|
|
|
serviceAccountName: monitoring-vault-sync
|
|
|
|
|
automountServiceAccountToken: true
|
2026-01-21 12:08:23 -03:00
|
|
|
restartPolicy: Never
|
2026-01-21 12:31:54 -03:00
|
|
|
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"]
|
2026-01-21 12:08:23 -03:00
|
|
|
containers:
|
|
|
|
|
- name: dedupe
|
2026-01-21 12:15:03 -03:00
|
|
|
image: python:3.12-slim
|
2026-01-21 12:08:23 -03:00
|
|
|
command:
|
|
|
|
|
- /bin/sh
|
|
|
|
|
- -c
|
|
|
|
|
args:
|
|
|
|
|
- |
|
|
|
|
|
set -euo pipefail
|
2026-01-21 12:25:53 -03:00
|
|
|
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
|
2026-01-21 12:11:28 -03:00
|
|
|
. /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"
|
2026-01-21 12:08:23 -03:00
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
if [ -z "${GRAFANA_DEDUPE_EMAILS}" ]; then
|
|
|
|
|
echo "GRAFANA_DEDUPE_EMAILS is required"
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
2026-01-21 12:15:03 -03:00
|
|
|
python - <<'PY'
|
|
|
|
|
import base64
|
|
|
|
|
import json
|
|
|
|
|
import os
|
|
|
|
|
import urllib.parse
|
2026-01-21 12:30:08 -03:00
|
|
|
import urllib.error
|
2026-01-21 12:15:03 -03:00
|
|
|
import urllib.request
|
|
|
|
|
|
|
|
|
|
grafana_url = os.environ["GRAFANA_URL"].rstrip("/")
|
|
|
|
|
user = os.environ["GRAFANA_USER"]
|
|
|
|
|
password = os.environ["GRAFANA_PASSWORD"]
|
2026-01-21 12:30:08 -03:00
|
|
|
lookups = [e.strip() for e in os.environ["GRAFANA_DEDUPE_EMAILS"].split(",") if e.strip()]
|
2026-01-21 12:15:03 -03:00
|
|
|
|
|
|
|
|
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:
|
2026-01-21 12:30:08 -03:00
|
|
|
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')}")
|
2026-01-21 12:15:03 -03:00
|
|
|
continue
|
2026-01-21 12:30:08 -03:00
|
|
|
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}")
|
2026-01-21 12:15:03 -03:00
|
|
|
continue
|
2026-01-21 12:30:08 -03:00
|
|
|
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"),
|
|
|
|
|
)
|
2026-01-21 12:15:03 -03:00
|
|
|
PY
|
2026-01-21 12:08:23 -03:00
|
|
|
echo "done"
|
|
|
|
|
env:
|
2026-01-21 12:11:28 -03:00
|
|
|
- name: GRAFANA_URL
|
2026-01-21 12:31:54 -03:00
|
|
|
value: http://grafana.monitoring.svc.cluster.local
|
2026-01-21 12:08:23 -03:00
|
|
|
- name: GRAFANA_DEDUPE_EMAILS
|
|
|
|
|
value: brad.stein@gmail.com,brad@bstein.dev
|