Skip to content

Commit f829c1f

Browse files
authored
Add one-time dashboard import and Akvorado datasource provisioning (#436)
* Add one-time dashboard import and Akvorado datasource provisioning - Stage dashboards and library panels from puppet:///svn staging dir - Import via Grafana API once (flag file prevents re-import), library panels first - Add Akvorado datasource provisioning * Add Akvorado server discovery and clean up import script - Populate akvorado_servers in grafana.py node classifier - Fix import script: move base64 import to top, remove unused auth variable, clean up spacing
1 parent fccf209 commit f829c1f

4 files changed

Lines changed: 131 additions & 1 deletion

File tree

modules/grafana.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,19 @@ def generate(host, *args):
1414
domain = 'unknown'
1515
prometheus_servers[server] = domain
1616

17+
akvorado_servers = {}
18+
for server in sorted(lib.get_nodes_with_package('akvorado').keys()):
19+
try:
20+
domain = lib.get_domain(server).lower()
21+
except lib.NoDomainError:
22+
domain = 'unknown'
23+
akvorado_servers[server] = domain
24+
1725
info = {
1826
'grafana': {
1927
'current_event': lib.get_current_event(),
2028
'prometheus_servers': prometheus_servers,
29+
'akvorado_servers': akvorado_servers,
2130
}
2231
}
2332

modules/grafana/manifests/init.pp

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
# for package details such as default paths etc.
1212
#
1313

14-
class grafana($current_event, $prometheus_servers = []) {
14+
class grafana($current_event, $prometheus_servers = [], $akvorado_servers = []) {
1515

1616
# Adding the apt repository
1717
package { 'apt-transport-https':
@@ -119,6 +119,43 @@
119119
notify => Service['grafana-server'],
120120
}
121121

122+
# Akvorado datasource provisioning
123+
file { 'grafana-akvorado-datasources':
124+
path => '/etc/grafana/provisioning/datasources/akvorado.yaml',
125+
content => template('grafana/akvorado-datasource.yaml.erb'),
126+
mode => '0644',
127+
require => Package['grafana'],
128+
notify => Service['grafana-server'],
129+
}
130+
131+
# Stage dashboards and library panels from SVN for one-time import
132+
file { '/var/lib/grafana/dashboards-staging':
133+
ensure => directory,
134+
source => "puppet:///svn/${current_event}/services/grafana/staging",
135+
recurse => true,
136+
owner => 'grafana',
137+
group => 'grafana',
138+
require => Package['grafana'],
139+
}
140+
141+
file { 'grafana-import-script':
142+
path => '/usr/local/bin/grafana-import-dashboards',
143+
content => template('grafana/grafana-import-dashboards.py.erb'),
144+
mode => '0755',
145+
require => Package['grafana'],
146+
}
147+
148+
# One-time import via API — flag file prevents re-import on subsequent Puppet runs
149+
exec { 'import-grafana-dashboards':
150+
command => '/usr/local/bin/grafana-import-dashboards',
151+
creates => '/var/lib/grafana/.dashboards-imported',
152+
require => [
153+
Service['grafana-server'],
154+
File['/var/lib/grafana/dashboards-staging'],
155+
File['grafana-import-script'],
156+
],
157+
}
158+
122159
# Setting up the Apache proxy
123160
apache::proxy { 'grafana-backend':
124161
url => '/',
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Managed by Puppet - do not edit manually
2+
apiVersion: 1
3+
4+
datasources:
5+
<% @akvorado_servers.each do |server, domain| -%>
6+
- name: akvorado_<%= domain %>
7+
uid: akvorado_<%= domain %>
8+
type: ovhcloud-akvorado-datasource
9+
url: http://<%= server %>
10+
access: proxy
11+
isDefault: false
12+
editable: true
13+
<% end -%>
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
#!/usr/bin/env python3
2+
# Managed by Puppet - do not edit manually
3+
# Imports dashboards from staging into Grafana via API.
4+
# Runs once; flag file prevents re-import on subsequent Puppet runs.
5+
6+
import base64
7+
import json
8+
import subprocess
9+
import sys
10+
import urllib.request
11+
import urllib.error
12+
from pathlib import Path
13+
14+
STAGING = Path('/var/lib/grafana/dashboards-staging')
15+
FLAG_FILE = Path('/var/lib/grafana/.dashboards-imported')
16+
GRAFANA_URL = 'http://localhost:3001'
17+
18+
lib_files = sorted((STAGING / 'Library_Panels').glob('*.json')) if (STAGING / 'Library_Panels').is_dir() else []
19+
dash_files = sorted((STAGING / 'General').glob('*.json')) if (STAGING / 'General').is_dir() else []
20+
21+
if not lib_files and not dash_files:
22+
print('No dashboards found in staging, skipping import.')
23+
sys.exit(0)
24+
25+
password = subprocess.check_output([
26+
'/usr/local/bin/dh-create-service-account',
27+
'--type', 'grafana',
28+
'--product', 'login',
29+
'--format', '{password}',
30+
], text=True).strip()
31+
32+
def post(path, payload):
33+
data = json.dumps(payload).encode()
34+
req = urllib.request.Request(
35+
GRAFANA_URL + path,
36+
data=data,
37+
headers={'Content-Type': 'application/json'},
38+
method='POST',
39+
)
40+
req.add_header('Authorization', 'Basic ' + base64.b64encode(b'admin:' + password.encode()).decode())
41+
try:
42+
with urllib.request.urlopen(req) as resp:
43+
print(f' OK ({resp.status}): {path}')
44+
except urllib.error.HTTPError as e:
45+
print(f' ERROR {e.code} importing {path}: {e.read().decode()}', file=sys.stderr)
46+
sys.exit(1)
47+
48+
# Import library panels first — dashboards may reference them by UID
49+
for f in lib_files:
50+
print(f'Importing library panel: {f.name}')
51+
raw = json.loads(f.read_text())
52+
result = raw['result']
53+
payload = {
54+
'kind': result['kind'],
55+
'name': result['name'],
56+
'model': result['model'],
57+
'uid': result['uid'],
58+
'folderId': result['folderId'],
59+
'folderUid': result['folderUid'],
60+
}
61+
post('/api/library-elements', payload)
62+
63+
# Import dashboards after library panels exist
64+
for f in dash_files:
65+
print(f'Importing dashboard: {f.name}')
66+
dashboard = json.loads(f.read_text())
67+
dashboard['id'] = None # Let Grafana assign a new ID; uid is preserved for references
68+
post('/api/dashboards/db', {'dashboard': dashboard, 'overwrite': False, 'folderId': 0})
69+
70+
FLAG_FILE.touch()
71+
print('Import complete.')

0 commit comments

Comments
 (0)