summaryrefslogtreecommitdiffstats
path: root/ansible_collections/community/grafana/plugins
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-18 05:52:35 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-18 05:52:35 +0000
commit7fec0b69a082aaeec72fee0612766aa42f6b1b4d (patch)
treeefb569b86ca4da888717f5433e757145fa322e08 /ansible_collections/community/grafana/plugins
parentReleasing progress-linux version 7.7.0+dfsg-3~progress7.99u1. (diff)
downloadansible-7fec0b69a082aaeec72fee0612766aa42f6b1b4d.tar.xz
ansible-7fec0b69a082aaeec72fee0612766aa42f6b1b4d.zip
Merging upstream version 9.4.0+dfsg.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/community/grafana/plugins')
-rw-r--r--ansible_collections/community/grafana/plugins/callback/grafana_annotations.py123
-rw-r--r--ansible_collections/community/grafana/plugins/doc_fragments/api_key.py7
-rw-r--r--ansible_collections/community/grafana/plugins/doc_fragments/basic_auth.py17
-rw-r--r--ansible_collections/community/grafana/plugins/lookup/grafana_dashboard.py106
-rw-r--r--ansible_collections/community/grafana/plugins/module_utils/base.py24
-rw-r--r--ansible_collections/community/grafana/plugins/modules/grafana_dashboard.py585
-rw-r--r--ansible_collections/community/grafana/plugins/modules/grafana_datasource.py596
-rw-r--r--ansible_collections/community/grafana/plugins/modules/grafana_folder.py141
-rw-r--r--ansible_collections/community/grafana/plugins/modules/grafana_notification_channel.py660
-rw-r--r--ansible_collections/community/grafana/plugins/modules/grafana_organization.py70
-rw-r--r--ansible_collections/community/grafana/plugins/modules/grafana_organization_user.py294
-rw-r--r--ansible_collections/community/grafana/plugins/modules/grafana_plugin.py203
-rw-r--r--ansible_collections/community/grafana/plugins/modules/grafana_team.py93
-rw-r--r--ansible_collections/community/grafana/plugins/modules/grafana_user.py89
14 files changed, 1885 insertions, 1123 deletions
diff --git a/ansible_collections/community/grafana/plugins/callback/grafana_annotations.py b/ansible_collections/community/grafana/plugins/callback/grafana_annotations.py
index 04555eae0..6030b0c8b 100644
--- a/ansible_collections/community/grafana/plugins/callback/grafana_annotations.py
+++ b/ansible_collections/community/grafana/plugins/callback/grafana_annotations.py
@@ -14,10 +14,11 @@
# 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)
+from __future__ import absolute_import, division, print_function
+
__metaclass__ = type
-DOCUMENTATION = '''
+DOCUMENTATION = """
name: grafana_annotations
type: notification
short_description: send ansible events as annotations on charts to grafana over http api.
@@ -29,7 +30,7 @@ DOCUMENTATION = '''
options:
grafana_url:
description: Grafana annotations api URL
- required: True
+ required: true
env:
- name: GRAFANA_URL
ini:
@@ -45,7 +46,7 @@ DOCUMENTATION = '''
key: validate_grafana_certs
- section: callback_grafana_annotations
key: validate_certs
- default: True
+ default: true
type: bool
aliases: [ validate_grafana_certs ]
http_agent:
@@ -104,7 +105,7 @@ DOCUMENTATION = '''
default: []
type: list
elements: integer
-'''
+"""
import json
import socket
@@ -148,7 +149,7 @@ Result:
def to_millis(dt):
- return int(dt.strftime('%s')) * 1000
+ return int(dt.strftime("%s")) * 1000
class CallbackModule(CallbackBase):
@@ -161,15 +162,14 @@ class CallbackModule(CallbackBase):
"""
CALLBACK_VERSION = 2.0
- CALLBACK_TYPE = 'aggregate'
- CALLBACK_NAME = 'community.grafana.grafana_annotations'
+ CALLBACK_TYPE = "aggregate"
+ CALLBACK_NAME = "community.grafana.grafana_annotations"
CALLBACK_NEEDS_WHITELIST = True
def __init__(self, display=None):
-
super(CallbackModule, self).__init__(display=display)
- self.headers = {'Content-Type': 'application/json'}
+ self.headers = {"Content-Type": "application/json"}
self.force_basic_auth = False
self.hostname = socket.gethostname()
self.username = getpass.getuser()
@@ -177,38 +177,42 @@ class CallbackModule(CallbackBase):
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')
+ 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
+ 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)
+ 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)
+ 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.hostname]
+ "time": to_millis(self.start_time),
+ "text": text,
+ "tags": ["ansible", "ansible_event_start", self.playbook, self.hostname],
}
self._send_annotation(data)
@@ -223,30 +227,39 @@ class CallbackModule(CallbackBase):
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))
+ 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.hostname]
+ "time": to_millis(self.start_time),
+ "timeEnd": to_millis(end_time),
+ "isRegion": True,
+ "text": text,
+ "tags": ["ansible", "ansible_report", self.playbook, self.hostname],
}
self._send_annotations(data)
def v2_runner_on_failed(self, result, ignore_errors=False, **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))
+ 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),
+ )
if ignore_errors:
return
data = {
- 'time': to_millis(datetime.now()),
- 'text': text,
- 'tags': ['ansible', 'ansible_event_failure', self.playbook, self.hostname]
+ "time": to_millis(datetime.now()),
+ "text": text,
+ "tags": ["ansible", "ansible_event_failure", self.playbook, self.hostname],
}
self.errors += 1
self._send_annotations(data)
@@ -263,10 +276,16 @@ class CallbackModule(CallbackBase):
def _send_annotation(self, annotation):
try:
- 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)
+ 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))
+ self._display.error("Could not submit message to Grafana: %s" % to_text(e))
diff --git a/ansible_collections/community/grafana/plugins/doc_fragments/api_key.py b/ansible_collections/community/grafana/plugins/doc_fragments/api_key.py
index ffea714e5..94fdb57a2 100644
--- a/ansible_collections/community/grafana/plugins/doc_fragments/api_key.py
+++ b/ansible_collections/community/grafana/plugins/doc_fragments/api_key.py
@@ -2,17 +2,16 @@
# Copyright: (c) 2019, Rémi REY (@rrey)
# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
-from __future__ import (absolute_import, division, print_function)
+from __future__ import absolute_import, division, print_function
__metaclass__ = type
class ModuleDocFragment(object):
-
- DOCUMENTATION = r'''options:
+ DOCUMENTATION = r"""options:
grafana_api_key:
description:
- The Grafana API key.
- If set, C(url_username) and C(url_password) will be ignored.
type: str
- '''
+ """
diff --git a/ansible_collections/community/grafana/plugins/doc_fragments/basic_auth.py b/ansible_collections/community/grafana/plugins/doc_fragments/basic_auth.py
index 8c41acdbe..5a8aec541 100644
--- a/ansible_collections/community/grafana/plugins/doc_fragments/basic_auth.py
+++ b/ansible_collections/community/grafana/plugins/doc_fragments/basic_auth.py
@@ -2,14 +2,13 @@
# Copyright: (c) 2019, Rémi REY (@rrey)
# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
-from __future__ import (absolute_import, division, print_function)
+from __future__ import absolute_import, division, print_function
__metaclass__ = type
class ModuleDocFragment(object):
-
- DOCUMENTATION = r'''options:
+ DOCUMENTATION = r"""options:
url:
description:
- The Grafana URL.
@@ -30,9 +29,9 @@ class ModuleDocFragment(object):
aliases: [ grafana_password ]
use_proxy:
description:
- - If C(no), it will not use a proxy, even if one is defined in an environment variable on the target hosts.
+ - If C(false), it will not use a proxy, even if one is defined in an environment variable on the target hosts.
type: bool
- default: yes
+ default: true
client_cert:
description:
- PEM formatted certificate chain file to be used for SSL client authentication.
@@ -45,8 +44,8 @@ class ModuleDocFragment(object):
type: path
validate_certs:
description:
- - If C(no), SSL certificates will not be validated.
- - This should only set to C(no) used on personally controlled sites using self-signed certificates.
+ - If C(false), SSL certificates will not be validated.
+ - This should only set to C(false) used on personally controlled sites using self-signed certificates.
type: bool
- default: yes
- '''
+ default: true
+ """
diff --git a/ansible_collections/community/grafana/plugins/lookup/grafana_dashboard.py b/ansible_collections/community/grafana/plugins/lookup/grafana_dashboard.py
index ff288a1f3..c7fa31574 100644
--- a/ansible_collections/community/grafana/plugins/lookup/grafana_dashboard.py
+++ b/ansible_collections/community/grafana/plugins/lookup/grafana_dashboard.py
@@ -1,9 +1,10 @@
# (c) 2018 Ansible Project
# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
-from __future__ import (absolute_import, division, print_function)
+from __future__ import absolute_import, division, print_function
+
__metaclass__ = type
-DOCUMENTATION = '''
+DOCUMENTATION = """
name: grafana_dashboard
author: Thierry Salle (@seuf)
short_description: list or search grafana dashboards
@@ -40,7 +41,7 @@ options:
description: optional filter for dashboard search.
env:
- name: GRAFANA_DASHBOARD_SEARCH
-'''
+"""
EXAMPLES = """
- name: get project foo grafana dashboards
@@ -64,30 +65,30 @@ from ansible.utils.display import Display
display = Display()
-ANSIBLE_GRAFANA_URL = 'http://127.0.0.1:3000'
+ANSIBLE_GRAFANA_URL = "http://127.0.0.1:3000"
ANSIBLE_GRAFANA_API_KEY = None
-ANSIBLE_GRAFANA_USER = 'admin'
-ANSIBLE_GRAFANA_PASSWORD = 'admin'
+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_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_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_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_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_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']
+if os.getenv("GRAFANA_DASHBOARD_SEARCH") is not None:
+ ANSIBLE_GRAFANA_DASHBOARD_SEARCH = os.environ["GRAFANA_DASHBOARD_SEARCH"]
class GrafanaAPIException(Exception):
@@ -96,35 +97,47 @@ class GrafanaAPIException(Exception):
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)
+ 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')
+ 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)))
+ 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())))
+ 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'}
+ headers = {"content-type": "application/json; charset=utf8"}
if self.grafana_api_key:
api_key = self.grafana_api_key
if len(api_key) % 4 == 2:
display.deprecated(
"Passing a mangled version of the API key to the grafana_dashboard lookup is no longer necessary and should not be done.",
"2.0.0",
- collection_name='community.grafana',
+ collection_name="community.grafana",
)
- api_key += '=='
- headers['Authorization'] = "Bearer %s" % api_key
+ api_key += "=="
+ headers["Authorization"] = "Bearer %s" % api_key
else:
- headers['Authorization'] = basic_auth_header(self.grafana_user, self.grafana_password)
+ headers["Authorization"] = basic_auth_header(
+ self.grafana_user, self.grafana_password
+ )
self.grafana_switch_organisation(headers)
return headers
@@ -136,35 +149,44 @@ class GrafanaAPI:
dashboard_list = []
try:
if self.search:
- r = open_url('%s/api/search?query=%s' % (self.grafana_url, self.search), headers=headers, method='GET')
+ 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')
+ 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))
+ 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))
+ raise GrafanaAPIException("Unable to parse json list %s" % to_native(e))
else:
- raise GrafanaAPIException('Unable to list grafana dashboards : %s' % str(r.getcode()))
+ 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_args = terms[0].split(" ")
grafana_dict = {}
ret = []
for param in grafana_args:
try:
- key, value = param.split('=', 1)
+ key, value = param.split("=", 1)
except ValueError:
- raise AnsibleError("grafana_dashboard lookup plugin needs key=value pairs, but received %s" % terms)
+ raise AnsibleError(
+ "grafana_dashboard lookup plugin needs key=value pairs, but received %s"
+ % terms
+ )
grafana_dict[key] = value
grafana = GrafanaAPI(**grafana_dict)
diff --git a/ansible_collections/community/grafana/plugins/module_utils/base.py b/ansible_collections/community/grafana/plugins/module_utils/base.py
index 3a0174bbd..7d51601d8 100644
--- a/ansible_collections/community/grafana/plugins/module_utils/base.py
+++ b/ansible_collections/community/grafana/plugins/module_utils/base.py
@@ -16,7 +16,7 @@
#
# Copyright: (c) 2019, Rémi REY (@rrey)
-from __future__ import (absolute_import, division, print_function)
+from __future__ import absolute_import, division, print_function
from ansible.module_utils.urls import url_argument_spec
__metaclass__ = type
@@ -29,26 +29,26 @@ def clean_url(url):
def grafana_argument_spec():
argument_spec = url_argument_spec()
- del argument_spec['force']
- del argument_spec['force_basic_auth']
- del argument_spec['http_agent']
+ del argument_spec["force"]
+ del argument_spec["force_basic_auth"]
+ del argument_spec["http_agent"]
# Avoid sanity error with devel
if "use_gssapi" in argument_spec:
- del argument_spec['use_gssapi']
+ del argument_spec["use_gssapi"]
argument_spec.update(
- state=dict(choices=['present', 'absent'], default='present'),
- url=dict(aliases=['grafana_url'], type='str', required=True),
- grafana_api_key=dict(type='str', no_log=True),
- url_username=dict(aliases=['grafana_user'], default='admin'),
- url_password=dict(aliases=['grafana_password'], default='admin', no_log=True),
+ state=dict(choices=["present", "absent"], default="present"),
+ url=dict(aliases=["grafana_url"], type="str", required=True),
+ grafana_api_key=dict(type="str", no_log=True),
+ url_username=dict(aliases=["grafana_user"], default="admin"),
+ url_password=dict(aliases=["grafana_password"], default="admin", no_log=True),
)
return argument_spec
def grafana_required_together():
- return [['url_username', 'url_password']]
+ return [["url_username", "url_password"]]
def grafana_mutually_exclusive():
- return [['url_username', 'grafana_api_key']]
+ return [["url_username", "grafana_api_key"]]
diff --git a/ansible_collections/community/grafana/plugins/modules/grafana_dashboard.py b/ansible_collections/community/grafana/plugins/modules/grafana_dashboard.py
index 99801d494..77a5a7565 100644
--- a/ansible_collections/community/grafana/plugins/modules/grafana_dashboard.py
+++ b/ansible_collections/community/grafana/plugins/modules/grafana_dashboard.py
@@ -6,7 +6,7 @@
from __future__ import absolute_import, division, print_function
-DOCUMENTATION = '''
+DOCUMENTATION = """
---
module: grafana_dashboard
author:
@@ -18,10 +18,17 @@ description:
options:
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..
+ - The Grafana organization ID where the dashboard will be imported / exported / deleted.
+ - Not used when I(grafana_api_key) is set, because the grafana_api_key only belongs to one organization.
+ - Mutually exclusive with C(org_name).
default: 1
type: int
+ org_name:
+ description:
+ - The Grafana organization name where the dashboard will be imported / exported / deleted.
+ - Not used when I(grafana_api_key) is set, because the grafana_api_key only belongs to one organization.
+ - Mutually exclusive with C(org_id).
+ type: str
folder:
description:
- The Grafana folder where this dashboard will be imported to.
@@ -58,7 +65,7 @@ options:
description:
- Override existing dashboard when state is present.
type: bool
- default: 'no'
+ default: false
dashboard_id:
description:
- Public Grafana.com dashboard id to import
@@ -80,55 +87,52 @@ options:
extends_documentation_fragment:
- community.grafana.basic_auth
- community.grafana.api_key
-'''
-
-EXAMPLES = '''
-- hosts: localhost
- connection: local
- tasks:
- - name: Import Grafana dashboard foo
- community.grafana.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: Import Grafana dashboard Zabbix
- community.grafana.grafana_dashboard:
- grafana_url: http://grafana.company.com
- grafana_api_key: "{{ grafana_api_key }}"
- folder: zabbix
- dashboard_id: 6098
- dashbord_revision: 1
-
- - name: Import Grafana dashboard zabbix
- community.grafana.grafana_dashboard:
- grafana_url: http://grafana.company.com
- grafana_api_key: "{{ grafana_api_key }}"
- folder: public
- dashboard_url: https://grafana.com/api/dashboards/6098/revisions/1/download
-
- - name: Export dashboard
- community.grafana.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 = '''
+"""
+
+EXAMPLES = """
+- name: Import Grafana dashboard foo
+ community.grafana.grafana_dashboard:
+ grafana_url: http://grafana.company.com
+ grafana_api_key: "{{ grafana_api_key }}"
+ state: present
+ commit_message: Updated by ansible
+ overwrite: true
+ path: /path/to/dashboards/foo.json
+
+- name: Import Grafana dashboard Zabbix
+ community.grafana.grafana_dashboard:
+ grafana_url: http://grafana.company.com
+ grafana_api_key: "{{ grafana_api_key }}"
+ folder: zabbix
+ dashboard_id: 6098
+ dashboard_revision: 1
+
+- name: Import Grafana dashboard zabbix
+ community.grafana.grafana_dashboard:
+ grafana_url: http://grafana.company.com
+ grafana_api_key: "{{ grafana_api_key }}"
+ folder: public
+ dashboard_url: https://grafana.com/api/dashboards/6098/revisions/1/download
+
+- name: Export dashboard
+ community.grafana.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
@@ -136,7 +140,10 @@ from ansible.module_utils.urls import fetch_url
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
-from ansible_collections.community.grafana.plugins.module_utils.base import grafana_argument_spec, clean_url
+from ansible_collections.community.grafana.plugins.module_utils.base import (
+ grafana_argument_spec,
+ clean_url,
+)
__metaclass__ = type
@@ -157,56 +164,91 @@ 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_organization_id_by_name(module, grafana_url, org_name, headers):
+ r, info = fetch_url(
+ module, "%s/api/user/orgs" % grafana_url, headers=headers, method="GET"
+ )
+ if info["status"] != 200:
+ raise GrafanaAPIException("Unable to retrieve users organizations: %s" % info)
+ organizations = json.loads(to_text(r.read()))
+ for org in organizations:
+ if org["name"] == org_name:
+ return org["orgId"]
+
+ raise GrafanaAPIException(
+ "Current user isn't member of organization: %s" % org_name
+ )
+
+
+def grafana_switch_organization(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']
+ 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['url'], data['org_id'], headers)
+ module.params["force_basic_auth"] = True
+ if module.params["org_name"]:
+ org_name = module.params["org_name"]
+ data["org_id"] = grafana_organization_id_by_name(
+ module, data["url"], org_name, headers
+ )
+ grafana_switch_organization(module, data["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:
+ 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')
+ grafana_version = settings["buildInfo"]["version"].split(".")[0]
+ except UnicodeError:
+ 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)
+ 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':
+ if folder_name == "General":
return True, 0
try:
- r, info = fetch_url(module, '%s/api/folders' % grafana_url, headers=headers, method='GET')
+ 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']))
+ 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']
+ if folder["title"] == folder_name:
+ return True, folder["id"]
except Exception as e:
raise GrafanaAPIException(e)
@@ -219,73 +261,73 @@ def grafana_dashboard_exists(module, grafana_url, uid, headers):
grafana_version = get_grafana_version(module, grafana_url, headers)
if grafana_version >= 5:
- uri = '%s/api/dashboards/uid/%s' % (grafana_url, uid)
+ uri = "%s/api/dashboards/uid/%s" % (grafana_url, uid)
else:
- uri = '%s/api/dashboards/db/%s' % (grafana_url, uid)
+ uri = "%s/api/dashboards/db/%s" % (grafana_url, uid)
- r, info = fetch_url(module, uri, headers=headers, method='GET')
+ r, info = fetch_url(module, uri, headers=headers, method="GET")
- if info['status'] == 200:
+ if info["status"] == 200:
dashboard_exists = True
try:
dashboard = json.loads(r.read())
except Exception as e:
raise GrafanaAPIException(e)
- elif info['status'] == 404:
+ elif info["status"] == 404:
dashboard_exists = False
else:
- raise GrafanaAPIException('Unable to get dashboard %s : %s' % (uid, info))
+ 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:
+ 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)
+ 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))
+ 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):
+def is_grafana_dashboard_changed(payload, dashboard):
# you don't need to set the version, but '0' is incremented to '1' by Grafana's API
- if 'version' in payload['dashboard']:
- del payload['dashboard']['version']
- if 'version' in dashboard['dashboard']:
- del dashboard['dashboard']['version']
+ if "version" in payload["dashboard"]:
+ del payload["dashboard"]["version"]
+ if "version" in dashboard["dashboard"]:
+ del dashboard["dashboard"]["version"]
# remove meta key if exists for compare
- if 'meta' in dashboard:
- del dashboard['meta']
- if 'meta' in payload:
- del payload['meta']
+ if "meta" in dashboard:
+ del dashboard["meta"]
+ if "meta" in payload:
+ del payload["meta"]
# if folderId is not provided in dashboard, set default folderId
- if 'folderId' not in dashboard:
- dashboard['folderId'] = 0
+ if "folderId" not in dashboard:
+ dashboard["folderId"] = 0
# Ignore dashboard ids since real identifier is uuid
- if 'id' in dashboard['dashboard']:
- del dashboard['dashboard']['id']
- if 'id' in payload['dashboard']:
- del payload['dashboard']['id']
+ if "id" in dashboard["dashboard"]:
+ del dashboard["dashboard"]["id"]
+ if "id" in payload["dashboard"]:
+ del payload["dashboard"]["id"]
if payload == dashboard:
return False
@@ -293,43 +335,48 @@ def grafana_dashboard_changed(payload, dashboard):
def grafana_create_dashboard(module, data):
-
# define data payload for grafana API
payload = {}
- if data.get('dashboard_id'):
- data['path'] = "https://grafana.com/api/dashboards/%s/revisions/%s/download" % (data['dashboard_id'], data['dashboard_revision'])
- if data['path'].startswith('http'):
- r, info = fetch_url(module, data['path'])
- if info['status'] != 200:
- raise GrafanaAPIException('Unable to download grafana dashboard from url %s : %s' % (data['path'], info))
+ if data.get("dashboard_id"):
+ data["path"] = "https://grafana.com/api/dashboards/%s/revisions/%s/download" % (
+ data["dashboard_id"],
+ data["dashboard_revision"],
+ )
+ if data["path"].startswith("http"):
+ r, info = fetch_url(module, data["path"])
+ if info["status"] != 200:
+ raise GrafanaAPIException(
+ "Unable to download grafana dashboard from url %s : %s"
+ % (data["path"], info)
+ )
payload = json.loads(r.read())
else:
try:
- with open(data['path'], 'r', encoding="utf-8") as json_file:
+ with open(data["path"], "r", encoding="utf-8") 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}
+ if "dashboard" not in payload:
+ payload = {"dashboard": payload}
# define http header
headers = grafana_headers(module, data)
- grafana_version = get_grafana_version(module, data['url'], headers)
+ grafana_version = get_grafana_version(module, data["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']
+ 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')
+ 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']
+ if data.get("uid"):
+ uid = data["uid"]
+ elif "uid" in payload["dashboard"]:
+ uid = payload["dashboard"]["uid"]
else:
uid = None
@@ -338,148 +385,223 @@ def grafana_create_dashboard(module, data):
# test if the folder exists
folder_exists = False
if grafana_version >= 5:
- folder_exists, folder_id = grafana_folder_exists(module, data['url'], data['folder'], headers)
+ folder_exists, folder_id = grafana_folder_exists(
+ module, data["url"], data["folder"], headers
+ )
if folder_exists is False:
- raise GrafanaAPIException("Dashboard folder '%s' does not exist." % data['folder'])
+ raise GrafanaAPIException(
+ "Dashboard folder '%s' does not exist." % data["folder"]
+ )
- payload['folderId'] = folder_id
+ payload["folderId"] = folder_id
# test if dashboard already exists
if uid:
dashboard_exists, dashboard = grafana_dashboard_exists(
- module, data['url'], uid, headers=headers)
+ module, data["url"], uid, headers=headers
+ )
else:
dashboard_exists, dashboard = grafana_dashboard_search(
- module, data['url'], folder_id, payload['dashboard']['title'], headers=headers)
+ module,
+ data["url"],
+ folder_id,
+ payload["dashboard"]["title"],
+ headers=headers,
+ )
if dashboard_exists is True:
- if grafana_dashboard_changed(payload, dashboard):
+ grafana_dashboard_changed = is_grafana_dashboard_changed(payload, dashboard)
+
+ if grafana_dashboard_changed:
+ if module.check_mode:
+ module.exit_json(
+ uid=uid,
+ failed=False,
+ changed=True,
+ msg="Dashboard %s will be updated" % payload["dashboard"]["title"],
+ )
# 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['url'],
- data=json.dumps(payload), headers=headers, method='POST')
- if info['status'] == 200:
+ 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["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']
+ uid = dashboard["uid"]
except Exception as e:
raise GrafanaAPIException(e)
- result['uid'] = uid
- result['msg'] = "Dashboard %s updated" % payload['dashboard']['title']
- result['changed'] = True
+ 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['message'], info['status']))
+ body = json.loads(info["body"])
+ raise GrafanaAPIException(
+ "Unable to update the dashboard %s : %s (HTTP: %d)"
+ % (uid, body["message"], info["status"])
+ )
else:
# unchanged
- result['uid'] = uid
- result['msg'] = "Dashboard %s unchanged." % payload['dashboard']['title']
- result['changed'] = False
+ result["uid"] = uid
+ result["msg"] = "Dashboard %s unchanged." % payload["dashboard"]["title"]
+ result["changed"] = False
else:
+ if module.check_mode:
+ module.exit_json(
+ failed=False,
+ changed=True,
+ msg="Dashboard %s will be created" % payload["dashboard"]["title"],
+ )
+
# Ensure there is no id in payload
- if 'id' in payload['dashboard']:
- del payload['dashboard']['id']
-
- r, info = fetch_url(module, '%s/api/dashboards/db' % data['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 "id" in payload["dashboard"]:
+ del payload["dashboard"]["id"]
+
+ r, info = fetch_url(
+ module,
+ "%s/api/dashboards/db" % data["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']
+ uid = dashboard["uid"]
except Exception as e:
raise GrafanaAPIException(e)
- result['uid'] = uid
+ result["uid"] = uid
else:
- raise GrafanaAPIException('Unable to create the new dashboard %s : %s - %s. (headers : %s)' %
- (payload['dashboard']['title'], info['status'], info, headers))
+ raise GrafanaAPIException(
+ "Unable to create the new dashboard %s : %s - %s. (headers : %s)"
+ % (payload["dashboard"]["title"], info["status"], info, headers)
+ )
return result
def grafana_delete_dashboard(module, data):
-
# define http headers
headers = grafana_headers(module, data)
- grafana_version = get_grafana_version(module, data['url'], headers)
+ grafana_version = get_grafana_version(module, data["url"], headers)
if grafana_version < 5:
- if data.get('slug'):
- uid = data['slug']
+ if data.get("slug"):
+ uid = data["slug"]
else:
- raise GrafanaMalformedJson('No slug parameter. Needed with grafana < 5')
+ raise GrafanaMalformedJson("No slug parameter. Needed with grafana < 5")
else:
- if data.get('uid'):
- uid = data['uid']
+ if data.get("uid"):
+ uid = data["uid"]
else:
- raise GrafanaDeleteException('No uid specified %s')
+ raise GrafanaDeleteException("No uid specified %s")
# test if dashboard already exists
- dashboard_exists, dashboard = grafana_dashboard_exists(module, data['url'], uid, headers=headers)
+ dashboard_exists, dashboard = grafana_dashboard_exists(
+ module, data["url"], uid, headers=headers
+ )
result = {}
if dashboard_exists is True:
+ if module.check_mode:
+ module.exit_json(
+ uid=uid,
+ failed=False,
+ changed=True,
+ msg="Dashboard %s will be deleted" % uid,
+ )
+
# delete
if grafana_version < 5:
- r, info = fetch_url(module, '%s/api/dashboards/db/%s' % (data['url'], uid), headers=headers, method='DELETE')
+ r, info = fetch_url(
+ module,
+ "%s/api/dashboards/db/%s" % (data["url"], uid),
+ headers=headers,
+ method="DELETE",
+ )
else:
- r, info = fetch_url(module, '%s/api/dashboards/uid/%s' % (data['url'], uid), headers=headers, method='DELETE')
- if info['status'] == 200:
- result['msg'] = "Dashboard %s deleted" % uid
- result['changed'] = True
- result['uid'] = uid
+ r, info = fetch_url(
+ module,
+ "%s/api/dashboards/uid/%s" % (data["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))
+ 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}
+ 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['url'], headers)
+ grafana_version = get_grafana_version(module, data["url"], headers)
if grafana_version < 5:
- if data.get('slug'):
- uid = data['slug']
+ if data.get("slug"):
+ uid = data["slug"]
else:
- raise GrafanaMalformedJson('No slug parameter. Needed with grafana < 5')
+ raise GrafanaMalformedJson("No slug parameter. Needed with grafana < 5")
else:
- if data.get('uid'):
- uid = data['uid']
+ if data.get("uid"):
+ uid = data["uid"]
else:
- raise GrafanaExportException('No uid specified')
+ raise GrafanaExportException("No uid specified")
# test if dashboard already exists
- dashboard_exists, dashboard = grafana_dashboard_exists(module, data['url'], uid, headers=headers)
+ dashboard_exists, dashboard = grafana_dashboard_exists(
+ module, data["url"], uid, headers=headers
+ )
if dashboard_exists is True:
+ if module.check_mode:
+ module.exit_json(
+ uid=uid,
+ failed=False,
+ changed=True,
+ msg="Dashboard %s will be exported to %s" % (uid, data["path"]),
+ )
try:
- with open(data['path'], 'w', encoding="utf-8") as f:
+ with open(data["path"], "w", encoding="utf-8") as f:
f.write(json.dumps(dashboard, indent=2))
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}
+ 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}
+ result = {
+ "msg": "Dashboard %s does not exist." % uid,
+ "uid": uid,
+ "changed": False,
+ }
return result
@@ -488,72 +610,75 @@ def main():
# use the predefined argument spec for url
argument_spec = grafana_argument_spec()
argument_spec.update(
- state=dict(choices=['present', 'absent', 'export'], default='present'),
- org_id=dict(default=1, type='int'),
- folder=dict(type='str', default='General'),
- uid=dict(type='str'),
- slug=dict(type='str'),
- path=dict(aliases=['dashboard_url'], type='str'),
- dashboard_id=dict(type='str'),
- dashboard_revision=dict(type='str', default='1'),
- overwrite=dict(type='bool', default=False),
- commit_message=dict(type='str', aliases=['message'],
- deprecated_aliases=[dict(name='message',
- version='2.0.0', collection_name="community.grafana")]),
+ state=dict(choices=["present", "absent", "export"], default="present"),
+ org_id=dict(default=1, type="int"),
+ org_name=dict(type="str"),
+ folder=dict(type="str", default="General"),
+ uid=dict(type="str"),
+ slug=dict(type="str"),
+ path=dict(aliases=["dashboard_url"], type="str"),
+ dashboard_id=dict(type="str"),
+ dashboard_revision=dict(type="str", default="1"),
+ overwrite=dict(type="bool", default=False),
+ commit_message=dict(
+ type="str",
+ aliases=["message"],
+ deprecated_aliases=[
+ dict(
+ name="message", version="2.0.0", collection_name="community.grafana"
+ )
+ ],
+ ),
)
module = AnsibleModule(
argument_spec=argument_spec,
- supports_check_mode=False,
+ supports_check_mode=True,
required_if=[
- ['state', 'export', ['path']],
+ ["state", "export", ["path"]],
+ ],
+ required_together=[["url_username", "url_password", "org_id"]],
+ mutually_exclusive=[
+ ["url_username", "grafana_api_key"],
+ ["uid", "slug"],
+ ["path", "dashboard_id"],
+ ["org_id", "org_name"],
],
- required_together=[['url_username', 'url_password', 'org_id']],
- mutually_exclusive=[['url_username', 'grafana_api_key'], ['uid', 'slug'], ['path', 'dashboard_id']],
)
module.params["url"] = clean_url(module.params["url"])
- if 'message' in module.params:
- module.fail_json(msg="'message' is reserved keyword, please change this parameter to 'commit_message'")
+ 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':
+ if module.params["state"] == "present":
result = grafana_create_dashboard(module, module.params)
- elif module.params['state'] == 'absent':
+ 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)
- )
+ 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)
- )
+ 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)
+ 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)
+ failed=True, msg="error : Can't export dashboard : %s" % to_native(e)
)
return
- module.exit_json(
- failed=False,
- **result
- )
+ module.exit_json(failed=False, **result)
return
-if __name__ == '__main__':
+if __name__ == "__main__":
main()
diff --git a/ansible_collections/community/grafana/plugins/modules/grafana_datasource.py b/ansible_collections/community/grafana/plugins/modules/grafana_datasource.py
index 6346038f4..29cdbea7c 100644
--- a/ansible_collections/community/grafana/plugins/modules/grafana_datasource.py
+++ b/ansible_collections/community/grafana/plugins/modules/grafana_datasource.py
@@ -5,9 +5,10 @@
# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
+
__metaclass__ = type
-DOCUMENTATION = '''
+DOCUMENTATION = """
module: grafana_datasource
author:
- Thierry Sallé (@seuf)
@@ -46,6 +47,8 @@ options:
- camptocamp-prometheus-alertmanager-datasource
- loki
- redis-datasource
+ - tempo
+ - quickwit-quickwit-datasource
type: str
ds_url:
description:
@@ -67,15 +70,18 @@ options:
(index name), C(mysql) or C(postgres).
required: false
type: str
+ default: ''
user:
description:
- The datasource login user for influxdb datasources.
type: str
+ default: ''
password:
description:
- The datasource password.
- Stored as secure data, see C(enforce_secure_data) and notes!
type: str
+ default: ''
basic_auth_user:
description:
- The datasource basic auth user.
@@ -83,7 +89,7 @@ options:
type: str
basic_auth_password:
description:
- - The datasource basic auth password, when C(basic auth) is C(yes).
+ - The datasource basic auth password, when C(basic auth) is C(true).
- Stored as secure data, see C(enforce_secure_data) and notes!
type: str
with_credentials:
@@ -91,7 +97,7 @@ options:
- Whether credentials such as cookies or auth headers should be sent with cross-site
requests.
type: bool
- default: 'no'
+ default: false
tls_client_cert:
description:
- The client TLS certificate.
@@ -120,14 +126,22 @@ options:
description:
- Make this datasource the default one.
type: bool
- default: 'no'
+ default: false
org_id:
description:
- - Grafana Organisation ID in which the datasource should be created.
+ - Grafana organization 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.
+ belongs to one organization.
+ - Mutually exclusive with C(org_name).
default: 1
type: int
+ org_name:
+ description:
+ - Grafana organization name in which the datasource should be created.
+ - Not used when C(grafana_api_key) is set, because the C(grafana_api_key) only
+ belongs to one organization.
+ - Mutually exclusive with C(org_id).
+ type: str
state:
description:
- Status of the datasource
@@ -166,7 +180,7 @@ options:
default: '@timestamp'
time_interval:
description:
- - Minimum group by interval for C(influxdb) or C(elasticsearch) datasources.
+ - Minimum group by interval for C(influxdb), C(elasticsearch) or C(prometheus) datasources.
- for example C(>10s).
type: str
interval:
@@ -180,6 +194,7 @@ options:
- Monthly
- Yearly
type: str
+ default: ''
tsdb_version:
description:
- The opentsdb version.
@@ -213,7 +228,7 @@ options:
description:
- Use trends or not for zabbix datasource type.
type: bool
- default: False
+ default: false
aws_auth_type:
description:
- Type for AWS authentication for CloudWatch datasource type (authType of grafana
@@ -280,7 +295,6 @@ options:
aws_custom_metrics_namespaces:
description:
- Namespaces of Custom Metrics for CloudWatch datasource type
- default: ''
required: false
type: str
azure_cloud:
@@ -350,9 +364,9 @@ notes:
data will not be updated after initial creation! To force the secure data update you have to set I(enforce_secure_data=True).
- Hint, with the C(enforce_secure_data) always reporting changed=True, you might just do one Task updating the datasource without
any secure data and make a separate playbook/task also changing the secure data. This way it will not break any workflow.
-'''
+"""
-EXAMPLES = '''
+EXAMPLES = """
---
- name: Create elasticsearch datasource
community.grafana.grafana_datasource:
@@ -460,9 +474,9 @@ EXAMPLES = '''
additional_secure_json_data:
httpHeaderValue1: "Bearer ihavenogroot"
enforce_secure_data: true
-'''
+"""
-RETURN = '''
+RETURN = """
---
datasource:
description: datasource created/updated by module
@@ -488,13 +502,13 @@ datasource:
"user": "",
"password": "",
"withCredentials": false }
-'''
+"""
import json
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, basic_auth_header
+from ansible.module_utils.urls import fetch_url, basic_auth_header
from ansible_collections.community.grafana.plugins.module_utils import base
@@ -506,175 +520,191 @@ ES_VERSION_MAPPING = {
def compare_datasources(new, current, compareSecureData=True):
- if new['uid'] is None:
- del current['uid']
- del new['uid']
- del current['typeLogoUrl']
- del current['id']
- if 'version' in current:
- del current['version']
- if 'readOnly' in current:
- del current['readOnly']
- if current['basicAuth'] is False:
- del current['basicAuthUser']
- if 'password' in current:
- del current['password']
- if 'basicAuthPassword' in current:
- del current['basicAuthPassword']
+ if new["uid"] is None:
+ del current["uid"]
+ del new["uid"]
+ del current["typeLogoUrl"]
+ del current["id"]
+ if "version" in current:
+ del current["version"]
+ if "readOnly" in current:
+ del current["readOnly"]
+ if current["basicAuth"] is False:
+ if "basicAuthUser" in current:
+ del current["basicAuthUser"]
+ if "password" in current:
+ del current["password"]
+ if "basicAuthPassword" in current:
+ del current["basicAuthPassword"]
# check if secureJsonData should be compared
if not compareSecureData:
# if we should ignore it just drop alltogether
- new.pop('secureJsonData', None)
- new.pop('secureJsonFields', None)
- current.pop('secureJsonData', None)
- current.pop('secureJsonFields', None)
+ new.pop("secureJsonData", None)
+ new.pop("secureJsonFields", None)
+ current.pop("secureJsonData", None)
+ current.pop("secureJsonFields", None)
else:
# handle secureJsonData/secureJsonFields, some current facts:
# - secureJsonFields is reporting each field set as true
# - secureJsonFields once set cant be removed (DS has to be deleted)
- if not new.get('secureJsonData'):
+ if not new.get("secureJsonData"):
# secureJsonData is not provided so just remove both for comparision
- new.pop('secureJsonData', None)
- current.pop('secureJsonFields', None)
+ new.pop("secureJsonData", None)
+ current.pop("secureJsonFields", None)
else:
# we have some secure data so just "rename" secureJsonFields for comparison as it will change anyhow everytime
- current['secureJsonData'] = current.pop('secureJsonFields')
+ current["secureJsonData"] = current.pop("secureJsonFields")
return dict(before=current, after=new)
-def get_datasource_payload(data):
+def get_datasource_payload(data, org_id=None):
payload = {
- 'orgId': data['org_id'],
- 'name': data['name'],
- 'uid': data['uid'],
- 'type': data['ds_type'],
- 'access': data['access'],
- 'url': data['ds_url'],
- 'database': data['database'],
- 'withCredentials': data['with_credentials'],
- 'isDefault': data['is_default'],
- 'user': data['user'],
- 'jsonData': data['additional_json_data'],
- 'secureJsonData': data['additional_secure_json_data']
+ "orgId": data["org_id"] if org_id is None else org_id,
+ "name": data["name"],
+ "uid": data["uid"],
+ "type": data["ds_type"],
+ "access": data["access"],
+ "url": data["ds_url"],
+ "database": data["database"],
+ "withCredentials": data["with_credentials"],
+ "isDefault": data["is_default"],
+ "user": data["user"],
+ "jsonData": data["additional_json_data"],
+ "secureJsonData": data["additional_secure_json_data"],
}
- json_data = payload['jsonData']
- secure_json_data = payload['secureJsonData']
+ json_data = payload["jsonData"]
+ secure_json_data = payload["secureJsonData"]
# define password
- if data.get('password'):
- secure_json_data['password'] = data['password']
+ if data.get("password"):
+ secure_json_data["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']
- secure_json_data['basicAuthPassword'] = data['basic_auth_password']
+ 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"]
+ secure_json_data["basicAuthPassword"] = data["basic_auth_password"]
else:
- payload['basicAuth'] = False
+ payload["basicAuth"] = False
# define tls auth
- if data.get('tls_client_cert') and data.get('tls_client_key'):
- json_data['tlsAuth'] = True
- if data.get('tls_ca_cert'):
- secure_json_data['tlsCACert'] = data['tls_ca_cert']
- secure_json_data['tlsClientCert'] = data['tls_client_cert']
- secure_json_data['tlsClientKey'] = data['tls_client_key']
- json_data['tlsAuthWithCACert'] = True
+ if data.get("tls_client_cert") and data.get("tls_client_key"):
+ json_data["tlsAuth"] = True
+ if data.get("tls_ca_cert"):
+ secure_json_data["tlsCACert"] = data["tls_ca_cert"]
+ secure_json_data["tlsClientCert"] = data["tls_client_cert"]
+ secure_json_data["tlsClientKey"] = data["tls_client_key"]
+ json_data["tlsAuthWithCACert"] = True
else:
- secure_json_data['tlsClientCert'] = data['tls_client_cert']
- secure_json_data['tlsClientKey'] = data['tls_client_key']
+ secure_json_data["tlsClientCert"] = data["tls_client_cert"]
+ secure_json_data["tlsClientKey"] = data["tls_client_key"]
else:
- json_data['tlsAuth'] = False
- json_data['tlsAuthWithCACert'] = False
- if data.get('tls_ca_cert'):
- json_data['tlsAuthWithCACert'] = True
- secure_json_data['tlsCACert'] = data['tls_ca_cert']
+ json_data["tlsAuth"] = False
+ json_data["tlsAuthWithCACert"] = False
+ if data.get("tls_ca_cert"):
+ json_data["tlsAuthWithCACert"] = True
+ secure_json_data["tlsCACert"] = data["tls_ca_cert"]
- if data.get('tls_skip_verify'):
- json_data['tlsSkipVerify'] = True
+ if data.get("tls_skip_verify"):
+ json_data["tlsSkipVerify"] = True
# datasource type related parameters
- if data['ds_type'] == 'elasticsearch':
-
- json_data['maxConcurrentShardRequests'] = data['max_concurrent_shard_requests']
- json_data['timeField'] = data['time_field']
- if data.get('interval'):
- json_data['interval'] = data['interval']
+ if data["ds_type"] == "elasticsearch":
+ json_data["maxConcurrentShardRequests"] = data["max_concurrent_shard_requests"]
+ json_data["timeField"] = data["time_field"]
+ if data.get("interval"):
+ json_data["interval"] = data["interval"]
# Handle changes in es_version format in Grafana < 8.x which used to
# be integers and is now semver format
try:
- es_version = int(data['es_version'])
+ es_version = int(data["es_version"])
if es_version < 56:
- json_data.pop('maxConcurrentShardRequests')
+ json_data.pop("maxConcurrentShardRequests")
except ValueError:
# Retrieve the Semver format expected by API
- es_version = ES_VERSION_MAPPING.get(data['es_version'])
- json_data['esVersion'] = es_version
+ es_version = ES_VERSION_MAPPING.get(data["es_version"])
+ json_data["esVersion"] = es_version
- 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"] in ["elasticsearch", "influxdb", "prometheus"]:
+ 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
+ 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['ds_type'] == 'alexanderzobnin-zabbix-datasource':
- if data.get('trends'):
- json_data['trends'] = True
- json_data['username'] = data['zabbix_user']
- json_data['password'] = data['zabbix_password']
-
- if data['ds_type'] == 'grafana-azure-monitor-datasource':
- json_data['tenantId'] = data['azure_tenant']
- json_data['clientId'] = data['azure_client']
- json_data['cloudName'] = data['azure_cloud']
- json_data['clientsecret'] = 'clientsecret'
- if data.get('azure_secret'):
- secure_json_data['clientSecret'] = data['azure_secret']
-
- if data['ds_type'] == 'cloudwatch':
- if data.get('aws_credentials_profile'):
- 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'):
- secure_json_data['accessKey'] = data.get('aws_access_key')
- secure_json_data['secretKey'] = data.get('aws_secret_key')
-
- payload['jsonData'] = json_data
- payload['secureJsonData'] = secure_json_data
+ json_data["tsdbResolution"] = 2
+
+ if data["ds_type"] == "postgres":
+ json_data["sslmode"] = data["sslmode"]
+
+ if data["ds_type"] == "alexanderzobnin-zabbix-datasource":
+ if data.get("trends"):
+ json_data["trends"] = True
+ json_data["username"] = data["zabbix_user"]
+ json_data["password"] = data["zabbix_password"]
+
+ if data["ds_type"] == "grafana-azure-monitor-datasource":
+ json_data["tenantId"] = data["azure_tenant"]
+ json_data["clientId"] = data["azure_client"]
+ json_data["cloudName"] = data["azure_cloud"]
+ json_data["clientsecret"] = "clientsecret"
+ if data.get("azure_secret"):
+ secure_json_data["clientSecret"] = data["azure_secret"]
+
+ if data["ds_type"] == "cloudwatch":
+ if data.get("aws_credentials_profile"):
+ 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"):
+ secure_json_data["accessKey"] = data.get("aws_access_key")
+ secure_json_data["secretKey"] = data.get("aws_secret_key")
+
+ payload["jsonData"] = json_data
+ payload["secureJsonData"] = secure_json_data
return payload
class GrafanaInterface(object):
-
def __init__(self, module):
self._module = module
self.grafana_url = base.clean_url(module.params.get("url"))
+ self.org_id = None
# {{{ Authentication header
self.headers = {"Content-Type": "application/json"}
- if module.params.get('grafana_api_key', None):
- self.headers["Authorization"] = "Bearer %s" % module.params['grafana_api_key']
+ if module.params.get("grafana_api_key", None):
+ self.headers["Authorization"] = (
+ "Bearer %s" % module.params["grafana_api_key"]
+ )
else:
- self.headers["Authorization"] = basic_auth_header(module.params['url_username'], module.params['url_password'])
- self.switch_organisation(module.params['org_id'])
+ self.headers["Authorization"] = basic_auth_header(
+ module.params["url_username"], module.params["url_password"]
+ )
+ self.org_id = (
+ self.organization_by_name(module.params["org_name"])
+ if module.params["org_name"]
+ else module.params["org_id"]
+ )
+ self.switch_organization(self.org_id)
# }}}
def _send_request(self, url, data=None, headers=None, method="GET"):
@@ -684,125 +714,200 @@ class GrafanaInterface(object):
headers = []
full_url = "{grafana_url}{path}".format(grafana_url=self.grafana_url, path=url)
- resp, info = fetch_url(self._module, full_url, data=data, headers=headers, method=method)
+ resp, info = fetch_url(
+ self._module, full_url, data=data, headers=headers, method=method
+ )
status_code = info["status"]
if status_code == 404:
return None
elif status_code == 401:
- self._module.fail_json(failed=True, msg="Unauthorized to perform action '%s' on '%s'" % (method, full_url))
+ self._module.fail_json(
+ failed=True,
+ msg="Unauthorized to perform action '%s' on '%s'" % (method, full_url),
+ )
elif status_code == 403:
self._module.fail_json(failed=True, msg="Permission Denied")
elif status_code == 200:
return self._module.from_json(resp.read())
- self._module.fail_json(failed=True, msg="Grafana API answered with HTTP %d for url %s and data %s" % (status_code, url, data))
+ self._module.fail_json(
+ failed=True,
+ msg="Grafana API answered with HTTP %d for url %s and data %s"
+ % (status_code, url, data),
+ )
- def switch_organisation(self, org_id):
+ def switch_organization(self, org_id):
url = "/api/user/using/%d" % org_id
- response = self._send_request(url, headers=self.headers, method='POST')
+ self._send_request(url, headers=self.headers, method="POST")
+
+ def organization_by_name(self, org_name):
+ url = "/api/user/orgs"
+ organizations = self._send_request(url, headers=self.headers, method="GET")
+ orga = next((org for org in organizations if org["name"] == org_name))
+ if orga:
+ return orga["orgId"]
+
+ return self._module.fail_json(
+ failed=True, msg="Current user isn't member of organization: %s" % org_name
+ )
def datasource_by_name(self, name):
- datasource_exists = False
- ds = {}
- url = "/api/datasources/name/%s" % quote(name, safe='')
- return self._send_request(url, headers=self.headers, method='GET')
+ url = "/api/datasources/name/%s" % quote(name, safe="")
+ return self._send_request(url, headers=self.headers, method="GET")
def delete_datasource(self, name):
- url = "/api/datasources/name/%s" % quote(name, safe='')
- self._send_request(url, headers=self.headers, method='DELETE')
+ url = "/api/datasources/name/%s" % quote(name, safe="")
+ self._send_request(url, headers=self.headers, method="DELETE")
def update_datasource(self, ds_id, data):
url = "/api/datasources/%d" % ds_id
- self._send_request(url, data=data, headers=self.headers, method='PUT')
+ self._send_request(url, data=data, headers=self.headers, method="PUT")
def create_datasource(self, data):
url = "/api/datasources"
- self._send_request(url, data=data, headers=self.headers, method='POST')
+ self._send_request(url, data=data, headers=self.headers, method="POST")
def setup_module_object():
argument_spec = base.grafana_argument_spec()
argument_spec.update(
- name=dict(required=True, type='str'),
- uid=dict(type='str'),
- ds_type=dict(choices=['graphite',
- 'prometheus',
- 'elasticsearch',
- 'influxdb',
- 'opentsdb',
- 'mysql',
- 'postgres',
- 'cloudwatch',
- 'alexanderzobnin-zabbix-datasource',
- 'grafana-azure-monitor-datasource',
- 'camptocamp-prometheus-alertmanager-datasource',
- 'sni-thruk-datasource',
- 'redis-datasource',
- 'loki']),
- ds_url=dict(type='str'),
- access=dict(default='proxy', choices=['proxy', 'direct']),
- database=dict(type='str', default=""),
- 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='str', default="7.10+", choices=["2", "5", "56", "60",
- "70", "7.7+", "7.10+",
- "8.0+"]),
- 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', 'default']),
- 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'),
- azure_cloud=dict(type='str', default='azuremonitor', choices=['azuremonitor', 'chinaazuremonitor', 'govazuremonitor', 'germanyazuremonitor']),
- azure_tenant=dict(type='str'),
- azure_client=dict(type='str'),
- azure_secret=dict(type='str', no_log=True),
- zabbix_user=dict(type='str'),
- zabbix_password=dict(type='str', no_log=True),
- additional_json_data=dict(type='dict', default={}, required=False),
- additional_secure_json_data=dict(type='dict', default={}, required=False),
- enforce_secure_data=dict(type='bool', default=False, required=False)
+ name=dict(required=True, type="str"),
+ uid=dict(type="str"),
+ ds_type=dict(
+ choices=[
+ "graphite",
+ "prometheus",
+ "elasticsearch",
+ "influxdb",
+ "opentsdb",
+ "mysql",
+ "postgres",
+ "cloudwatch",
+ "alexanderzobnin-zabbix-datasource",
+ "grafana-azure-monitor-datasource",
+ "camptocamp-prometheus-alertmanager-datasource",
+ "sni-thruk-datasource",
+ "redis-datasource",
+ "loki",
+ "tempo",
+ "quickwit-quickwit-datasource",
+ ]
+ ),
+ ds_url=dict(type="str"),
+ access=dict(default="proxy", choices=["proxy", "direct"]),
+ database=dict(type="str", default=""),
+ 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"),
+ org_name=dict(type="str"),
+ es_version=dict(
+ type="str",
+ default="7.10+",
+ choices=["2", "5", "56", "60", "70", "7.7+", "7.10+", "8.0+"],
+ ),
+ 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", "default"]
+ ),
+ 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"),
+ azure_cloud=dict(
+ type="str",
+ default="azuremonitor",
+ choices=[
+ "azuremonitor",
+ "chinaazuremonitor",
+ "govazuremonitor",
+ "germanyazuremonitor",
+ ],
+ ),
+ azure_tenant=dict(type="str"),
+ azure_client=dict(type="str"),
+ azure_secret=dict(type="str", no_log=True),
+ zabbix_user=dict(type="str"),
+ zabbix_password=dict(type="str", no_log=True),
+ additional_json_data=dict(type="dict", default={}, required=False),
+ additional_secure_json_data=dict(type="dict", default={}, required=False),
+ enforce_secure_data=dict(type="bool", default=False, required=False),
)
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_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"],
+ ["org_id", "org_name"],
+ ],
required_if=[
- ['state', 'present', ['ds_type', 'ds_url']],
- ['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']],
- ['es_version', "60", ['max_concurrent_shard_requests']],
- ['es_version', "70", ['max_concurrent_shard_requests']]
+ ["state", "present", ["ds_type", "ds_url"]],
+ ["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"]],
+ ["es_version", "60", ["max_concurrent_shard_requests"]],
+ ["es_version", "70", ["max_concurrent_shard_requests"]],
],
)
return module
@@ -811,35 +916,52 @@ def setup_module_object():
def main():
module = setup_module_object()
- state = module.params['state']
- name = module.params['name']
- enforce_secure_data = module.params['enforce_secure_data']
+ state = module.params["state"]
+ name = module.params["name"]
+ enforce_secure_data = module.params["enforce_secure_data"]
grafana_iface = GrafanaInterface(module)
ds = grafana_iface.datasource_by_name(name)
- if state == 'present':
- payload = get_datasource_payload(module.params)
+ if state == "present":
+ payload = get_datasource_payload(module.params, grafana_iface.org_id)
if ds is None:
grafana_iface.create_datasource(payload)
ds = grafana_iface.datasource_by_name(name)
- module.exit_json(changed=True, datasource=ds, msg='Datasource %s created' % name)
+ module.exit_json(
+ changed=True, datasource=ds, msg="Datasource %s created" % name
+ )
else:
diff = compare_datasources(payload.copy(), ds.copy(), enforce_secure_data)
- if diff.get('before') == diff.get('after'):
- module.exit_json(changed=False, datasource=ds, msg='Datasource %s unchanged' % name)
- grafana_iface.update_datasource(ds.get('id'), payload)
+ if diff.get("before") == diff.get("after"):
+ module.exit_json(
+ changed=False, datasource=ds, msg="Datasource %s unchanged" % name
+ )
+ grafana_iface.update_datasource(ds.get("id"), payload)
ds = grafana_iface.datasource_by_name(name)
- if diff.get('before') == diff.get('after'):
- module.exit_json(changed=False, datasource=ds, msg='Datasource %s unchanged' % name)
-
- module.exit_json(changed=True, diff=diff, datasource=ds, msg='Datasource %s updated' % name)
+ if diff.get("before") == diff.get("after"):
+ module.exit_json(
+ changed=False, datasource=ds, msg="Datasource %s unchanged" % name
+ )
+
+ module.exit_json(
+ changed=True,
+ diff=diff,
+ datasource=ds,
+ msg="Datasource %s updated" % name,
+ )
else:
if ds is None:
- module.exit_json(changed=False, datasource=None, msg='Datasource %s does not exist.' % name)
+ module.exit_json(
+ changed=False,
+ datasource=None,
+ msg="Datasource %s does not exist." % name,
+ )
grafana_iface.delete_datasource(name)
- module.exit_json(changed=True, datasource=None, msg='Datasource %s deleted.' % name)
+ module.exit_json(
+ changed=True, datasource=None, msg="Datasource %s deleted." % name
+ )
-if __name__ == '__main__':
+if __name__ == "__main__":
main()
diff --git a/ansible_collections/community/grafana/plugins/modules/grafana_folder.py b/ansible_collections/community/grafana/plugins/modules/grafana_folder.py
index d39e56e41..73c437dbb 100644
--- a/ansible_collections/community/grafana/plugins/modules/grafana_folder.py
+++ b/ansible_collections/community/grafana/plugins/modules/grafana_folder.py
@@ -19,7 +19,7 @@
from __future__ import absolute_import, division, print_function
-DOCUMENTATION = '''
+DOCUMENTATION = """
---
module: grafana_folder
author:
@@ -44,20 +44,35 @@ options:
default: present
type: str
choices: ["present", "absent"]
+ org_id:
+ description:
+ - Grafana organization ID in which the datasource should be created.
+ - Not used when C(grafana_api_key) is set, because the C(grafana_api_key) only
+ belongs to one organization.
+ - Mutually exclusive with C(org_name).
+ default: 1
+ type: int
+ org_name:
+ description:
+ - Grafana organization name in which the datasource should be created.
+ - Not used when C(grafana_api_key) is set, because the C(grafana_api_key) only
+ belongs to one organization.
+ - Mutually exclusive with C(org_id).
+ type: str
skip_version_check:
description:
- Skip Grafana version check and try to reach api endpoint anyway.
- - This parameter can be useful if you enabled `hide_version` in grafana.ini
+ - This parameter can be useful if you enabled C(hide_version) in grafana.ini
required: False
type: bool
- default: False
+ default: false
version_added: "1.2.0"
extends_documentation_fragment:
- community.grafana.basic_auth
- community.grafana.api_key
-'''
+"""
-EXAMPLES = '''
+EXAMPLES = """
---
- name: Create a folder
community.grafana.grafana_folder:
@@ -72,9 +87,9 @@ EXAMPLES = '''
grafana_api_key: "{{ some_api_token_value }}"
title: "grafana_working_group"
state: absent
-'''
+"""
-RETURN = '''
+RETURN = """
---
folder:
description: Information about the Folder
@@ -159,7 +174,7 @@ folder:
type: int
sample:
- 1
-'''
+"""
import json
@@ -177,24 +192,36 @@ class GrafanaError(Exception):
class GrafanaFolderInterface(object):
-
def __init__(self, module):
self._module = module
+ self.grafana_url = base.clean_url(module.params.get("url"))
+ self.org_id = None
# {{{ Authentication header
self.headers = {"Content-Type": "application/json"}
- if module.params.get('grafana_api_key', None):
- self.headers["Authorization"] = "Bearer %s" % module.params['grafana_api_key']
+ if module.params.get("grafana_api_key", None):
+ self.headers["Authorization"] = (
+ "Bearer %s" % module.params["grafana_api_key"]
+ )
else:
- self.headers["Authorization"] = basic_auth_header(module.params['url_username'], module.params['url_password'])
+ self.headers["Authorization"] = basic_auth_header(
+ module.params["url_username"], module.params["url_password"]
+ )
+ self.org_id = (
+ self.organization_by_name(module.params["org_name"])
+ if module.params["org_name"]
+ else module.params["org_id"]
+ )
+ self.switch_organization(self.org_id)
# }}}
- self.grafana_url = base.clean_url(module.params.get("url"))
if module.params.get("skip_version_check") is False:
try:
grafana_version = self.get_version()
except GrafanaError as e:
self._module.fail_json(failed=True, msg=to_text(e))
if grafana_version["major"] < 5:
- self._module.fail_json(failed=True, msg="Folders API is available starting Grafana v5")
+ self._module.fail_json(
+ failed=True, msg="Folders API is available starting Grafana v5"
+ )
def _send_request(self, url, data=None, headers=None, method="GET"):
if data is not None:
@@ -203,24 +230,51 @@ class GrafanaFolderInterface(object):
headers = []
full_url = "{grafana_url}{path}".format(grafana_url=self.grafana_url, path=url)
- resp, info = fetch_url(self._module, full_url, data=data, headers=headers, method=method)
+ resp, info = fetch_url(
+ self._module, full_url, data=data, headers=headers, method=method
+ )
status_code = info["status"]
if status_code == 404:
return None
elif status_code == 401:
- self._module.fail_json(failed=True, msg="Unauthorized to perform action '%s' on '%s'" % (method, full_url))
+ self._module.fail_json(
+ failed=True,
+ msg="Unauthorized to perform action '%s' on '%s'" % (method, full_url),
+ )
elif status_code == 403:
self._module.fail_json(failed=True, msg="Permission Denied")
elif status_code == 412:
- error_msg = resp.read()['message']
+ error_msg = resp.read()["message"]
self._module.fail_json(failed=True, msg=error_msg)
elif status_code == 200:
- return self._module.from_json(resp.read())
- self._module.fail_json(failed=True, msg="Grafana Folders API answered with HTTP %d" % status_code)
+ # XXX: Grafana folders endpoint stopped sending back json in response for delete operations
+ # see https://github.com/grafana/grafana/issues/77673
+ response = resp.read() or "{}"
+ return self._module.from_json(response)
+ self._module.fail_json(
+ failed=True, msg="Grafana Folders API answered with HTTP %d" % status_code
+ )
+
+ def switch_organization(self, org_id):
+ url = "/api/user/using/%d" % org_id
+ self._send_request(url, headers=self.headers, method="POST")
+
+ def organization_by_name(self, org_name):
+ url = "/api/user/orgs"
+ organizations = self._send_request(url, headers=self.headers, method="GET")
+ orga = next((org for org in organizations if org["name"] == org_name))
+ if orga:
+ return orga["orgId"]
+
+ self._module.fail_json(
+ failed=True, msg="Current user isn't member of organization: %s" % org_name
+ )
def get_version(self):
url = "/api/health"
- response = self._send_request(url, data=None, headers=self.headers, method="GET")
+ response = self._send_request(
+ url, data=None, headers=self.headers, method="GET"
+ )
version = response.get("version")
if version is not None:
major, minor, rev = version.split(".")
@@ -230,7 +284,9 @@ class GrafanaFolderInterface(object):
def create_folder(self, title):
url = "/api/folders"
folder = dict(title=title)
- response = self._send_request(url, data=folder, headers=self.headers, method="POST")
+ response = self._send_request(
+ url, data=folder, headers=self.headers, method="POST"
+ )
return response
def get_folder(self, title):
@@ -247,34 +303,33 @@ class GrafanaFolderInterface(object):
return response
-def setup_module_object():
+def main():
+ argument_spec = base.grafana_argument_spec()
+ argument_spec.update(
+ name=dict(type="str", aliases=["title"], required=True),
+ state=dict(type="str", default="present", choices=["present", "absent"]),
+ skip_version_check=dict(type="bool", default=False),
+ org_id=dict(default=1, type="int"),
+ org_name=dict(type="str"),
+ )
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=False,
- required_together=base.grafana_required_together(),
- mutually_exclusive=base.grafana_mutually_exclusive(),
+ required_together=base.grafana_required_together()
+ + [["url_username", "url_password", "org_id"]],
+ mutually_exclusive=base.grafana_mutually_exclusive()
+ + [
+ ["org_id", "org_name"],
+ ],
)
- return module
-
-
-argument_spec = base.grafana_argument_spec()
-argument_spec.update(
- name=dict(type='str', aliases=['title'], required=True),
- state=dict(type='str', default='present', choices=['present', 'absent']),
- skip_version_check=dict(type='bool', default=False),
-)
-
-
-def main():
-
- module = setup_module_object()
- state = module.params['state']
- title = module.params['name']
+ state = module.params["state"]
+ title = module.params["name"]
+ module.params["url"] = base.clean_url(module.params["url"])
grafana_iface = GrafanaFolderInterface(module)
changed = False
- if state == 'present':
+ if state == "present":
folder = grafana_iface.get_folder(title)
if folder is None:
grafana_iface.create_folder(title)
@@ -282,7 +337,7 @@ def main():
changed = True
folder = grafana_iface.get_folder(title)
module.exit_json(changed=changed, folder=folder)
- elif state == 'absent':
+ elif state == "absent":
folder = grafana_iface.get_folder(title)
if folder is None:
module.exit_json(changed=False, message="No folder found")
@@ -290,5 +345,5 @@ def main():
module.exit_json(changed=True, message=result)
-if __name__ == '__main__':
+if __name__ == "__main__":
main()
diff --git a/ansible_collections/community/grafana/plugins/modules/grafana_notification_channel.py b/ansible_collections/community/grafana/plugins/modules/grafana_notification_channel.py
index eb808fa1b..30b5b1124 100644
--- a/ansible_collections/community/grafana/plugins/modules/grafana_notification_channel.py
+++ b/ansible_collections/community/grafana/plugins/modules/grafana_notification_channel.py
@@ -16,9 +16,10 @@
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
from __future__ import absolute_import, division, print_function
+
__metaclass__ = type
-DOCUMENTATION = '''
+DOCUMENTATION = """
---
module: grafana_notification_channel
notes:
@@ -82,17 +83,17 @@ options:
- Required when I(state) is C(present).
is_default:
type: bool
- default: 'no'
+ default: false
description:
- Use this channel for all alerts.
include_image:
type: bool
- default: 'no'
+ default: false
description:
- Capture a visualization image and attach it to notifications.
disable_resolve_message:
type: bool
- default: 'no'
+ default: false
description:
- Disable the resolve message.
reminder_frequency:
@@ -368,10 +369,10 @@ options:
extends_documentation_fragment:
- community.grafana.basic_auth
- community.grafana.api_key
-'''
+"""
-EXAMPLES = '''
+EXAMPLES = """
- name: Create slack notification channel
register: result
grafana_notification_channel:
@@ -391,9 +392,9 @@ EXAMPLES = '''
grafana_url: "{{ grafana_url }}"
grafana_user: "{{ grafana_username }}"
grafana_password: "{{ grafana_password}}"
-'''
+"""
-RETURN = '''
+RETURN = """
notification_channel:
description: Notification channel created or updated by the module.
returned: changed
@@ -416,14 +417,17 @@ notification_channel:
"uid": "slack-oops",
"updated": "2020-11-10T21:10:19.675308112+03:00"
}
-'''
+"""
import json
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.urls import fetch_url
from ansible.module_utils._text import to_text
-from ansible_collections.community.grafana.plugins.module_utils.base import grafana_argument_spec, clean_url
+from ansible_collections.community.grafana.plugins.module_utils.base import (
+ grafana_argument_spec,
+ clean_url,
+)
from ansible.module_utils.urls import basic_auth_header
@@ -432,412 +436,448 @@ class GrafanaAPIException(Exception):
def dingding_channel_payload(data, payload):
- payload['settings']['url'] = data['dingding_url']
- if data.get('dingding_message_type'):
- payload['settings']['msgType'] = {
- 'link': 'link',
- 'action_card': 'actionCard',
- }[data['dingding_message_type']]
+ payload["settings"]["url"] = data["dingding_url"]
+ if data.get("dingding_message_type"):
+ payload["settings"]["msgType"] = {
+ "link": "link",
+ "action_card": "actionCard",
+ }[data["dingding_message_type"]]
def discord_channel_payload(data, payload):
- payload['settings']['url'] = data['discord_url']
- if data.get('discord_message_content'):
- payload['settings']['content'] = data['discord_message_content']
+ payload["settings"]["url"] = data["discord_url"]
+ if data.get("discord_message_content"):
+ payload["settings"]["content"] = data["discord_message_content"]
def email_channel_payload(data, payload):
- payload['settings']['addresses'] = ';'.join(data['email_addresses'])
- if data.get('email_single'):
- payload['settings']['singleEmail'] = data['email_single']
+ payload["settings"]["addresses"] = ";".join(data["email_addresses"])
+ if data.get("email_single"):
+ payload["settings"]["singleEmail"] = data["email_single"]
def hipchat_channel_payload(data, payload):
- payload['settings']['url'] = data['hipchat_url']
- if data.get('hipchat_api_key'):
- payload['settings']['apiKey'] = data['hipchat_api_key']
- if data.get('hipchat_room_id'):
- payload['settings']['roomid'] = data['hipchat_room_id']
+ payload["settings"]["url"] = data["hipchat_url"]
+ if data.get("hipchat_api_key"):
+ payload["settings"]["apiKey"] = data["hipchat_api_key"]
+ if data.get("hipchat_room_id"):
+ payload["settings"]["roomid"] = data["hipchat_room_id"]
def pagerduty_channel_payload(data, payload):
- payload['settings']['integrationKey'] = data['pagerduty_integration_key']
- if data.get('pagerduty_severity'):
- payload['settings']['severity'] = data['pagerduty_severity']
- if data.get('pagerduty_auto_resolve'):
- payload['settings']['autoResolve'] = data['pagerduty_auto_resolve']
- if data.get('pagerduty_message_in_details'):
- payload['settings']['messageInDetails'] = data['pagerduty_message_in_details']
+ payload["settings"]["integrationKey"] = data["pagerduty_integration_key"]
+ if data.get("pagerduty_severity"):
+ payload["settings"]["severity"] = data["pagerduty_severity"]
+ if data.get("pagerduty_auto_resolve"):
+ payload["settings"]["autoResolve"] = data["pagerduty_auto_resolve"]
+ if data.get("pagerduty_message_in_details"):
+ payload["settings"]["messageInDetails"] = data["pagerduty_message_in_details"]
def prometheus_channel_payload(data, payload):
- payload['type'] = 'prometheus-alertmanager'
- payload['settings']['url'] = data['prometheus_url']
- if data.get('prometheus_username'):
- payload['settings']['basicAuthUser'] = data['prometheus_username']
- if data.get('prometheus_password'):
- payload['settings']['basicAuthPassword'] = data['prometheus_password']
+ payload["type"] = "prometheus-alertmanager"
+ payload["settings"]["url"] = data["prometheus_url"]
+ if data.get("prometheus_username"):
+ payload["settings"]["basicAuthUser"] = data["prometheus_username"]
+ if data.get("prometheus_password"):
+ payload["settings"]["basicAuthPassword"] = data["prometheus_password"]
def pushover_channel_payload(data, payload):
- payload['settings']['apiToken'] = data['pushover_api_token']
- payload['settings']['userKey'] = data['pushover_user_key']
- if data.get('pushover_devices'):
- payload['settings']['device'] = ';'.join(data['pushover_devices'])
- if data.get('pushover_priority'):
- payload['settings']['priority'] = {
- 'emergency': '2',
- 'high': '1',
- 'normal': '0',
- 'low': '-1',
- 'lowest': '-2'
- }[data['pushover_priority']]
- if data.get('pushover_retry'):
- payload['settings']['retry'] = str(data['pushover_retry'])
- if data.get('pushover_expire'):
- payload['settings']['expire'] = str(data['pushover_expire'])
- if data.get('pushover_alert_sound'):
- payload['settings']['sound'] = data['pushover_alert_sound']
- if data.get('pushover_ok_sound'):
- payload['settings']['okSound'] = data['pushover_ok_sound']
+ payload["settings"]["apiToken"] = data["pushover_api_token"]
+ payload["settings"]["userKey"] = data["pushover_user_key"]
+ if data.get("pushover_devices"):
+ payload["settings"]["device"] = ";".join(data["pushover_devices"])
+ if data.get("pushover_priority"):
+ payload["settings"]["priority"] = {
+ "emergency": "2",
+ "high": "1",
+ "normal": "0",
+ "low": "-1",
+ "lowest": "-2",
+ }[data["pushover_priority"]]
+ if data.get("pushover_retry"):
+ payload["settings"]["retry"] = str(data["pushover_retry"])
+ if data.get("pushover_expire"):
+ payload["settings"]["expire"] = str(data["pushover_expire"])
+ if data.get("pushover_alert_sound"):
+ payload["settings"]["sound"] = data["pushover_alert_sound"]
+ if data.get("pushover_ok_sound"):
+ payload["settings"]["okSound"] = data["pushover_ok_sound"]
def sensu_channel_payload(data, payload):
- payload['settings']['url'] = data['sensu_url']
- if data.get('sensu_source'):
- payload['settings']['source'] = data['sensu_source']
- if data.get('sensu_handler'):
- payload['settings']['handler'] = data['sensu_handler']
- if data.get('sensu_username'):
- payload['settings']['username'] = data['sensu_username']
- if data.get('sensu_password'):
- payload['settings']['password'] = data['sensu_password']
+ payload["settings"]["url"] = data["sensu_url"]
+ if data.get("sensu_source"):
+ payload["settings"]["source"] = data["sensu_source"]
+ if data.get("sensu_handler"):
+ payload["settings"]["handler"] = data["sensu_handler"]
+ if data.get("sensu_username"):
+ payload["settings"]["username"] = data["sensu_username"]
+ if data.get("sensu_password"):
+ payload["settings"]["password"] = data["sensu_password"]
def slack_channel_payload(data, payload):
- payload['settings']['url'] = data['slack_url']
- if data.get('slack_recipient'):
- payload['settings']['recipient'] = data['slack_recipient']
- if data.get('slack_username'):
- payload['settings']['username'] = data['slack_username']
- if data.get('slack_icon_emoji'):
- payload['settings']['iconEmoji'] = data['slack_icon_emoji']
- if data.get('slack_icon_url'):
- payload['settings']['iconUrl'] = data['slack_icon_url']
- if data.get('slack_mention_users'):
- payload['settings']['mentionUsers'] = ','.join(data['slack_mention_users'])
- if data.get('slack_mention_groups'):
- payload['settings']['mentionGroups'] = ','.join(data['slack_mention_groups'])
- if data.get('slack_mention_channel'):
- payload['settings']['mentionChannel'] = data['slack_mention_channel']
- if data.get('slack_token'):
- payload['settings']['token'] = data['slack_token']
+ payload["settings"]["url"] = data["slack_url"]
+ if data.get("slack_recipient"):
+ payload["settings"]["recipient"] = data["slack_recipient"]
+ if data.get("slack_username"):
+ payload["settings"]["username"] = data["slack_username"]
+ if data.get("slack_icon_emoji"):
+ payload["settings"]["iconEmoji"] = data["slack_icon_emoji"]
+ if data.get("slack_icon_url"):
+ payload["settings"]["iconUrl"] = data["slack_icon_url"]
+ if data.get("slack_mention_users"):
+ payload["settings"]["mentionUsers"] = ",".join(data["slack_mention_users"])
+ if data.get("slack_mention_groups"):
+ payload["settings"]["mentionGroups"] = ",".join(data["slack_mention_groups"])
+ if data.get("slack_mention_channel"):
+ payload["settings"]["mentionChannel"] = data["slack_mention_channel"]
+ if data.get("slack_token"):
+ payload["settings"]["token"] = data["slack_token"]
def webhook_channel_payload(data, payload):
- payload['settings']['url'] = data['webhook_url']
- if data.get('webhook_http_method'):
- payload['settings']['httpMethod'] = data['webhook_http_method']
- if data.get('webhook_username'):
- payload['settings']['username'] = data['webhook_username']
- if data.get('webhook_password'):
- payload['settings']['password'] = data['webhook_password']
+ payload["settings"]["url"] = data["webhook_url"]
+ if data.get("webhook_http_method"):
+ payload["settings"]["httpMethod"] = data["webhook_http_method"]
+ if data.get("webhook_username"):
+ payload["settings"]["username"] = data["webhook_username"]
+ if data.get("webhook_password"):
+ payload["settings"]["password"] = data["webhook_password"]
def grafana_notification_channel_payload(data):
payload = {
- 'uid': data['uid'],
- 'name': data['name'],
- 'type': data['type'],
- 'isDefault': data['is_default'],
- 'disableResolveMessage': data['disable_resolve_message'],
- 'settings': {
- 'uploadImage': data['include_image']
- }
+ "uid": data["uid"],
+ "name": data["name"],
+ "type": data["type"],
+ "isDefault": data["is_default"],
+ "disableResolveMessage": data["disable_resolve_message"],
+ "settings": {"uploadImage": data["include_image"]},
}
- if data.get('reminder_frequency'):
- payload['sendReminder'] = True
- payload['frequency'] = data['reminder_frequency']
+ if data.get("reminder_frequency"):
+ payload["sendReminder"] = True
+ payload["frequency"] = data["reminder_frequency"]
- if data['type'] == 'dingding':
+ if data["type"] == "dingding":
dingding_channel_payload(data, payload)
- elif data['type'] == 'discord':
+ elif data["type"] == "discord":
discord_channel_payload(data, payload)
- elif data['type'] == 'email':
+ elif data["type"] == "email":
email_channel_payload(data, payload)
- elif data['type'] == 'googlechat':
- payload['settings']['url'] = data['googlechat_url']
- elif data['type'] == 'hipchat':
+ elif data["type"] == "googlechat":
+ payload["settings"]["url"] = data["googlechat_url"]
+ elif data["type"] == "hipchat":
hipchat_channel_payload(data, payload)
- elif data['type'] == 'kafka':
- payload['settings']['kafkaRestProxy'] = data['kafka_url']
- payload['settings']['kafkaTopic'] = data['kafka_topic']
- elif data['type'] == 'line':
- payload['settings']['token'] = data['line_token']
- elif data['type'] == 'teams':
- payload['settings']['url'] = data['teams_url']
- elif data['type'] == 'opsgenie':
- payload['settings']['apiUrl'] = data['opsgenie_url']
- payload['settings']['apiKey'] = data['opsgenie_api_key']
- elif data['type'] == 'pagerduty':
+ elif data["type"] == "kafka":
+ payload["settings"]["kafkaRestProxy"] = data["kafka_url"]
+ payload["settings"]["kafkaTopic"] = data["kafka_topic"]
+ elif data["type"] == "line":
+ payload["settings"]["token"] = data["line_token"]
+ elif data["type"] == "teams":
+ payload["settings"]["url"] = data["teams_url"]
+ elif data["type"] == "opsgenie":
+ payload["settings"]["apiUrl"] = data["opsgenie_url"]
+ payload["settings"]["apiKey"] = data["opsgenie_api_key"]
+ elif data["type"] == "pagerduty":
pagerduty_channel_payload(data, payload)
- elif data['type'] == 'prometheus':
+ elif data["type"] == "prometheus":
prometheus_channel_payload(data, payload)
- elif data['type'] == 'pushover':
+ elif data["type"] == "pushover":
pushover_channel_payload(data, payload)
- elif data['type'] == 'sensu':
+ elif data["type"] == "sensu":
sensu_channel_payload(data, payload)
- elif data['type'] == 'slack':
+ elif data["type"] == "slack":
slack_channel_payload(data, payload)
- elif data['type'] == 'telegram':
- payload['settings']['bottoken'] = data['telegram_bot_token']
- payload['settings']['chatid'] = data['telegram_chat_id']
- elif data['type'] == 'threema':
- payload['settings']['gateway_id'] = data['threema_gateway_id']
- payload['settings']['recipient_id'] = data['threema_recipient_id']
- payload['settings']['api_secret'] = data['threema_api_secret']
- elif data['type'] == 'victorops':
- payload['settings']['url'] = data['victorops_url']
- if data.get('victorops_auto_resolve'):
- payload['settings']['autoResolve'] = data['victorops_auto_resolve']
- elif data['type'] == 'webhook':
+ elif data["type"] == "telegram":
+ payload["settings"]["bottoken"] = data["telegram_bot_token"]
+ payload["settings"]["chatid"] = data["telegram_chat_id"]
+ elif data["type"] == "threema":
+ payload["settings"]["gateway_id"] = data["threema_gateway_id"]
+ payload["settings"]["recipient_id"] = data["threema_recipient_id"]
+ payload["settings"]["api_secret"] = data["threema_api_secret"]
+ elif data["type"] == "victorops":
+ payload["settings"]["url"] = data["victorops_url"]
+ if data.get("victorops_auto_resolve"):
+ payload["settings"]["autoResolve"] = data["victorops_auto_resolve"]
+ elif data["type"] == "webhook":
webhook_channel_payload(data, payload)
return payload
class GrafanaNotificationChannelInterface(object):
-
def __init__(self, module):
self._module = module
# {{{ Authentication header
self.headers = {"Content-Type": "application/json"}
- if module.params.get('grafana_api_key', None):
- self.headers["Authorization"] = "Bearer %s" % module.params['grafana_api_key']
+ if module.params.get("grafana_api_key", None):
+ self.headers["Authorization"] = (
+ "Bearer %s" % module.params["grafana_api_key"]
+ )
else:
- self.headers["Authorization"] = basic_auth_header(module.params['url_username'], module.params['url_password'])
+ self.headers["Authorization"] = basic_auth_header(
+ module.params["url_username"], module.params["url_password"]
+ )
# }}}
self.grafana_url = clean_url(module.params.get("url"))
def grafana_switch_organisation(self, grafana_url, org_id):
- r, info = fetch_url(self._module, '%s/api/user/using/%s' % (grafana_url, org_id),
- headers=self.headers, method='POST')
- if info['status'] != 200:
- raise GrafanaAPIException('Unable to switch to organization %s : %s' %
- (org_id, info))
+ r, info = fetch_url(
+ self._module,
+ "%s/api/user/using/%s" % (grafana_url, org_id),
+ headers=self.headers,
+ method="POST",
+ )
+ if info["status"] != 200:
+ raise GrafanaAPIException(
+ "Unable to switch to organization %s : %s" % (org_id, info)
+ )
def grafana_create_notification_channel(self, data, payload):
- r, info = fetch_url(self._module, '%s/api/alert-notifications' % data['url'],
- data=json.dumps(payload), headers=self.headers, method='POST')
- if info['status'] == 200:
+ r, info = fetch_url(
+ self._module,
+ "%s/api/alert-notifications" % data["url"],
+ data=json.dumps(payload),
+ headers=self.headers,
+ method="POST",
+ )
+ if info["status"] == 200:
return {
- 'state': 'present',
- 'changed': True,
- 'channel': json.loads(to_text(r.read())),
+ "state": "present",
+ "changed": True,
+ "channel": json.loads(to_text(r.read())),
}
else:
- raise GrafanaAPIException("Unable to create notification channel: %s" % info)
+ raise GrafanaAPIException(
+ "Unable to create notification channel: %s" % info
+ )
def grafana_update_notification_channel(self, data, payload, before):
- r, info = fetch_url(self._module, '%s/api/alert-notifications/uid/%s' %
- (data['url'], data['uid']),
- data=json.dumps(payload), headers=self.headers, method='PUT')
- if info['status'] == 200:
- del before['created']
- del before['updated']
+ r, info = fetch_url(
+ self._module,
+ "%s/api/alert-notifications/uid/%s" % (data["url"], data["uid"]),
+ data=json.dumps(payload),
+ headers=self.headers,
+ method="PUT",
+ )
+ if info["status"] == 200:
+ del before["created"]
+ del before["updated"]
channel = json.loads(to_text(r.read()))
after = channel.copy()
- del after['created']
- del after['updated']
+ del after["created"]
+ del after["updated"]
if before == after:
return {
- 'changed': False,
- 'channel': channel,
+ "changed": False,
+ "channel": channel,
}
else:
return {
- 'changed': True,
- 'diff': {
- 'before': before,
- 'after': after,
+ "changed": True,
+ "diff": {
+ "before": before,
+ "after": after,
},
- 'channel': channel,
+ "channel": channel,
}
else:
- raise GrafanaAPIException("Unable to update notification channel %s : %s" %
- (data['uid'], info))
+ raise GrafanaAPIException(
+ "Unable to update notification channel %s : %s" % (data["uid"], info)
+ )
def grafana_create_or_update_notification_channel(self, data):
payload = grafana_notification_channel_payload(data)
- r, info = fetch_url(self._module, '%s/api/alert-notifications/uid/%s' %
- (data['url'], data['uid']), headers=self.headers)
- if info['status'] == 200:
+ r, info = fetch_url(
+ self._module,
+ "%s/api/alert-notifications/uid/%s" % (data["url"], data["uid"]),
+ headers=self.headers,
+ )
+ if info["status"] == 200:
before = json.loads(to_text(r.read()))
return self.grafana_update_notification_channel(data, payload, before)
- elif info['status'] == 404:
+ elif info["status"] == 404:
return self.grafana_create_notification_channel(data, payload)
else:
- raise GrafanaAPIException("Unable to get notification channel %s : %s" %
- (data['uid'], info))
+ raise GrafanaAPIException(
+ "Unable to get notification channel %s : %s" % (data["uid"], info)
+ )
def grafana_delete_notification_channel(self, data):
- r, info = fetch_url(self._module, '%s/api/alert-notifications/uid/%s' %
- (data['url'], data['uid']),
- headers=self.headers, method='DELETE')
- if info['status'] == 200:
- return {
- 'state': 'absent',
- 'changed': True
- }
- elif info['status'] == 404:
- return {
- 'changed': False
- }
+ r, info = fetch_url(
+ self._module,
+ "%s/api/alert-notifications/uid/%s" % (data["url"], data["uid"]),
+ headers=self.headers,
+ method="DELETE",
+ )
+ if info["status"] == 200:
+ return {"state": "absent", "changed": True}
+ elif info["status"] == 404:
+ return {"changed": False}
else:
- raise GrafanaAPIException("Unable to delete notification channel %s : %s" %
- (data['uid'], info))
+ raise GrafanaAPIException(
+ "Unable to delete notification channel %s : %s" % (data["uid"], info)
+ )
def main():
argument_spec = grafana_argument_spec()
argument_spec.update(
- org_id=dict(type='int', default=1),
- uid=dict(type='str'),
- name=dict(type='str'),
- type=dict(type='str',
- choices=['dingding', 'discord', 'email', 'googlechat', 'hipchat',
- 'kafka', 'line', 'teams', 'opsgenie', 'pagerduty',
- 'prometheus', 'pushover', 'sensu', 'slack', 'telegram',
- 'threema', 'victorops', 'webhook']),
- is_default=dict(type='bool', default=False),
- include_image=dict(type='bool', default=False),
- disable_resolve_message=dict(type='bool', default=False),
- reminder_frequency=dict(type='str'),
-
- dingding_url=dict(type='str'),
- dingding_message_type=dict(type='list', elements='str',
- choices=['link', 'action_card']),
-
- discord_url=dict(type='str'),
- discord_message_content=dict(type='str'),
-
- email_addresses=dict(type='list', elements='str'),
- email_single=dict(type='bool'),
-
- googlechat_url=dict(type='str'),
-
- hipchat_url=dict(type='str'),
- hipchat_api_key=dict(type='str', no_log=True),
- hipchat_room_id=dict(type='str'),
-
- kafka_url=dict(type='str'),
- kafka_topic=dict(type='str'),
-
- line_token=dict(type='str', no_log=True),
-
- teams_url=dict(type='str'),
-
- opsgenie_url=dict(type='str'),
- opsgenie_api_key=dict(type='str', no_log=True),
- opsgenie_auto_close=dict(type='bool'),
- opsgenie_override_priority=dict(type='bool'),
-
- pagerduty_integration_key=dict(type='str', no_log=True),
- pagerduty_severity=dict(type='list', elements='str',
- choices=['critical', 'error', 'warning', 'info']),
- pagerduty_auto_resolve=dict(type='bool'),
- pagerduty_message_in_details=dict(type='bool'),
-
- prometheus_url=dict(type='str'),
- prometheus_username=dict(type='str'),
- prometheus_password=dict(type='str', no_log=True),
-
- pushover_api_token=dict(type='str', no_log=True),
- pushover_user_key=dict(type='str', no_log=True),
- pushover_devices=dict(type='list', elements='str'),
- pushover_priority=dict(type='list', elements='str',
- choices=['emergency', 'high', 'normal', 'low', 'lowest']),
- pushover_retry=dict(type='int'), # TODO: only when priority==emergency
- pushover_expire=dict(type='int'), # TODO: only when priority==emergency
- pushover_alert_sound=dict(type='str'), # TODO: add sound choices
- pushover_ok_sound=dict(type='str'), # TODO: add sound choices
-
- sensu_url=dict(type='str'),
- sensu_source=dict(type='str'),
- sensu_handler=dict(type='str'),
- sensu_username=dict(type='str'),
- sensu_password=dict(type='str', no_log=True),
-
- slack_url=dict(type='str', no_log=True),
- slack_recipient=dict(type='str'),
- slack_username=dict(type='str'),
- slack_icon_emoji=dict(type='str'),
- slack_icon_url=dict(type='str'),
- slack_mention_users=dict(type='list', elements='str'),
- slack_mention_groups=dict(type='list', elements='str'),
- slack_mention_channel=dict(type='list', elements='str',
- choices=['here', 'channel']),
- slack_token=dict(type='str', no_log=True),
-
- telegram_bot_token=dict(type='str', no_log=True),
- telegram_chat_id=dict(type='str'),
-
- threema_gateway_id=dict(type='str'),
- threema_recipient_id=dict(type='str'),
- threema_api_secret=dict(type='str', no_log=True),
-
- victorops_url=dict(type='str'),
- victorops_auto_resolve=dict(type='bool'),
-
- webhook_url=dict(type='str'),
- webhook_username=dict(type='str'),
- webhook_password=dict(type='str', no_log=True),
- webhook_http_method=dict(type='list', elements='str', choices=['POST', 'PUT'])
+ org_id=dict(type="int", default=1),
+ uid=dict(type="str"),
+ name=dict(type="str"),
+ type=dict(
+ type="str",
+ choices=[
+ "dingding",
+ "discord",
+ "email",
+ "googlechat",
+ "hipchat",
+ "kafka",
+ "line",
+ "teams",
+ "opsgenie",
+ "pagerduty",
+ "prometheus",
+ "pushover",
+ "sensu",
+ "slack",
+ "telegram",
+ "threema",
+ "victorops",
+ "webhook",
+ ],
+ ),
+ is_default=dict(type="bool", default=False),
+ include_image=dict(type="bool", default=False),
+ disable_resolve_message=dict(type="bool", default=False),
+ reminder_frequency=dict(type="str"),
+ dingding_url=dict(type="str"),
+ dingding_message_type=dict(
+ type="list", elements="str", choices=["link", "action_card"]
+ ),
+ discord_url=dict(type="str"),
+ discord_message_content=dict(type="str"),
+ email_addresses=dict(type="list", elements="str"),
+ email_single=dict(type="bool"),
+ googlechat_url=dict(type="str"),
+ hipchat_url=dict(type="str"),
+ hipchat_api_key=dict(type="str", no_log=True),
+ hipchat_room_id=dict(type="str"),
+ kafka_url=dict(type="str"),
+ kafka_topic=dict(type="str"),
+ line_token=dict(type="str", no_log=True),
+ teams_url=dict(type="str"),
+ opsgenie_url=dict(type="str"),
+ opsgenie_api_key=dict(type="str", no_log=True),
+ opsgenie_auto_close=dict(type="bool"),
+ opsgenie_override_priority=dict(type="bool"),
+ pagerduty_integration_key=dict(type="str", no_log=True),
+ pagerduty_severity=dict(
+ type="list",
+ elements="str",
+ choices=["critical", "error", "warning", "info"],
+ ),
+ pagerduty_auto_resolve=dict(type="bool"),
+ pagerduty_message_in_details=dict(type="bool"),
+ prometheus_url=dict(type="str"),
+ prometheus_username=dict(type="str"),
+ prometheus_password=dict(type="str", no_log=True),
+ pushover_api_token=dict(type="str", no_log=True),
+ pushover_user_key=dict(type="str", no_log=True),
+ pushover_devices=dict(type="list", elements="str"),
+ pushover_priority=dict(
+ type="list",
+ elements="str",
+ choices=["emergency", "high", "normal", "low", "lowest"],
+ ),
+ pushover_retry=dict(type="int"), # TODO: only when priority==emergency
+ pushover_expire=dict(type="int"), # TODO: only when priority==emergency
+ pushover_alert_sound=dict(type="str"), # TODO: add sound choices
+ pushover_ok_sound=dict(type="str"), # TODO: add sound choices
+ sensu_url=dict(type="str"),
+ sensu_source=dict(type="str"),
+ sensu_handler=dict(type="str"),
+ sensu_username=dict(type="str"),
+ sensu_password=dict(type="str", no_log=True),
+ slack_url=dict(type="str", no_log=True),
+ slack_recipient=dict(type="str"),
+ slack_username=dict(type="str"),
+ slack_icon_emoji=dict(type="str"),
+ slack_icon_url=dict(type="str"),
+ slack_mention_users=dict(type="list", elements="str"),
+ slack_mention_groups=dict(type="list", elements="str"),
+ slack_mention_channel=dict(
+ type="list", elements="str", choices=["here", "channel"]
+ ),
+ slack_token=dict(type="str", no_log=True),
+ telegram_bot_token=dict(type="str", no_log=True),
+ telegram_chat_id=dict(type="str"),
+ threema_gateway_id=dict(type="str"),
+ threema_recipient_id=dict(type="str"),
+ threema_api_secret=dict(type="str", no_log=True),
+ victorops_url=dict(type="str"),
+ victorops_auto_resolve=dict(type="bool"),
+ webhook_url=dict(type="str"),
+ webhook_username=dict(type="str"),
+ webhook_password=dict(type="str", no_log=True),
+ webhook_http_method=dict(type="list", elements="str", choices=["POST", "PUT"]),
)
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=False,
- required_together=[['url_username', 'url_password', 'org_id'],
- ['prometheus_username', 'prometheus_password'],
- ['sensu_username', 'sensu_password']],
- mutually_exclusive=[['url_username', 'grafana_api_key']],
+ required_together=[
+ ["url_username", "url_password", "org_id"],
+ ["prometheus_username", "prometheus_password"],
+ ["sensu_username", "sensu_password"],
+ ],
+ mutually_exclusive=[["url_username", "grafana_api_key"]],
required_if=[
- ['state', 'present', ['name', 'type']],
- ['type', 'dingding', ['dingding_url']],
- ['type', 'discord', ['discord_url']],
- ['type', 'email', ['email_addresses']],
- ['type', 'googlechat', ['googlechat_url']],
- ['type', 'hipchat', ['hipchat_url']],
- ['type', 'kafka', ['kafka_url', 'kafka_topic']],
- ['type', 'line', ['line_token']],
- ['type', 'teams', ['teams_url']],
- ['type', 'opsgenie', ['opsgenie_url', 'opsgenie_api_key']],
- ['type', 'pagerduty', ['pagerduty_integration_key']],
- ['type', 'prometheus', ['prometheus_url']],
- ['type', 'pushover', ['pushover_api_token', 'pushover_user_key']],
- ['type', 'sensu', ['sensu_url']],
- ['type', 'slack', ['slack_url']],
- ['type', 'telegram', ['telegram_bot_token', 'telegram_chat_id']],
- ['type', 'threema', ['threema_gateway_id', 'threema_recipient_id',
- 'threema_api_secret']],
- ['type', 'victorops', ['victorops_url']],
- ['type', 'webhook', ['webhook_url']]
- ]
+ ["state", "present", ["name", "type"]],
+ ["type", "dingding", ["dingding_url"]],
+ ["type", "discord", ["discord_url"]],
+ ["type", "email", ["email_addresses"]],
+ ["type", "googlechat", ["googlechat_url"]],
+ ["type", "hipchat", ["hipchat_url"]],
+ ["type", "kafka", ["kafka_url", "kafka_topic"]],
+ ["type", "line", ["line_token"]],
+ ["type", "teams", ["teams_url"]],
+ ["type", "opsgenie", ["opsgenie_url", "opsgenie_api_key"]],
+ ["type", "pagerduty", ["pagerduty_integration_key"]],
+ ["type", "prometheus", ["prometheus_url"]],
+ ["type", "pushover", ["pushover_api_token", "pushover_user_key"]],
+ ["type", "sensu", ["sensu_url"]],
+ ["type", "slack", ["slack_url"]],
+ ["type", "telegram", ["telegram_bot_token", "telegram_chat_id"]],
+ [
+ "type",
+ "threema",
+ ["threema_gateway_id", "threema_recipient_id", "threema_api_secret"],
+ ],
+ ["type", "victorops", ["victorops_url"]],
+ ["type", "webhook", ["webhook_url"]],
+ ],
)
module.params["url"] = clean_url(module.params["url"])
alert_channel_iface = GrafanaNotificationChannelInterface(module)
- if module.params['state'] == 'present':
- result = alert_channel_iface.grafana_create_or_update_notification_channel(module.params)
+ if module.params["state"] == "present":
+ result = alert_channel_iface.grafana_create_or_update_notification_channel(
+ module.params
+ )
module.exit_json(failed=False, **result)
else:
result = alert_channel_iface.grafana_delete_notification_channel(module.params)
module.exit_json(failed=False, **result)
-if __name__ == '__main__':
+if __name__ == "__main__":
main()
diff --git a/ansible_collections/community/grafana/plugins/modules/grafana_organization.py b/ansible_collections/community/grafana/plugins/modules/grafana_organization.py
index 7fad4c876..7bddcb3f4 100644
--- a/ansible_collections/community/grafana/plugins/modules/grafana_organization.py
+++ b/ansible_collections/community/grafana/plugins/modules/grafana_organization.py
@@ -19,7 +19,7 @@
from __future__ import absolute_import, division, print_function
-DOCUMENTATION = '''
+DOCUMENTATION = """
---
module: grafana_organization
author:
@@ -43,9 +43,9 @@ options:
choices: ["present", "absent"]
extends_documentation_fragment:
- community.grafana.basic_auth
-'''
+"""
-EXAMPLES = '''
+EXAMPLES = """
---
- name: Create a Grafana organization
community.grafana.grafana_organization:
@@ -62,9 +62,9 @@ EXAMPLES = '''
url_password: changeme
name: orgtest
state: absent
-'''
+"""
-RETURN = '''
+RETURN = """
---
org:
description: Information about the organization
@@ -94,7 +94,7 @@ org:
country: ""
state: ""
zipCode: ""
-'''
+"""
import json
@@ -107,12 +107,13 @@ __metaclass__ = type
class GrafanaOrgInterface(object):
-
def __init__(self, module):
self._module = module
# {{{ Authentication header
self.headers = {"Content-Type": "application/json"}
- self.headers["Authorization"] = basic_auth_header(module.params['url_username'], module.params['url_password'])
+ self.headers["Authorization"] = basic_auth_header(
+ module.params["url_username"], module.params["url_password"]
+ )
# }}}
self.grafana_url = base.clean_url(module.params.get("url"))
@@ -123,20 +124,35 @@ class GrafanaOrgInterface(object):
headers = []
full_url = "{grafana_url}{path}".format(grafana_url=self.grafana_url, path=url)
- resp, info = fetch_url(self._module, full_url, data=data, headers=headers, method=method)
+ resp, info = fetch_url(
+ self._module, full_url, data=data, headers=headers, method=method
+ )
status_code = info["status"]
if status_code == 404:
return None
elif status_code == 401:
- self._module.fail_json(failed=True, msg="Unauthorized to perform action '%s' on '%s' header: %s" % (method, full_url, self.headers))
+ self._module.fail_json(
+ failed=True,
+ msg="Unauthorized to perform action '%s' on '%s' header: %s"
+ % (method, full_url, self.headers),
+ )
elif status_code == 403:
self._module.fail_json(failed=True, msg="Permission Denied")
elif status_code == 200:
return self._module.from_json(resp.read())
if resp is None:
- self._module.fail_json(failed=True, msg="Cannot connect to API Grafana %s" % info['msg'], status=status_code, url=info['url'])
+ self._module.fail_json(
+ failed=True,
+ msg="Cannot connect to API Grafana %s" % info["msg"],
+ status=status_code,
+ url=info["url"],
+ )
else:
- self._module.fail_json(failed=True, msg="Grafana Org API answered with HTTP %d" % status_code, body=self._module.from_json(resp.read()))
+ self._module.fail_json(
+ failed=True,
+ msg="Grafana Org API answered with HTTP %d" % status_code,
+ body=self._module.from_json(resp.read()),
+ )
def get_actual_org(self, name):
# https://grafana.com/docs/grafana/latest/http_api/org/#get-organization-by-name
@@ -160,40 +176,48 @@ def setup_module_object():
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=False,
- required_together=base.grafana_required_together()
+ required_together=base.grafana_required_together(),
)
return module
argument_spec = base.grafana_argument_spec()
argument_spec.update(
- state=dict(choices=['present', 'absent'], default='present'),
- name=dict(type='str', required=True),
+ state=dict(choices=["present", "absent"], default="present"),
+ name=dict(type="str", required=True),
)
-argument_spec.pop('grafana_api_key')
+argument_spec.pop("grafana_api_key")
def main():
module = setup_module_object()
- state = module.params['state']
- name = module.params['name']
+ state = module.params["state"]
+ name = module.params["name"]
grafana_iface = GrafanaOrgInterface(module)
# search org by name
actual_org = grafana_iface.get_actual_org(name)
- if state == 'present':
+ if state == "present":
has_changed = False
if actual_org is None:
# create new org
actual_org = grafana_iface.create_org(name)
has_changed = True
- module.exit_json(changed=has_changed, msg='Organization %s created.' % name, org=actual_org)
+ module.exit_json(
+ changed=has_changed,
+ msg="Organization %s created." % name,
+ org=actual_org,
+ )
else:
- module.exit_json(changed=has_changed, msg='Organization %s already created.' % name, org=actual_org)
+ module.exit_json(
+ changed=has_changed,
+ msg="Organization %s already created." % name,
+ org=actual_org,
+ )
- elif state == 'absent':
+ elif state == "absent":
if actual_org is None:
module.exit_json(msg="No org found, nothing to do")
# delete org
@@ -201,5 +225,5 @@ def main():
module.exit_json(changed=True, msg=result.get("message"))
-if __name__ == '__main__':
+if __name__ == "__main__":
main()
diff --git a/ansible_collections/community/grafana/plugins/modules/grafana_organization_user.py b/ansible_collections/community/grafana/plugins/modules/grafana_organization_user.py
new file mode 100644
index 000000000..11171f38f
--- /dev/null
+++ b/ansible_collections/community/grafana/plugins/modules/grafana_organization_user.py
@@ -0,0 +1,294 @@
+#!/usr/bin/python
+# -*- 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/>.
+#
+# Copyright: (c) 2021
+
+from __future__ import absolute_import, division, print_function
+
+DOCUMENTATION = """
+---
+module: grafana_organization_user
+author:
+ - Aliaksandr Mianzhynski (@amenzhinsky)
+version_added: "1.6.0"
+short_description: Manage Grafana Organization Users.
+description:
+ - Add or remove users or change their roles in Grafana organizations through org API.
+ - The user has to exist before using this module. See U(https://docs.ansible.com/ansible/latest/collections/community/grafana/grafana_user_module.html).
+options:
+ login:
+ type: str
+ required: True
+ description:
+ - Username or email.
+ role:
+ type: str
+ choices:
+ - viewer
+ - editor
+ - admin
+ default: viewer
+ description:
+ - User's role in the organization.
+ state:
+ type: str
+ default: present
+ choices:
+ - present
+ - absent
+ description:
+ - Status of a user's organization membership.
+ org_id:
+ type: int
+ default: 1
+ description:
+ - Organization ID.
+ - Mutually exclusive with C(org_name).
+ org_name:
+ type: str
+ description:
+ - Organization name.
+ - Mutually exclusive with C(org_id).
+
+extends_documentation_fragment:
+ - community.grafana.basic_auth
+"""
+
+EXAMPLES = """
+---
+- name: Add user to organization
+ community.grafana.grafana_organization_user:
+ url: "{{ grafana_url }}"
+ url_username: "{{ grafana_username }}"
+ url_password: "{{ grafana_password }}"
+ login: john
+ role: admin
+
+- name: Remove user from organization
+ community.grafana.grafana_organization_user:
+ url: "{{ grafana_url }}"
+ url_username: "{{ grafana_username }}"
+ url_password: "{{ grafana_password }}"
+ login: john
+ state: absent
+"""
+
+RETURN = """
+---
+user:
+ description: Information about the organization user
+ returned: when state present
+ type: complex
+ contains:
+ email:
+ description: The User email address
+ returned: always
+ type: str
+ sample:
+ - "foo.bar@example.com"
+ login:
+ description: The User login
+ returned: always
+ type: str
+ sample:
+ - "batman"
+ name:
+ description: The User name (same as login)
+ returned: always
+ type: str
+ sample:
+ - "batman"
+ orgId:
+ description: The organization id that the team is part of.
+ returned: always
+ type: int
+ sample:
+ - 1
+ role:
+ description: The user role in the organization
+ returned: always
+ type: str
+ choices:
+ - Viewer
+ - Editor
+ - Admin
+ sample:
+ - Viewer
+"""
+
+
+import json
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.urls import fetch_url
+from ansible.module_utils._text import to_text
+from ansible_collections.community.grafana.plugins.module_utils.base import (
+ grafana_argument_spec,
+ clean_url,
+)
+from ansible.module_utils.urls import basic_auth_header
+
+__metaclass__ = type
+
+
+class GrafanaAPIException(Exception):
+ pass
+
+
+class GrafanaOrganizationUserInterface(object):
+ def __init__(self, module):
+ self._module = module
+ # {{{ Authentication header
+ self.headers = {"Content-Type": "application/json"}
+ self.headers["Authorization"] = basic_auth_header(
+ module.params["url_username"], module.params["url_password"]
+ )
+ # }}}
+ self.grafana_url = clean_url(module.params.get("url"))
+
+ def _api_call(self, method, path, payload):
+ data = None
+ if payload:
+ data = json.dumps(payload)
+ return fetch_url(
+ self._module,
+ self.grafana_url + "/api/" + path,
+ headers=self.headers,
+ method=method,
+ data=data,
+ )
+
+ def _organization_by_name(self, org_name):
+ r, info = self._api_call("GET", "orgs/name/%s" % org_name, None)
+ if info["status"] != 200:
+ raise GrafanaAPIException("Unable to retrieve organization: %s" % info)
+ return json.loads(to_text(r.read()))
+
+ def _organization_users(self, org_id):
+ r, info = self._api_call("GET", "orgs/%d/users" % org_id, None)
+ if info["status"] != 200:
+ raise GrafanaAPIException(
+ "Unable to retrieve organization users: %s" % info
+ )
+ return json.loads(to_text(r.read()))
+
+ def _create_organization_user(self, org_id, login, role):
+ return self._api_call(
+ "POST",
+ "orgs/%d/users" % org_id,
+ {
+ "loginOrEmail": login,
+ "role": role,
+ },
+ )
+
+ def _update_organization_user_role(self, org_id, user_id, role):
+ return self._api_call(
+ "PATCH",
+ "orgs/%d/users/%s" % (org_id, user_id),
+ {
+ "role": role,
+ },
+ )
+
+ def _remove_organization_user(self, org_id, user_id):
+ return self._api_call("DELETE", "orgs/%d/users/%s" % (org_id, user_id), None)
+
+ def _organization_user_by_login(self, org_id, login):
+ for user in self._organization_users(org_id):
+ if login in (user["login"], user["email"]):
+ return user
+
+ def create_or_update_user(self, org_id, login, role):
+ r, info = self._create_organization_user(org_id, login, role)
+ if info["status"] == 200:
+ return {
+ "state": "present",
+ "changed": True,
+ "user": self._organization_user_by_login(org_id, login),
+ }
+ if info["status"] == 409: # already member
+ user = self._organization_user_by_login(org_id, login)
+ if not user:
+ raise Exception("[BUG] User not found in organization")
+
+ if user["role"] == role:
+ return {"changed": False}
+
+ r, info = self._update_organization_user_role(org_id, user["userId"], role)
+ if info["status"] == 200:
+ return {
+ "changed": True,
+ "user": self._organization_user_by_login(org_id, login),
+ }
+ else:
+ raise GrafanaAPIException(
+ "Unable to update organization user: %s" % info
+ )
+ else:
+ raise GrafanaAPIException("Unable to add user to organization: %s" % info)
+
+ def remove_user(self, org_id, login):
+ user = self._organization_user_by_login(org_id, login)
+ if not user:
+ return {"changed": False}
+
+ r, info = self._remove_organization_user(org_id, user["userId"])
+ if info["status"] == 200:
+ return {"state": "absent", "changed": True}
+ else:
+ raise GrafanaAPIException("Unable to delete organization user: %s" % info)
+
+
+def main():
+ argument_spec = grafana_argument_spec()
+ argument_spec.pop("grafana_api_key")
+ argument_spec.update(
+ org_id=dict(type="int", default=1),
+ org_name=dict(type="str"),
+ login=dict(type="str", required=True),
+ role=dict(type="str", choices=["viewer", "editor", "admin"], default="viewer"),
+ )
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=False,
+ mutually_exclusive=[
+ ("org_id", "org_name"),
+ ],
+ required_if=[
+ ["state", "present", ["role"]],
+ ],
+ )
+
+ org_id = module.params["org_id"]
+ login = module.params["login"]
+ iface = GrafanaOrganizationUserInterface(module)
+ if module.params["org_name"]:
+ org_name = module.params["org_name"]
+ organization = iface._organization_by_name(org_name)
+ org_id = organization["id"]
+ if module.params["state"] == "present":
+ role = module.params["role"].capitalize()
+ result = iface.create_or_update_user(org_id, login, role)
+ module.exit_json(failed=False, **result)
+ else:
+ result = iface.remove_user(org_id, login)
+ module.exit_json(failed=False, **result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/community/grafana/plugins/modules/grafana_plugin.py b/ansible_collections/community/grafana/plugins/modules/grafana_plugin.py
index 7fd418760..c510f02ba 100644
--- a/ansible_collections/community/grafana/plugins/modules/grafana_plugin.py
+++ b/ansible_collections/community/grafana/plugins/modules/grafana_plugin.py
@@ -6,7 +6,7 @@
from __future__ import absolute_import, division, print_function
-DOCUMENTATION = '''module: grafana_plugin
+DOCUMENTATION = """module: grafana_plugin
author:
- Thierry Sallé (@seuf)
short_description: Manage Grafana plugins via grafana-cli
@@ -50,26 +50,26 @@ options:
validate_certs:
description:
- Boolean variable to include --insecure while installing pluging
- default: False
+ default: false
type: bool
-'''
+"""
-EXAMPLES = '''
+EXAMPLES = """
---
- name: Install/update Grafana piechart panel plugin
community.grafana.grafana_plugin:
name: grafana-piechart-panel
version: latest
state: present
-'''
+"""
-RETURN = '''
+RETURN = """
---
version:
description: version of the installed/removed/updated plugin.
type: str
returned: always
-'''
+"""
import os
from ansible.module_utils.basic import AnsibleModule
@@ -82,18 +82,18 @@ class GrafanaCliException(Exception):
def parse_version(string):
- name, version = string.split('@')
+ name, version = string.split("@")
return name.strip(), version.strip()
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'
+ """
+ program = "grafana-cli"
grafana_cli = None
def is_exe(fpath):
@@ -112,50 +112,57 @@ def grafana_cli_bin(params):
break
if grafana_cli is None:
- raise GrafanaCliException('grafana-cli binary is not present or not in PATH')
+ 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')
+ 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))
+ 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:
+ if line.find(" @ ") != -1:
line = line.rstrip()
plugin_name, plugin_version = parse_version(line)
- if plugin_name == params['name']:
+ 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']))
+ 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()
@@ -163,108 +170,126 @@ def get_grafana_plugin_version_latest(module, params):
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':
+ 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}
+ 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 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'])
+ 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'])
+ 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}
+ 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'])
+ 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'])
+ cmd = "{0} install {1} {2}".format(
+ grafana_cli, params["name"], params["version"]
+ )
else:
- cmd = '{0} install {1}'.format(grafana_cli, params['name'])
+ cmd = "{0} install {1}".format(grafana_cli, params["name"])
else:
- cmd = '{0} uninstall {1}'.format(grafana_cli, params['name'])
+ 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:
+ if line.find(params["name"]):
+ if line.find(" @ ") != -1:
line = line.rstrip()
plugin_name, plugin_version = parse_version(line)
else:
plugin_version = None
- if params['state'] == 'present':
- return {'msg': 'Grafana plugin {0} installed : {1}'.format(params['name'], cmd),
- 'changed': True,
- 'version': plugin_version}
+ if params["state"] == "present":
+ return {
+ "msg": "Grafana plugin {0} installed : {1}".format(
+ params["name"], cmd
+ ),
+ "changed": True,
+ "version": plugin_version,
+ }
else:
- return {'msg': 'Grafana plugin {0} uninstalled : {1}'.format(params['name'], cmd),
- 'changed': True}
+ return {
+ "msg": "Grafana plugin {0} uninstalled : {1}".format(
+ params["name"], cmd
+ ),
+ "changed": True,
+ }
else:
- if params['state'] == 'absent' and stdout.find("plugin does not exist"):
- return {'msg': 'Grafana plugin {0} already uninstalled : {1}'.format(params['name'], cmd), 'changed': False}
- raise GrafanaCliException("'{0}' execution returned an error : [{1}] {2} {3}".format(cmd, rc, stdout, stderr))
+ if params["state"] == "absent" and stdout.find("plugin does not exist"):
+ return {
+ "msg": "Grafana plugin {0} already uninstalled : {1}".format(
+ params["name"], cmd
+ ),
+ "changed": False,
+ }
+ 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'),
- validate_certs=dict(type='bool', default=False),
- state=dict(choices=['present', 'absent'],
- default='present')
+ 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"),
+ validate_certs=dict(type="bool", default=False),
+ state=dict(choices=["present", "absent"], default="present"),
),
- supports_check_mode=False
+ supports_check_mode=False,
)
try:
result = grafana_plugin(module, module.params)
except GrafanaCliException as e:
- module.fail_json(
- failed=True,
- msg="{0}".format(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)
- )
+ module.fail_json(failed=True, msg="{0} : {1} ".format(type(e), e))
return
- module.exit_json(
- failed=False,
- **result
- )
+ module.exit_json(failed=False, **result)
return
-if __name__ == '__main__':
+if __name__ == "__main__":
main()
diff --git a/ansible_collections/community/grafana/plugins/modules/grafana_team.py b/ansible_collections/community/grafana/plugins/modules/grafana_team.py
index 7f8de8457..76dc70a62 100644
--- a/ansible_collections/community/grafana/plugins/modules/grafana_team.py
+++ b/ansible_collections/community/grafana/plugins/modules/grafana_team.py
@@ -19,7 +19,7 @@
from __future__ import absolute_import, division, print_function
-DOCUMENTATION = '''
+DOCUMENTATION = """
---
module: grafana_team
author:
@@ -59,22 +59,22 @@ options:
description:
- Delete the members not found in the C(members) parameters from the
- list of members found on the Team.
- default: False
+ default: false
type: bool
skip_version_check:
description:
- Skip Grafana version check and try to reach api endpoint anyway.
- - This parameter can be useful if you enabled `hide_version` in grafana.ini
+ - This parameter can be useful if you enabled C(hide_version) in grafana.ini
required: False
type: bool
- default: False
+ default: false
version_added: "1.2.0"
extends_documentation_fragment:
- community.grafana.basic_auth
- community.grafana.api_key
-'''
+"""
-EXAMPLES = '''
+EXAMPLES = """
---
- name: Create a team
community.grafana.grafana_team:
@@ -104,7 +104,7 @@ EXAMPLES = '''
members:
- john.doe@example.com
- jane.doe@example.com
- enforce_members: yes
+ enforce_members: true
state: present
- name: Delete a team
@@ -114,9 +114,9 @@ EXAMPLES = '''
name: "grafana_working_group"
email: "foo.bar@example.com"
state: absent
-'''
+"""
-RETURN = '''
+RETURN = """
---
team:
description: Information about the Team
@@ -165,7 +165,7 @@ team:
type: int
sample:
- 1
-'''
+"""
import json
@@ -183,15 +183,18 @@ class GrafanaError(Exception):
class GrafanaTeamInterface(object):
-
def __init__(self, module):
self._module = module
# {{{ Authentication header
self.headers = {"Content-Type": "application/json"}
- if module.params.get('grafana_api_key', None):
- self.headers["Authorization"] = "Bearer %s" % module.params['grafana_api_key']
+ if module.params.get("grafana_api_key", None):
+ self.headers["Authorization"] = (
+ "Bearer %s" % module.params["grafana_api_key"]
+ )
else:
- self.headers["Authorization"] = basic_auth_header(module.params['url_username'], module.params['url_password'])
+ self.headers["Authorization"] = basic_auth_header(
+ module.params["url_username"], module.params["url_password"]
+ )
# }}}
self.grafana_url = base.clean_url(module.params.get("url"))
if module.params.get("skip_version_check") is False:
@@ -200,7 +203,9 @@ class GrafanaTeamInterface(object):
except GrafanaError as e:
self._module.fail_json(failed=True, msg=to_text(e))
if grafana_version["major"] < 5:
- self._module.fail_json(failed=True, msg="Teams API is available starting Grafana v5")
+ self._module.fail_json(
+ failed=True, msg="Teams API is available starting Grafana v5"
+ )
def _send_request(self, url, data=None, headers=None, method="GET"):
if data is not None:
@@ -209,23 +214,32 @@ class GrafanaTeamInterface(object):
headers = []
full_url = "{grafana_url}{path}".format(grafana_url=self.grafana_url, path=url)
- resp, info = fetch_url(self._module, full_url, data=data, headers=headers, method=method)
+ resp, info = fetch_url(
+ self._module, full_url, data=data, headers=headers, method=method
+ )
status_code = info["status"]
if status_code == 404:
return None
elif status_code == 401:
- self._module.fail_json(failed=True, msg="Unauthorized to perform action '%s' on '%s'" % (method, full_url))
+ self._module.fail_json(
+ failed=True,
+ msg="Unauthorized to perform action '%s' on '%s'" % (method, full_url),
+ )
elif status_code == 403:
self._module.fail_json(failed=True, msg="Permission Denied")
elif status_code == 409:
self._module.fail_json(failed=True, msg="Team name is taken")
elif status_code == 200:
return self._module.from_json(resp.read())
- self._module.fail_json(failed=True, msg="Grafana Teams API answered with HTTP %d" % status_code)
+ self._module.fail_json(
+ failed=True, msg="Grafana Teams API answered with HTTP %d" % status_code
+ )
def get_version(self):
url = "/api/health"
- response = self._send_request(url, data=None, headers=self.headers, method="GET")
+ response = self._send_request(
+ url, data=None, headers=self.headers, method="GET"
+ )
version = response.get("version")
if version is not None:
major, minor, rev = version.split(".")
@@ -235,7 +249,9 @@ class GrafanaTeamInterface(object):
def create_team(self, name, email):
url = "/api/teams"
team = dict(email=email, name=name)
- response = self._send_request(url, data=team, headers=self.headers, method="POST")
+ response = self._send_request(
+ url, data=team, headers=self.headers, method="POST"
+ )
return response
def get_team(self, name):
@@ -251,7 +267,9 @@ class GrafanaTeamInterface(object):
def update_team(self, team_id, name, email):
url = "/api/teams/{team_id}".format(team_id=team_id)
team = dict(email=email, name=name)
- response = self._send_request(url, data=team, headers=self.headers, method="PUT")
+ response = self._send_request(
+ url, data=team, headers=self.headers, method="PUT"
+ )
return response
def delete_team(self, team_id):
@@ -272,7 +290,9 @@ class GrafanaTeamInterface(object):
def delete_team_member(self, team_id, email):
user_id = self.get_user_id_from_mail(email)
- url = "/api/teams/{team_id}/members/{user_id}".format(team_id=team_id, user_id=user_id)
+ url = "/api/teams/{team_id}/members/{user_id}".format(
+ team_id=team_id, user_id=user_id
+ )
self._send_request(url, headers=self.headers, method="DELETE")
def get_user_id_from_mail(self, email):
@@ -295,27 +315,26 @@ def setup_module_object():
argument_spec = base.grafana_argument_spec()
argument_spec.update(
- name=dict(type='str', required=True),
- email=dict(type='str', required=True),
- members=dict(type='list', elements='str', required=False),
- enforce_members=dict(type='bool', default=False),
- skip_version_check=dict(type='bool', default=False),
+ name=dict(type="str", required=True),
+ email=dict(type="str", required=True),
+ members=dict(type="list", elements="str", required=False),
+ enforce_members=dict(type="bool", default=False),
+ skip_version_check=dict(type="bool", default=False),
)
def main():
-
module = setup_module_object()
- state = module.params['state']
- name = module.params['name']
- email = module.params['email']
- members = module.params['members']
- enforce_members = module.params['enforce_members']
+ state = module.params["state"]
+ name = module.params["name"]
+ email = module.params["email"]
+ members = module.params["members"]
+ enforce_members = module.params["enforce_members"]
grafana_iface = GrafanaTeamInterface(module)
changed = False
- if state == 'present':
+ if state == "present":
team = grafana_iface.get_team(name)
if team is None:
grafana_iface.create_team(name, email)
@@ -332,9 +351,9 @@ def main():
grafana_iface.delete_team_member(team.get("id"), member)
changed = True
team = grafana_iface.get_team(name)
- team['members'] = grafana_iface.get_team_members(team.get("id"))
+ team["members"] = grafana_iface.get_team_members(team.get("id"))
module.exit_json(failed=False, changed=changed, team=team)
- elif state == 'absent':
+ elif state == "absent":
team = grafana_iface.get_team(name)
if team is None:
module.exit_json(failed=False, changed=False, message="No team found")
@@ -353,5 +372,5 @@ def diff_members(target, current):
return diff
-if __name__ == '__main__':
+if __name__ == "__main__":
main()
diff --git a/ansible_collections/community/grafana/plugins/modules/grafana_user.py b/ansible_collections/community/grafana/plugins/modules/grafana_user.py
index 3247b534a..6e99718ac 100644
--- a/ansible_collections/community/grafana/plugins/modules/grafana_user.py
+++ b/ansible_collections/community/grafana/plugins/modules/grafana_user.py
@@ -19,7 +19,7 @@
from __future__ import absolute_import, division, print_function
-DOCUMENTATION = '''
+DOCUMENTATION = """
---
module: grafana_user
author:
@@ -68,13 +68,13 @@ options:
type: str
choices: ["present", "absent"]
notes:
-- Unlike other modules from the collection, this module does not support `grafana_api_key` authentication type. The Grafana API endpoint for users management
+- Unlike other modules from the collection, this module does not support C(grafana_api_key) authentication type. The Grafana API endpoint for users management
requires basic auth and admin privileges.
extends_documentation_fragment:
- community.grafana.basic_auth
-'''
+"""
-EXAMPLES = '''
+EXAMPLES = """
---
- name: Create or update a Grafana user
community.grafana.grafana_user:
@@ -95,9 +95,9 @@ EXAMPLES = '''
url_password: changeme
login: batman
state: absent
-'''
+"""
-RETURN = '''
+RETURN = """
---
user:
description: Information about the User
@@ -152,7 +152,7 @@ user:
type: bool
sample:
- false
-'''
+"""
import json
@@ -165,12 +165,13 @@ __metaclass__ = type
class GrafanaUserInterface(object):
-
def __init__(self, module):
self._module = module
# {{{ Authentication header
self.headers = {"Content-Type": "application/json"}
- self.headers["Authorization"] = basic_auth_header(module.params['url_username'], module.params['url_password'])
+ self.headers["Authorization"] = basic_auth_header(
+ module.params["url_username"], module.params["url_password"]
+ )
# }}}
self.grafana_url = base.clean_url(module.params.get("url"))
@@ -181,22 +182,34 @@ class GrafanaUserInterface(object):
headers = []
full_url = "{grafana_url}{path}".format(grafana_url=self.grafana_url, path=url)
- resp, info = fetch_url(self._module, full_url, data=data, headers=headers, method=method)
+ resp, info = fetch_url(
+ self._module, full_url, data=data, headers=headers, method=method
+ )
status_code = info["status"]
if status_code == 404:
return None
elif status_code == 401:
- self._module.fail_json(failed=True, msg="Unauthorized to perform action '%s' on '%s' header: %s" % (method, full_url, self.headers))
+ self._module.fail_json(
+ failed=True,
+ msg="Unauthorized to perform action '%s' on '%s' header: %s"
+ % (method, full_url, self.headers),
+ )
elif status_code == 403:
self._module.fail_json(failed=True, msg="Permission Denied")
elif status_code == 200:
return self._module.from_json(resp.read())
- self._module.fail_json(failed=True, msg="Grafana Users API answered with HTTP %d" % status_code, body=self._module.from_json(resp.read()))
+ self._module.fail_json(
+ failed=True,
+ msg="Grafana Users API answered with HTTP %d" % status_code,
+ body=self._module.from_json(resp.read()),
+ )
def create_user(self, name, email, login, password):
# https://grafana.com/docs/http_api/admin/#global-users
if not password:
- self._module.fail_json(failed=True, msg="missing required arguments: password")
+ self._module.fail_json(
+ failed=True, msg="missing required arguments: password"
+ )
url = "/api/admin/users"
user = dict(name=name, email=email, login=login, password=password)
self._send_request(url, data=user, headers=self.headers, method="POST")
@@ -218,7 +231,9 @@ class GrafanaUserInterface(object):
# https://grafana.com/docs/http_api/admin/#permissions
url = "/api/admin/users/{user_id}/permissions".format(user_id=user_id)
permissions = dict(isGrafanaAdmin=is_admin)
- return self._send_request(url, data=permissions, headers=self.headers, method="PUT")
+ return self._send_request(
+ url, data=permissions, headers=self.headers, method="PUT"
+ )
def delete_user(self, user_id):
# https://grafana.com/docs/http_api/admin/#delete-global-user
@@ -232,7 +247,7 @@ def is_user_update_required(target_user, email, name, login, is_admin):
email=target_user.get("email"),
name=target_user.get("name"),
login=target_user.get("login"),
- is_admin=target_user.get("isGrafanaAdmin")
+ is_admin=target_user.get("isGrafanaAdmin"),
)
param_dict = dict(email=email, name=name, login=login, is_admin=is_admin)
return target_user_dict != param_dict
@@ -243,44 +258,46 @@ def setup_module_object():
argument_spec=argument_spec,
supports_check_mode=False,
required_if=[
- ['state', 'present', ['name', 'email']],
+ ["state", "present", ["name", "email"]],
],
- required_together=base.grafana_required_together()
+ required_together=base.grafana_required_together(),
)
return module
argument_spec = base.grafana_argument_spec()
argument_spec.update(
- state=dict(choices=['present', 'absent'], default='present'),
- name=dict(type='str', required=False),
- email=dict(type='str', required=False),
- login=dict(type='str', required=True),
- password=dict(type='str', required=False, no_log=True),
- is_admin=dict(type='bool', default=False),
+ state=dict(choices=["present", "absent"], default="present"),
+ name=dict(type="str", required=False),
+ email=dict(type="str", required=False),
+ login=dict(type="str", required=True),
+ password=dict(type="str", required=False, no_log=True),
+ is_admin=dict(type="bool", default=False),
)
-argument_spec.pop('grafana_api_key')
+argument_spec.pop("grafana_api_key")
def main():
module = setup_module_object()
- state = module.params['state']
- name = module.params['name']
- email = module.params['email']
- login = module.params['login']
- password = module.params['password']
- is_admin = module.params['is_admin']
+ state = module.params["state"]
+ name = module.params["name"]
+ email = module.params["email"]
+ login = module.params["login"]
+ password = module.params["password"]
+ is_admin = module.params["is_admin"]
grafana_iface = GrafanaUserInterface(module)
# search user by login
actual_grafana_user = grafana_iface.get_user_from_login(login)
- if state == 'present':
+ if state == "present":
has_changed = False
if actual_grafana_user is None:
# create new user
- actual_grafana_user = grafana_iface.create_user(name, email, login, password)
+ actual_grafana_user = grafana_iface.create_user(
+ name, email, login, password
+ )
has_changed = True
if is_user_update_required(actual_grafana_user, email, name, login, is_admin):
@@ -288,17 +305,19 @@ def main():
actual_grafana_user_id = actual_grafana_user.get("id")
if is_admin != actual_grafana_user.get("isGrafanaAdmin"):
grafana_iface.update_user_permissions(actual_grafana_user_id, is_admin)
- actual_grafana_user = grafana_iface.update_user(actual_grafana_user_id, email, name, login)
+ actual_grafana_user = grafana_iface.update_user(
+ actual_grafana_user_id, email, name, login
+ )
has_changed = True
module.exit_json(changed=has_changed, user=actual_grafana_user)
- elif state == 'absent':
+ elif state == "absent":
if actual_grafana_user is None:
module.exit_json(message="No user found, nothing to do")
result = grafana_iface.delete_user(actual_grafana_user.get("id"))
module.exit_json(changed=True, message=result.get("message"))
-if __name__ == '__main__':
+if __name__ == "__main__":
main()