# logging/grafana-values.yaml
# Grafana — dashboarding and log/metrics exploration UI.
# Deployed in the logging namespace alongside Loki and Promtail.
#
# Secrets provided by ArgoCD SOPS CMP plugin:
#   adminPassword        — mounted from the grafana-admin K8s Secret (admin.existingSecret)
#   OAuth client secret  — mounted from the grafana-oidc K8s Secret (envFromSecret below)

replicas: 1

# RWO PVC (Longhorn) — old pod must fully terminate before the new one can
# mount the volume. Recreate avoids the "two pods fighting over one PVC" failure.
deploymentStrategy:
  type: Recreate

podAnnotations:
  secret.reloader.stakater.com/reload: "grafana-oidc,grafana-admin"

adminUser: admin
admin:
  existingSecret: grafana-admin
  existingSecretKey: admin-password

resources:
  requests:
    cpu: 100m
    memory: 128Mi
  limits:
    cpu: 500m
    memory: 256Mi

# ── Persistence ───────────────────────────────────────────────────────────────
# Stores dashboards saved in the UI, datasource edits, and user sessions.
# Longhorn provides the RWO block volume; 5Gi is ample for dashboard JSON.
persistence:
  enabled: true
  storageClassName: longhorn
  accessModes:
    - ReadWriteOnce
  size: 5Gi

# ── Grafana config ────────────────────────────────────────────────────────────
grafana.ini:
  server:
    root_url: https://grafana.riotpiao.com

  # No anonymous read access — every user must log in via Authentik SSO.
  auth.anonymous:
    enabled: false

  # Explore tab: required for ad-hoc LogQL/PromQL queries against Loki/Prometheus.
  explore:
    enabled: true

  # WAL for the embedded SQLite DB — prevents corruption on ungraceful shutdown.
  database:
    wal: true

  # ── Authentik OIDC (generic OAuth2) ───────────────────────────────────────
  # Grafana v10+ supports OIDC auto-discovery; we wire it manually here because
  # Authentik's discovery endpoint is internal-only (no external DNS for iam.svc).
  # All URLs use the external hostname so CoreDNS rewrites them to
  # authentik-server.iam.svc — this keeps the Host header correct so Authentik
  # doesn't return localhost redirects in its token responses.
  #
  # role_attribute_path: JMESPath expression evaluated against the userinfo
  # response. Members of the 'grafana-admins' Authentik group get Admin role;
  # everyone else gets Viewer. The group name must match exactly what Authentik
  # sends in the 'groups' claim.
  auth.generic_oauth:
    enabled: true
    name: Authentik
    allow_sign_up: true
    client_id: grafana
    scopes: openid email profile
    auth_url: https://authentik.riotpiao.com/application/o/authorize/
    token_url: https://authentik.riotpiao.com/application/o/token/
    api_url: https://authentik.riotpiao.com/application/o/userinfo/
    role_attribute_path: "contains(groups[*], 'grafana-admins') && 'Admin' || 'Viewer'"
    use_pkce: false
    use_refresh_token: false
    skip_org_role_sync: false
    tls_skip_verify_insecure: true   # Authentik uses self-signed cert; verify in prod

# GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET is injected from the grafana-oidc K8s
# Secret (created by k8s/talos-iam/setup_talos_iam.sh). envFromSecret mounts
# every key in that secret as an environment variable — keeps secrets out of
# values files and out of git.
envFromSecret: grafana-oidc

# ── Datasources ───────────────────────────────────────────────────────────────
# Provisioned at install — no manual "Add datasource" step in the UI.
# Loki is the default (log exploration); Prometheus is secondary (metrics).
# Both use in-cluster service DNS — Grafana never leaves the cluster for data.
datasources:
  datasources.yaml:
    apiVersion: 1
    datasources:
      - name: Loki
        type: loki
        uid: loki
        access: proxy
        url: http://loki.logging.svc.cluster.local:3100
        isDefault: true
        version: 1
        editable: true
        jsonData:
          maxLines: 1000
          timeout: 60
      - name: Prometheus
        type: prometheus
        uid: prometheus
        access: proxy
        url: http://prometheus-kube-prometheus-prometheus.monitoring.svc.cluster.local:9090
        isDefault: false
        version: 1
        editable: true
        jsonData:
          timeInterval: 30s
          timeout: 60

# ── Dashboard providers ───────────────────────────────────────────────────────
# Tells Grafana to watch a directory for dashboard JSON files.
# The `dashboards` block below populates that directory via an init container
# that downloads from grafana.com at install time.
dashboardProviders:
  dashboardproviders.yaml:
    apiVersion: 1
    providers:
      - name: default
        orgId: 1
        folder: ""
        type: file
        disableDeletion: false
        editable: true
        options:
          path: /var/lib/grafana/dashboards/default

# ── Pre-loaded dashboards ──────────────────────────────────────────────────────
# Fetched from grafana.com by an init container at helm-install time and baked
# into ConfigMaps. gnetId + revision pin the exact dashboard version so an
# upstream change on grafana.com doesn't silently alter what's deployed.
dashboards:
  default:
    node-exporter-full:
      gnetId: 1860
      revision: 37
      datasource: Prometheus
    kubernetes-cluster:
      gnetId: 7249
      revision: 1
      datasource: Prometheus

# Allow scheduling on the control-plane node (talos-cp-1 carries NoSchedule taint).
tolerations:
  - key: node-role.kubernetes.io/control-plane
    operator: Exists
    effect: NoSchedule

# Pin to az-a (talos-cp-1) — the only node running Longhorn. Without this the
# scheduler may place grafana on cp-2/cp-3, where its Longhorn PVC cannot attach
# ("CSINode does not contain driver driver.longhorn.io").
affinity:
  nodeAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
      nodeSelectorTerms:
        - matchExpressions:
            - key: topology.kubernetes.io/zone
              operator: In
              values: [az-a]

# ClusterIP only — access via ingress (grafana.riotpiao.com) or port-forward.
service:
  type: ClusterIP
  port: 80

# Ingress managed by k8s/ingress/ingress.yaml — not the chart's built-in ingress.
ingress:
  enabled: false

serviceMonitor:
  enabled: false

# ── Dashboard-as-code (sidecar discovery) ──────────────────────────────────────
# Additive to the gnetId-download mechanism above. The sidecar watches for
# ConfigMaps labeled grafana_dashboard=1 in ANY namespace and loads them live —
# no Grafana restart needed when a new dashboard ConfigMap is applied.
sidecar:
  dashboards:
    enabled: true
    label: grafana_dashboard
    labelValue: "1"
    folder: /var/lib/grafana/dashboards/custom
    folderAnnotation: grafana_folder
    provider:
      name: custom
      folder: "Homelab"
      disableDelete: false
      foldersFromFilesStructure: true
    searchNamespace: ALL
