Grafana dashboard grafana 5 compatible (#41249)

* Grafana dashboard module compatible with grafana 5+

* Use url_argument_spec to init modules arguments

* Add changelog fragment
pull/42965/merge
Seuf 6 years ago committed by Sam Doran
parent a7f45c0660
commit af55b8e992

@ -0,0 +1,3 @@
---
bugfixes:
- Grafana dashboard module compatible with grafana 5 (https://github.com/ansible/ansible/pull/41249)

@ -16,24 +16,30 @@ DOCUMENTATION = '''
--- ---
module: grafana_dashboard module: grafana_dashboard
author: author:
- Thierry Sallé (@tsalle) - Thierry Sallé (@seuf)
version_added: "2.5" version_added: "2.5"
short_description: Manage Grafana dashboards short_description: Manage Grafana dashboards
description: description:
- Create, update, delete, export Grafana dashboards via API. - Create, update, delete, export Grafana dashboards via API.
options: options:
grafana_url: url:
description: description:
- The Grafana URL. - The Grafana URL.
required: true required: true
grafana_user: aliases: [ grafana_url ]
version_added: 2.7
url_username:
description: description:
- The Grafana API user. - The Grafana API user.
default: admin default: admin
grafana_password: aliases: [ grafana_user ]
version_added: 2.7
url_password:
description: description:
- The Grafana API password. - The Grafana API password.
default: admin default: admin
aliases: [ grafana_password ]
version_added: 2.7
grafana_api_key: grafana_api_key:
description: description:
- The Grafana API key. - The Grafana API key.
@ -51,10 +57,15 @@ options:
default: present default: present
slug: slug:
description: description:
- Deprecated since Grafana 5. Use grafana dashboard uid instead.
- slug of the dashboard. It's the friendly url name of the dashboard. - slug of the dashboard. It's the friendly url name of the dashboard.
- When C(state) is C(present), this parameter can override the slug in the meta section of the json file. - When C(state) is C(present), this parameter can override the slug in the meta section of the json file.
- If you want to import a json dashboard exported directly from the interface (not from the api), - If you want to import a json dashboard exported directly from the interface (not from the api),
you have to specify the slug parameter because there is no meta section in the exported json. you have to specify the slug parameter because there is no meta section in the exported json.
uid:
version_added: 2.7
description:
- uid of the dasboard to export when C(state) is C(export) or C(absent).
path: path:
description: description:
- The path to the json file containing the Grafana dashboard to import or export. - The path to the json file containing the Grafana dashboard to import or export.
@ -73,42 +84,63 @@ options:
- This should only be used on personally controlled sites using self-signed certificates. - This should only be used on personally controlled sites using self-signed certificates.
type: bool type: bool
default: 'yes' default: 'yes'
client_cert:
description:
- PEM formatted certificate chain file to be used for SSL client authentication.
- This file can also include the key as well, and if the key is included, client_key is not required
version_added: 2.7
client_key:
description:
- PEM formatted file that contains your private key to be used for SSL client
- authentication. If client_cert contains both the certificate and key, this option is not required
version_added: 2.7
use_proxy:
description:
- Boolean of whether or not to use proxy.
default: 'yes'
type: bool
version_added: 2.7
''' '''
EXAMPLES = ''' EXAMPLES = '''
- name: Import Grafana dashboard foo - hosts: localhost
grafana_dashboard: connection: local
grafana_url: http://grafana.company.com tasks:
grafana_api_key: XXXXXXXXXXXX - name: Import Grafana dashboard foo
state: present grafana_dashboard:
message: Updated by ansible grafana_url: http://grafana.company.com
overwrite: yes grafana_api_key: "{{ grafana_api_key }}"
path: /path/to/dashboards/foo.json state: present
message: Updated by ansible
- name: Export dashboard overwrite: yes
grafana_dashboard: path: /path/to/dashboards/foo.json
grafana_url: http://grafana.company.com
grafana_api_key: XXXXXXXXXXXX - name: Export dashboard
state: export grafana_dashboard:
slug: foo grafana_url: http://grafana.company.com
path: /path/to/dashboards/foo.json grafana_user: "admin"
grafana_password: "{{ grafana_password }}"
org_id: 1
state: export
uid: "000000653"
path: "/path/to/dashboards/000000653.json"
''' '''
RETURN = ''' RETURN = '''
--- ---
slug: uid:
description: slug of the created / deleted / exported dashboard. description: uid or slug of the created / deleted / exported dashboard.
returned: success returned: success
type: string type: string
sample: foo sample: 000000063
''' '''
import json import json
import base64 import string
from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.urls import fetch_url from ansible.module_utils.urls import fetch_url, url_argument_spec
from ansible.module_utils._text import to_bytes from ansible.module_utils._text import to_native
__metaclass__ = type __metaclass__ = type
@ -125,26 +157,62 @@ class GrafanaExportException(Exception):
pass pass
class GrafanaDeleteException(Exception):
pass
def grafana_switch_organisation(module, grafana_url, org_id, headers): def grafana_switch_organisation(module, grafana_url, org_id, headers):
r, info = fetch_url(module, '%s/api/user/using/%s' % (grafana_url, org_id), headers=headers, method='POST') r, info = fetch_url(module, '%s/api/user/using/%s' % (grafana_url, org_id), headers=headers, method='POST')
if info['status'] != 200: if info['status'] != 200:
raise GrafanaAPIException('Unable to switch to organization %s : %s' % (org_id, info)) raise GrafanaAPIException('Unable to switch to organization %s : %s' % (org_id, info))
def grafana_dashboard_exists(module, grafana_url, slug, headers): def grafana_headers(module, data):
headers = {'content-type': 'application/json; charset=utf8'}
if 'grafana_api_key' in data and data['grafana_api_key']:
headers['Authorization'] = "Bearer %s" % data['grafana_api_key']
else:
module.params['force_basic_auth'] = True
grafana_switch_organisation(module, data['grafana_url'], data['org_id'], headers)
return headers
def get_grafana_version(module, grafana_url, headers):
grafana_version = None
r, info = fetch_url(module, '%s/api/frontend/settings' % grafana_url, headers=headers, method='GET')
if info['status'] == 200:
try:
settings = json.loads(r.read())
grafana_version = string.split(settings['buildInfo']['version'], '.')[0]
except Exception as e:
raise GrafanaAPIException(e)
else:
raise GrafanaAPIException('Unable to get grafana version : %s' % info)
return int(grafana_version)
def grafana_dashboard_exists(module, grafana_url, uid, headers):
dashboard_exists = False dashboard_exists = False
dashboard = {} dashboard = {}
r, info = fetch_url(module, '%s/api/dashboards/db/%s' % (grafana_url, slug), headers=headers, method='GET')
grafana_version = get_grafana_version(module, grafana_url, headers)
if grafana_version >= 5:
r, info = fetch_url(module, '%s/api/dashboards/uid/%s' % (grafana_url, uid), headers=headers, method='GET')
else:
r, info = fetch_url(module, '%s/api/dashboards/db/%s' % (grafana_url, uid), headers=headers, method='GET')
if info['status'] == 200: if info['status'] == 200:
dashboard_exists = True dashboard_exists = True
try: try:
dashboard = json.loads(r.read()) dashboard = json.loads(r.read())
except Exception as e: except Exception as e:
raise GrafanaMalformedJson(e) raise GrafanaAPIException(e)
elif info['status'] == 404: elif info['status'] == 404:
dashboard_exists = False dashboard_exists = False
else: else:
raise GrafanaAPIException('Unable to get dashboard %s : %s' % (slug, info)) raise GrafanaAPIException('Unable to get dashboard %s : %s' % (uid, info))
return dashboard_exists, dashboard return dashboard_exists, dashboard
@ -156,60 +224,75 @@ def grafana_create_dashboard(module, data):
with open(data['path'], 'r') as json_file: with open(data['path'], 'r') as json_file:
payload = json.load(json_file) payload = json.load(json_file)
except Exception as e: except Exception as e:
raise GrafanaMalformedJson("Can't load json file %s" % str(e)) raise GrafanaAPIException("Can't load json file %s" % to_native(e))
# define http header # define http header
headers = {'content-type': 'application/json; charset=utf8'} headers = grafana_headers(module, data)
if 'grafana_api_key' in data and data['grafana_api_key']:
headers['Authorization'] = "Bearer %s" % data['grafana_api_key'] grafana_version = get_grafana_version(module, data['grafana_url'], headers)
else: if grafana_version < 5:
auth = base64.b64encode(to_bytes('%s:%s' % (data['grafana_user'], data['grafana_password'])).replace('\n', '')) if data.get('slug'):
headers['Authorization'] = 'Basic %s' % auth uid = data['slug']
grafana_switch_organisation(module, data['grafana_url'], data['org_id'], headers) elif 'meta' in payload and 'slug' in payload['meta']:
uid = payload['meta']['slug']
if data.get('slug'): else:
slug = data['slug'] raise GrafanaMalformedJson('No slug found in json. Needed with grafana < 5')
elif 'meta' in payload and 'slug' in payload['meta']:
slug = payload['meta']['slug']
else: else:
raise GrafanaMalformedJson('No slug found in json') if data.get('uid'):
uid = data['uid']
elif 'uid' in payload['dashboard']:
uid = payload['dashboard']['uid']
else:
uid = None
# test if dashboard already exists # test if dashboard already exists
dashboard_exists, dashboard = grafana_dashboard_exists(module, data['grafana_url'], slug, headers=headers) dashboard_exists, dashboard = grafana_dashboard_exists(module, data['grafana_url'], uid, headers=headers)
result = {} result = {}
if dashboard_exists is True: if dashboard_exists is True:
if dashboard == payload: if dashboard == payload:
# unchanged # unchanged
result['slug'] = data['slug'] result['uid'] = uid
result['msg'] = "Dashboard %s unchanged." % data['slug'] result['msg'] = "Dashboard %s unchanged." % uid
result['changed'] = False result['changed'] = False
else: else:
# update # update
if 'overwrite' in data and data['overwrite']: if 'overwrite' in data and data['overwrite'] == 'yes':
payload['overwrite'] = True payload['overwrite'] = True
if 'message' in data and data['message']: if 'message' in data and data['message']:
payload['message'] = data['message'] payload['message'] = data['message']
r, info = fetch_url(module, '%s/api/dashboards/db' % data['grafana_url'], data=json.dumps(payload), headers=headers, method='POST') r, info = fetch_url(module, '%s/api/dashboards/db' % data['grafana_url'], data=json.dumps(payload), headers=headers, method='POST')
if info['status'] == 200: if info['status'] == 200:
result['slug'] = slug if grafana_version >= 5:
result['msg'] = "Dashboard %s updated" % slug try:
dashboard = json.loads(r.read())
uid = dashboard['uid']
except Exception as e:
raise GrafanaAPIException(e)
result['uid'] = uid
result['msg'] = "Dashboard %s updated" % uid
result['changed'] = True result['changed'] = True
else: else:
body = json.loads(info['body']) body = json.loads(info['body'])
raise GrafanaAPIException('Unable to update the dashboard %s : %s' % (slug, body['message'])) raise GrafanaAPIException('Unable to update the dashboard %s : %s' % (uid, body['message']))
else: else:
# create # create
if 'dashboard' not in payload: if 'dashboard' not in payload:
payload = {'dashboard': payload} payload = {'dashboard': payload}
r, info = fetch_url(module, '%s/api/dashboards/db' % data['grafana_url'], data=json.dumps(payload), headers=headers, method='POST') r, info = fetch_url(module, '%s/api/dashboards/db' % data['grafana_url'], data=json.dumps(payload), headers=headers, method='POST')
if info['status'] == 200: if info['status'] == 200:
result['msg'] = "Dashboard %s created" % slug result['msg'] = "Dashboard %s created" % uid
result['changed'] = True result['changed'] = True
result['slug'] = slug if grafana_version >= 5:
try:
dashboard = json.loads(r.read())
uid = dashboard['uid']
except Exception as e:
raise GrafanaAPIException(e)
result['uid'] = uid
else: else:
raise GrafanaAPIException('Unable to create the new dashboard %s : %s - %s.' % (slug, info['status'], info)) raise GrafanaAPIException('Unable to create the new dashboard %s : %s - %s.' % (uid, info['status'], info))
return result return result
@ -217,32 +300,41 @@ def grafana_create_dashboard(module, data):
def grafana_delete_dashboard(module, data): def grafana_delete_dashboard(module, data):
# define http headers # define http headers
headers = {'content-type': 'application/json'} headers = grafana_headers(module, data)
if 'grafana_api_key' in data and data['grafana_api_key']:
headers['Authorization'] = "Bearer %s" % data['grafana_api_key'] grafana_version = get_grafana_version(module, data['grafana_url'], headers)
if grafana_version < 5:
if data.get('slug'):
uid = data['slug']
else:
raise GrafanaMalformedJson('No slug parameter. Needed with grafana < 5')
else: else:
auth = base64.b64encode(to_bytes('%s:%s' % (data['grafana_user'], data['grafana_password'])).replace('\n', '')) if data.get('uid'):
headers['Authorization'] = 'Basic %s' % auth uid = data['uid']
grafana_switch_organisation(module, data['grafana_url'], data['org_id'], headers) else:
raise GrafanaDeleteException('No uid specified %s')
# test if dashboard already exists # test if dashboard already exists
dashboard_exists, dashboard = grafana_dashboard_exists(module, data['grafana_url'], data['slug'], headers=headers) dashboard_exists, dashboard = grafana_dashboard_exists(module, data['grafana_url'], uid, headers=headers)
result = {} result = {}
if dashboard_exists is True: if dashboard_exists is True:
# delete # delete
r, info = fetch_url(module, '%s/api/dashboards/db/%s' % (data['grafana_url'], data['slug']), headers=headers, method='DELETE') if grafana_version < 5:
r, info = fetch_url(module, '%s/api/dashboards/db/%s' % (data['grafana_url'], uid), headers=headers, method='DELETE')
else:
r, info = fetch_url(module, '%s/api/dashboards/uid/%s' % (data['grafana_url'], uid), headers=headers, method='DELETE')
if info['status'] == 200: if info['status'] == 200:
result['msg'] = "Dashboard %s deleted" % data['slug'] result['msg'] = "Dashboard %s deleted" % uid
result['changed'] = True result['changed'] = True
result['slug'] = data['slug'] result['uid'] = uid
else: else:
raise GrafanaAPIException('Unable to update the dashboard %s : %s' % (data['slug'], info)) raise GrafanaAPIException('Unable to update the dashboard %s : %s' % (uid, info))
else: else:
# dashboard does not exist, do nothing # dashboard does not exist, do nothing
result = {'msg': "Dashboard %s does not exist." % data['slug'], result = {'msg': "Dashboard %s does not exist." % uid,
'changed': False, 'changed': False,
'slug': data['slug']} 'uid': uid}
return result return result
@ -250,53 +342,65 @@ def grafana_delete_dashboard(module, data):
def grafana_export_dashboard(module, data): def grafana_export_dashboard(module, data):
# define http headers # define http headers
headers = {'content-type': 'application/json'} headers = grafana_headers(module, data)
if 'grafana_api_key' in data and data['grafana_api_key']:
headers['Authorization'] = "Bearer %s" % data['grafana_api_key'] grafana_version = get_grafana_version(module, data['grafana_url'], headers)
if grafana_version < 5:
if data.get('slug'):
uid = data['slug']
else:
raise GrafanaMalformedJson('No slug parameter. Needed with grafana < 5')
else: else:
auth = base64.b64encode(to_bytes('%s:%s' % (data['grafana_user'], data['grafana_password'])).replace('\n', '')) if data.get('uid'):
headers['Authorization'] = 'Basic %s' % auth uid = data['uid']
grafana_switch_organisation(module, data['grafana_url'], data['org_id'], headers) else:
raise GrafanaExportException('No uid specified')
# test if dashboard already exists # test if dashboard already exists
dashboard_exists, dashboard = grafana_dashboard_exists(module, data['grafana_url'], data['slug'], headers=headers) dashboard_exists, dashboard = grafana_dashboard_exists(module, data['grafana_url'], uid, headers=headers)
if dashboard_exists is True: if dashboard_exists is True:
try: try:
with open(data['path'], 'w') as f: with open(data['path'], 'w') as f:
f.write(json.dumps(dashboard)) f.write(json.dumps(dashboard))
except Exception as e: except Exception as e:
raise GrafanaExportException("Can't write json file : %s" % str(e)) raise GrafanaExportException("Can't write json file : %s" % to_native(e))
result = {'msg': "Dashboard %s exported to %s" % (data['slug'], data['path']), result = {'msg': "Dashboard %s exported to %s" % (uid, data['path']),
'slug': data['slug'], 'uid': uid,
'changed': True} 'changed': True}
else: else:
result = {'msg': "Dashboard %s does not exist." % data['slug'], result = {'msg': "Dashboard %s does not exist." % uid,
'slug': data['slug'], 'uid': uid,
'changed': False} 'changed': False}
return result return result
def main(): def main():
# use the predefined argument spec for url
argument_spec = url_argument_spec()
# remove unnecessary arguments
del argument_spec['force']
del argument_spec['force_basic_auth']
del argument_spec['http_agent']
argument_spec.update(
state=dict(choices=['present', 'absent', 'export'], default='present'),
url=dict(aliases=['grafana_url'], required=True),
url_username=dict(aliases=['grafana_user'], default='admin'),
url_password=dict(aliases=['grafana_password'], default='admin', no_log=True),
grafana_api_key=dict(type='str', no_log=True),
org_id=dict(default=1, type='int'),
uid=dict(type='str'),
slug=dict(type='str'),
path=dict(type='str'),
overwrite=dict(type='bool', default=False),
message=dict(type='str'),
)
module = AnsibleModule( module = AnsibleModule(
argument_spec=dict( argument_spec=argument_spec,
state=dict(choices=['present', 'absent', 'export'],
default='present'),
grafana_url=dict(required=True),
grafana_user=dict(default='admin'),
grafana_password=dict(default='admin', no_log=True),
grafana_api_key=dict(type='str', no_log=True),
org_id=dict(default=1, type='int'),
slug=dict(type='str'),
path=dict(type='str'),
overwrite=dict(type='bool', default=False),
message=dict(type='str'),
validate_certs=dict(type='bool', default=True)
),
supports_check_mode=False, supports_check_mode=False,
required_together=[['grafana_user', 'grafana_password', 'org_id']], required_together=[['url_username', 'url_password', 'org_id']],
mutually_exclusive=[['grafana_user', 'grafana_api_key']], mutually_exclusive=[['grafana_user', 'grafana_api_key'], ['uid', 'slug']],
) )
try: try:
@ -309,7 +413,7 @@ def main():
except GrafanaAPIException as e: except GrafanaAPIException as e:
module.fail_json( module.fail_json(
failed=True, failed=True,
msg="error : %s" % e msg="error : %s" % to_native(e)
) )
return return
except GrafanaMalformedJson as e: except GrafanaMalformedJson as e:
@ -318,10 +422,16 @@ def main():
msg="error : json file does not contain a meta section with a slug parameter, or you did'nt specify the slug parameter" msg="error : json file does not contain a meta section with a slug parameter, or you did'nt specify the slug parameter"
) )
return return
except GrafanaDeleteException as e:
module.fail_json(
failed=True,
msg="error : Can't delete dashboard : %s" % to_native(e)
)
return
except GrafanaExportException as e: except GrafanaExportException as e:
module.fail_json( module.fail_json(
failed=True, failed=True,
msg="error : json file cannot be written : %s" % str(e) msg="error : Can't export dashboard : %s" % to_native(e)
) )
return return

Loading…
Cancel
Save