mirror of https://github.com/ansible/ansible.git
Migrated to community.grafana
parent
4e488d8435
commit
43f47d553e
@ -1,556 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# Copyright: (c) 2017, Thierry Sallé (@seuf)
|
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {
|
|
||||||
'status': ['preview'],
|
|
||||||
'supported_by': 'community',
|
|
||||||
'metadata_version': '1.1'
|
|
||||||
}
|
|
||||||
|
|
||||||
DOCUMENTATION = '''
|
|
||||||
---
|
|
||||||
module: grafana_dashboard
|
|
||||||
author:
|
|
||||||
- Thierry Sallé (@seuf)
|
|
||||||
version_added: "2.5"
|
|
||||||
short_description: Manage Grafana dashboards
|
|
||||||
description:
|
|
||||||
- Create, update, delete, export Grafana dashboards via API.
|
|
||||||
options:
|
|
||||||
url:
|
|
||||||
description:
|
|
||||||
- The Grafana URL.
|
|
||||||
required: true
|
|
||||||
aliases: [ grafana_url ]
|
|
||||||
version_added: 2.7
|
|
||||||
url_username:
|
|
||||||
description:
|
|
||||||
- The Grafana API user.
|
|
||||||
default: admin
|
|
||||||
aliases: [ grafana_user ]
|
|
||||||
version_added: 2.7
|
|
||||||
url_password:
|
|
||||||
description:
|
|
||||||
- The Grafana API password.
|
|
||||||
default: admin
|
|
||||||
aliases: [ grafana_password ]
|
|
||||||
version_added: 2.7
|
|
||||||
grafana_api_key:
|
|
||||||
description:
|
|
||||||
- The Grafana API key.
|
|
||||||
- If set, I(grafana_user) and I(grafana_password) will be ignored.
|
|
||||||
org_id:
|
|
||||||
description:
|
|
||||||
- The Grafana Organisation ID where the dashboard will be imported / exported.
|
|
||||||
- Not used when I(grafana_api_key) is set, because the grafana_api_key only belongs to one organisation..
|
|
||||||
default: 1
|
|
||||||
folder:
|
|
||||||
description:
|
|
||||||
- The Grafana folder where this dashboard will be imported to.
|
|
||||||
default: General
|
|
||||||
version_added: '2.10'
|
|
||||||
state:
|
|
||||||
description:
|
|
||||||
- State of the dashboard.
|
|
||||||
required: true
|
|
||||||
choices: [ absent, export, present ]
|
|
||||||
default: present
|
|
||||||
slug:
|
|
||||||
description:
|
|
||||||
- Deprecated since Grafana 5. Use grafana dashboard uid instead.
|
|
||||||
- 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.
|
|
||||||
- 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.
|
|
||||||
uid:
|
|
||||||
version_added: 2.7
|
|
||||||
description:
|
|
||||||
- uid of the dashboard to export when C(state) is C(export) or C(absent).
|
|
||||||
path:
|
|
||||||
description:
|
|
||||||
- The path to the json file containing the Grafana dashboard to import or export.
|
|
||||||
overwrite:
|
|
||||||
description:
|
|
||||||
- Override existing dashboard when state is present.
|
|
||||||
type: bool
|
|
||||||
default: 'no'
|
|
||||||
commit_message:
|
|
||||||
description:
|
|
||||||
- Set a commit message for the version history.
|
|
||||||
- Only used when C(state) is C(present).
|
|
||||||
- C(message) alias is deprecated in Ansible 2.10, since it is used internally by Ansible Core Engine.
|
|
||||||
aliases: [ 'message' ]
|
|
||||||
validate_certs:
|
|
||||||
description:
|
|
||||||
- If C(no), SSL certificates will not be validated.
|
|
||||||
- This should only be used on personally controlled sites using self-signed certificates.
|
|
||||||
type: bool
|
|
||||||
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 = '''
|
|
||||||
- hosts: localhost
|
|
||||||
connection: local
|
|
||||||
tasks:
|
|
||||||
- name: Import Grafana dashboard foo
|
|
||||||
grafana_dashboard:
|
|
||||||
grafana_url: http://grafana.company.com
|
|
||||||
grafana_api_key: "{{ grafana_api_key }}"
|
|
||||||
state: present
|
|
||||||
commit_message: Updated by ansible
|
|
||||||
overwrite: yes
|
|
||||||
path: /path/to/dashboards/foo.json
|
|
||||||
|
|
||||||
- name: Export dashboard
|
|
||||||
grafana_dashboard:
|
|
||||||
grafana_url: http://grafana.company.com
|
|
||||||
grafana_user: "admin"
|
|
||||||
grafana_password: "{{ grafana_password }}"
|
|
||||||
org_id: 1
|
|
||||||
state: export
|
|
||||||
uid: "000000653"
|
|
||||||
path: "/path/to/dashboards/000000653.json"
|
|
||||||
'''
|
|
||||||
|
|
||||||
RETURN = '''
|
|
||||||
---
|
|
||||||
uid:
|
|
||||||
description: uid or slug of the created / deleted / exported dashboard.
|
|
||||||
returned: success
|
|
||||||
type: str
|
|
||||||
sample: 000000063
|
|
||||||
'''
|
|
||||||
|
|
||||||
import json
|
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
|
||||||
from ansible.module_utils.urls import fetch_url, url_argument_spec
|
|
||||||
from ansible.module_utils.six.moves.urllib.parse import urlencode
|
|
||||||
from ansible.module_utils._text import to_native
|
|
||||||
from ansible.module_utils._text import to_text
|
|
||||||
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
|
|
||||||
class GrafanaAPIException(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class GrafanaMalformedJson(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class GrafanaExportException(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class GrafanaDeleteException(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
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')
|
|
||||||
if info['status'] != 200:
|
|
||||||
raise GrafanaAPIException('Unable to switch to organization %s : %s' % (org_id, info))
|
|
||||||
|
|
||||||
|
|
||||||
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(to_text(r.read()))
|
|
||||||
grafana_version = settings['buildInfo']['version'].split('.')[0]
|
|
||||||
except UnicodeError as e:
|
|
||||||
raise GrafanaAPIException('Unable to decode version string to Unicode')
|
|
||||||
except Exception as e:
|
|
||||||
raise GrafanaAPIException(e)
|
|
||||||
else:
|
|
||||||
raise GrafanaAPIException('Unable to get grafana version : %s' % info)
|
|
||||||
|
|
||||||
return int(grafana_version)
|
|
||||||
|
|
||||||
|
|
||||||
def grafana_folder_exists(module, grafana_url, folder_name, headers):
|
|
||||||
# the 'General' folder is a special case, it's ID is always '0'
|
|
||||||
if folder_name == 'General':
|
|
||||||
return True, 0
|
|
||||||
|
|
||||||
try:
|
|
||||||
r, info = fetch_url(module, '%s/api/folders' % grafana_url, headers=headers, method='GET')
|
|
||||||
|
|
||||||
if info['status'] != 200:
|
|
||||||
raise GrafanaAPIException("Unable to query Grafana API for folders (name: %s): %d" % (folder_name, info['status']))
|
|
||||||
|
|
||||||
folders = json.loads(r.read())
|
|
||||||
|
|
||||||
for folder in folders:
|
|
||||||
if folder['title'] == folder_name:
|
|
||||||
return True, folder['id']
|
|
||||||
except Exception as e:
|
|
||||||
raise GrafanaAPIException(e)
|
|
||||||
|
|
||||||
return False, 0
|
|
||||||
|
|
||||||
|
|
||||||
def grafana_dashboard_exists(module, grafana_url, uid, headers):
|
|
||||||
dashboard_exists = False
|
|
||||||
dashboard = {}
|
|
||||||
|
|
||||||
grafana_version = get_grafana_version(module, grafana_url, headers)
|
|
||||||
if grafana_version >= 5:
|
|
||||||
uri = '%s/api/dashboards/uid/%s' % (grafana_url, uid)
|
|
||||||
else:
|
|
||||||
uri = '%s/api/dashboards/db/%s' % (grafana_url, uid)
|
|
||||||
|
|
||||||
r, info = fetch_url(module, uri, headers=headers, method='GET')
|
|
||||||
|
|
||||||
if info['status'] == 200:
|
|
||||||
dashboard_exists = True
|
|
||||||
try:
|
|
||||||
dashboard = json.loads(r.read())
|
|
||||||
except Exception as e:
|
|
||||||
raise GrafanaAPIException(e)
|
|
||||||
elif info['status'] == 404:
|
|
||||||
dashboard_exists = False
|
|
||||||
else:
|
|
||||||
raise GrafanaAPIException('Unable to get dashboard %s : %s' % (uid, info))
|
|
||||||
|
|
||||||
return dashboard_exists, dashboard
|
|
||||||
|
|
||||||
|
|
||||||
def grafana_dashboard_search(module, grafana_url, folder_id, title, headers):
|
|
||||||
|
|
||||||
# search by title
|
|
||||||
uri = '%s/api/search?%s' % (grafana_url, urlencode({
|
|
||||||
'folderIds': folder_id,
|
|
||||||
'query': title,
|
|
||||||
'type': 'dash-db'
|
|
||||||
}))
|
|
||||||
r, info = fetch_url(module, uri, headers=headers, method='GET')
|
|
||||||
|
|
||||||
if info['status'] == 200:
|
|
||||||
try:
|
|
||||||
dashboards = json.loads(r.read())
|
|
||||||
for d in dashboards:
|
|
||||||
if d['title'] == title:
|
|
||||||
return grafana_dashboard_exists(module, grafana_url, d['uid'], headers)
|
|
||||||
except Exception as e:
|
|
||||||
raise GrafanaAPIException(e)
|
|
||||||
else:
|
|
||||||
raise GrafanaAPIException('Unable to search dashboard %s : %s' % (title, info))
|
|
||||||
|
|
||||||
return False, None
|
|
||||||
|
|
||||||
|
|
||||||
# for comparison, we sometimes need to ignore a few keys
|
|
||||||
def grafana_dashboard_changed(payload, dashboard):
|
|
||||||
# you don't need to set the version, but '0' is incremented to '1' by Grafana's API
|
|
||||||
if payload['dashboard']['version'] == 0:
|
|
||||||
del(payload['dashboard']['version'])
|
|
||||||
del(dashboard['dashboard']['version'])
|
|
||||||
|
|
||||||
# the meta key is not part of the 'payload' ever
|
|
||||||
if 'meta' in dashboard:
|
|
||||||
del(dashboard['meta'])
|
|
||||||
|
|
||||||
# new dashboards don't require an id attribute (or, it can be 'null'), Grafana's API will generate it
|
|
||||||
if payload['dashboard']['id'] is None:
|
|
||||||
del(dashboard['dashboard']['id'])
|
|
||||||
del(payload['dashboard']['id'])
|
|
||||||
|
|
||||||
if payload == dashboard:
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def grafana_create_dashboard(module, data):
|
|
||||||
|
|
||||||
# define data payload for grafana API
|
|
||||||
try:
|
|
||||||
with open(data['path'], 'r') as json_file:
|
|
||||||
payload = json.load(json_file)
|
|
||||||
except Exception as e:
|
|
||||||
raise GrafanaAPIException("Can't load json file %s" % to_native(e))
|
|
||||||
|
|
||||||
# Check that the dashboard JSON is nested under the 'dashboard' key
|
|
||||||
if 'dashboard' not in payload:
|
|
||||||
payload = {'dashboard': payload}
|
|
||||||
|
|
||||||
# define http header
|
|
||||||
headers = grafana_headers(module, data)
|
|
||||||
|
|
||||||
grafana_version = get_grafana_version(module, data['grafana_url'], headers)
|
|
||||||
if grafana_version < 5:
|
|
||||||
if data.get('slug'):
|
|
||||||
uid = data['slug']
|
|
||||||
elif 'meta' in payload and 'slug' in payload['meta']:
|
|
||||||
uid = payload['meta']['slug']
|
|
||||||
else:
|
|
||||||
raise GrafanaMalformedJson('No slug found in json. Needed with grafana < 5')
|
|
||||||
else:
|
|
||||||
if data.get('uid'):
|
|
||||||
uid = data['uid']
|
|
||||||
elif 'uid' in payload['dashboard']:
|
|
||||||
uid = payload['dashboard']['uid']
|
|
||||||
else:
|
|
||||||
uid = None
|
|
||||||
|
|
||||||
result = {}
|
|
||||||
|
|
||||||
# test if the folder exists
|
|
||||||
if grafana_version >= 5:
|
|
||||||
folder_exists, folder_id = grafana_folder_exists(module, data['grafana_url'], data['folder'], headers)
|
|
||||||
if folder_exists is False:
|
|
||||||
result['msg'] = "Dashboard folder '%s' does not exist." % data['folder']
|
|
||||||
result['uid'] = uid
|
|
||||||
result['changed'] = False
|
|
||||||
return result
|
|
||||||
|
|
||||||
payload['folderId'] = folder_id
|
|
||||||
|
|
||||||
# test if dashboard already exists
|
|
||||||
if uid:
|
|
||||||
dashboard_exists, dashboard = grafana_dashboard_exists(
|
|
||||||
module, data['grafana_url'], uid, headers=headers)
|
|
||||||
else:
|
|
||||||
dashboard_exists, dashboard = grafana_dashboard_search(
|
|
||||||
module, data['grafana_url'], folder_id, payload['dashboard']['title'], headers=headers)
|
|
||||||
|
|
||||||
if dashboard_exists is True:
|
|
||||||
if grafana_dashboard_changed(payload, dashboard):
|
|
||||||
# update
|
|
||||||
if 'overwrite' in data and data['overwrite']:
|
|
||||||
payload['overwrite'] = True
|
|
||||||
if 'commit_message' in data and data['commit_message']:
|
|
||||||
payload['message'] = data['commit_message']
|
|
||||||
|
|
||||||
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 grafana_version >= 5:
|
|
||||||
try:
|
|
||||||
dashboard = json.loads(r.read())
|
|
||||||
uid = dashboard['uid']
|
|
||||||
except Exception as e:
|
|
||||||
raise GrafanaAPIException(e)
|
|
||||||
result['uid'] = uid
|
|
||||||
result['msg'] = "Dashboard %s updated" % payload['dashboard']['title']
|
|
||||||
result['changed'] = True
|
|
||||||
else:
|
|
||||||
body = json.loads(info['body'])
|
|
||||||
raise GrafanaAPIException('Unable to update the dashboard %s : %s (HTTP: %d)' %
|
|
||||||
(uid, body['commit_message'], info['status']))
|
|
||||||
else:
|
|
||||||
# unchanged
|
|
||||||
result['uid'] = uid
|
|
||||||
result['msg'] = "Dashboard %s unchanged." % payload['dashboard']['title']
|
|
||||||
result['changed'] = False
|
|
||||||
else:
|
|
||||||
# create
|
|
||||||
if folder_exists is True:
|
|
||||||
payload['folderId'] = folder_id
|
|
||||||
|
|
||||||
r, info = fetch_url(module, '%s/api/dashboards/db' % data['grafana_url'],
|
|
||||||
data=json.dumps(payload), headers=headers, method='POST')
|
|
||||||
if info['status'] == 200:
|
|
||||||
result['msg'] = "Dashboard %s created" % payload['dashboard']['title']
|
|
||||||
result['changed'] = True
|
|
||||||
if grafana_version >= 5:
|
|
||||||
try:
|
|
||||||
dashboard = json.loads(r.read())
|
|
||||||
uid = dashboard['uid']
|
|
||||||
except Exception as e:
|
|
||||||
raise GrafanaAPIException(e)
|
|
||||||
result['uid'] = uid
|
|
||||||
else:
|
|
||||||
raise GrafanaAPIException('Unable to create the new dashboard %s : %s - %s.' %
|
|
||||||
(payload['dashboard']['title'], info['status'], info))
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def grafana_delete_dashboard(module, data):
|
|
||||||
|
|
||||||
# define http headers
|
|
||||||
headers = grafana_headers(module, data)
|
|
||||||
|
|
||||||
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:
|
|
||||||
if data.get('uid'):
|
|
||||||
uid = data['uid']
|
|
||||||
else:
|
|
||||||
raise GrafanaDeleteException('No uid specified %s')
|
|
||||||
|
|
||||||
# test if dashboard already exists
|
|
||||||
dashboard_exists, dashboard = grafana_dashboard_exists(module, data['grafana_url'], uid, headers=headers)
|
|
||||||
|
|
||||||
result = {}
|
|
||||||
if dashboard_exists is True:
|
|
||||||
# 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:
|
|
||||||
result['msg'] = "Dashboard %s deleted" % uid
|
|
||||||
result['changed'] = True
|
|
||||||
result['uid'] = uid
|
|
||||||
else:
|
|
||||||
raise GrafanaAPIException('Unable to update the dashboard %s : %s' % (uid, info))
|
|
||||||
else:
|
|
||||||
# dashboard does not exist, do nothing
|
|
||||||
result = {'msg': "Dashboard %s does not exist." % uid,
|
|
||||||
'changed': False,
|
|
||||||
'uid': uid}
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def grafana_export_dashboard(module, data):
|
|
||||||
|
|
||||||
# define http headers
|
|
||||||
headers = grafana_headers(module, data)
|
|
||||||
|
|
||||||
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:
|
|
||||||
if data.get('uid'):
|
|
||||||
uid = data['uid']
|
|
||||||
else:
|
|
||||||
raise GrafanaExportException('No uid specified')
|
|
||||||
|
|
||||||
# test if dashboard already exists
|
|
||||||
dashboard_exists, dashboard = grafana_dashboard_exists(module, data['grafana_url'], uid, headers=headers)
|
|
||||||
|
|
||||||
if dashboard_exists is True:
|
|
||||||
try:
|
|
||||||
with open(data['path'], 'w') as f:
|
|
||||||
f.write(json.dumps(dashboard))
|
|
||||||
except Exception as e:
|
|
||||||
raise GrafanaExportException("Can't write json file : %s" % to_native(e))
|
|
||||||
result = {'msg': "Dashboard %s exported to %s" % (uid, data['path']),
|
|
||||||
'uid': uid,
|
|
||||||
'changed': True}
|
|
||||||
else:
|
|
||||||
result = {'msg': "Dashboard %s does not exist." % uid,
|
|
||||||
'uid': uid,
|
|
||||||
'changed': False}
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
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'),
|
|
||||||
folder=dict(type='str', default='General'),
|
|
||||||
uid=dict(type='str'),
|
|
||||||
slug=dict(type='str'),
|
|
||||||
path=dict(type='str'),
|
|
||||||
overwrite=dict(type='bool', default=False),
|
|
||||||
commit_message=dict(type='str', aliases=['message'], deprecated_aliases=[dict(name='message', version='2.14')]),
|
|
||||||
)
|
|
||||||
module = AnsibleModule(
|
|
||||||
argument_spec=argument_spec,
|
|
||||||
supports_check_mode=False,
|
|
||||||
required_together=[['url_username', 'url_password', 'org_id']],
|
|
||||||
mutually_exclusive=[['grafana_user', 'grafana_api_key'], ['uid', 'slug']],
|
|
||||||
)
|
|
||||||
|
|
||||||
if 'message' in module.params:
|
|
||||||
module.fail_json(msg="'message' is reserved keyword, please change this parameter to 'commit_message'")
|
|
||||||
|
|
||||||
try:
|
|
||||||
if module.params['state'] == 'present':
|
|
||||||
result = grafana_create_dashboard(module, module.params)
|
|
||||||
elif module.params['state'] == 'absent':
|
|
||||||
result = grafana_delete_dashboard(module, module.params)
|
|
||||||
else:
|
|
||||||
result = grafana_export_dashboard(module, module.params)
|
|
||||||
except GrafanaAPIException as e:
|
|
||||||
module.fail_json(
|
|
||||||
failed=True,
|
|
||||||
msg="error : %s" % to_native(e)
|
|
||||||
)
|
|
||||||
return
|
|
||||||
except GrafanaMalformedJson as e:
|
|
||||||
module.fail_json(
|
|
||||||
failed=True,
|
|
||||||
msg="error : %s" % to_native(e)
|
|
||||||
)
|
|
||||||
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:
|
|
||||||
module.fail_json(
|
|
||||||
failed=True,
|
|
||||||
msg="error : Can't export dashboard : %s" % to_native(e)
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
module.exit_json(
|
|
||||||
failed=False,
|
|
||||||
**result
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
@ -1,683 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# Copyright: (c) 2017, Thierry Sallé (@seuf)
|
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {
|
|
||||||
'status': ['preview'],
|
|
||||||
'supported_by': 'community',
|
|
||||||
'metadata_version': '1.1'
|
|
||||||
}
|
|
||||||
|
|
||||||
DOCUMENTATION = '''
|
|
||||||
---
|
|
||||||
module: grafana_datasource
|
|
||||||
author:
|
|
||||||
- Thierry Sallé (@seuf)
|
|
||||||
- Martin Wang (@martinwangjian)
|
|
||||||
version_added: "2.5"
|
|
||||||
short_description: Manage Grafana datasources
|
|
||||||
description:
|
|
||||||
- Create/update/delete Grafana datasources via API.
|
|
||||||
options:
|
|
||||||
grafana_url:
|
|
||||||
description:
|
|
||||||
- The Grafana URL.
|
|
||||||
required: true
|
|
||||||
name:
|
|
||||||
description:
|
|
||||||
- The name of the datasource.
|
|
||||||
required: true
|
|
||||||
ds_type:
|
|
||||||
description:
|
|
||||||
- The type of the datasource.
|
|
||||||
required: true
|
|
||||||
choices: [ graphite, prometheus, elasticsearch, influxdb, opentsdb, mysql, postgres, cloudwatch, alexanderzobnin-zabbix-datasource]
|
|
||||||
url:
|
|
||||||
description:
|
|
||||||
- The URL of the datasource.
|
|
||||||
required: true
|
|
||||||
aliases: [ ds_url ]
|
|
||||||
access:
|
|
||||||
description:
|
|
||||||
- The access mode for this datasource.
|
|
||||||
choices: [ direct, proxy ]
|
|
||||||
default: proxy
|
|
||||||
url_username:
|
|
||||||
description:
|
|
||||||
- The Grafana API user.
|
|
||||||
default: admin
|
|
||||||
aliases: [ grafana_user ]
|
|
||||||
version_added: 2.7
|
|
||||||
url_password:
|
|
||||||
description:
|
|
||||||
- The Grafana API password.
|
|
||||||
default: admin
|
|
||||||
aliases: [ grafana_password ]
|
|
||||||
version_added: 2.7
|
|
||||||
grafana_api_key:
|
|
||||||
description:
|
|
||||||
- The Grafana API key.
|
|
||||||
- If set, C(grafana_user) and C(grafana_password) will be ignored.
|
|
||||||
database:
|
|
||||||
description:
|
|
||||||
- Name of the database for the datasource.
|
|
||||||
- This options is required when the C(ds_type) is C(influxdb), C(elasticsearch) (index name), C(mysql) or C(postgres).
|
|
||||||
required: false
|
|
||||||
user:
|
|
||||||
description:
|
|
||||||
- The datasource login user for influxdb datasources.
|
|
||||||
password:
|
|
||||||
description:
|
|
||||||
- The datasource password
|
|
||||||
basic_auth_user:
|
|
||||||
description:
|
|
||||||
- The datasource basic auth user.
|
|
||||||
- Setting this option with basic_auth_password will enable basic auth.
|
|
||||||
basic_auth_password:
|
|
||||||
description:
|
|
||||||
- The datasource basic auth password, when C(basic auth) is C(yes).
|
|
||||||
with_credentials:
|
|
||||||
description:
|
|
||||||
- Whether credentials such as cookies or auth headers should be sent with cross-site requests.
|
|
||||||
type: bool
|
|
||||||
default: 'no'
|
|
||||||
tls_client_cert:
|
|
||||||
description:
|
|
||||||
- The client TLS certificate.
|
|
||||||
- If C(tls_client_cert) and C(tls_client_key) are set, this will enable TLS authentication.
|
|
||||||
- Starts with ----- BEGIN CERTIFICATE -----
|
|
||||||
tls_client_key:
|
|
||||||
description:
|
|
||||||
- The client TLS private key
|
|
||||||
- Starts with ----- BEGIN RSA PRIVATE KEY -----
|
|
||||||
tls_ca_cert:
|
|
||||||
description:
|
|
||||||
- The TLS CA certificate for self signed certificates.
|
|
||||||
- Only used when C(tls_client_cert) and C(tls_client_key) are set.
|
|
||||||
tls_skip_verify:
|
|
||||||
description:
|
|
||||||
- Skip the TLS datasource certificate verification.
|
|
||||||
type: bool
|
|
||||||
default: False
|
|
||||||
version_added: 2.6
|
|
||||||
is_default:
|
|
||||||
description:
|
|
||||||
- Make this datasource the default one.
|
|
||||||
type: bool
|
|
||||||
default: 'no'
|
|
||||||
org_id:
|
|
||||||
description:
|
|
||||||
- Grafana Organisation ID in which the datasource should be created.
|
|
||||||
- Not used when C(grafana_api_key) is set, because the C(grafana_api_key) only belong to one organisation.
|
|
||||||
default: 1
|
|
||||||
state:
|
|
||||||
description:
|
|
||||||
- Status of the datasource
|
|
||||||
choices: [ absent, present ]
|
|
||||||
default: present
|
|
||||||
es_version:
|
|
||||||
description:
|
|
||||||
- Elasticsearch version (for C(ds_type = elasticsearch) only)
|
|
||||||
- Version 56 is for elasticsearch 5.6+ where you can specify the C(max_concurrent_shard_requests) option.
|
|
||||||
choices: [ 2, 5, 56 ]
|
|
||||||
default: 5
|
|
||||||
max_concurrent_shard_requests:
|
|
||||||
description:
|
|
||||||
- Starting with elasticsearch 5.6, you can specify the max concurrent shard per requests.
|
|
||||||
default: 256
|
|
||||||
time_field:
|
|
||||||
description:
|
|
||||||
- Name of the time field in elasticsearch ds.
|
|
||||||
- For example C(@timestamp)
|
|
||||||
default: timestamp
|
|
||||||
time_interval:
|
|
||||||
description:
|
|
||||||
- Minimum group by interval for C(influxdb) or C(elasticsearch) datasources.
|
|
||||||
- for example C(>10s)
|
|
||||||
interval:
|
|
||||||
description:
|
|
||||||
- For elasticsearch C(ds_type), this is the index pattern used.
|
|
||||||
choices: ['', 'Hourly', 'Daily', 'Weekly', 'Monthly', 'Yearly']
|
|
||||||
tsdb_version:
|
|
||||||
description:
|
|
||||||
- The opentsdb version.
|
|
||||||
- Use C(1) for <=2.1, C(2) for ==2.2, C(3) for ==2.3.
|
|
||||||
choices: [ 1, 2, 3 ]
|
|
||||||
default: 1
|
|
||||||
tsdb_resolution:
|
|
||||||
description:
|
|
||||||
- The opentsdb time resolution.
|
|
||||||
choices: [ millisecond, second ]
|
|
||||||
default: second
|
|
||||||
sslmode:
|
|
||||||
description:
|
|
||||||
- SSL mode for C(postgres) datasource type.
|
|
||||||
choices: [ disable, require, verify-ca, verify-full ]
|
|
||||||
trends:
|
|
||||||
required: false
|
|
||||||
description:
|
|
||||||
- Use trends or not for zabbix datasource type
|
|
||||||
type: bool
|
|
||||||
version_added: 2.6
|
|
||||||
client_cert:
|
|
||||||
required: false
|
|
||||||
description:
|
|
||||||
- TLS certificate path used by ansible to query grafana api
|
|
||||||
version_added: 2.8
|
|
||||||
client_key:
|
|
||||||
required: false
|
|
||||||
description:
|
|
||||||
- TLS private key path used by ansible to query grafana api
|
|
||||||
version_added: 2.8
|
|
||||||
validate_certs:
|
|
||||||
description:
|
|
||||||
- Whether to validate the Grafana certificate.
|
|
||||||
type: bool
|
|
||||||
default: 'yes'
|
|
||||||
use_proxy:
|
|
||||||
description:
|
|
||||||
- Boolean of whether or not to use proxy.
|
|
||||||
default: 'yes'
|
|
||||||
type: bool
|
|
||||||
version_added: 2.8
|
|
||||||
aws_auth_type:
|
|
||||||
description:
|
|
||||||
- Type for AWS authentication for CloudWatch datasource type (authType of grafana api)
|
|
||||||
default: 'keys'
|
|
||||||
choices: [ keys, credentials, arn ]
|
|
||||||
version_added: 2.8
|
|
||||||
aws_default_region:
|
|
||||||
description:
|
|
||||||
- AWS default region for CloudWatch datasource type
|
|
||||||
default: 'us-east-1'
|
|
||||||
choices: [
|
|
||||||
ap-northeast-1, ap-northeast-2, ap-southeast-1, ap-southeast-2, ap-south-1,
|
|
||||||
ca-central-1,
|
|
||||||
cn-north-1, cn-northwest-1,
|
|
||||||
eu-central-1, eu-west-1, eu-west-2, eu-west-3,
|
|
||||||
sa-east-1,
|
|
||||||
us-east-1, us-east-2, us-gov-west-1, us-west-1, us-west-2
|
|
||||||
]
|
|
||||||
version_added: 2.8
|
|
||||||
aws_credentials_profile:
|
|
||||||
description:
|
|
||||||
- Profile for AWS credentials for CloudWatch datasource type when C(aws_auth_type) is C(credentials)
|
|
||||||
default: ''
|
|
||||||
required: false
|
|
||||||
version_added: 2.8
|
|
||||||
aws_access_key:
|
|
||||||
description:
|
|
||||||
- AWS access key for CloudWatch datasource type when C(aws_auth_type) is C(keys)
|
|
||||||
default: ''
|
|
||||||
required: false
|
|
||||||
version_added: 2.8
|
|
||||||
aws_secret_key:
|
|
||||||
description:
|
|
||||||
- AWS secret key for CloudWatch datasource type when C(aws_auth_type) is C(keys)
|
|
||||||
default: ''
|
|
||||||
required: false
|
|
||||||
version_added: 2.8
|
|
||||||
aws_assume_role_arn:
|
|
||||||
description:
|
|
||||||
- AWS IAM role arn to assume for CloudWatch datasource type when C(aws_auth_type) is C(arn)
|
|
||||||
default: ''
|
|
||||||
required: false
|
|
||||||
version_added: 2.8
|
|
||||||
aws_custom_metrics_namespaces:
|
|
||||||
description:
|
|
||||||
- Namespaces of Custom Metrics for CloudWatch datasource type
|
|
||||||
default: ''
|
|
||||||
required: false
|
|
||||||
version_added: 2.8
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
---
|
|
||||||
- name: Create elasticsearch datasource
|
|
||||||
grafana_datasource:
|
|
||||||
name: "datasource-elastic"
|
|
||||||
grafana_url: "https://grafana.company.com"
|
|
||||||
grafana_user: "admin"
|
|
||||||
grafana_password: "xxxxxx"
|
|
||||||
org_id: "1"
|
|
||||||
ds_type: "elasticsearch"
|
|
||||||
ds_url: "https://elastic.company.com:9200"
|
|
||||||
database: "[logstash_]YYYY.MM.DD"
|
|
||||||
basic_auth_user: "grafana"
|
|
||||||
basic_auth_password: "******"
|
|
||||||
time_field: "@timestamp"
|
|
||||||
time_interval: "1m"
|
|
||||||
interval: "Daily"
|
|
||||||
es_version: 56
|
|
||||||
max_concurrent_shard_requests: 42
|
|
||||||
tls_ca_cert: "/etc/ssl/certs/ca.pem"
|
|
||||||
|
|
||||||
- name: Create influxdb datasource
|
|
||||||
grafana_datasource:
|
|
||||||
name: "datasource-influxdb"
|
|
||||||
grafana_url: "https://grafana.company.com"
|
|
||||||
grafana_user: "admin"
|
|
||||||
grafana_password: "xxxxxx"
|
|
||||||
org_id: "1"
|
|
||||||
ds_type: "influxdb"
|
|
||||||
ds_url: "https://influx.company.com:8086"
|
|
||||||
database: "telegraf"
|
|
||||||
time_interval: ">10s"
|
|
||||||
tls_ca_cert: "/etc/ssl/certs/ca.pem"
|
|
||||||
|
|
||||||
- name: Create postgres datasource
|
|
||||||
grafana_datasource:
|
|
||||||
name: "datasource-postgres"
|
|
||||||
grafana_url: "https://grafana.company.com"
|
|
||||||
grafana_user: "admin"
|
|
||||||
grafana_password: "xxxxxx"
|
|
||||||
org_id: "1"
|
|
||||||
ds_type: "postgres"
|
|
||||||
ds_url: "postgres.company.com:5432"
|
|
||||||
database: "db"
|
|
||||||
user: "postgres"
|
|
||||||
password: "iampgroot"
|
|
||||||
sslmode: "verify-full"
|
|
||||||
|
|
||||||
- name: Create cloudwatch datasource
|
|
||||||
grafana_datasource:
|
|
||||||
name: "datasource-cloudwatch"
|
|
||||||
grafana_url: "https://grafana.company.com"
|
|
||||||
grafana_user: "admin"
|
|
||||||
grafana_password: "xxxxxx"
|
|
||||||
org_id: "1"
|
|
||||||
ds_type: "cloudwatch"
|
|
||||||
url: "http://monitoring.us-west-1.amazonaws.com"
|
|
||||||
aws_auth_type: "keys"
|
|
||||||
aws_default_region: "us-west-1"
|
|
||||||
aws_access_key: "speakFriendAndEnter"
|
|
||||||
aws_secret_key: "mel10n"
|
|
||||||
aws_custom_metrics_namespaces: "n1,n2"
|
|
||||||
'''
|
|
||||||
|
|
||||||
RETURN = '''
|
|
||||||
---
|
|
||||||
name:
|
|
||||||
description: name of the datasource created.
|
|
||||||
returned: success
|
|
||||||
type: str
|
|
||||||
sample: test-ds
|
|
||||||
id:
|
|
||||||
description: Id of the datasource
|
|
||||||
returned: success
|
|
||||||
type: int
|
|
||||||
sample: 42
|
|
||||||
before:
|
|
||||||
description: datasource returned by grafana api
|
|
||||||
returned: changed
|
|
||||||
type: dict
|
|
||||||
sample: { "access": "proxy",
|
|
||||||
"basicAuth": false,
|
|
||||||
"database": "test_*",
|
|
||||||
"id": 1035,
|
|
||||||
"isDefault": false,
|
|
||||||
"jsonData": {
|
|
||||||
"esVersion": 5,
|
|
||||||
"timeField": "@timestamp",
|
|
||||||
"timeInterval": "1m",
|
|
||||||
},
|
|
||||||
"name": "grafana_datasource_test",
|
|
||||||
"orgId": 1,
|
|
||||||
"type": "elasticsearch",
|
|
||||||
"url": "http://elastic.company.com:9200",
|
|
||||||
"user": "",
|
|
||||||
"password": "",
|
|
||||||
"withCredentials": false }
|
|
||||||
after:
|
|
||||||
description: datasource updated by module
|
|
||||||
returned: changed
|
|
||||||
type: dict
|
|
||||||
sample: { "access": "proxy",
|
|
||||||
"basicAuth": false,
|
|
||||||
"database": "test_*",
|
|
||||||
"id": 1035,
|
|
||||||
"isDefault": false,
|
|
||||||
"jsonData": {
|
|
||||||
"esVersion": 5,
|
|
||||||
"timeField": "@timestamp",
|
|
||||||
"timeInterval": "10s",
|
|
||||||
},
|
|
||||||
"name": "grafana_datasource_test",
|
|
||||||
"orgId": 1,
|
|
||||||
"type": "elasticsearch",
|
|
||||||
"url": "http://elastic.company.com:9200",
|
|
||||||
"user": "",
|
|
||||||
"password": "",
|
|
||||||
"withCredentials": false }
|
|
||||||
'''
|
|
||||||
|
|
||||||
import json
|
|
||||||
import base64
|
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
|
||||||
from ansible.module_utils.six.moves.urllib.parse import quote
|
|
||||||
from ansible.module_utils.urls import fetch_url, url_argument_spec
|
|
||||||
from ansible.module_utils._text import to_text
|
|
||||||
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
|
|
||||||
class GrafanaAPIException(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def grafana_switch_organisation(module, grafana_url, org_id, headers):
|
|
||||||
r, info = fetch_url(module, '%s/api/user/using/%d' % (grafana_url, org_id), headers=headers, method='POST')
|
|
||||||
if info['status'] != 200:
|
|
||||||
raise GrafanaAPIException('Unable to switch to organization %s : %s' % (org_id, info))
|
|
||||||
|
|
||||||
|
|
||||||
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 grafana_datasource_exists(module, grafana_url, name, headers):
|
|
||||||
datasource_exists = False
|
|
||||||
ds = {}
|
|
||||||
r, info = fetch_url(module, '%s/api/datasources/name/%s' % (grafana_url, quote(name)), headers=headers, method='GET')
|
|
||||||
if info['status'] == 200:
|
|
||||||
datasource_exists = True
|
|
||||||
ds = json.loads(to_text(r.read(), errors='surrogate_or_strict'))
|
|
||||||
elif info['status'] == 404:
|
|
||||||
datasource_exists = False
|
|
||||||
else:
|
|
||||||
raise GrafanaAPIException('Unable to get datasource %s : %s' % (name, info))
|
|
||||||
|
|
||||||
return datasource_exists, ds
|
|
||||||
|
|
||||||
|
|
||||||
def grafana_create_datasource(module, data):
|
|
||||||
|
|
||||||
# define data payload for grafana API
|
|
||||||
payload = {'orgId': data['org_id'],
|
|
||||||
'name': data['name'],
|
|
||||||
'type': data['ds_type'],
|
|
||||||
'access': data['access'],
|
|
||||||
'url': data['url'],
|
|
||||||
'database': data['database'],
|
|
||||||
'withCredentials': data['with_credentials'],
|
|
||||||
'isDefault': data['is_default'],
|
|
||||||
'user': data['user'],
|
|
||||||
'password': data['password']}
|
|
||||||
|
|
||||||
# define basic auth
|
|
||||||
if 'basic_auth_user' in data and data['basic_auth_user'] and 'basic_auth_password' in data and data['basic_auth_password']:
|
|
||||||
payload['basicAuth'] = True
|
|
||||||
payload['basicAuthUser'] = data['basic_auth_user']
|
|
||||||
payload['basicAuthPassword'] = data['basic_auth_password']
|
|
||||||
else:
|
|
||||||
payload['basicAuth'] = False
|
|
||||||
|
|
||||||
# define tls auth
|
|
||||||
json_data = {}
|
|
||||||
if data.get('tls_client_cert') and data.get('tls_client_key'):
|
|
||||||
json_data['tlsAuth'] = True
|
|
||||||
if data.get('tls_ca_cert'):
|
|
||||||
payload['secureJsonData'] = {
|
|
||||||
'tlsCACert': data['tls_ca_cert'],
|
|
||||||
'tlsClientCert': data['tls_client_cert'],
|
|
||||||
'tlsClientKey': data['tls_client_key']
|
|
||||||
}
|
|
||||||
json_data['tlsAuthWithCACert'] = True
|
|
||||||
else:
|
|
||||||
payload['secureJsonData'] = {
|
|
||||||
'tlsClientCert': data['tls_client_cert'],
|
|
||||||
'tlsClientKey': data['tls_client_key']
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
json_data['tlsAuth'] = False
|
|
||||||
json_data['tlsAuthWithCACert'] = False
|
|
||||||
if data.get('tls_ca_cert'):
|
|
||||||
payload['secureJsonData'] = {
|
|
||||||
'tlsCACert': data['tls_ca_cert']
|
|
||||||
}
|
|
||||||
|
|
||||||
if data.get('tls_skip_verify'):
|
|
||||||
json_data['tlsSkipVerify'] = True
|
|
||||||
|
|
||||||
# datasource type related parameters
|
|
||||||
if data['ds_type'] == 'elasticsearch':
|
|
||||||
json_data['esVersion'] = data['es_version']
|
|
||||||
json_data['timeField'] = data['time_field']
|
|
||||||
if data.get('interval'):
|
|
||||||
json_data['interval'] = data['interval']
|
|
||||||
if data['es_version'] >= 56:
|
|
||||||
json_data['maxConcurrentShardRequests'] = data['max_concurrent_shard_requests']
|
|
||||||
|
|
||||||
if data['ds_type'] == 'elasticsearch' or data['ds_type'] == 'influxdb':
|
|
||||||
if data.get('time_interval'):
|
|
||||||
json_data['timeInterval'] = data['time_interval']
|
|
||||||
|
|
||||||
if data['ds_type'] == 'opentsdb':
|
|
||||||
json_data['tsdbVersion'] = data['tsdb_version']
|
|
||||||
if data['tsdb_resolution'] == 'second':
|
|
||||||
json_data['tsdbResolution'] = 1
|
|
||||||
else:
|
|
||||||
json_data['tsdbResolution'] = 2
|
|
||||||
|
|
||||||
if data['ds_type'] == 'postgres':
|
|
||||||
json_data['sslmode'] = data['sslmode']
|
|
||||||
if data.get('password'):
|
|
||||||
payload['secureJsonData'] = {'password': data.get('password')}
|
|
||||||
|
|
||||||
if data['ds_type'] == 'alexanderzobnin-zabbix-datasource':
|
|
||||||
if data.get('trends'):
|
|
||||||
json_data['trends'] = True
|
|
||||||
|
|
||||||
if data['ds_type'] == 'cloudwatch':
|
|
||||||
if data.get('aws_credentials_profle'):
|
|
||||||
payload['database'] = data.get('aws_credentials_profile')
|
|
||||||
|
|
||||||
json_data['authType'] = data['aws_auth_type']
|
|
||||||
json_data['defaultRegion'] = data['aws_default_region']
|
|
||||||
|
|
||||||
if data.get('aws_custom_metrics_namespaces'):
|
|
||||||
json_data['customMetricsNamespaces'] = data.get('aws_custom_metrics_namespaces')
|
|
||||||
if data.get('aws_assume_role_arn'):
|
|
||||||
json_data['assumeRoleArn'] = data.get('aws_assume_role_arn')
|
|
||||||
if data.get('aws_access_key') and data.get('aws_secret_key'):
|
|
||||||
payload['secureJsonData'] = {'accessKey': data.get('aws_access_key'), 'secretKey': data.get('aws_secret_key')}
|
|
||||||
|
|
||||||
payload['jsonData'] = json_data
|
|
||||||
|
|
||||||
# define http header
|
|
||||||
headers = grafana_headers(module, data)
|
|
||||||
|
|
||||||
# test if datasource already exists
|
|
||||||
datasource_exists, ds = grafana_datasource_exists(module, data['grafana_url'], data['name'], headers=headers)
|
|
||||||
|
|
||||||
result = {}
|
|
||||||
if datasource_exists is True:
|
|
||||||
del ds['typeLogoUrl']
|
|
||||||
if ds.get('version'):
|
|
||||||
del ds['version']
|
|
||||||
if ds.get('readOnly'):
|
|
||||||
del ds['readOnly']
|
|
||||||
if ds['basicAuth'] is False:
|
|
||||||
del ds['basicAuthUser']
|
|
||||||
del ds['basicAuthPassword']
|
|
||||||
if 'jsonData' in ds:
|
|
||||||
if 'tlsAuth' in ds['jsonData'] and ds['jsonData']['tlsAuth'] is False:
|
|
||||||
del ds['secureJsonFields']
|
|
||||||
if 'tlsAuth' not in ds['jsonData']:
|
|
||||||
del ds['secureJsonFields']
|
|
||||||
payload['id'] = ds['id']
|
|
||||||
if ds == payload:
|
|
||||||
# unchanged
|
|
||||||
result['name'] = data['name']
|
|
||||||
result['id'] = ds['id']
|
|
||||||
result['msg'] = "Datasource %s unchanged." % data['name']
|
|
||||||
result['changed'] = False
|
|
||||||
else:
|
|
||||||
# update
|
|
||||||
r, info = fetch_url(module, '%s/api/datasources/%d' % (data['grafana_url'], ds['id']), data=json.dumps(payload), headers=headers, method='PUT')
|
|
||||||
if info['status'] == 200:
|
|
||||||
res = json.loads(to_text(r.read(), errors='surrogate_or_strict'))
|
|
||||||
result['name'] = data['name']
|
|
||||||
result['id'] = ds['id']
|
|
||||||
result['before'] = ds
|
|
||||||
result['after'] = payload
|
|
||||||
result['msg'] = "Datasource %s updated %s" % (data['name'], res['message'])
|
|
||||||
result['changed'] = True
|
|
||||||
else:
|
|
||||||
raise GrafanaAPIException('Unable to update the datasource id %d : %s' % (ds['id'], info))
|
|
||||||
else:
|
|
||||||
# create
|
|
||||||
r, info = fetch_url(module, '%s/api/datasources' % data['grafana_url'], data=json.dumps(payload), headers=headers, method='POST')
|
|
||||||
if info['status'] == 200:
|
|
||||||
res = json.loads(to_text(r.read(), errors='surrogate_or_strict'))
|
|
||||||
result['msg'] = "Datasource %s created : %s" % (data['name'], res['message'])
|
|
||||||
result['changed'] = True
|
|
||||||
result['name'] = data['name']
|
|
||||||
result['id'] = res['id']
|
|
||||||
else:
|
|
||||||
raise GrafanaAPIException('Unable to create the new datasource %s : %s - %s.' % (data['name'], info['status'], info))
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def grafana_delete_datasource(module, data):
|
|
||||||
|
|
||||||
headers = grafana_headers(module, data)
|
|
||||||
|
|
||||||
# test if datasource already exists
|
|
||||||
datasource_exists, ds = grafana_datasource_exists(module, data['grafana_url'], data['name'], headers=headers)
|
|
||||||
|
|
||||||
result = {}
|
|
||||||
if datasource_exists is True:
|
|
||||||
# delete
|
|
||||||
r, info = fetch_url(module, '%s/api/datasources/name/%s' % (data['grafana_url'], quote(data['name'])), headers=headers, method='DELETE')
|
|
||||||
if info['status'] == 200:
|
|
||||||
res = json.loads(to_text(r.read(), errors='surrogate_or_strict'))
|
|
||||||
result['msg'] = "Datasource %s deleted : %s" % (data['name'], res['message'])
|
|
||||||
result['changed'] = True
|
|
||||||
result['name'] = data['name']
|
|
||||||
result['id'] = 0
|
|
||||||
else:
|
|
||||||
raise GrafanaAPIException('Unable to update the datasource id %s : %s' % (ds['id'], info))
|
|
||||||
else:
|
|
||||||
# datasource does not exist, do nothing
|
|
||||||
result = {'msg': "Datasource %s does not exist." % data['name'],
|
|
||||||
'changed': False,
|
|
||||||
'id': 0,
|
|
||||||
'name': data['name']}
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
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(
|
|
||||||
name=dict(required=True, type='str'),
|
|
||||||
state=dict(choices=['present', 'absent'],
|
|
||||||
default='present'),
|
|
||||||
grafana_url=dict(type='str', required=True),
|
|
||||||
url_username=dict(aliases=['grafana_user'], default='admin'),
|
|
||||||
url_password=dict(aliases=['grafana_password'], default='admin', no_log=True),
|
|
||||||
ds_type=dict(choices=['graphite',
|
|
||||||
'prometheus',
|
|
||||||
'elasticsearch',
|
|
||||||
'influxdb',
|
|
||||||
'opentsdb',
|
|
||||||
'mysql',
|
|
||||||
'postgres',
|
|
||||||
'cloudwatch',
|
|
||||||
'alexanderzobnin-zabbix-datasource']),
|
|
||||||
url=dict(required=True, type='str', aliases=['ds_url']),
|
|
||||||
access=dict(default='proxy', choices=['proxy', 'direct']),
|
|
||||||
grafana_api_key=dict(type='str', no_log=True),
|
|
||||||
database=dict(type='str'),
|
|
||||||
user=dict(default='', type='str'),
|
|
||||||
password=dict(default='', no_log=True, type='str'),
|
|
||||||
basic_auth_user=dict(type='str'),
|
|
||||||
basic_auth_password=dict(type='str', no_log=True),
|
|
||||||
with_credentials=dict(default=False, type='bool'),
|
|
||||||
tls_client_cert=dict(type='str', no_log=True),
|
|
||||||
tls_client_key=dict(type='str', no_log=True),
|
|
||||||
tls_ca_cert=dict(type='str', no_log=True),
|
|
||||||
tls_skip_verify=dict(type='bool', default=False),
|
|
||||||
is_default=dict(default=False, type='bool'),
|
|
||||||
org_id=dict(default=1, type='int'),
|
|
||||||
es_version=dict(type='int', default=5, choices=[2, 5, 56]),
|
|
||||||
max_concurrent_shard_requests=dict(type='int', default=256),
|
|
||||||
time_field=dict(default='@timestamp', type='str'),
|
|
||||||
time_interval=dict(type='str'),
|
|
||||||
interval=dict(type='str', choices=['', 'Hourly', 'Daily', 'Weekly', 'Monthly', 'Yearly'], default=''),
|
|
||||||
tsdb_version=dict(type='int', default=1, choices=[1, 2, 3]),
|
|
||||||
tsdb_resolution=dict(type='str', default='second', choices=['second', 'millisecond']),
|
|
||||||
sslmode=dict(default='disable', choices=['disable', 'require', 'verify-ca', 'verify-full']),
|
|
||||||
trends=dict(default=False, type='bool'),
|
|
||||||
aws_auth_type=dict(default='keys', choices=['keys', 'credentials', 'arn']),
|
|
||||||
aws_default_region=dict(default='us-east-1', choices=['ap-northeast-1', 'ap-northeast-2', 'ap-southeast-1', 'ap-southeast-2', 'ap-south-1',
|
|
||||||
'ca-central-1',
|
|
||||||
'cn-north-1', 'cn-northwest-1',
|
|
||||||
'eu-central-1', 'eu-west-1', 'eu-west-2', 'eu-west-3',
|
|
||||||
'sa-east-1',
|
|
||||||
'us-east-1', 'us-east-2', 'us-gov-west-1', 'us-west-1', 'us-west-2']),
|
|
||||||
aws_access_key=dict(default='', no_log=True, type='str'),
|
|
||||||
aws_secret_key=dict(default='', no_log=True, type='str'),
|
|
||||||
aws_credentials_profile=dict(default='', type='str'),
|
|
||||||
aws_assume_role_arn=dict(default='', type='str'),
|
|
||||||
aws_custom_metrics_namespaces=dict(type='str'),
|
|
||||||
)
|
|
||||||
|
|
||||||
module = AnsibleModule(
|
|
||||||
argument_spec=argument_spec,
|
|
||||||
supports_check_mode=False,
|
|
||||||
required_together=[['url_username', 'url_password', 'org_id'], ['tls_client_cert', 'tls_client_key']],
|
|
||||||
mutually_exclusive=[['url_username', 'grafana_api_key'], ['tls_ca_cert', 'tls_skip_verify']],
|
|
||||||
required_if=[
|
|
||||||
['ds_type', 'opentsdb', ['tsdb_version', 'tsdb_resolution']],
|
|
||||||
['ds_type', 'influxdb', ['database']],
|
|
||||||
['ds_type', 'elasticsearch', ['database', 'es_version', 'time_field', 'interval']],
|
|
||||||
['ds_type', 'mysql', ['database']],
|
|
||||||
['ds_type', 'postgres', ['database', 'sslmode']],
|
|
||||||
['ds_type', 'cloudwatch', ['aws_auth_type', 'aws_default_region']],
|
|
||||||
['es_version', 56, ['max_concurrent_shard_requests']]
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
if module.params['state'] == 'present':
|
|
||||||
result = grafana_create_datasource(module, module.params)
|
|
||||||
else:
|
|
||||||
result = grafana_delete_datasource(module, module.params)
|
|
||||||
except GrafanaAPIException as e:
|
|
||||||
module.fail_json(
|
|
||||||
failed=True,
|
|
||||||
msg="error %s : %s " % (type(e), e)
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
module.exit_json(
|
|
||||||
failed=False,
|
|
||||||
**result
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
@ -1,257 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# Copyright: (c) 2017, Thierry Sallé (@seuf)
|
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {
|
|
||||||
'status': ['preview'],
|
|
||||||
'supported_by': 'community',
|
|
||||||
'metadata_version': '1.1'
|
|
||||||
}
|
|
||||||
|
|
||||||
DOCUMENTATION = '''
|
|
||||||
---
|
|
||||||
module: grafana_plugin
|
|
||||||
author:
|
|
||||||
- Thierry Sallé (@seuf)
|
|
||||||
version_added: "2.5"
|
|
||||||
short_description: Manage Grafana plugins via grafana-cli
|
|
||||||
description:
|
|
||||||
- Install and remove Grafana plugins.
|
|
||||||
- See U(https://grafana.com/docs/plugins/installation/) for upstream documentation.
|
|
||||||
options:
|
|
||||||
name:
|
|
||||||
description:
|
|
||||||
- Name of the plugin.
|
|
||||||
required: true
|
|
||||||
version:
|
|
||||||
description:
|
|
||||||
- Version of the plugin to install.
|
|
||||||
- Defaults to C(latest).
|
|
||||||
grafana_plugins_dir:
|
|
||||||
description:
|
|
||||||
- Directory where the Grafana plugin will be installed.
|
|
||||||
- If omitted, defaults to C(/var/lib/grafana/plugins).
|
|
||||||
grafana_repo:
|
|
||||||
description:
|
|
||||||
- URL to the Grafana plugin repository.
|
|
||||||
- "If omitted, grafana-cli will use the default value: U(https://grafana.com/api/plugins)."
|
|
||||||
grafana_plugin_url:
|
|
||||||
description:
|
|
||||||
- Full URL to the plugin zip file instead of downloading the file from U(https://grafana.com/api/plugins).
|
|
||||||
- Requires grafana 4.6.x or later.
|
|
||||||
state:
|
|
||||||
description:
|
|
||||||
- Whether the plugin should be installed.
|
|
||||||
choices:
|
|
||||||
- present
|
|
||||||
- absent
|
|
||||||
default: present
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
---
|
|
||||||
- name: Install/update Grafana piechart panel plugin
|
|
||||||
grafana_plugin:
|
|
||||||
name: grafana-piechart-panel
|
|
||||||
version: latest
|
|
||||||
state: present
|
|
||||||
'''
|
|
||||||
|
|
||||||
RETURN = '''
|
|
||||||
---
|
|
||||||
version:
|
|
||||||
description: version of the installed/removed/updated plugin.
|
|
||||||
type: str
|
|
||||||
returned: always
|
|
||||||
'''
|
|
||||||
|
|
||||||
import base64
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
|
||||||
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
|
|
||||||
class GrafanaCliException(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def grafana_cli_bin(params):
|
|
||||||
'''
|
|
||||||
Get the grafana-cli binary path with global options.
|
|
||||||
Raise a GrafanaCliException if the grafana-cli is not present or not in PATH
|
|
||||||
|
|
||||||
:param params: ansible module params. Used to fill grafana-cli global params.
|
|
||||||
'''
|
|
||||||
program = 'grafana-cli'
|
|
||||||
grafana_cli = None
|
|
||||||
|
|
||||||
def is_exe(fpath):
|
|
||||||
return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
|
|
||||||
|
|
||||||
fpath, fname = os.path.split(program)
|
|
||||||
if fpath:
|
|
||||||
if is_exe(program):
|
|
||||||
grafana_cli = program
|
|
||||||
else:
|
|
||||||
for path in os.environ["PATH"].split(os.pathsep):
|
|
||||||
path = path.strip('"')
|
|
||||||
exe_file = os.path.join(path, program)
|
|
||||||
if is_exe(exe_file):
|
|
||||||
grafana_cli = exe_file
|
|
||||||
break
|
|
||||||
|
|
||||||
if grafana_cli is None:
|
|
||||||
raise GrafanaCliException('grafana-cli binary is not present or not in PATH')
|
|
||||||
else:
|
|
||||||
if 'grafana_plugin_url' in params and params['grafana_plugin_url']:
|
|
||||||
grafana_cli = '{0} {1} {2}'.format(grafana_cli, '--pluginUrl', params['grafana_plugin_url'])
|
|
||||||
if 'grafana_plugins_dir' in params and params['grafana_plugins_dir']:
|
|
||||||
grafana_cli = '{0} {1} {2}'.format(grafana_cli, '--pluginsDir', params['grafana_plugins_dir'])
|
|
||||||
if 'grafana_repo' in params and params['grafana_repo']:
|
|
||||||
grafana_cli = '{0} {1} {2}'.format(grafana_cli, '--repo', params['grafana_repo'])
|
|
||||||
if 'validate_certs' in params and params['validate_certs'] is False:
|
|
||||||
grafana_cli = '{0} {1}'.format(grafana_cli, '--insecure')
|
|
||||||
|
|
||||||
return '{0} {1}'.format(grafana_cli, 'plugins')
|
|
||||||
|
|
||||||
|
|
||||||
def get_grafana_plugin_version(module, params):
|
|
||||||
'''
|
|
||||||
Fetch grafana installed plugin version. Return None if plugin is not installed.
|
|
||||||
|
|
||||||
:param module: ansible module object. used to run system commands.
|
|
||||||
:param params: ansible module params.
|
|
||||||
'''
|
|
||||||
grafana_cli = grafana_cli_bin(params)
|
|
||||||
rc, stdout, stderr = module.run_command('{0} ls'.format(grafana_cli))
|
|
||||||
stdout_lines = stdout.split("\n")
|
|
||||||
for line in stdout_lines:
|
|
||||||
if line.find(' @ ') != -1:
|
|
||||||
line = line.rstrip()
|
|
||||||
plugin_name, plugin_version = line.split(' @ ')
|
|
||||||
if plugin_name == params['name']:
|
|
||||||
return plugin_version
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def get_grafana_plugin_version_latest(module, params):
|
|
||||||
'''
|
|
||||||
Fetch the latest version available from grafana-cli.
|
|
||||||
Return the newest version number or None not found.
|
|
||||||
|
|
||||||
:param module: ansible module object. used to run system commands.
|
|
||||||
:param params: ansible module params.
|
|
||||||
'''
|
|
||||||
grafana_cli = grafana_cli_bin(params)
|
|
||||||
rc, stdout, stderr = module.run_command('{0} list-versions {1}'.format(grafana_cli,
|
|
||||||
params['name']))
|
|
||||||
stdout_lines = stdout.split("\n")
|
|
||||||
if stdout_lines[0]:
|
|
||||||
return stdout_lines[0].rstrip()
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def grafana_plugin(module, params):
|
|
||||||
'''
|
|
||||||
Install update or remove grafana plugin
|
|
||||||
|
|
||||||
:param module: ansible module object. used to run system commands.
|
|
||||||
:param params: ansible module params.
|
|
||||||
'''
|
|
||||||
grafana_cli = grafana_cli_bin(params)
|
|
||||||
|
|
||||||
if params['state'] == 'present':
|
|
||||||
grafana_plugin_version = get_grafana_plugin_version(module, params)
|
|
||||||
if grafana_plugin_version is not None:
|
|
||||||
if 'version' in params and params['version']:
|
|
||||||
if params['version'] == grafana_plugin_version:
|
|
||||||
return {'msg': 'Grafana plugin already installed',
|
|
||||||
'changed': False,
|
|
||||||
'version': grafana_plugin_version}
|
|
||||||
else:
|
|
||||||
if params['version'] == 'latest' or params['version'] is None:
|
|
||||||
latest_version = get_grafana_plugin_version_latest(module, params)
|
|
||||||
if latest_version == grafana_plugin_version:
|
|
||||||
return {'msg': 'Grafana plugin already installed',
|
|
||||||
'changed': False,
|
|
||||||
'version': grafana_plugin_version}
|
|
||||||
cmd = '{0} update {1}'.format(grafana_cli, params['name'])
|
|
||||||
else:
|
|
||||||
cmd = '{0} install {1} {2}'.format(grafana_cli, params['name'], params['version'])
|
|
||||||
else:
|
|
||||||
return {'msg': 'Grafana plugin already installed',
|
|
||||||
'changed': False,
|
|
||||||
'version': grafana_plugin_version}
|
|
||||||
else:
|
|
||||||
if 'version' in params:
|
|
||||||
if params['version'] == 'latest' or params['version'] is None:
|
|
||||||
cmd = '{0} install {1}'.format(grafana_cli, params['name'])
|
|
||||||
else:
|
|
||||||
cmd = '{0} install {1} {2}'.format(grafana_cli, params['name'], params['version'])
|
|
||||||
else:
|
|
||||||
cmd = '{0} install {1}'.format(grafana_cli, params['name'])
|
|
||||||
else:
|
|
||||||
cmd = '{0} uninstall {1}'.format(grafana_cli, params['name'])
|
|
||||||
|
|
||||||
rc, stdout, stderr = module.run_command(cmd)
|
|
||||||
if rc == 0:
|
|
||||||
stdout_lines = stdout.split("\n")
|
|
||||||
for line in stdout_lines:
|
|
||||||
if line.find(params['name']):
|
|
||||||
if line.find(' @ ') != -1:
|
|
||||||
line = line.rstrip()
|
|
||||||
plugin_name, plugin_version = line.split(' @ ')
|
|
||||||
else:
|
|
||||||
plugin_version = None
|
|
||||||
return {'msg': 'Grafana plugin {0} installed : {1}'.format(params['name'], cmd),
|
|
||||||
'changed': True,
|
|
||||||
'version': plugin_version}
|
|
||||||
else:
|
|
||||||
raise GrafanaCliException("'{0}' execution returned an error : [{1}] {2} {3}".format(cmd, rc, stdout, stderr))
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
module = AnsibleModule(
|
|
||||||
argument_spec=dict(
|
|
||||||
name=dict(required=True,
|
|
||||||
type='str'),
|
|
||||||
version=dict(type='str'),
|
|
||||||
grafana_plugins_dir=dict(type='str'),
|
|
||||||
grafana_repo=dict(type='str'),
|
|
||||||
grafana_plugin_url=dict(type='str'),
|
|
||||||
state=dict(choices=['present', 'absent'],
|
|
||||||
default='present')
|
|
||||||
),
|
|
||||||
supports_check_mode=False
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
result = grafana_plugin(module, module.params)
|
|
||||||
except GrafanaCliException as e:
|
|
||||||
module.fail_json(
|
|
||||||
failed=True,
|
|
||||||
msg="{0}".format(e)
|
|
||||||
)
|
|
||||||
return
|
|
||||||
except Exception as e:
|
|
||||||
module.fail_json(
|
|
||||||
failed=True,
|
|
||||||
msg="{0} : {1} ".format(type(e), e)
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
module.exit_json(
|
|
||||||
failed=False,
|
|
||||||
**result
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
@ -1,273 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# This file is part of Ansible
|
|
||||||
#
|
|
||||||
# Ansible is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# Ansible is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
from __future__ import (absolute_import, division, print_function)
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
import os
|
|
||||||
import json
|
|
||||||
import socket
|
|
||||||
import getpass
|
|
||||||
from base64 import b64encode
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from ansible.module_utils._text import to_text
|
|
||||||
from ansible.module_utils.urls import open_url
|
|
||||||
from ansible.plugins.callback import CallbackBase
|
|
||||||
|
|
||||||
|
|
||||||
DOCUMENTATION = """
|
|
||||||
callback: grafana_annotations
|
|
||||||
callback_type: notification
|
|
||||||
short_description: send ansible events as annotations on charts to grafana over http api.
|
|
||||||
author: "Rémi REY (@rrey)"
|
|
||||||
description:
|
|
||||||
- This callback will report start, failed and stats events to Grafana as annotations (https://grafana.com)
|
|
||||||
version_added: "2.6"
|
|
||||||
requirements:
|
|
||||||
- whitelisting in configuration
|
|
||||||
options:
|
|
||||||
grafana_url:
|
|
||||||
description: Grafana annotations api URL
|
|
||||||
required: True
|
|
||||||
env:
|
|
||||||
- name: GRAFANA_URL
|
|
||||||
ini:
|
|
||||||
- section: callback_grafana_annotations
|
|
||||||
key: grafana_url
|
|
||||||
type: string
|
|
||||||
validate_certs:
|
|
||||||
description: validate the SSL certificate of the Grafana server. (For HTTPS url)
|
|
||||||
env:
|
|
||||||
- name: GRAFANA_VALIDATE_CERT
|
|
||||||
ini:
|
|
||||||
- section: callback_grafana_annotations
|
|
||||||
key: validate_grafana_certs
|
|
||||||
- section: callback_grafana_annotations
|
|
||||||
key: validate_certs
|
|
||||||
default: True
|
|
||||||
type: bool
|
|
||||||
aliases: [ validate_grafana_certs ]
|
|
||||||
http_agent:
|
|
||||||
description: The HTTP 'User-agent' value to set in HTTP requets.
|
|
||||||
env:
|
|
||||||
- name: HTTP_AGENT
|
|
||||||
ini:
|
|
||||||
- section: callback_grafana_annotations
|
|
||||||
key: http_agent
|
|
||||||
default: 'Ansible (grafana_annotations callback)'
|
|
||||||
type: string
|
|
||||||
grafana_api_key:
|
|
||||||
description: Grafana API key, allowing to authenticate when posting on the HTTP API.
|
|
||||||
If not provided, grafana_login and grafana_password will
|
|
||||||
be required.
|
|
||||||
env:
|
|
||||||
- name: GRAFANA_API_KEY
|
|
||||||
ini:
|
|
||||||
- section: callback_grafana_annotations
|
|
||||||
key: grafana_api_key
|
|
||||||
type: string
|
|
||||||
grafana_user:
|
|
||||||
description: Grafana user used for authentication. Ignored if grafana_api_key is provided.
|
|
||||||
env:
|
|
||||||
- name: GRAFANA_USER
|
|
||||||
ini:
|
|
||||||
- section: callback_grafana_annotations
|
|
||||||
key: grafana_user
|
|
||||||
default: ansible
|
|
||||||
type: string
|
|
||||||
grafana_password:
|
|
||||||
description: Grafana password used for authentication. Ignored if grafana_api_key is provided.
|
|
||||||
env:
|
|
||||||
- name: GRAFANA_PASSWORD
|
|
||||||
ini:
|
|
||||||
- section: callback_grafana_annotations
|
|
||||||
key: grafana_password
|
|
||||||
default: ansible
|
|
||||||
type: string
|
|
||||||
grafana_dashboard_id:
|
|
||||||
description: The grafana dashboard id where the annotation shall be created.
|
|
||||||
env:
|
|
||||||
- name: GRAFANA_DASHBOARD_ID
|
|
||||||
ini:
|
|
||||||
- section: callback_grafana_annotations
|
|
||||||
key: grafana_dashboard_id
|
|
||||||
type: integer
|
|
||||||
grafana_panel_ids:
|
|
||||||
description: The grafana panel ids where the annotation shall be created.
|
|
||||||
Give a single integer or a comma-separated list of integers.
|
|
||||||
env:
|
|
||||||
- name: GRAFANA_PANEL_IDS
|
|
||||||
ini:
|
|
||||||
- section: callback_grafana_annotations
|
|
||||||
key: grafana_panel_ids
|
|
||||||
default: []
|
|
||||||
type: list
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
PLAYBOOK_START_TXT = """\
|
|
||||||
Started playbook {playbook}
|
|
||||||
|
|
||||||
From '{hostname}'
|
|
||||||
By user '{username}'
|
|
||||||
"""
|
|
||||||
|
|
||||||
PLAYBOOK_ERROR_TXT = """\
|
|
||||||
Playbook {playbook} Failure !
|
|
||||||
|
|
||||||
From '{hostname}'
|
|
||||||
By user '{username}'
|
|
||||||
|
|
||||||
'{task}' failed on {host}
|
|
||||||
|
|
||||||
debug: {result}
|
|
||||||
"""
|
|
||||||
|
|
||||||
PLAYBOOK_STATS_TXT = """\
|
|
||||||
Playbook {playbook}
|
|
||||||
Duration: {duration}
|
|
||||||
Status: {status}
|
|
||||||
|
|
||||||
From '{hostname}'
|
|
||||||
By user '{username}'
|
|
||||||
|
|
||||||
Result:
|
|
||||||
{summary}
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def to_millis(dt):
|
|
||||||
return int(dt.strftime('%s')) * 1000
|
|
||||||
|
|
||||||
|
|
||||||
class CallbackModule(CallbackBase):
|
|
||||||
"""
|
|
||||||
ansible grafana callback plugin
|
|
||||||
ansible.cfg:
|
|
||||||
callback_plugins = <path_to_callback_plugins_folder>
|
|
||||||
callback_whitelist = grafana_annotations
|
|
||||||
and put the plugin in <path_to_callback_plugins_folder>
|
|
||||||
"""
|
|
||||||
|
|
||||||
CALLBACK_VERSION = 2.0
|
|
||||||
CALLBACK_TYPE = 'aggregate'
|
|
||||||
CALLBACK_NAME = 'grafana_annotations'
|
|
||||||
CALLBACK_NEEDS_WHITELIST = True
|
|
||||||
|
|
||||||
def __init__(self, display=None):
|
|
||||||
|
|
||||||
super(CallbackModule, self).__init__(display=display)
|
|
||||||
|
|
||||||
self.headers = {'Content-Type': 'application/json'}
|
|
||||||
self.force_basic_auth = False
|
|
||||||
self.hostname = socket.gethostname()
|
|
||||||
self.username = getpass.getuser()
|
|
||||||
self.start_time = datetime.now()
|
|
||||||
self.errors = 0
|
|
||||||
|
|
||||||
def set_options(self, task_keys=None, var_options=None, direct=None):
|
|
||||||
|
|
||||||
super(CallbackModule, self).set_options(task_keys=task_keys, var_options=var_options, direct=direct)
|
|
||||||
|
|
||||||
self.grafana_api_key = self.get_option('grafana_api_key')
|
|
||||||
self.grafana_url = self.get_option('grafana_url')
|
|
||||||
self.validate_grafana_certs = self.get_option('validate_certs')
|
|
||||||
self.http_agent = self.get_option('http_agent')
|
|
||||||
self.grafana_user = self.get_option('grafana_user')
|
|
||||||
self.grafana_password = self.get_option('grafana_password')
|
|
||||||
self.dashboard_id = self.get_option('grafana_dashboard_id')
|
|
||||||
self.panel_ids = self.get_option('grafana_panel_ids')
|
|
||||||
|
|
||||||
if self.grafana_api_key:
|
|
||||||
self.headers['Authorization'] = "Bearer %s" % self.grafana_api_key
|
|
||||||
else:
|
|
||||||
self.force_basic_auth = True
|
|
||||||
|
|
||||||
if self.grafana_url is None:
|
|
||||||
self.disabled = True
|
|
||||||
self._display.warning('Grafana URL was not provided. The '
|
|
||||||
'Grafana URL can be provided using '
|
|
||||||
'the `GRAFANA_URL` environment variable.')
|
|
||||||
self._display.debug('Grafana URL: %s' % self.grafana_url)
|
|
||||||
|
|
||||||
def v2_playbook_on_start(self, playbook):
|
|
||||||
self.playbook = playbook._file_name
|
|
||||||
text = PLAYBOOK_START_TXT.format(playbook=self.playbook, hostname=self.hostname,
|
|
||||||
username=self.username)
|
|
||||||
data = {
|
|
||||||
'time': to_millis(self.start_time),
|
|
||||||
'text': text,
|
|
||||||
'tags': ['ansible', 'ansible_event_start', self.playbook]
|
|
||||||
}
|
|
||||||
self._send_annotation(data)
|
|
||||||
|
|
||||||
def v2_playbook_on_stats(self, stats):
|
|
||||||
end_time = datetime.now()
|
|
||||||
duration = end_time - self.start_time
|
|
||||||
summarize_stat = {}
|
|
||||||
for host in stats.processed.keys():
|
|
||||||
summarize_stat[host] = stats.summarize(host)
|
|
||||||
|
|
||||||
status = "FAILED"
|
|
||||||
if self.errors == 0:
|
|
||||||
status = "OK"
|
|
||||||
|
|
||||||
text = PLAYBOOK_STATS_TXT.format(playbook=self.playbook, hostname=self.hostname,
|
|
||||||
duration=duration.total_seconds(),
|
|
||||||
status=status, username=self.username,
|
|
||||||
summary=json.dumps(summarize_stat))
|
|
||||||
|
|
||||||
data = {
|
|
||||||
'time': to_millis(self.start_time),
|
|
||||||
'timeEnd': to_millis(end_time),
|
|
||||||
'isRegion': True,
|
|
||||||
'text': text,
|
|
||||||
'tags': ['ansible', 'ansible_report', self.playbook]
|
|
||||||
}
|
|
||||||
self._send_annotations(data)
|
|
||||||
|
|
||||||
def v2_runner_on_failed(self, result, **kwargs):
|
|
||||||
text = PLAYBOOK_ERROR_TXT.format(playbook=self.playbook, hostname=self.hostname,
|
|
||||||
username=self.username, task=result._task,
|
|
||||||
host=result._host.name, result=self._dump_results(result._result))
|
|
||||||
data = {
|
|
||||||
'time': to_millis(datetime.now()),
|
|
||||||
'text': text,
|
|
||||||
'tags': ['ansible', 'ansible_event_failure', self.playbook]
|
|
||||||
}
|
|
||||||
self.errors += 1
|
|
||||||
self._send_annotations(data)
|
|
||||||
|
|
||||||
def _send_annotations(self, data):
|
|
||||||
if self.dashboard_id:
|
|
||||||
data["dashboardId"] = int(self.dashboard_id)
|
|
||||||
if self.panel_ids:
|
|
||||||
for panel_id in self.panel_ids:
|
|
||||||
data["panelId"] = int(panel_id)
|
|
||||||
self._send_annotation(data)
|
|
||||||
else:
|
|
||||||
self._send_annotation(data)
|
|
||||||
|
|
||||||
def _send_annotation(self, annotation):
|
|
||||||
try:
|
|
||||||
response = open_url(self.grafana_url, data=json.dumps(annotation), headers=self.headers,
|
|
||||||
method="POST",
|
|
||||||
validate_certs=self.validate_grafana_certs,
|
|
||||||
url_username=self.grafana_user, url_password=self.grafana_password,
|
|
||||||
http_agent=self.http_agent, force_basic_auth=self.force_basic_auth)
|
|
||||||
except Exception as e:
|
|
||||||
self._display.error(u'Could not submit message to Grafana: %s' % to_text(e))
|
|
@ -1,174 +0,0 @@
|
|||||||
# (c) 2018 Ansible Project
|
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
from __future__ import (absolute_import, division, print_function)
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['preview'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
DOCUMENTATION = """
|
|
||||||
lookup: grafana_dashboard
|
|
||||||
author: Thierry Salle (@seuf)
|
|
||||||
version_added: "2.7"
|
|
||||||
short_description: list or search grafana dashboards
|
|
||||||
description:
|
|
||||||
- This lookup returns a list of grafana dashboards with possibility to filter them by query.
|
|
||||||
options:
|
|
||||||
grafana_url:
|
|
||||||
description: url of grafana.
|
|
||||||
env:
|
|
||||||
- name: GRAFANA_URL
|
|
||||||
default: http://127.0.0.1:3000
|
|
||||||
grafana_api_key:
|
|
||||||
description:
|
|
||||||
- api key of grafana.
|
|
||||||
- when C(grafana_api_key) is set, the options C(grafan_user), C(grafana_password) and C(grafana_org_id) are ignored.
|
|
||||||
- Attention, please remove the two == at the end of the grafana_api_key
|
|
||||||
- because ansible lookup plugins options are split on = (see example).
|
|
||||||
env:
|
|
||||||
- name: GRAFANA_API_KEY
|
|
||||||
grafana_user:
|
|
||||||
description: grafana authentication user.
|
|
||||||
env:
|
|
||||||
- name: GRAFANA_USER
|
|
||||||
default: admin
|
|
||||||
grafana_password:
|
|
||||||
description: grafana authentication password.
|
|
||||||
env:
|
|
||||||
- name: GRAFANA_PASSWORD
|
|
||||||
default: admin
|
|
||||||
grafana_org_id:
|
|
||||||
description: grafana organisation id.
|
|
||||||
env:
|
|
||||||
- name: GRAFANA_ORG_ID
|
|
||||||
default: 1
|
|
||||||
search:
|
|
||||||
description: optional filter for dashboard search.
|
|
||||||
env:
|
|
||||||
- name: GRAFANA_DASHBOARD_SEARCH
|
|
||||||
"""
|
|
||||||
|
|
||||||
EXAMPLES = """
|
|
||||||
- name: get project foo grafana dashboards
|
|
||||||
set_fact:
|
|
||||||
grafana_dashboards: "{{ lookup('grafana_dashboard', 'grafana_url=http://grafana.company.com grafana_user=admin grafana_password=admin search=foo') }}"
|
|
||||||
|
|
||||||
- name: get all grafana dashboards
|
|
||||||
set_fact:
|
|
||||||
grafana_dashboards: "{{ lookup('grafana_dashboard', 'grafana_url=http://grafana.company.com grafana_api_key=' ~ grafana_api_key|replace('==', '')) }}"
|
|
||||||
"""
|
|
||||||
|
|
||||||
import base64
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
from ansible.errors import AnsibleError, AnsibleParserError
|
|
||||||
from ansible.plugins.lookup import LookupBase
|
|
||||||
from ansible.module_utils.urls import basic_auth_header, open_url
|
|
||||||
from ansible.module_utils._text import to_bytes, to_native
|
|
||||||
from ansible.module_utils.six.moves.urllib.error import HTTPError
|
|
||||||
from ansible.utils.display import Display
|
|
||||||
|
|
||||||
display = Display()
|
|
||||||
|
|
||||||
|
|
||||||
ANSIBLE_GRAFANA_URL = 'http://127.0.0.1:3000'
|
|
||||||
ANSIBLE_GRAFANA_API_KEY = None
|
|
||||||
ANSIBLE_GRAFANA_USER = 'admin'
|
|
||||||
ANSIBLE_GRAFANA_PASSWORD = 'admin'
|
|
||||||
ANSIBLE_GRAFANA_ORG_ID = 1
|
|
||||||
ANSIBLE_GRAFANA_DASHBOARD_SEARCH = None
|
|
||||||
|
|
||||||
if os.getenv('GRAFANA_URL') is not None:
|
|
||||||
ANSIBLE_GRAFANA_URL = os.environ['GRAFANA_URL']
|
|
||||||
|
|
||||||
if os.getenv('GRAFANA_API_KEY') is not None:
|
|
||||||
ANSIBLE_GRAFANA_API_KEY = os.environ['GRAFANA_API_KEY']
|
|
||||||
|
|
||||||
if os.getenv('GRAFANA_USER') is not None:
|
|
||||||
ANSIBLE_GRAFANA_USER = os.environ['GRAFANA_USER']
|
|
||||||
|
|
||||||
if os.getenv('GRAFANA_PASSWORD') is not None:
|
|
||||||
ANSIBLE_GRAFANA_PASSWORD = os.environ['GRAFANA_PASSWORD']
|
|
||||||
|
|
||||||
if os.getenv('GRAFANA_ORG_ID') is not None:
|
|
||||||
ANSIBLE_GRAFANA_ORG_ID = os.environ['GRAFANA_ORG_ID']
|
|
||||||
|
|
||||||
if os.getenv('GRAFANA_DASHBOARD_SEARCH') is not None:
|
|
||||||
ANSIBLE_GRAFANA_DASHBOARD_SEARCH = os.environ['GRAFANA_DASHBOARD_SEARCH']
|
|
||||||
|
|
||||||
|
|
||||||
class GrafanaAPIException(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class GrafanaAPI:
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
self.grafana_url = kwargs.get('grafana_url', ANSIBLE_GRAFANA_URL)
|
|
||||||
self.grafana_api_key = kwargs.get('grafana_api_key', ANSIBLE_GRAFANA_API_KEY)
|
|
||||||
self.grafana_user = kwargs.get('grafana_user', ANSIBLE_GRAFANA_USER)
|
|
||||||
self.grafana_password = kwargs.get('grafana_password', ANSIBLE_GRAFANA_PASSWORD)
|
|
||||||
self.grafana_org_id = kwargs.get('grafana_org_id', ANSIBLE_GRAFANA_ORG_ID)
|
|
||||||
self.search = kwargs.get('search', ANSIBLE_GRAFANA_DASHBOARD_SEARCH)
|
|
||||||
|
|
||||||
def grafana_switch_organisation(self, headers):
|
|
||||||
try:
|
|
||||||
r = open_url('%s/api/user/using/%s' % (self.grafana_url, self.grafana_org_id), headers=headers, method='POST')
|
|
||||||
except HTTPError as e:
|
|
||||||
raise GrafanaAPIException('Unable to switch to organization %s : %s' % (self.grafana_org_id, to_native(e)))
|
|
||||||
if r.getcode() != 200:
|
|
||||||
raise GrafanaAPIException('Unable to switch to organization %s : %s' % (self.grafana_org_id, str(r.getcode())))
|
|
||||||
|
|
||||||
def grafana_headers(self):
|
|
||||||
headers = {'content-type': 'application/json; charset=utf8'}
|
|
||||||
if self.grafana_api_key:
|
|
||||||
headers['Authorization'] = "Bearer %s==" % self.grafana_api_key
|
|
||||||
else:
|
|
||||||
headers['Authorization'] = basic_auth_header(self.grafana_user, self.grafana_password)
|
|
||||||
self.grafana_switch_organisation(headers)
|
|
||||||
|
|
||||||
return headers
|
|
||||||
|
|
||||||
def grafana_list_dashboards(self):
|
|
||||||
# define http headers
|
|
||||||
headers = self.grafana_headers()
|
|
||||||
|
|
||||||
dashboard_list = []
|
|
||||||
try:
|
|
||||||
if self.search:
|
|
||||||
r = open_url('%s/api/search?query=%s' % (self.grafana_url, self.search), headers=headers, method='GET')
|
|
||||||
else:
|
|
||||||
r = open_url('%s/api/search/' % self.grafana_url, headers=headers, method='GET')
|
|
||||||
except HTTPError as e:
|
|
||||||
raise GrafanaAPIException('Unable to search dashboards : %s' % to_native(e))
|
|
||||||
if r.getcode() == 200:
|
|
||||||
try:
|
|
||||||
dashboard_list = json.loads(r.read())
|
|
||||||
except Exception as e:
|
|
||||||
raise GrafanaAPIException('Unable to parse json list %s' % to_native(e))
|
|
||||||
else:
|
|
||||||
raise GrafanaAPIException('Unable to list grafana dashboards : %s' % str(r.getcode()))
|
|
||||||
|
|
||||||
return dashboard_list
|
|
||||||
|
|
||||||
|
|
||||||
class LookupModule(LookupBase):
|
|
||||||
|
|
||||||
def run(self, terms, variables=None, **kwargs):
|
|
||||||
|
|
||||||
grafana_args = terms[0].split(' ')
|
|
||||||
grafana_dict = {}
|
|
||||||
ret = []
|
|
||||||
|
|
||||||
for param in grafana_args:
|
|
||||||
try:
|
|
||||||
key, value = param.split('=')
|
|
||||||
except ValueError:
|
|
||||||
raise AnsibleError("grafana_dashboard lookup plugin needs key=value pairs, but received %s" % terms)
|
|
||||||
grafana_dict[key] = value
|
|
||||||
|
|
||||||
grafana = GrafanaAPI(**grafana_dict)
|
|
||||||
|
|
||||||
ret = grafana.grafana_list_dashboards()
|
|
||||||
|
|
||||||
return ret
|
|
@ -1,2 +0,0 @@
|
|||||||
shippable/posix/group1
|
|
||||||
skip/aix
|
|
@ -1,2 +0,0 @@
|
|||||||
dependencies:
|
|
||||||
- setup_grafana
|
|
@ -1,67 +0,0 @@
|
|||||||
---
|
|
||||||
|
|
||||||
- name: Create cloudwatch datasource
|
|
||||||
grafana_datasource:
|
|
||||||
name: "datasource-cloudwatch"
|
|
||||||
grafana_url: "http://127.0.0.1:3000"
|
|
||||||
grafana_user: "admin"
|
|
||||||
grafana_password: "admin"
|
|
||||||
org_id: "1"
|
|
||||||
ds_type: "cloudwatch"
|
|
||||||
url: "http://monitoring.us-west-1.amazonaws.com"
|
|
||||||
aws_auth_type: "keys"
|
|
||||||
aws_default_region: "us-west-1"
|
|
||||||
aws_access_key: "speakFriendAndEnter"
|
|
||||||
aws_secret_key: "mel10n"
|
|
||||||
aws_custom_metrics_namespaces: "n1,n2"
|
|
||||||
register: result
|
|
||||||
|
|
||||||
- debug:
|
|
||||||
var: result
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
- "result.changed == true"
|
|
||||||
- "result.name == 'datasource-cloudwatch'"
|
|
||||||
- "result.msg == 'Datasource datasource-cloudwatch created : Datasource added'"
|
|
||||||
|
|
||||||
- name: Check cloudwatch datasource creation idempotency
|
|
||||||
grafana_datasource:
|
|
||||||
name: "datasource-cloudwatch"
|
|
||||||
grafana_url: "http://127.0.0.1:3000"
|
|
||||||
grafana_user: "admin"
|
|
||||||
grafana_password: "admin"
|
|
||||||
org_id: "1"
|
|
||||||
ds_type: "cloudwatch"
|
|
||||||
url: "http://monitoring.us-west-1.amazonaws.com"
|
|
||||||
aws_auth_type: "keys"
|
|
||||||
aws_default_region: "us-west-1"
|
|
||||||
aws_access_key: "speakFriendAndEnter"
|
|
||||||
aws_secret_key: "mel10n"
|
|
||||||
aws_custom_metrics_namespaces: "n1,n2"
|
|
||||||
register: result
|
|
||||||
|
|
||||||
- debug:
|
|
||||||
var: result
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
# Idempotency is not working currently
|
|
||||||
# "result.changed == false"
|
|
||||||
- "result.name == 'datasource-cloudwatch'"
|
|
||||||
- "result.after.access == 'proxy'"
|
|
||||||
- "result.after.basicAuth == false"
|
|
||||||
- "result.after.database == None"
|
|
||||||
- "result.after.isDefault == false"
|
|
||||||
- "result.after.jsonData.authType == 'keys'"
|
|
||||||
- "result.after.jsonData.customMetricsNamespaces == 'n1,n2'"
|
|
||||||
- "result.after.jsonData.defaultRegion == 'us-west-1'"
|
|
||||||
- "result.after.jsonData.tlsAuth == false"
|
|
||||||
- "result.after.jsonData.tlsAuthWithCACert == false"
|
|
||||||
- "result.after.name == 'datasource-cloudwatch'"
|
|
||||||
- "result.after.orgId == 1"
|
|
||||||
- "result.after.password == ''"
|
|
||||||
- "result.after.type == 'cloudwatch'"
|
|
||||||
- "result.after.url == 'http://monitoring.us-west-1.amazonaws.com'"
|
|
||||||
- "result.after.user == ''"
|
|
||||||
- "result.after.withCredentials == false"
|
|
@ -1,77 +0,0 @@
|
|||||||
---
|
|
||||||
|
|
||||||
- name: Create elasticsearch datasource
|
|
||||||
grafana_datasource:
|
|
||||||
name: "datasource-elastic"
|
|
||||||
grafana_url: "http://127.0.0.1:3000"
|
|
||||||
grafana_user: "admin"
|
|
||||||
grafana_password: "admin"
|
|
||||||
org_id: "1"
|
|
||||||
ds_type: "elasticsearch"
|
|
||||||
ds_url: "https://elastic.company.com:9200"
|
|
||||||
database: "[logstash_]YYYY.MM.DD"
|
|
||||||
basic_auth_user: "grafana"
|
|
||||||
basic_auth_password: "******"
|
|
||||||
time_field: "@timestamp"
|
|
||||||
time_interval: "1m"
|
|
||||||
interval: "Daily"
|
|
||||||
es_version: 56
|
|
||||||
max_concurrent_shard_requests: 42
|
|
||||||
tls_ca_cert: "/etc/ssl/certs/ca.pem"
|
|
||||||
register: result
|
|
||||||
|
|
||||||
- debug:
|
|
||||||
var: result
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
- "result.changed == true"
|
|
||||||
- "result.name == 'datasource-elastic'"
|
|
||||||
- "result.msg == 'Datasource datasource-elastic created : Datasource added'"
|
|
||||||
|
|
||||||
- name: Check elasticsearch datasource creation idempotency
|
|
||||||
grafana_datasource:
|
|
||||||
name: "datasource-elastic"
|
|
||||||
grafana_url: "http://127.0.0.1:3000"
|
|
||||||
grafana_user: "admin"
|
|
||||||
grafana_password: "admin"
|
|
||||||
org_id: "1"
|
|
||||||
ds_type: "elasticsearch"
|
|
||||||
ds_url: "https://elastic.company.com:9200"
|
|
||||||
database: "[logstash_]YYYY.MM.DD"
|
|
||||||
basic_auth_user: "grafana"
|
|
||||||
basic_auth_password: "******"
|
|
||||||
time_field: "@timestamp"
|
|
||||||
time_interval: "1m"
|
|
||||||
interval: "Daily"
|
|
||||||
es_version: 56
|
|
||||||
max_concurrent_shard_requests: 42
|
|
||||||
tls_ca_cert: "/etc/ssl/certs/ca.pem"
|
|
||||||
register: result
|
|
||||||
|
|
||||||
- debug:
|
|
||||||
var: result
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
# Idempotency is not working currently
|
|
||||||
# "result.changed == false"
|
|
||||||
- "result.name == 'datasource-elastic'"
|
|
||||||
- "result.after.basicAuth == true"
|
|
||||||
- "result.after.basicAuthUser == 'grafana'"
|
|
||||||
- "result.after.access == 'proxy'"
|
|
||||||
- "result.after.database == '[logstash_]YYYY.MM.DD'"
|
|
||||||
- "result.after.isDefault == false"
|
|
||||||
- "result.after.jsonData.esVersion == 56"
|
|
||||||
- "result.after.jsonData.interval == 'Daily'"
|
|
||||||
- "result.after.jsonData.maxConcurrentShardRequests == 42"
|
|
||||||
- "result.after.jsonData.timeField == '@timestamp'"
|
|
||||||
- "result.after.jsonData.tlsAuth == false"
|
|
||||||
- "result.after.jsonData.tlsAuthWithCACert == false"
|
|
||||||
- "result.after.name == 'datasource-elastic'"
|
|
||||||
- "result.after.orgId == 1"
|
|
||||||
- "result.after.password == ''"
|
|
||||||
- "result.after.type == 'elasticsearch'"
|
|
||||||
- "result.after.url == 'https://elastic.company.com:9200'"
|
|
||||||
- "result.after.user == ''"
|
|
||||||
- "result.after.withCredentials == false"
|
|
@ -1,61 +0,0 @@
|
|||||||
---
|
|
||||||
|
|
||||||
- name: Create influxdb datasource
|
|
||||||
grafana_datasource:
|
|
||||||
name: "datasource-influxdb"
|
|
||||||
grafana_url: "http://127.0.0.1:3000"
|
|
||||||
grafana_user: "admin"
|
|
||||||
grafana_password: "admin"
|
|
||||||
org_id: "1"
|
|
||||||
ds_type: "influxdb"
|
|
||||||
ds_url: "https://influx.company.com:8086"
|
|
||||||
database: "telegraf"
|
|
||||||
time_interval: ">10s"
|
|
||||||
tls_ca_cert: "/etc/ssl/certs/ca.pem"
|
|
||||||
register: result
|
|
||||||
|
|
||||||
- debug:
|
|
||||||
var: result
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
- "result.changed == true"
|
|
||||||
- "result.name == 'datasource-influxdb'"
|
|
||||||
- "result.msg == 'Datasource datasource-influxdb created : Datasource added'"
|
|
||||||
|
|
||||||
- name: Check influxdb datasource creation idempotency
|
|
||||||
grafana_datasource:
|
|
||||||
name: "datasource-influxdb"
|
|
||||||
grafana_url: "http://127.0.0.1:3000"
|
|
||||||
grafana_user: "admin"
|
|
||||||
grafana_password: "admin"
|
|
||||||
org_id: "1"
|
|
||||||
ds_type: "influxdb"
|
|
||||||
ds_url: "https://influx.company.com:8086"
|
|
||||||
database: "telegraf"
|
|
||||||
time_interval: ">10s"
|
|
||||||
tls_ca_cert: "/etc/ssl/certs/ca.pem"
|
|
||||||
register: result
|
|
||||||
|
|
||||||
- debug:
|
|
||||||
var: result
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
# Idempotency is not working currently
|
|
||||||
# "result.changed == false"
|
|
||||||
- "result.name == 'datasource-influxdb'"
|
|
||||||
- "result.after.basicAuth == false"
|
|
||||||
- "result.after.access == 'proxy'"
|
|
||||||
- "result.after.database == 'telegraf'"
|
|
||||||
- "result.after.isDefault == false"
|
|
||||||
- "result.after.jsonData.timeInterval == '>10s'"
|
|
||||||
- "result.after.jsonData.tlsAuth == false"
|
|
||||||
- "result.after.jsonData.tlsAuthWithCACert == false"
|
|
||||||
- "result.after.name == 'datasource-influxdb'"
|
|
||||||
- "result.after.orgId == 1"
|
|
||||||
- "result.after.password == ''"
|
|
||||||
- "result.after.type == 'influxdb'"
|
|
||||||
- "result.after.url == 'https://influx.company.com:8086'"
|
|
||||||
- "result.after.user == ''"
|
|
||||||
- "result.after.withCredentials == false"
|
|
@ -1,8 +0,0 @@
|
|||||||
- block:
|
|
||||||
- include: elastic.yml
|
|
||||||
- include: influx.yml
|
|
||||||
- include: postgres.yml
|
|
||||||
- include: cloudwatch.yml
|
|
||||||
when:
|
|
||||||
- ansible_distribution == 'Ubuntu'
|
|
||||||
- ansible_distribution_release != 'trusty'
|
|
@ -1,61 +0,0 @@
|
|||||||
---
|
|
||||||
|
|
||||||
- name: Create postgres datasource
|
|
||||||
grafana_datasource:
|
|
||||||
name: "datasource-postgres"
|
|
||||||
grafana_url: "http://127.0.0.1:3000"
|
|
||||||
grafana_user: "admin"
|
|
||||||
grafana_password: "admin"
|
|
||||||
org_id: "1"
|
|
||||||
ds_type: "postgres"
|
|
||||||
ds_url: "postgres.company.com:5432"
|
|
||||||
database: "db"
|
|
||||||
user: "postgres"
|
|
||||||
password: "iampgroot"
|
|
||||||
sslmode: "verify-full"
|
|
||||||
register: result
|
|
||||||
|
|
||||||
- debug:
|
|
||||||
var: result
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
- "result.changed == true"
|
|
||||||
- "result.name == 'datasource-postgres'"
|
|
||||||
- "result.msg == 'Datasource datasource-postgres created : Datasource added'"
|
|
||||||
|
|
||||||
- name: Check postgres datasource creation idempotency
|
|
||||||
grafana_datasource:
|
|
||||||
name: "datasource-postgres"
|
|
||||||
grafana_url: "http://127.0.0.1:3000"
|
|
||||||
grafana_user: "admin"
|
|
||||||
grafana_password: "admin"
|
|
||||||
org_id: "1"
|
|
||||||
ds_type: "postgres"
|
|
||||||
ds_url: "postgres.company.com:5432"
|
|
||||||
database: "db"
|
|
||||||
user: "postgres"
|
|
||||||
password: "iampgroot"
|
|
||||||
sslmode: "verify-full"
|
|
||||||
register: result
|
|
||||||
|
|
||||||
- debug:
|
|
||||||
var: result
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
# Idempotency is not working currently
|
|
||||||
# "result.changed == false"
|
|
||||||
- "result.name == 'datasource-postgres'"
|
|
||||||
- "result.after.basicAuth == false"
|
|
||||||
- "result.after.database == 'db'"
|
|
||||||
- "result.after.isDefault == false"
|
|
||||||
- "result.after.jsonData.sslmode == 'verify-full'"
|
|
||||||
- "result.after.jsonData.tlsAuth == false"
|
|
||||||
- "result.after.jsonData.tlsAuthWithCACert == false"
|
|
||||||
- "result.after.name == 'datasource-postgres'"
|
|
||||||
- "result.after.orgId == 1"
|
|
||||||
- "result.after.type == 'postgres'"
|
|
||||||
- "result.after.url == 'postgres.company.com:5432'"
|
|
||||||
- "result.after.user == 'postgres'"
|
|
||||||
- "result.after.withCredentials == false"
|
|
Loading…
Reference in New Issue