Initial Grafana role, split out of ansible-role-eom

Moves the Grafana Helm deployment and LDAP template out of
ericomeehan.eom into its own role, and:

- maps cn=DevOps Members,ou=Teams to Editor in the DevOps org (org 2),
  after DevOps Owners so owners keep Admin
- pins chart 11.4.2 from Bitnami's OCI registry (the HTTP index no
  longer carries it) so redeploys don't upgrade to Grafana 12
- pins the bitnamilegacy/grafana image the cluster is already running

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-06 14:00:23 -05:00
co-authored by Claude Opus 5.5
commit 41943f1174
9 changed files with 243 additions and 0 deletions
+59
View File
@@ -0,0 +1,59 @@
ansible-role-grafana
====================
Deploys Grafana to the eom.dev Kubernetes cluster with the Bitnami Helm chart,
with LDAP sign-in mapped to Grafana organizations.
Requirements
------------
- `kubernetes.core` collection, Helm on the target host
- OpenLDAP at `openldap.openldap.svc.cluster.local`
- Prometheus and Alertmanager in the `prometheus` namespace
Role Variables
--------------
Required (from `secrets.yaml`):
- `grafana_admin_password`
- `openldap_readonly_password`
Defaults (`defaults/main.yml`):
| Variable | Default |
|---|---|
| `grafana_chart_ref` | `oci://registry-1.docker.io/bitnamicharts/grafana` |
| `grafana_chart_version` | `11.4.2` |
| `grafana_image_repository` | `bitnamilegacy/grafana` |
| `grafana_image_tag` | `11.4.0-debian-12-r0` |
| `grafana_domain` | `grafana.eom.dev` |
LDAP group mappings
-------------------
Defined in `templates/ldap.toml.j2`. Grafana uses the first mapping that
matches per organization, so owner groups must come before member groups.
| LDAP group | Org | Role |
|---|---|---|
| Grafana Administrators | 1 (Main) | Admin + server admin |
| Grafana Users | 1 (Main) | Viewer |
| DevOps Owners | 2 (DevOps) | Admin |
| DevOps Members | 2 (DevOps) | Editor |
| Finance Owners | 3 (Finance) | Admin |
Example Playbook
----------------
- hosts: alpha-control-plane
become: true
vars_files:
- ../secrets.yaml
roles:
- role: ericomeehan.grafana
License
-------
MIT-0
+15
View File
@@ -0,0 +1,15 @@
#SPDX-License-Identifier: MIT-0
---
# defaults file for ansible-role-grafana
# Bitnami removed old chart versions from charts.bitnami.com; pull the pinned
# version from the OCI registry so redeploys don't silently upgrade Grafana.
grafana_chart_ref: oci://registry-1.docker.io/bitnamicharts/grafana
grafana_chart_version: 11.4.2
# Bitnami moved its free images to bitnamilegacy; the old bitnami/grafana tag
# referenced by the chart no longer pulls.
grafana_image_repository: bitnamilegacy/grafana
grafana_image_tag: 11.4.0-debian-12-r0
grafana_domain: grafana.eom.dev
+3
View File
@@ -0,0 +1,3 @@
#SPDX-License-Identifier: MIT-0
---
# handlers file for ansible-role-grafana
+13
View File
@@ -0,0 +1,13 @@
#SPDX-License-Identifier: MIT-0
galaxy_info:
author: Eric Meehan
description: Deploy Grafana on the eom.dev Kubernetes cluster with LDAP sign-in
company: eom.dev
license: MIT-0
min_ansible_version: "2.15"
galaxy_tags:
- grafana
- monitoring
- kubernetes
dependencies: []
+66
View File
@@ -0,0 +1,66 @@
#SPDX-License-Identifier: MIT-0
---
# tasks file for ansible-role-grafana
- name: Deploy Grafana
kubernetes.core.helm:
name: grafana
chart_ref: "{{ grafana_chart_ref }}"
chart_version: "{{ grafana_chart_version }}"
release_namespace: grafana
create_namespace: true
values:
image:
repository: "{{ grafana_image_repository }}"
tag: "{{ grafana_image_tag }}"
metrics:
enabled: true
admin:
user: grafana_admin
password: "{{ grafana_admin_password }}"
persistence:
size: 64Gi
grafana:
extraEnvVars:
- name: GF_SERVER_DOMAIN
value: "{{ grafana_domain }}"
- name: GF_SERVER_ROOT_URL
value: "https://{{ grafana_domain }}/"
smtp:
enabled: true
user: grafana
password: "{{ grafana_admin_password }}"
host: mail.eom.dev:587
fromAddress: grafana@eom.dev
fromName: Grafana
ldap:
enabled: true
allowSignUp: true
configuration: "{{ lookup('template', 'ldap.toml.j2') }}"
ingress:
enabled: true
pathType: Prefix
hostname: "{{ grafana_domain }}"
annotations:
cert-manager.io/cluster-issuer: ca-issuer
ingressClassName: nginx
tls: true
datasources:
secretDefinition:
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
access: proxy
orgId: 1
url: http://prometheus-server.prometheus.svc.cluster.local
version: 1
editable: true
isDefault: true
- name: Alertmanager
uid: alertmanager
type: alertmanager
access: proxy
orgId: 1
url: http://prometheus-alertmanager.prometheus.svc.cluster.local
version: 1
editable: true
+75
View File
@@ -0,0 +1,75 @@
[[servers]]
# Ldap server host (specify multiple hosts space separated)
host = "openldap.openldap.svc.cluster.local"
# Default port is 389 or 636 if use_ssl = true
port = 389
# Set to true if LDAP server should use an encrypted TLS connection (either with STARTTLS or LDAPS)
use_ssl = false
# If set to true, use LDAP with STARTTLS instead of LDAPS
start_tls = false
# The value of an accepted TLS cipher. By default, this value is empty. Example value: ["TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"])
# For a complete list of supported ciphers and TLS versions, refer to: https://go.dev/src/crypto/tls/cipher_suites.go
# Starting with Grafana v11.0 only ciphers with ECDHE support are accepted for TLS 1.2 connections.
tls_ciphers = []
# This is the minimum TLS version allowed. By default, this value is empty. Accepted values are: TLS1.1 (only for Grafana v10.4 or earlier), TLS1.2, TLS1.3.
min_tls_version = ""
# set to true if you want to skip SSL cert validation
ssl_skip_verify = false
# set to the path to your root CA certificate or leave unset to use system defaults
# root_ca_cert = "/path/to/certificate.crt"
# Authentication against LDAP servers requiring client certificates
# client_cert = "/path/to/client.crt"
# client_key = "/path/to/client.key"
# Search user bind dn
bind_dn = "cn=readonly,dc=eom,dc=dev"
# Search user bind password
# If the password contains # or ; you have to wrap it with triple quotes. Ex """#password;"""
bind_password = "{{ openldap_readonly_password }}"
# We recommend using variable expansion for the bind_password, for more info https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#variable-expansion
# bind_password = '$__env{LDAP_BIND_PASSWORD}'
# Timeout in seconds. Applies to each host specified in the 'host' entry (space separated).
timeout = 30
# User search filter, for example "(cn=%s)" or "(sAMAccountName=%s)" or "(uid=%s)"
# Allow login from email or username, example "(|(sAMAccountName=%s)(userPrincipalName=%s))"
search_filter = "(&(objectClass=posixAccount)(uid=%s)(memberOf=cn=Grafana Users,ou=Grafana,ou=Services,dc=eom,dc=dev))"
# An array of base dns to search through
search_base_dns = ["dc=eom,dc=dev"]
# Specify names of the LDAP attributes your LDAP uses
[servers.attributes]
username = "uid"
email = "mail"
name = "givenName"
surname = "sn"
member_of = "memberOf"
[[servers.group_mappings]]
group_dn = "cn=Grafana Administrators,ou=Grafana,ou=Services,dc=eom,dc=dev"
org_id = 1
org_role = "Admin"
grafana_admin = true
[[servers.group_mappings]]
group_dn = "cn=Grafana Users,ou=Grafana,ou=Services,dc=eom,dc=dev"
org_id = 1
org_role = "Viewer"
[[servers.group_mappings]]
group_dn = "cn=DevOps Owners,ou=Teams,dc=eom,dc=dev"
org_id = 2
org_role = "Admin"
# Must stay after DevOps Owners: Grafana applies the first matching mapping per org.
[[servers.group_mappings]]
group_dn = "cn=DevOps Members,ou=Teams,dc=eom,dc=dev"
org_id = 2
org_role = "Editor"
[[servers.group_mappings]]
group_dn = "cn=Finance Owners,ou=Teams,dc=eom,dc=dev"
org_id = 3
org_role = "Admin"
+3
View File
@@ -0,0 +1,3 @@
#SPDX-License-Identifier: MIT-0
localhost
+6
View File
@@ -0,0 +1,6 @@
#SPDX-License-Identifier: MIT-0
---
- hosts: localhost
remote_user: root
roles:
- ansible-role-grafana
+3
View File
@@ -0,0 +1,3 @@
#SPDX-License-Identifier: MIT-0
---
# vars file for ansible-role-grafana