blob: e6937fc6a8ef6f1ebd147462e40967e296eb3c06 [file] [log] [blame]
Ales Komarek3f044b22016-10-30 00:27:24 +02001# -*- coding: utf-8 -*-
2'''
3Manage Grafana v3.0 data sources
4
5.. versionadded:: 2016.3.0
6
7Token auth setup
8
9.. code-block:: yaml
10
Guillaume Thouveninab102342016-11-03 10:40:29 +010011 grafana_version: 3
Ales Komarek3f044b22016-10-30 00:27:24 +020012 grafana:
Ales Komarek3f044b22016-10-30 00:27:24 +020013 grafana_timeout: 5
14 grafana_token: qwertyuiop
15 grafana_url: 'https://url.com'
16
17Basic auth setup
18
19.. code-block:: yaml
20
Guillaume Thouveninab102342016-11-03 10:40:29 +010021 grafana_version: 3
Ales Komarek3f044b22016-10-30 00:27:24 +020022 grafana:
Ales Komarek3f044b22016-10-30 00:27:24 +020023 grafana_timeout: 5
24 grafana_user: grafana
25 grafana_password: qwertyuiop
26 grafana_url: 'https://url.com'
27
28.. code-block:: yaml
29
30 Ensure influxdb data source is present:
31 grafana_datasource.present:
32 - name: influxdb
33 - type: influxdb
34 - url: http://localhost:8086
35 - access: proxy
36 - basic_auth: true
37 - basic_auth_user: myuser
38 - basic_auth_password: mypass
39 - is_default: true
40'''
41from __future__ import absolute_import
42
Guillaume Thouvenine3e6e3f2016-11-03 14:52:16 +010043import json
Ales Komarek3f044b22016-10-30 00:27:24 +020044import requests
45
46from salt.ext.six import string_types
47
48
49def __virtual__():
50 '''Only load if grafana v3.0 is configured.'''
51 return __salt__['config.get']('grafana_version', 1) == 3
52
53
54def present(name,
55 type,
56 url,
57 access='proxy',
58 user='',
59 password='',
60 database='',
61 basic_auth=False,
62 basic_auth_user='',
63 basic_auth_password='',
Michal Kobus2f77faa2018-08-13 18:27:30 +020064 mode=None,
65 domain='default',
66 project=None,
Ales Komarek3f044b22016-10-30 00:27:24 +020067 is_default=False,
Ales Komarek3f044b22016-10-30 00:27:24 +020068 profile='grafana'):
69 '''
70 Ensure that a data source is present.
71
72 name
73 Name of the data source.
74
75 type
76 Which type of data source it is ('graphite', 'influxdb' etc.).
77
Guillaume Thouveninab102342016-11-03 10:40:29 +010078 access
79 Use proxy or direct. Default: proxy
80
Ales Komarek3f044b22016-10-30 00:27:24 +020081 url
82 The URL to the data source API.
83
84 user
85 Optional - user to authenticate with the data source
86
87 password
88 Optional - password to authenticate with the data source
89
Guillaume Thouveninab102342016-11-03 10:40:29 +010090 database
91 Optional - database to use with the data source
92
Ales Komarek3f044b22016-10-30 00:27:24 +020093 basic_auth
94 Optional - set to True to use HTTP basic auth to authenticate with the
95 data source.
96
97 basic_auth_user
98 Optional - HTTP basic auth username.
99
100 basic_auth_password
101 Optional - HTTP basic auth password.
102
Michal Kobus2f77faa2018-08-13 18:27:30 +0200103 mode
104 Optional - Gnocchi authentication mode.
105
106 domain
107 Optional - Gnocchi domain, defaults to "default".
108
109 project
110 Optional - Keystone user for Gnocchi.
111
Ales Komarek3f044b22016-10-30 00:27:24 +0200112 is_default
Guillaume Thouveninab102342016-11-03 10:40:29 +0100113 Optional - Set data source as default. Default: False
Ales Komarek3f044b22016-10-30 00:27:24 +0200114 '''
115 if isinstance(profile, string_types):
116 profile = __salt__['config.option'](profile)
117
118 ret = {'name': name, 'result': None, 'comment': None, 'changes': None}
119 datasource = _get_datasource(profile, name)
Guillaume Thouveninab102342016-11-03 10:40:29 +0100120 data = _get_json_data(name, type, url,
121 access=access,
122 user=user,
123 password=password,
124 database=database,
125 basic_auth=basic_auth,
126 basic_auth_user=basic_auth_user,
127 basic_auth_password=basic_auth_password,
Michal Kobus2f77faa2018-08-13 18:27:30 +0200128 mode=mode,
129 domain=domain,
130 project=project,
Guillaume Thouveninab102342016-11-03 10:40:29 +0100131 is_default=is_default)
Ales Komarek3f044b22016-10-30 00:27:24 +0200132
133 if datasource:
Guillaume Thouvenine3e6e3f2016-11-03 14:52:16 +0100134 requests.put(
135 _get_url(profile, datasource['id']),
136 data=json.dumps(data),
137 auth=_get_auth(profile),
138 headers=_get_headers(profile),
139 timeout=profile.get('grafana_timeout', 3),
140 )
Ales Komarek3f044b22016-10-30 00:27:24 +0200141 ret['result'] = True
142 ret['changes'] = _diff(datasource, data)
143 if ret['changes']['new'] or ret['changes']['old']:
144 ret['comment'] = 'Data source {0} updated'.format(name)
145 else:
146 ret['changes'] = None
147 ret['comment'] = 'Data source {0} already up-to-date'.format(name)
148 else:
Guillaume Thouvenine3e6e3f2016-11-03 14:52:16 +0100149 requests.post(
150 '{0}/api/datasources'.format(profile['grafana_url']),
151 data=json.dumps(data),
152 auth=_get_auth(profile),
153 headers=_get_headers(profile),
154 timeout=profile.get('grafana_timeout', 3),
155 )
Ales Komarek3f044b22016-10-30 00:27:24 +0200156 ret['result'] = True
157 ret['comment'] = 'New data source {0} added'.format(name)
158 ret['changes'] = data
159
160 return ret
161
162
163def absent(name, profile='grafana'):
164 '''
165 Ensure that a data source is present.
166
167 name
168 Name of the data source to remove.
169 '''
170 if isinstance(profile, string_types):
171 profile = __salt__['config.option'](profile)
172
173 ret = {'result': None, 'comment': None, 'changes': None}
174 datasource = _get_datasource(profile, name)
175
176 if not datasource:
177 ret['result'] = True
178 ret['comment'] = 'Data source {0} already absent'.format(name)
179 return ret
180
Guillaume Thouvenine3e6e3f2016-11-03 14:52:16 +0100181 requests.delete(
182 _get_url(profile, datasource['id']),
183 auth=_get_auth(profile),
184 headers=_get_headers(profile),
185 timeout=profile.get('grafana_timeout', 3),
186 )
Ales Komarek3f044b22016-10-30 00:27:24 +0200187
188 ret['result'] = True
189 ret['comment'] = 'Data source {0} was deleted'.format(name)
190
191 return ret
192
193
194def _get_url(profile, datasource_id):
195 return '{0}/api/datasources/{1}'.format(
196 profile['grafana_url'],
197 datasource_id
198 )
199
200
201def _get_datasource(profile, name):
Guillaume Thouvenine3e6e3f2016-11-03 14:52:16 +0100202 response = requests.get(
203 '{0}/api/datasources'.format(profile['grafana_url']),
204 auth=_get_auth(profile),
205 headers=_get_headers(profile),
206 timeout=profile.get('grafana_timeout', 3),
207 )
Ales Komarek3f044b22016-10-30 00:27:24 +0200208 data = response.json()
209 for datasource in data:
210 if datasource['name'] == name:
211 return datasource
Ales Komarek3f044b22016-10-30 00:27:24 +0200212
213
214def _get_headers(profile):
Guillaume Thouvenine3e6e3f2016-11-03 14:52:16 +0100215
216 headers = {'Content-type': 'application/json'}
217
Michal Kobus2f77faa2018-08-13 18:27:30 +0200218 if profile.get('grafana_token'):
Guillaume Thouvenine3e6e3f2016-11-03 14:52:16 +0100219 headers['Authorization'] = 'Bearer {0}'.format(profile['grafana_token'])
220
221 return headers
Ales Komarek3f044b22016-10-30 00:27:24 +0200222
223
224def _get_auth(profile):
Michal Kobus2f77faa2018-08-13 18:27:30 +0200225 if profile.get('grafana_token'):
Guillaume Thouvenine3e6e3f2016-11-03 14:52:16 +0100226 return None
227
Ales Komarek3f044b22016-10-30 00:27:24 +0200228 return requests.auth.HTTPBasicAuth(
229 profile['grafana_user'],
230 profile['grafana_password']
231 )
232
233
234def _get_json_data(name,
235 type,
236 url,
237 access='proxy',
238 user='',
239 password='',
240 database='',
241 basic_auth=False,
242 basic_auth_user='',
243 basic_auth_password='',
Michal Kobus2f77faa2018-08-13 18:27:30 +0200244 mode=None,
245 domain=None,
246 project=None,
Ales Komarek3f044b22016-10-30 00:27:24 +0200247 is_default=False,
Guillaume Thouveninab102342016-11-03 10:40:29 +0100248 type_logo_url='public/app/plugins/datasource/influxdb/img/influxdb_logo.svg',
249 with_credentials=False):
Michal Kobus2f77faa2018-08-13 18:27:30 +0200250 data = {
Ales Komarek3f044b22016-10-30 00:27:24 +0200251 'name': name,
252 'type': type,
253 'url': url,
254 'access': access,
255 'user': user,
256 'password': password,
257 'database': database,
258 'basicAuth': basic_auth,
259 'basicAuthUser': basic_auth_user,
260 'basicAuthPassword': basic_auth_password,
261 'isDefault': is_default,
262 'typeLogoUrl': type_logo_url,
263 'withCredentials': with_credentials,
Ales Komarek3f044b22016-10-30 00:27:24 +0200264 }
Dmitry Kalashnik40944e82018-11-30 13:45:14 +0400265
266 if data['type'] == 'prometheus':
267 data.update({
268 'jsonData': {
269 'httpMethod': 'GET',
270 'keepCookies': []
271 }
272 })
273
Michal Kobus2f77faa2018-08-13 18:27:30 +0200274 if data['type'] == 'gnocchixyz-gnocchi-datasource':
275 json_data = {}
276 for special in ['mode', 'domain', 'project', 'user', 'password']:
277 value = locals().get(special)
278 if value is not None:
279 if special == 'user':
280 json_data['username'] = value
281 else:
282 json_data[special] = value
283 if json_data:
284 data['jsonData'] = json_data
285 return data
Ales Komarek3f044b22016-10-30 00:27:24 +0200286
287
288def _diff(old, new):
289 old_keys = old.keys()
290 old = old.copy()
291 new = new.copy()
292 for key in old_keys:
Michal Kobus2f77faa2018-08-13 18:27:30 +0200293 if key in ['id', 'orgId']:
Ales Komarek3f044b22016-10-30 00:27:24 +0200294 del old[key]
thouvengb3880422016-12-12 16:53:38 +0100295 # New versions of Grafana can introduce new keys that are not present
296 # in _get_json_data.
297 elif key in new and old[key] == new[key]:
Ales Komarek3f044b22016-10-30 00:27:24 +0200298 del old[key]
299 del new[key]
300 return {'old': old, 'new': new}