From 5c6c9cdf1456a79728e8f473acb1d6b94493270c Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Mon, 26 Feb 2024 13:45:58 +0100 Subject: [PATCH 01/52] feat: switc org by name --- .../modules/grafana_notification_channel.py | 34 ++++++++++++++++--- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/plugins/modules/grafana_notification_channel.py b/plugins/modules/grafana_notification_channel.py index 30b5b112..4fcf646c 100644 --- a/plugins/modules/grafana_notification_channel.py +++ b/plugins/modules/grafana_notification_channel.py @@ -611,6 +611,7 @@ def grafana_notification_channel_payload(data): class GrafanaNotificationChannelInterface(object): def __init__(self, module): self._module = module + self.org_id = None # {{{ Authentication header self.headers = {"Content-Type": "application/json"} if module.params.get("grafana_api_key", None): @@ -621,13 +622,18 @@ def __init__(self, module): 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.grafana_switch_organisation(module.params, self.org_id) # }}} - self.grafana_url = clean_url(module.params.get("url")) - def grafana_switch_organisation(self, grafana_url, org_id): + def grafana_switch_organisation(self, data, org_id): r, info = fetch_url( self._module, - "%s/api/user/using/%s" % (grafana_url, org_id), + "%s/api/user/using/%s" % (data["url"], org_id), headers=self.headers, method="POST", ) @@ -636,6 +642,22 @@ def grafana_switch_organisation(self, grafana_url, org_id): "Unable to switch to organization %s : %s" % (org_id, info) ) + def organization_by_name(self, data, org_name): + r, info = fetch_url( + self._module, + "%s/api/user/orgs" % data["url"], + headers=self.headers, + method="GET", + ) + organizations = json.loads(to_text(r.read())) + orga = next((org for org in organizations if org["name"] == org_name)) + if orga: + return orga["orgId"] + + raise GrafanaAPIException( + "Current user isn't member of organization: %s" % org_name + ) + def grafana_create_notification_channel(self, data, payload): r, info = fetch_url( self._module, @@ -729,6 +751,7 @@ def main(): argument_spec = grafana_argument_spec() argument_spec.update( org_id=dict(type="int", default=1), + org_name=dict(type="str"), uid=dict(type="str"), name=dict(type="str"), type=dict( @@ -797,8 +820,8 @@ def main(): 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_retry=dict(type="int"), + pushover_expire=dict(type="int"), 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"), @@ -863,6 +886,7 @@ def main(): ], ["type", "victorops", ["victorops_url"]], ["type", "webhook", ["webhook_url"]], + ["pushover_priority", "emergency", ["pushover_retry", "pushover_expire"]], ], ) From 0459e03ccf50da55d6079062c8dc19b9250dbb06 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Mon, 26 Feb 2024 13:49:24 +0100 Subject: [PATCH 02/52] feat: check if unified grafana is enabled --- .../modules/grafana_notification_channel.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/plugins/modules/grafana_notification_channel.py b/plugins/modules/grafana_notification_channel.py index 4fcf646c..8faf2d6b 100644 --- a/plugins/modules/grafana_notification_channel.py +++ b/plugins/modules/grafana_notification_channel.py @@ -629,6 +629,26 @@ def __init__(self, module): ) self.grafana_switch_organisation(module.params, self.org_id) # }}} + self.grafana_check_unified_alerting(module.params) + + def grafana_check_unified_alerting(self, data): + self.grafana_unified_alerting = None + r, info = fetch_url( + self._module, + "%s/api/frontend/settings" % data["url"], + headers=self.headers, + method="GET", + ) + if info["status"] == 200: + try: + settings = json.loads(to_text(r.read())) + self.grafana_unified_alerting = settings["unifiedAlertingEnabled"] + 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) def grafana_switch_organisation(self, data, org_id): r, info = fetch_url( From 15a68e57a94c1ca52f7fee9d14f586f78897fef7 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Mon, 26 Feb 2024 13:53:05 +0100 Subject: [PATCH 03/52] docs: org id description --- plugins/modules/grafana_notification_channel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/grafana_notification_channel.py b/plugins/modules/grafana_notification_channel.py index 8faf2d6b..d30069c4 100644 --- a/plugins/modules/grafana_notification_channel.py +++ b/plugins/modules/grafana_notification_channel.py @@ -36,7 +36,7 @@ options: org_id: description: - - The Grafana Organisation ID where the dashboard will be imported / exported. + - The Grafana Organisation ID where the notification channel will be handled. - Not used when I(grafana_api_key) is set, because the grafana_api_key only belongs to one organisation.. default: 1 type: int From dac7382f1fcdf5ef3b19c824d418becf3c458bd2 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Mon, 26 Feb 2024 13:56:36 +0100 Subject: [PATCH 04/52] docs: added org_name description --- plugins/modules/grafana_notification_channel.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/plugins/modules/grafana_notification_channel.py b/plugins/modules/grafana_notification_channel.py index d30069c4..504b9ac2 100644 --- a/plugins/modules/grafana_notification_channel.py +++ b/plugins/modules/grafana_notification_channel.py @@ -38,8 +38,15 @@ description: - The Grafana Organisation ID where the notification channel will be handled. - Not used when I(grafana_api_key) is set, because the grafana_api_key only belongs to one organisation.. + - Mutually exclusive with C(org_name). default: 1 type: int + org_name: + description: + - The Grafana Organisation name where the notification channel will be handled. + - Not used when I(grafana_api_key) is set, because the grafana_api_key only belongs to one organisation.. + - Mutually exclusive with C(org_id). + type: str state: type: str default: present From 39fcc665dc35ef08eff4bc1eefd7eadbb017f585 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Mon, 26 Feb 2024 15:05:06 +0100 Subject: [PATCH 05/52] feat: url and exception toggle for api handling --- .../modules/grafana_notification_channel.py | 62 ++++++++++++++----- 1 file changed, 47 insertions(+), 15 deletions(-) diff --git a/plugins/modules/grafana_notification_channel.py b/plugins/modules/grafana_notification_channel.py index 504b9ac2..e7098d9b 100644 --- a/plugins/modules/grafana_notification_channel.py +++ b/plugins/modules/grafana_notification_channel.py @@ -630,13 +630,16 @@ def __init__(self, module): module.params["url_username"], module.params["url_password"] ) self.org_id = ( - self.organization_by_name(module.params["org_name"]) + self.grafana_organization_by_name(module.params["org_name"]) if module.params["org_name"] else module.params["org_id"] ) self.grafana_switch_organisation(module.params, self.org_id) # }}} self.grafana_check_unified_alerting(module.params) + self.endpoint_type = ( + "contact point" if self.grafana_unified_alerting else "notification channel" + ) def grafana_check_unified_alerting(self, data): self.grafana_unified_alerting = None @@ -669,7 +672,7 @@ def grafana_switch_organisation(self, data, org_id): "Unable to switch to organization %s : %s" % (org_id, info) ) - def organization_by_name(self, data, org_name): + def grafana_organization_by_name(self, data, org_name): r, info = fetch_url( self._module, "%s/api/user/orgs" % data["url"], @@ -686,28 +689,45 @@ def organization_by_name(self, data, org_name): ) def grafana_create_notification_channel(self, data, payload): + url = ( + "%s/api/v1/provisioning/contact-points" + if self.grafana_unified_alerting + else "%s/api/alert-notifications" + ) r, info = fetch_url( self._module, - "%s/api/alert-notifications" % data["url"], + url % 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())), - } + if self.grafana_unified_alerting: + return { + "state": "present", + "changed": True, + "contact-point": json.loads(to_text(r.read())), + } + else: + return { + "state": "present", + "changed": True, + "channel": json.loads(to_text(r.read())), + } else: raise GrafanaAPIException( - "Unable to create notification channel: %s" % info + "Unable to create %s: %s" % (self.endpoint_type, info) ) def grafana_update_notification_channel(self, data, payload, before): + url = ( + "%s/api/v1/provisioning/contact-points/%s" + if self.grafana_unified_alerting + else "%s/api/alert-notifications/uid/%s" + ) r, info = fetch_url( self._module, - "%s/api/alert-notifications/uid/%s" % (data["url"], data["uid"]), + url % (data["url"], data["uid"]), data=json.dumps(payload), headers=self.headers, method="PUT", @@ -737,15 +757,22 @@ def grafana_update_notification_channel(self, data, payload, before): } else: raise GrafanaAPIException( - "Unable to update notification channel %s : %s" % (data["uid"], info) + "Unable to update %s %s : %s" % (self.endpoint_type, data["uid"], info) ) def grafana_create_or_update_notification_channel(self, data): payload = grafana_notification_channel_payload(data) + url = ( + "%s/api/v1/provisioning/contact-points" % data["url"] + if self.grafana_unified_alerting + else "%s/api/alert-notifications/uid/%s" % (data["url"], data["uid"]) + ) r, info = fetch_url( self._module, - "%s/api/alert-notifications/uid/%s" % (data["url"], data["uid"]), + url, + data=json.dumps(payload), headers=self.headers, + method="GET", ) if info["status"] == 200: before = json.loads(to_text(r.read())) @@ -754,13 +781,18 @@ def grafana_create_or_update_notification_channel(self, data): return self.grafana_create_notification_channel(data, payload) else: raise GrafanaAPIException( - "Unable to get notification channel %s : %s" % (data["uid"], info) + "Unable to get %s %s : %s" % (self.endpoint_type, data["uid"], info) ) def grafana_delete_notification_channel(self, data): + url = ( + "%s/api/v1/provisioning/contact-points/%s" + if self.grafana_unified_alerting + else "%s/api/alert-notifications/uid/%s" + ) r, info = fetch_url( self._module, - "%s/api/alert-notifications/uid/%s" % (data["url"], data["uid"]), + url % (data["url"], data["uid"]), headers=self.headers, method="DELETE", ) @@ -770,7 +802,7 @@ def grafana_delete_notification_channel(self, data): return {"changed": False} else: raise GrafanaAPIException( - "Unable to delete notification channel %s : %s" % (data["uid"], info) + "Unable to delete %s %s : %s" % (self.endpoint_type, data["uid"], info) ) From d3187f0d23e51cbcd251d48287292be166918074 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Tue, 27 Feb 2024 16:18:18 +0100 Subject: [PATCH 06/52] fix: create or update function --- plugins/modules/grafana_notification_channel.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/plugins/modules/grafana_notification_channel.py b/plugins/modules/grafana_notification_channel.py index e7098d9b..e1f7c44e 100644 --- a/plugins/modules/grafana_notification_channel.py +++ b/plugins/modules/grafana_notification_channel.py @@ -431,6 +431,7 @@ 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.module_utils.six.moves.urllib.parse import quote from ansible_collections.community.grafana.plugins.module_utils.base import ( grafana_argument_spec, clean_url, @@ -763,7 +764,7 @@ def grafana_update_notification_channel(self, data, payload, before): def grafana_create_or_update_notification_channel(self, data): payload = grafana_notification_channel_payload(data) url = ( - "%s/api/v1/provisioning/contact-points" % data["url"] + "%s/api/v1/provisioning/contact-points?name=%s" % (data["url"], quote(data["name"])) if self.grafana_unified_alerting else "%s/api/alert-notifications/uid/%s" % (data["url"], data["uid"]) ) @@ -774,10 +775,10 @@ def grafana_create_or_update_notification_channel(self, data): headers=self.headers, method="GET", ) - if info["status"] == 200: - before = json.loads(to_text(r.read())) + before = json.loads(to_text(r.read())) + if info["status"] == 200 and before != None: return self.grafana_update_notification_channel(data, payload, before) - elif info["status"] == 404: + elif info["status"] == 404 or before == None: return self.grafana_create_notification_channel(data, payload) else: raise GrafanaAPIException( From aff1368ea7214fda04853b7f6062fd1d452d9aeb Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Tue, 27 Feb 2024 17:01:13 +0100 Subject: [PATCH 07/52] fix: delete contact points logic --- .../modules/grafana_notification_channel.py | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/plugins/modules/grafana_notification_channel.py b/plugins/modules/grafana_notification_channel.py index e1f7c44e..9d6c4515 100644 --- a/plugins/modules/grafana_notification_channel.py +++ b/plugins/modules/grafana_notification_channel.py @@ -764,7 +764,8 @@ def grafana_update_notification_channel(self, data, payload, before): def grafana_create_or_update_notification_channel(self, data): payload = grafana_notification_channel_payload(data) url = ( - "%s/api/v1/provisioning/contact-points?name=%s" % (data["url"], quote(data["name"])) + "%s/api/v1/provisioning/contact-points?name=%s" + % (data["url"], quote(data["name"])) if self.grafana_unified_alerting else "%s/api/alert-notifications/uid/%s" % (data["url"], data["uid"]) ) @@ -777,9 +778,14 @@ def grafana_create_or_update_notification_channel(self, data): ) before = json.loads(to_text(r.read())) if info["status"] == 200 and before != None: - return self.grafana_update_notification_channel(data, payload, before) - elif info["status"] == 404 or before == None: + if data["state"] == "present": + return self.grafana_update_notification_channel(data, payload, before) + else: + return self.grafana_delete_notification_channel(data) + elif info["status"] == 404 or (before == None and data["state"] == "present"): return self.grafana_create_notification_channel(data, payload) + elif info["status"] == 200 and before == None and data["state"] == "absent": + return {"changed": False} else: raise GrafanaAPIException( "Unable to get %s %s : %s" % (self.endpoint_type, data["uid"], info) @@ -797,7 +803,7 @@ def grafana_delete_notification_channel(self, data): headers=self.headers, method="DELETE", ) - if info["status"] == 200: + if info["status"] == 200 or info["status"] == 202: return {"state": "absent", "changed": True} elif info["status"] == 404: return {"changed": False} @@ -951,16 +957,12 @@ def main(): ) 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 - ) - module.exit_json(failed=False, **result) - else: - result = alert_channel_iface.grafana_delete_notification_channel(module.params) - module.exit_json(failed=False, **result) + alert_channel_iface = GrafanaNotificationChannelInterface(module) + result = alert_channel_iface.grafana_create_or_update_notification_channel( + module.params + ) + module.exit_json(failed=False, **result) if __name__ == "__main__": From 699ac33efa192e8e25cf1db7de3c40a4a9d89625 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Tue, 27 Feb 2024 17:10:14 +0100 Subject: [PATCH 08/52] chore: format --- .../modules/grafana_notification_channel.py | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/plugins/modules/grafana_notification_channel.py b/plugins/modules/grafana_notification_channel.py index 9d6c4515..c6d3287b 100644 --- a/plugins/modules/grafana_notification_channel.py +++ b/plugins/modules/grafana_notification_channel.py @@ -721,18 +721,21 @@ def grafana_create_notification_channel(self, data, payload): ) def grafana_update_notification_channel(self, data, payload, before): - url = ( + url_template = ( "%s/api/v1/provisioning/contact-points/%s" if self.grafana_unified_alerting else "%s/api/alert-notifications/uid/%s" ) + url = url_template % (data["url"], data["uid"]) + r, info = fetch_url( self._module, - url % (data["url"], data["uid"]), + url, data=json.dumps(payload), headers=self.headers, method="PUT", ) + if info["status"] == 200: del before["created"] del before["updated"] @@ -742,18 +745,12 @@ def grafana_update_notification_channel(self, data, payload, before): del after["created"] del after["updated"] - if before == after: - return { - "changed": False, - "channel": channel, - } + if before == channel: + return {"changed": False, "channel": channel} else: return { "changed": True, - "diff": { - "before": before, - "after": after, - }, + "diff": {"before": before, "after": after}, "channel": channel, } else: @@ -769,6 +766,7 @@ def grafana_create_or_update_notification_channel(self, data): if self.grafana_unified_alerting else "%s/api/alert-notifications/uid/%s" % (data["url"], data["uid"]) ) + r, info = fetch_url( self._module, url, @@ -776,15 +774,16 @@ def grafana_create_or_update_notification_channel(self, data): headers=self.headers, method="GET", ) + before = json.loads(to_text(r.read())) - if info["status"] == 200 and before != None: + if info["status"] == 200 and before is not None: if data["state"] == "present": return self.grafana_update_notification_channel(data, payload, before) else: return self.grafana_delete_notification_channel(data) - elif info["status"] == 404 or (before == None and data["state"] == "present"): + elif info["status"] == 404 or (before is None and data["state"] == "present"): return self.grafana_create_notification_channel(data, payload) - elif info["status"] == 200 and before == None and data["state"] == "absent": + elif info["status"] == 200 and before is None and data["state"] == "absent": return {"changed": False} else: raise GrafanaAPIException( @@ -792,17 +791,20 @@ def grafana_create_or_update_notification_channel(self, data): ) def grafana_delete_notification_channel(self, data): - url = ( + url_template = ( "%s/api/v1/provisioning/contact-points/%s" if self.grafana_unified_alerting else "%s/api/alert-notifications/uid/%s" ) + url = url_template % (data["url"], data["uid"]) + r, info = fetch_url( self._module, - url % (data["url"], data["uid"]), + url, headers=self.headers, method="DELETE", ) + if info["status"] == 200 or info["status"] == 202: return {"state": "absent", "changed": True} elif info["status"] == 404: From e2e9164e38e0c32bdcabbae99fa038dc2eb4c963 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Wed, 28 Feb 2024 14:45:09 +0100 Subject: [PATCH 09/52] refactor: handling of get response --- .../modules/grafana_notification_channel.py | 63 ++++++++++++------- 1 file changed, 40 insertions(+), 23 deletions(-) diff --git a/plugins/modules/grafana_notification_channel.py b/plugins/modules/grafana_notification_channel.py index c6d3287b..c62c42f7 100644 --- a/plugins/modules/grafana_notification_channel.py +++ b/plugins/modules/grafana_notification_channel.py @@ -690,31 +690,31 @@ def grafana_organization_by_name(self, data, org_name): ) def grafana_create_notification_channel(self, data, payload): - url = ( + url_template = ( "%s/api/v1/provisioning/contact-points" if self.grafana_unified_alerting else "%s/api/alert-notifications" ) + url = url_template % data["url"] + r, info = fetch_url( self._module, - url % data["url"], + url, data=json.dumps(payload), headers=self.headers, method="POST", ) - if info["status"] == 200: - if self.grafana_unified_alerting: - return { - "state": "present", - "changed": True, - "contact-point": json.loads(to_text(r.read())), - } - else: - return { - "state": "present", - "changed": True, - "channel": json.loads(to_text(r.read())), - } + + status = info["status"] + channel = json.loads(to_text(r.read())) + + if status in [200, 202]: + type_key = "contact-point" if self.grafana_unified_alerting else "channel" + return { + "state": "present", + "changed": True, + type_key: channel, + } else: raise GrafanaAPIException( "Unable to create %s: %s" % (self.endpoint_type, info) @@ -736,7 +736,9 @@ def grafana_update_notification_channel(self, data, payload, before): method="PUT", ) - if info["status"] == 200: + status = info["status"] + + if status == 200: del before["created"] del before["updated"] @@ -753,6 +755,12 @@ def grafana_update_notification_channel(self, data, payload, before): "diff": {"before": before, "after": after}, "channel": channel, } + elif status == 202: + contact_point = json.loads(to_text(r.read())) + return { + "changed": True, + "contact-point": contact_point, + } else: raise GrafanaAPIException( "Unable to update %s %s : %s" % (self.endpoint_type, data["uid"], info) @@ -776,15 +784,24 @@ def grafana_create_or_update_notification_channel(self, data): ) before = json.loads(to_text(r.read())) - if info["status"] == 200 and before is not None: - if data["state"] == "present": - return self.grafana_update_notification_channel(data, payload, before) + status = info.get("status") + state = data["state"] + + if status == 200: + if state == "present": + if before is None: + return self.grafana_create_notification_channel(data, payload) + else: + return self.grafana_update_notification_channel( + data, payload, before + ) else: - return self.grafana_delete_notification_channel(data) - elif info["status"] == 404 or (before is None and data["state"] == "present"): + if before is None: + return {"changed": False} + else: + return self.grafana_delete_notification_channel(data) + elif status == 404: return self.grafana_create_notification_channel(data, payload) - elif info["status"] == 200 and before is None and data["state"] == "absent": - return {"changed": False} else: raise GrafanaAPIException( "Unable to get %s %s : %s" % (self.endpoint_type, data["uid"], info) From 11383b4427da1f10d53792b22ba3b2bfded1c75d Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Wed, 28 Feb 2024 18:35:43 +0100 Subject: [PATCH 10/52] fix: reset notification channel --- .../modules/grafana_notification_channel.py | 185 ++++-------------- 1 file changed, 40 insertions(+), 145 deletions(-) diff --git a/plugins/modules/grafana_notification_channel.py b/plugins/modules/grafana_notification_channel.py index c62c42f7..30b5b112 100644 --- a/plugins/modules/grafana_notification_channel.py +++ b/plugins/modules/grafana_notification_channel.py @@ -36,17 +36,10 @@ options: org_id: description: - - The Grafana Organisation ID where the notification channel will be handled. + - 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.. - - Mutually exclusive with C(org_name). default: 1 type: int - org_name: - description: - - The Grafana Organisation name where the notification channel will be handled. - - Not used when I(grafana_api_key) is set, because the grafana_api_key only belongs to one organisation.. - - Mutually exclusive with C(org_id). - type: str state: type: str default: present @@ -431,7 +424,6 @@ 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.module_utils.six.moves.urllib.parse import quote from ansible_collections.community.grafana.plugins.module_utils.base import ( grafana_argument_spec, clean_url, @@ -619,7 +611,6 @@ def grafana_notification_channel_payload(data): class GrafanaNotificationChannelInterface(object): def __init__(self, module): self._module = module - self.org_id = None # {{{ Authentication header self.headers = {"Content-Type": "application/json"} if module.params.get("grafana_api_key", None): @@ -630,41 +621,13 @@ def __init__(self, module): self.headers["Authorization"] = basic_auth_header( module.params["url_username"], module.params["url_password"] ) - self.org_id = ( - self.grafana_organization_by_name(module.params["org_name"]) - if module.params["org_name"] - else module.params["org_id"] - ) - self.grafana_switch_organisation(module.params, self.org_id) # }}} - self.grafana_check_unified_alerting(module.params) - self.endpoint_type = ( - "contact point" if self.grafana_unified_alerting else "notification channel" - ) + self.grafana_url = clean_url(module.params.get("url")) - def grafana_check_unified_alerting(self, data): - self.grafana_unified_alerting = None + def grafana_switch_organisation(self, grafana_url, org_id): r, info = fetch_url( self._module, - "%s/api/frontend/settings" % data["url"], - headers=self.headers, - method="GET", - ) - if info["status"] == 200: - try: - settings = json.loads(to_text(r.read())) - self.grafana_unified_alerting = settings["unifiedAlertingEnabled"] - 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) - - def grafana_switch_organisation(self, data, org_id): - r, info = fetch_url( - self._module, - "%s/api/user/using/%s" % (data["url"], org_id), + "%s/api/user/using/%s" % (grafana_url, org_id), headers=self.headers, method="POST", ) @@ -673,72 +636,34 @@ def grafana_switch_organisation(self, data, org_id): "Unable to switch to organization %s : %s" % (org_id, info) ) - def grafana_organization_by_name(self, data, org_name): - r, info = fetch_url( - self._module, - "%s/api/user/orgs" % data["url"], - headers=self.headers, - method="GET", - ) - organizations = json.loads(to_text(r.read())) - orga = next((org for org in organizations if org["name"] == org_name)) - if orga: - return orga["orgId"] - - raise GrafanaAPIException( - "Current user isn't member of organization: %s" % org_name - ) - def grafana_create_notification_channel(self, data, payload): - url_template = ( - "%s/api/v1/provisioning/contact-points" - if self.grafana_unified_alerting - else "%s/api/alert-notifications" - ) - url = url_template % data["url"] - r, info = fetch_url( self._module, - url, + "%s/api/alert-notifications" % data["url"], data=json.dumps(payload), headers=self.headers, method="POST", ) - - status = info["status"] - channel = json.loads(to_text(r.read())) - - if status in [200, 202]: - type_key = "contact-point" if self.grafana_unified_alerting else "channel" + if info["status"] == 200: return { "state": "present", "changed": True, - type_key: channel, + "channel": json.loads(to_text(r.read())), } else: raise GrafanaAPIException( - "Unable to create %s: %s" % (self.endpoint_type, info) + "Unable to create notification channel: %s" % info ) def grafana_update_notification_channel(self, data, payload, before): - url_template = ( - "%s/api/v1/provisioning/contact-points/%s" - if self.grafana_unified_alerting - else "%s/api/alert-notifications/uid/%s" - ) - url = url_template % (data["url"], data["uid"]) - r, info = fetch_url( self._module, - url, + "%s/api/alert-notifications/uid/%s" % (data["url"], data["uid"]), data=json.dumps(payload), headers=self.headers, method="PUT", ) - - status = info["status"] - - if status == 200: + if info["status"] == 200: del before["created"] del before["updated"] @@ -747,88 +672,56 @@ def grafana_update_notification_channel(self, data, payload, before): del after["created"] del after["updated"] - if before == channel: - return {"changed": False, "channel": channel} + if before == after: + return { + "changed": False, + "channel": channel, + } else: return { "changed": True, - "diff": {"before": before, "after": after}, + "diff": { + "before": before, + "after": after, + }, "channel": channel, } - elif status == 202: - contact_point = json.loads(to_text(r.read())) - return { - "changed": True, - "contact-point": contact_point, - } else: raise GrafanaAPIException( - "Unable to update %s %s : %s" % (self.endpoint_type, data["uid"], info) + "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) - url = ( - "%s/api/v1/provisioning/contact-points?name=%s" - % (data["url"], quote(data["name"])) - if self.grafana_unified_alerting - else "%s/api/alert-notifications/uid/%s" % (data["url"], data["uid"]) - ) - r, info = fetch_url( self._module, - url, - data=json.dumps(payload), + "%s/api/alert-notifications/uid/%s" % (data["url"], data["uid"]), headers=self.headers, - method="GET", ) - - before = json.loads(to_text(r.read())) - status = info.get("status") - state = data["state"] - - if status == 200: - if state == "present": - if before is None: - return self.grafana_create_notification_channel(data, payload) - else: - return self.grafana_update_notification_channel( - data, payload, before - ) - else: - if before is None: - return {"changed": False} - else: - return self.grafana_delete_notification_channel(data) - elif status == 404: + if info["status"] == 200: + before = json.loads(to_text(r.read())) + return self.grafana_update_notification_channel(data, payload, before) + elif info["status"] == 404: return self.grafana_create_notification_channel(data, payload) else: raise GrafanaAPIException( - "Unable to get %s %s : %s" % (self.endpoint_type, data["uid"], info) + "Unable to get notification channel %s : %s" % (data["uid"], info) ) def grafana_delete_notification_channel(self, data): - url_template = ( - "%s/api/v1/provisioning/contact-points/%s" - if self.grafana_unified_alerting - else "%s/api/alert-notifications/uid/%s" - ) - url = url_template % (data["url"], data["uid"]) - r, info = fetch_url( self._module, - url, + "%s/api/alert-notifications/uid/%s" % (data["url"], data["uid"]), headers=self.headers, method="DELETE", ) - - if info["status"] == 200 or info["status"] == 202: + if info["status"] == 200: return {"state": "absent", "changed": True} elif info["status"] == 404: return {"changed": False} else: raise GrafanaAPIException( - "Unable to delete %s %s : %s" % (self.endpoint_type, data["uid"], info) + "Unable to delete notification channel %s : %s" % (data["uid"], info) ) @@ -836,7 +729,6 @@ def main(): argument_spec = grafana_argument_spec() argument_spec.update( org_id=dict(type="int", default=1), - org_name=dict(type="str"), uid=dict(type="str"), name=dict(type="str"), type=dict( @@ -905,8 +797,8 @@ def main(): elements="str", choices=["emergency", "high", "normal", "low", "lowest"], ), - pushover_retry=dict(type="int"), - pushover_expire=dict(type="int"), + 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"), @@ -971,17 +863,20 @@ def main(): ], ["type", "victorops", ["victorops_url"]], ["type", "webhook", ["webhook_url"]], - ["pushover_priority", "emergency", ["pushover_retry", "pushover_expire"]], ], ) module.params["url"] = clean_url(module.params["url"]) - alert_channel_iface = GrafanaNotificationChannelInterface(module) - result = alert_channel_iface.grafana_create_or_update_notification_channel( - module.params - ) - module.exit_json(failed=False, **result) + + 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__": From 3feb22e3dfaff456dd21efe92c54b4f768882ed2 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Wed, 28 Feb 2024 18:37:21 +0100 Subject: [PATCH 11/52] feat: intial add working contact point module --- plugins/modules/grafana_contact_point.yml | 277 ++++++++++++++++++++++ 1 file changed, 277 insertions(+) create mode 100644 plugins/modules/grafana_contact_point.yml diff --git a/plugins/modules/grafana_contact_point.yml b/plugins/modules/grafana_contact_point.yml new file mode 100644 index 00000000..a4b4e8f7 --- /dev/null +++ b/plugins/modules/grafana_contact_point.yml @@ -0,0 +1,277 @@ +#!/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 . + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = """ +--- +module: grafana_contact_point +author: + - Moritz Pötschk (@nemental) +version_added: "1.9.0" +short_description: Manage Grafana Contact Points +description: + - Create/Update/Delete Grafana Contact Points via API. + +extends_documentation_fragment: + - community.grafana.basic_auth + - community.grafana.api_key +""" + + +EXAMPLES = """ +- name: Create email contact point + community.grafana.grafana_contact_point: + grafana_url: "{{ grafana_url }}" + grafana_user: "{{ grafana_username }}" + grafana_password: "{{ grafana_password }}" + uid: email + name: E-Mail + type: email + email_addresses: + - example@example.com + +- name: Delete email contact point + community.grafana.grafana_contact_point: + grafana_url: "{{ grafana_url }}" + grafana_user: "{{ grafana_username }}" + grafana_password: "{{ grafana_password }}" + uid: email + state: absent +""" + +RETURN = """ +contact_point: + description: Contact point created or updated by the module. + returned: changed +""" + +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.module_utils.six.moves.urllib.parse import quote +from ansible_collections.community.grafana.plugins.module_utils.base import ( + grafana_argument_spec, + clean_url, +) +from ansible.module_utils.urls import basic_auth_header + + +class GrafanaAPIException(Exception): + pass + + +def grafana_contact_point_payload_email(data, payload): + payload["settings"]["addresses"] = ";".join(data["email_addresses"]) + if data.get("email_single"): + payload["settings"]["singleEmail"] = data["email_single"] + + +def grafana_contact_point_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"]}, + } + + if data["type"] == "email": + grafana_contact_point_payload_email(data, payload) + + return payload + + +class GrafanaContactPointInterface(object): + def __init__(self, module): + self._module = module + 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"] + ) + else: + self.headers["Authorization"] = basic_auth_header( + module.params["url_username"], module.params["url_password"] + ) + self.org_id = ( + self.grafana_organization_by_name(module.params["org_name"]) + if module.params["org_name"] + else module.params["org_id"] + ) + self.grafana_switch_organisation(module.params, self.org_id) + # }}} + + def grafana_organization_by_name(self, data, org_name): + r, info = fetch_url( + self._module, + "%s/api/user/orgs" % data["url"], + headers=self.headers, + method="GET", + ) + organizations = json.loads(to_text(r.read())) + orga = next((org for org in organizations if org["name"] == org_name)) + if orga: + return orga["orgId"] + + raise GrafanaAPIException( + "Current user isn't member of organization: %s" % org_name + ) + + def grafana_switch_organisation(self, data, org_id): + r, info = fetch_url( + self._module, + "%s/api/user/using/%s" % (data["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_check_contact_point_match(self, data): + r, info = fetch_url( + self._module, + "%s/api/v1/provisioning/contact-points" % data["url"], + headers=self.headers, + method="GET", + ) + + if info["status"] == 200: + contact_points = json.loads(to_text(r.read())) + before = next( + (cp for cp in contact_points if cp["uid"] == data["uid"]), None + ) + return self.grafana_handle_contact_point(data, before) + else: + raise GrafanaAPIException( + "Unable to get contact point %s : %s" % (data["uid"], info) + ) + + def grafana_handle_contact_point(self, data, before): + payload = grafana_contact_point_payload(data) + + if data["state"] == "present": + if before: + return self.grafana_update_contact_point(data, payload, before) + else: + return self.grafana_create_contact_point(data, payload) + else: + if before: + return self.grafana_delete_contact_point(data) + else: + return {"changed": False} + + def grafana_create_contact_point(self, data, payload): + r, info = fetch_url( + self._module, + "%s/api/v1/provisioning/contact-points" % data["url"], + data=json.dumps(payload), + headers=self.headers, + method="POST", + ) + + if info["status"] == 202: + contact_point = json.loads(to_text(r.read())) + return {"changed": True, "state": data["state"], "contact_point": contact_point} + else: + raise GrafanaAPIException( + "Unable to create contact point: %s" % info + ) + + def grafana_update_contact_point(self, data, payload, before): + r, info = fetch_url( + self._module, + "%s/api/v1/provisioning/contact-points/%s" % (data["url"], data["uid"]), + data=json.dumps(payload), + headers=self.headers, + method="PUT", + ) + + if info["status"] == 202: + contact_point = json.loads(to_text(r.read())) + + if before == contact_point: + return {"changed": False} + else: + return {"changed": True, "diff": {"before": before, "after": payload}, "contact_point": contact_point} + else: + raise GrafanaAPIException( + "Unable to update contact point %s : %s" % (data["uid"], info) + ) + + def grafana_delete_contact_point(self, data): + r, info = fetch_url( + self._module, + "%s/api/v1/provisioning/contact-points/%s" % (data["url"], data["uid"]), + headers=self.headers, + method="DELETE", + ) + + if info["status"] == 202: + return {"state": "absent", "changed": True} + elif info["status"] == 404: + return {"changed": False} + else: + raise GrafanaAPIException( + "Unable to delete contact point %s : %s" % (data["uid"], info) + ) + + +def main(): + argument_spec = grafana_argument_spec() + argument_spec.update( + org_id=dict(type="int", default=1), + org_name=dict(type="str"), + uid=dict(type="str"), + name=dict(type="str"), + type=dict(type="str", choices=["email"]), + is_default=dict(type="bool", default=False), + include_image=dict(type="bool", default=False), + disable_resolve_message=dict(type="bool", default=False), + email_addresses=dict(type="list", elements="str"), + email_single=dict(type="bool"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=False, + required_together=[["url_username", "url_password", "org_id"]], + mutually_exclusive=[["url_username", "grafana_api_key"]], + required_if=[ + ["state", "present", ["name", "type"]], + ["type", "email", ["email_addresses"]], + ], + ) + + module.params["url"] = clean_url(module.params["url"]) + grafana_iface = GrafanaContactPointInterface(module) + + result = grafana_iface.grafana_check_contact_point_match(module.params) + module.exit_json(failed=False, **result) + + +if __name__ == "__main__": + main() From 8e57f4a9954cbad4e3febb61b00ab21b186b2b6c Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Wed, 28 Feb 2024 18:44:35 +0100 Subject: [PATCH 12/52] chore: format --- plugins/modules/grafana_contact_point.yml | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/plugins/modules/grafana_contact_point.yml b/plugins/modules/grafana_contact_point.yml index a4b4e8f7..9858782e 100644 --- a/plugins/modules/grafana_contact_point.yml +++ b/plugins/modules/grafana_contact_point.yml @@ -195,11 +195,13 @@ class GrafanaContactPointInterface(object): if info["status"] == 202: contact_point = json.loads(to_text(r.read())) - return {"changed": True, "state": data["state"], "contact_point": contact_point} + return { + "changed": True, + "state": data["state"], + "contact_point": contact_point, + } else: - raise GrafanaAPIException( - "Unable to create contact point: %s" % info - ) + raise GrafanaAPIException("Unable to create contact point: %s" % info) def grafana_update_contact_point(self, data, payload, before): r, info = fetch_url( @@ -216,7 +218,11 @@ class GrafanaContactPointInterface(object): if before == contact_point: return {"changed": False} else: - return {"changed": True, "diff": {"before": before, "after": payload}, "contact_point": contact_point} + return { + "changed": True, + "diff": {"before": before, "after": payload}, + "contact_point": contact_point, + } else: raise GrafanaAPIException( "Unable to update contact point %s : %s" % (data["uid"], info) From d155269b8dfad8f51330c8db637ee75684971a85 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Wed, 28 Feb 2024 20:17:00 +0100 Subject: [PATCH 13/52] chore: wtf i named it yml --- .../{grafana_contact_point.yml => grafana_contact_point.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename plugins/modules/{grafana_contact_point.yml => grafana_contact_point.py} (100%) diff --git a/plugins/modules/grafana_contact_point.yml b/plugins/modules/grafana_contact_point.py similarity index 100% rename from plugins/modules/grafana_contact_point.yml rename to plugins/modules/grafana_contact_point.py From 48f64fe6ffb983daa23d245efe67f84599b92026 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Thu, 29 Feb 2024 08:11:01 +0100 Subject: [PATCH 14/52] chore: remove unused import --- plugins/modules/grafana_contact_point.py | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/modules/grafana_contact_point.py b/plugins/modules/grafana_contact_point.py index 9858782e..a447d768 100644 --- a/plugins/modules/grafana_contact_point.py +++ b/plugins/modules/grafana_contact_point.py @@ -67,7 +67,6 @@ 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.module_utils.six.moves.urllib.parse import quote from ansible_collections.community.grafana.plugins.module_utils.base import ( grafana_argument_spec, clean_url, From 10a726ff0c68951402fa63a2fc3687f8b224fb79 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Thu, 29 Feb 2024 08:35:13 +0100 Subject: [PATCH 15/52] docs: changelog fragment --- changelogs/fragments/352-module-contact-point.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelogs/fragments/352-module-contact-point.yml diff --git a/changelogs/fragments/352-module-contact-point.yml b/changelogs/fragments/352-module-contact-point.yml new file mode 100644 index 00000000..14915de6 --- /dev/null +++ b/changelogs/fragments/352-module-contact-point.yml @@ -0,0 +1,5 @@ +minor_changes: + - Add `grafana_contact_point` module + - Add support of `grafana_contact_point` in grafana role +trivial: + - Add tests for `grafana_contact_point` module From 280642f032d29b4737aff876b09f78fa5ae2ac14 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Thu, 29 Feb 2024 09:10:41 +0100 Subject: [PATCH 16/52] feat: initial support of contact point module in grafana role --- meta/runtime.yml | 1 + roles/grafana/README.md | 10 ++++++++++ roles/grafana/defaults/main.yml | 1 + roles/grafana/tasks/main.yml | 15 +++++++++++++++ 4 files changed, 27 insertions(+) diff --git a/meta/runtime.yml b/meta/runtime.yml index d29f1dda..71243ba1 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -6,6 +6,7 @@ action_groups: - grafana_datasource - grafana_folder - grafana_notification_channel + - grafana_contact_point - grafana_organization - grafana_organization_user - grafana_plugin diff --git a/roles/grafana/README.md b/roles/grafana/README.md index f76e687f..ea7eda39 100644 --- a/roles/grafana/README.md +++ b/roles/grafana/README.md @@ -170,6 +170,16 @@ Configure Grafana organizations, dashboards, folders, datasources, teams and use | matchers | yes | | starts_at | yes | | state | no | +| [**grafana_contact_point**](https://docs.ansible.com/ansible/latest/collections/community/grafana/grafana_contact_point_module.html) | +| email_addresses | no | +| email_single | no | +| is_default | no | +| name | yes | +| org_id | no | +| org_name | no | +| state | no | +| type | yes | +| uid | no | ## Example Playbook diff --git a/roles/grafana/defaults/main.yml b/roles/grafana/defaults/main.yml index 9756a133..a3f90760 100644 --- a/roles/grafana/defaults/main.yml +++ b/roles/grafana/defaults/main.yml @@ -8,3 +8,4 @@ grafana_folders: [] grafana_dashboards: [] grafana_notification_channels: [] grafana_silences: [] +grafana_contact_points: [] diff --git a/roles/grafana/tasks/main.yml b/roles/grafana/tasks/main.yml index 1665a856..4b0918f9 100644 --- a/roles/grafana/tasks/main.yml +++ b/roles/grafana/tasks/main.yml @@ -89,6 +89,21 @@ loop_control: {loop_var: notification_channel} tags: notification_channel + - name: Manage contact point + community.grafana.grafana_contact_point: + email_addresses: "{{ contact_point.email_addresses | default(omit) }}" + email_single: "{{ contact_point.email_single | default(omit) }}" + is_default: "{{ contact_point.is_default | default(omit) }}" + name: "{{ contact_point.name }}" + org_id: "{{ contact_point.org_id | default(omit) }}" + org_name: "{{ contact_point.org_id | default(omit) }}" + state: "{{ contact_point.state | default(omit) }}" + type: "{{ contact_point.type }}" + uid: "{{ contact_point.uid | default(omit) }}" + loop: "{{ grafana_contact_points }}" + loop_control: {loop_var: contact_point} + tags: contact_point + - name: Manage datasource community.grafana.grafana_datasource: access: "{{ datasource.access | default(omit) }}" From c8c06bfb3ad330aa0636fda0bb9d3ba89724b5f4 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Thu, 29 Feb 2024 11:36:08 +0100 Subject: [PATCH 17/52] docs: add module to readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 21ce4d90..b1404c93 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ Click on the name of a plugin or module to view that content's documentation: - [grafana_datasource](https://docs.ansible.com/ansible/latest/collections/community/grafana/grafana_datasource_module.html) - [grafana_folder](https://docs.ansible.com/ansible/latest/collections/community/grafana/grafana_folder_module.html) - [grafana_notification_channel](https://docs.ansible.com/ansible/latest/collections/community/grafana/grafana_notification_channel_module.html) + - [grafana_contact_point](https://docs.ansible.com/ansible/latest/collections/community/grafana/grafana_contact_point_module.html) - [grafana_organization](https://docs.ansible.com/ansible/latest/collections/community/grafana/grafana_organization_module.html) - [grafana_organization_user](https://docs.ansible.com/ansible/latest/collections/community/grafana/grafana_organization_user_module.html) - [grafana_plugin](https://docs.ansible.com/ansible/latest/collections/community/grafana/grafana_plugin_module.html) From 458cd184506a6371b98421046db75416b1782f99 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Mon, 4 Mar 2024 10:27:59 +0100 Subject: [PATCH 18/52] chore: arguments for all contact points --- plugins/modules/grafana_contact_point.py | 204 ++++++++++++++++++++++- 1 file changed, 198 insertions(+), 6 deletions(-) diff --git a/plugins/modules/grafana_contact_point.py b/plugins/modules/grafana_contact_point.py index a447d768..81dd8f75 100644 --- a/plugins/modules/grafana_contact_point.py +++ b/plugins/modules/grafana_contact_point.py @@ -78,6 +78,18 @@ class GrafanaAPIException(Exception): pass +def grafana_contact_point_payload(data): + payload = { + "uid": data["uid"], + "name": data["name"], + "type": data["type"], + "isDefault": data["is_default"], + "disableResolveMessage": data["disable_resolve_message"], + } + + return payload + + def grafana_contact_point_payload_email(data, payload): payload["settings"]["addresses"] = ";".join(data["email_addresses"]) if data.get("email_single"): @@ -248,16 +260,177 @@ def grafana_delete_contact_point(self, data): def main(): argument_spec = grafana_argument_spec() argument_spec.update( + # general arguments + disable_resolve_message=dict(type="bool", default=False), + include_image=dict(type="bool", default=False), + is_default=dict(type="bool", default=False), + name=dict(type="str"), org_id=dict(type="int", default=1), org_name=dict(type="str"), + type=dict( + type="str", + choices=[ + "alertmanager", + "dingding", + "discord", + "email", + "googlechat", + "kaska", + "line", + "opsgenie", + "pagerduty", + "pushover", + "sensugo", + "slack", + "teams", + "telegram", + "threema", + "victorops", + "webex", + "webhook", + "wecom", + ], + ), uid=dict(type="str"), - name=dict(type="str"), - type=dict(type="str", choices=["email"]), - is_default=dict(type="bool", default=False), - include_image=dict(type="bool", default=False), - disable_resolve_message=dict(type="bool", default=False), + # type: alertmanager + alertmanager_password=dict(type="str", no_log=True), + alertmanager_url=dict(type="str"), + alertmanager_username=dict(type="str"), + # type: dingding + dingding_message=dict(type="str"), + dingding_message_type=dict(type="str"), + dingding_title=dict(type="str"), + dingding_url=dict(type="str"), + # type: discord + discord_avatar_url=dict(type="str"), + discord_message=dict(type="str"), + discord_title=dict(type="str"), + discord_url=dict(type="str", no_log=True), + discord_use_username=dict(type="bool", default=False), + # type: email email_addresses=dict(type="list", elements="str"), - email_single=dict(type="bool"), + email_message=dict(type="str"), + email_single=dict(type="bool", default=False), + email_subject=dict(type="str"), + # type: googlechat + googlechat_url=dict(type="str", no_log=True), + googlechat_message=dict(type="str"), + googlechat_title=dict(type="str"), + # type: kafka + kafka_api_version=dict(type="str", default="v2"), + kafka_cluster_id=dict(type="str"), + kafka_description=dict(type="str"), + kafka_details=dict(type="str"), + kafka_password=dict(type="str", no_log=True), + kafka_rest_proxy_url=dict(type="str", no_log=True), + kafka_topic=dict(type="str"), + kafka_username=dict(type="str"), + # type: line + line_description=dict(type="str"), + line_title=dict(type="str"), + line_token=dict(type="str", no_log=True), + # type: opsgenie + opsgenie_api_key=dict(type="str", no_log=True), + opsgenie_auto_close=dict(type="bool"), + opsgenie_description=dict(type="str"), + opsgenie_message=dict(type="str"), + opsgenie_override_priority=dict(type="bool"), + opsgenie_responders=dict(type="list", elements="dict"), + opsgenie_send_tags_as=dict(type="str"), + opsgenie_url=dict(type="str"), + # type: pagerduty + pagerduty_class=dict(type="str"), + pagerduty_client=dict(type="str"), + pagerduty_client_url=dict(type="str"), + pagerduty_component=dict(type="str"), + pagerduty_details=dict(type="list", elements="dict"), + pagerduty_group=dict(type="str"), + pagerduty_integration_key=dict(type="str", no_log=True), + pagerduty_severity=dict(type="str", choices=["critical", "error", "warning", "info"]), + pagerduty_source=dict(type="str"), + pagerduty_summary=dict(type="str"), + # type: pushover + pushover_api_token=dict(type="str", no_log=True), + pushover_devices=dict(type="list", elements="str"), + pushover_expire=dict(type="int"), + pushover_message=dict(type="str"), + pushover_ok_priority=dict(type="int"), + pushover_ok_sound=dict(type="str"), + pushover_priority=dict(type="int"), + pushover_retry=dict(type="int"), + pushover_sound=dict(type="str"), + pushover_title=dict(type="str"), + pushover_upload_image=dict(type="bool", default=True), + pushover_user_key=dict(type="str", no_log=True), + # type: sensugo + sensugo_api_key=dict(type="str", no_log=True), + sensugo_url=dict(type="str"), + sensugo_check=dict(type="str"), + sensugo_entity=dict(type="str"), + sensugo_handler=dict(type="str"), + sensugo_message=dict(type="str"), + sensugo_namespace=dict(type="str"), + # type: slack + slack_endpoint_url=dict(type="str"), + slack_icon_emoji=dict(type="str"), + slack_icon_url=dict(type="str"), + slack_mention_channel=dict(type="str", choices=["here", "channel"]), + slack_mention_groups=dict(type="list"), + slack_mention_users=dict(type="list"), + slack_recipient=dict(type="str"), + slack_text=dict(type="str"), + slack_title=dict(type="str"), + slack_token=dict(type="str", no_log=True), + slack_url=dict(type="str", no_log=True), + slack_username=dict(type="str"), + # type: teams + teams_message=dict(type="str"), + teams_section_title=dict(type="str"), + teams_title=dict(type="str"), + teams_url=dict(type="str", no_log=True), + # type: telegram + telegram_chat_id=dict(type="str"), + telegram_disable_notifications=dict(type="bool"), + telegram_message=dict(type="str"), + telegram_parse_mode=dict(type="str"), + telegram_protect_content=dict(type="bool"), + telegram_token=dict(type="str", no_log=True), + telegram_web_page_view=dict(type="bool"), + # type: threema + threema_api_secret=dict(type="str", no_log=True), + threema_description=dict(type="str"), + threema_gateway_id=dict(type="str"), + threema_recipient_id=dict(type="str"), + threema_title=dict(type="str"), + # type: victorops + victorops_description=dict(type="str"), + victorops_message_type=dict(type="str", choices=["CRITICAL", "RECOVERY"]), + victorops_title=dict(type="str"), + victorops_url=dict(type="str"), + # type: webex + webex_api_url=dict(type="str"), + webex_message=dict(type="str"), + webex_room_id=dict(type="str"), + webex_token=dict(type="str", no_log=True), + # type: webhook + webhook_authorization_credentials=dict(type="str", no_log=True), + webhook_authorization_scheme=dict(type="str"), + webhook_http_method=dict(type="str", choices=["POST", "PUT"]), + webhook_max_alerts=dict(type="int"), + webhook_message=dict(type="str"), + webhook_password=dict(type="str", no_log=True), + webhook_title=dict(type="str"), + webhook_url=dict(type="str"), + webhook_username=dict(type="str"), + # type: wecom + wecom_agent_id=dict(type="str"), + wecom_corp_id=dict(type="str"), + wecom_message=dict(type="str"), + wecom_msg_type=dict(type="str"), + wecom_secret=dict(type="str", no_log=True), + wecom_title=dict(type="str"), + wecom_to_user=dict(type="list"), + wecom_url=dict(type="str", no_log=True), ) module = AnsibleModule( @@ -267,7 +440,26 @@ def main(): mutually_exclusive=[["url_username", "grafana_api_key"]], required_if=[ ["state", "present", ["name", "type"]], + ["state", "absent", ["uid"]], + ["type", "alertmanager", ["alertmanager_url"]], + ["type", "dingding", ["dingding_url"]], + ["type", "discord", ["discord_url"]], ["type", "email", ["email_addresses"]], + ["type", "googlechat", ["googlechat_url"]], + ["type", "kafka", ["kafka_rest_proxy_url", "kafka_topic"]], + ["type", "line", ["line_token"]], + ["type", "opsgenie", ["opsgenie_api_key"]], + ["type", "pagerduty", ["pagerduty_integration_key"]], + ["type", "pushover", ["pushover_api_token", "pushover_user_key"]], + ["type", "sensugo", ["sensugo_api_key", "sensugo_url"]], + ["type", "slack", ["slack_recipient", "slack_token", "slack_url"]], + ["type", "teams", ["teams_url"]], + ["type", "telegram", ["telegram_chat_id", "telegram_token"]], + ["type", "threema", ["threema_api_secret", "threema_gateway_id", "threema_recipient_id"]], + ["type", "victorops", ["victorops_url"]], + ["type", "webex", ["webex_token", "webex_room_id"]], + ["type", "webhook", ["webhook_url"]], + ["type", "wecom", ["wecom_url", "wecom_agent_id", "wecom_corp_id", "wecom_secret"]], ], ) From 27a8f02da498a0133a4133c62557a8cb902e1535 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Mon, 4 Mar 2024 10:32:03 +0100 Subject: [PATCH 19/52] chore: format --- plugins/modules/grafana_contact_point.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/plugins/modules/grafana_contact_point.py b/plugins/modules/grafana_contact_point.py index 81dd8f75..38f8e1b7 100644 --- a/plugins/modules/grafana_contact_point.py +++ b/plugins/modules/grafana_contact_point.py @@ -346,7 +346,9 @@ def main(): pagerduty_details=dict(type="list", elements="dict"), pagerduty_group=dict(type="str"), pagerduty_integration_key=dict(type="str", no_log=True), - pagerduty_severity=dict(type="str", choices=["critical", "error", "warning", "info"]), + pagerduty_severity=dict( + type="str", choices=["critical", "error", "warning", "info"] + ), pagerduty_source=dict(type="str"), pagerduty_summary=dict(type="str"), # type: pushover @@ -455,11 +457,19 @@ def main(): ["type", "slack", ["slack_recipient", "slack_token", "slack_url"]], ["type", "teams", ["teams_url"]], ["type", "telegram", ["telegram_chat_id", "telegram_token"]], - ["type", "threema", ["threema_api_secret", "threema_gateway_id", "threema_recipient_id"]], + [ + "type", + "threema", + ["threema_api_secret", "threema_gateway_id", "threema_recipient_id"], + ], ["type", "victorops", ["victorops_url"]], ["type", "webex", ["webex_token", "webex_room_id"]], ["type", "webhook", ["webhook_url"]], - ["type", "wecom", ["wecom_url", "wecom_agent_id", "wecom_corp_id", "wecom_secret"]], + [ + "type", + "wecom", + ["wecom_url", "wecom_agent_id", "wecom_corp_id", "wecom_secret"], + ], ], ) From 79f44cf2367120356ecc5b367dab55200833ed91 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Mon, 4 Mar 2024 13:55:45 +0100 Subject: [PATCH 20/52] chore: handle provisioning --- plugins/modules/grafana_contact_point.py | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/plugins/modules/grafana_contact_point.py b/plugins/modules/grafana_contact_point.py index 38f8e1b7..cc3467e8 100644 --- a/plugins/modules/grafana_contact_point.py +++ b/plugins/modules/grafana_contact_point.py @@ -78,24 +78,11 @@ class GrafanaAPIException(Exception): pass -def grafana_contact_point_payload(data): - payload = { - "uid": data["uid"], - "name": data["name"], - "type": data["type"], - "isDefault": data["is_default"], - "disableResolveMessage": data["disable_resolve_message"], - } - - return payload - - def grafana_contact_point_payload_email(data, payload): payload["settings"]["addresses"] = ";".join(data["email_addresses"]) if data.get("email_single"): payload["settings"]["singleEmail"] = data["email_single"] - def grafana_contact_point_payload(data): payload = { "uid": data["uid"], @@ -103,7 +90,7 @@ def grafana_contact_point_payload(data): "type": data["type"], "isDefault": data["is_default"], "disableResolveMessage": data["disable_resolve_message"], - "settings": {"uploadImage": data["include_image"]}, + "settings": {}, } if data["type"] == "email": @@ -134,6 +121,10 @@ def __init__(self, module): self.grafana_switch_organisation(module.params, self.org_id) # }}} + def grafana_api_provisioning(self, data): + if not data["provisioning"]: + self.headers["X-Disable-Provenance"] = "true" + def grafana_organization_by_name(self, data, org_name): r, info = fetch_url( self._module, @@ -188,6 +179,7 @@ def grafana_handle_contact_point(self, data, before): if before: return self.grafana_update_contact_point(data, payload, before) else: + self.grafana_api_provisioning(data) return self.grafana_create_contact_point(data, payload) else: if before: @@ -267,6 +259,7 @@ def main(): name=dict(type="str"), org_id=dict(type="int", default=1), org_name=dict(type="str"), + provisioning=dict(type="bool", default=True), type=dict( type="str", choices=[ From dc00206135bddb995bde7ba0c1b307f35bbe5b10 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Mon, 4 Mar 2024 16:16:29 +0100 Subject: [PATCH 21/52] chore: payload with type settings map and for loop --- plugins/modules/grafana_contact_point.py | 190 ++++++++++++++++++++++- 1 file changed, 183 insertions(+), 7 deletions(-) diff --git a/plugins/modules/grafana_contact_point.py b/plugins/modules/grafana_contact_point.py index cc3467e8..c81c2be4 100644 --- a/plugins/modules/grafana_contact_point.py +++ b/plugins/modules/grafana_contact_point.py @@ -78,11 +78,6 @@ class GrafanaAPIException(Exception): pass -def grafana_contact_point_payload_email(data, payload): - payload["settings"]["addresses"] = ";".join(data["email_addresses"]) - if data.get("email_single"): - payload["settings"]["singleEmail"] = data["email_single"] - def grafana_contact_point_payload(data): payload = { "uid": data["uid"], @@ -93,8 +88,189 @@ def grafana_contact_point_payload(data): "settings": {}, } - if data["type"] == "email": - grafana_contact_point_payload_email(data, payload) + type_settings_map = { + "alertmanager": { + "basicAuthPassword": "alertmanager_password", + "url": "alertmanager_url", + "basicAuthUser": "alertmanager_username", + }, + "dingding": { + "message": "dingding_message", + "msgType": "dingding_message_type", + "title": "dingding_title", + "url": "dingding_url", + }, + "discord": { + "avatar_url": "discord_avatar_url", + "message": "discord_message", + "title": "discord_title", + "url": "discord_url", + "use_discord_username": "discord_use_username", + }, + "email": { + "addresses": "email_addresses", + "message": "email_message", + "singleEmail": "email_single", + "subject": "email_subject", + }, + "googlechat": { + "url": "googlechat_url", + "message": "googlechat_message", + "title": "googlechat_title", + }, + "kafka": { + "apiVersion": "kafka_api_version", + "kafkaClusterId": "kafka_cluster_id", + "description": "kafka_description", + "details": "kafka_details", + "password": "kafka_password", + "kafkaRestProxy": "kafka_rest_proxy_url", + "kafkaTopic": "kafka_topic", + "username": "kafka_username", + }, + "line": { + "description": "line_description", + "title": "line_title", + "token": "line_token", + }, + "opsgenie": { + "apiKey": "opsgenie_api_key", + "autoClose": "opsgenie_auto_close", + "description": "opsgenie_description", + "message": "opsgenie_message", + "overridePriority": "opsgenie_override_priority", + "responders": "opsgenie_responders", + "sendTagsAs": "opsgenie_send_tags_as", + "apiUrl": "opsgenie_url", + }, + "pagerduty": { + "class": "pagerduty_class", + "client": "pagerduty_client", + "client_url": "pagerduty_client_url", + "component": "pagerduty_component", + "details": "pagerduty_details", + "group": "pagerduty_group", + "integrationKey": "pagerduty_integration_key", + "severity": "pagerduty_severity", + "source": "pagerduty_source", + "summary": "pagerduty_summary", + }, + "pushover": { + "apiToken": "pushover_api_token", + "device": "pushover_devices", + "expire": "pushover_expire", + "message": "pushover_message", + "okPriority": "pushover_ok_priority", + "okSound": "pushover_ok_sound", + "priority": "pushover_priority", + "retry": "pushover_retry", + "sound": "pushover_sound", + "title": "pushover_title", + "uploadImage": "pushover_upload_image", + "userKey": "pushover_user_key", + }, + "sensugo": { + "apiKey": "sensugo_api_key", + "url": "sensugo_url", + "check": "sensugo_check", + "entity": "sensugo_entity", + "handler": "sensugo_handler", + "message": "sensugo_message", + "namespace": "sensugo_namespace", + }, + "slack": { + "endpointUrl": "slack_endpoint_url", + "icon_emoji": "slack_icon_emoji", + "icon_url": "slack_icon_url", + "mentionChannel": "slack_mention_channel", + "mentionGroups": "slack_mention_groups", + "mentionUsers": "slack_mention_users", + "recipient": "slack_recipient", + "text": "slack_text", + "title": "slack_title", + "token": "slack_token", + "url": "slack_url", + "username": "slack_username", + }, + "teams": { + "message": "teams_message", + "sectiontitle": "teams_section_title", + "title": "teams_title", + "url": "teams_url", + }, + "telegram": { + "chatid": "telegram_chat_id", + "disable_notification": "telegram_disable_notifications", + "message": "telegram_message", + "parse_mode": "telegram_parse_mode", + "protect_content": "telegram_protect_content", + "bottoken": "telegram_token", + "disable_web_page_preview": "telegram_web_page_view", + }, + "threema": { + "api_secret": "threema_api_secret", + "description": "threema_description", + "gateway_id": "threema_gateway_id", + "recipient_id": "threema_recipient_id", + "title": "threema_title", + }, + "victorops": { + "description": "victorops_description", + "messageType": "victorops_message_type", + "title": "victorops_title", + "url": "victorops_url", + }, + "webex": { + "api_url": "webex_api_url", + "message": "webex_message", + "room_id": "webex_room_id", + "bot_token": "webex_token", + }, + "webhook": { + "authorization_credentials": "webhook_authorization_credentials", + "authorization_scheme": "webhook_authorization_scheme", + "httpMethod": "webhook_http_method", + "maxAlerts": "webhook_max_alerts", + "message": "webhook_message", + "password": "webhook_password", + "title": "webhook_title", + "url": "webhook_url", + "username": "webhook_username", + }, + "wecom": { + "agent_id": "wecom_agent_id", + "corp_id": "wecom_corp_id", + "message": "wecom_message", + "msgtype": "wecom_msg_type", + "secret": "wecom_secret", + "title": "wecom_title", + "touser": "wecom_to_user", + "url": "wecom_url", + }, + } + + type_settings = type_settings_map.get(data["type"]) + if type_settings: + for setting_key, data_key in type_settings.items(): + if data_key == "pushover_priority": + payload["settings"][setting_key] = { + "emergency": "2", + "high": "1", + "normal": "0", + "low": "-1", + "lowest": "-2", + }[data[data_key]] + elif data_key == "dingding_message_type": + payload["settings"][setting_key] = { + "link": "link", + "action_card": "actionCard", + }[data[data_key]] + elif data_key in ["email_addresses", "pushover_devices"]: + payload["settings"][setting_key] = ";".join(data[data_key]) + elif data_key in ["slack_mention_users", "slack_mention_groups"]: + payload["settings"][setting_key] = ",".join(data[data_key]) + elif data.get(data_key): + payload["settings"][setting_key] = data[data_key] return payload From aed177a2b43306cfe02f756e62f8d8b33c13a95e Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Mon, 4 Mar 2024 20:16:47 +0100 Subject: [PATCH 22/52] fix: alertmanager type --- plugins/modules/grafana_contact_point.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/modules/grafana_contact_point.py b/plugins/modules/grafana_contact_point.py index c81c2be4..583a7978 100644 --- a/plugins/modules/grafana_contact_point.py +++ b/plugins/modules/grafana_contact_point.py @@ -88,6 +88,9 @@ def grafana_contact_point_payload(data): "settings": {}, } + if data["type"] == "alertmanager": + payload["type"] = "prometheus-alertmanager" + type_settings_map = { "alertmanager": { "basicAuthPassword": "alertmanager_password", From 18de3a3c1306d37741c4bdef19a3aa4c0c42b173 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Mon, 4 Mar 2024 20:18:10 +0100 Subject: [PATCH 23/52] test: initial add contact point integration tests --- .../targets/grafana_contact_point/runme.sh | 5 ++ .../targets/grafana_contact_point/site.yml | 11 +++ .../tasks/alertmanager.yml | 56 ++++++++++++ .../grafana_contact_point/tasks/dingding.yml | 56 ++++++++++++ .../grafana_contact_point/tasks/discord.yml | 56 ++++++++++++ .../grafana_contact_point/tasks/email.yml | 61 +++++++++++++ .../tasks/googlechat.yml | 56 ++++++++++++ .../grafana_contact_point/tasks/kafka.yml | 58 +++++++++++++ .../grafana_contact_point/tasks/line.yml | 56 ++++++++++++ .../grafana_contact_point/tasks/main.yml | 16 ++++ .../grafana_contact_point/tasks/opsgenie.yml | 59 +++++++++++++ .../grafana_contact_point/tasks/pagerduty.yml | 56 ++++++++++++ .../grafana_contact_point/tasks/pushover.yml | 58 +++++++++++++ .../grafana_contact_point/tasks/sensugo.yml | 56 ++++++++++++ .../grafana_contact_point/tasks/slack.yml | 86 +++++++++++++++++++ .../grafana_contact_point/tasks/teams.yml | 56 ++++++++++++ .../grafana_contact_point/tasks/telegram.yml | 58 +++++++++++++ .../grafana_contact_point/tasks/threema.yml | 60 +++++++++++++ .../grafana_contact_point/tasks/victorops.yml | 56 ++++++++++++ .../grafana_contact_point/tasks/webhook.yml | 56 ++++++++++++ 20 files changed, 1032 insertions(+) create mode 100755 tests/integration/targets/grafana_contact_point/runme.sh create mode 100644 tests/integration/targets/grafana_contact_point/site.yml create mode 100644 tests/integration/targets/grafana_contact_point/tasks/alertmanager.yml create mode 100644 tests/integration/targets/grafana_contact_point/tasks/dingding.yml create mode 100644 tests/integration/targets/grafana_contact_point/tasks/discord.yml create mode 100644 tests/integration/targets/grafana_contact_point/tasks/email.yml create mode 100644 tests/integration/targets/grafana_contact_point/tasks/googlechat.yml create mode 100644 tests/integration/targets/grafana_contact_point/tasks/kafka.yml create mode 100644 tests/integration/targets/grafana_contact_point/tasks/line.yml create mode 100644 tests/integration/targets/grafana_contact_point/tasks/main.yml create mode 100644 tests/integration/targets/grafana_contact_point/tasks/opsgenie.yml create mode 100644 tests/integration/targets/grafana_contact_point/tasks/pagerduty.yml create mode 100644 tests/integration/targets/grafana_contact_point/tasks/pushover.yml create mode 100644 tests/integration/targets/grafana_contact_point/tasks/sensugo.yml create mode 100644 tests/integration/targets/grafana_contact_point/tasks/slack.yml create mode 100644 tests/integration/targets/grafana_contact_point/tasks/teams.yml create mode 100644 tests/integration/targets/grafana_contact_point/tasks/telegram.yml create mode 100644 tests/integration/targets/grafana_contact_point/tasks/threema.yml create mode 100644 tests/integration/targets/grafana_contact_point/tasks/victorops.yml create mode 100644 tests/integration/targets/grafana_contact_point/tasks/webhook.yml diff --git a/tests/integration/targets/grafana_contact_point/runme.sh b/tests/integration/targets/grafana_contact_point/runme.sh new file mode 100755 index 00000000..867afb0d --- /dev/null +++ b/tests/integration/targets/grafana_contact_point/runme.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +set -eux + +ansible-playbook site.yml diff --git a/tests/integration/targets/grafana_contact_point/site.yml b/tests/integration/targets/grafana_contact_point/site.yml new file mode 100644 index 00000000..aeb027f0 --- /dev/null +++ b/tests/integration/targets/grafana_contact_point/site.yml @@ -0,0 +1,11 @@ +--- +- name: Run tests for grafana_contact_point + hosts: localhost + module_defaults: + community.grafana.grafana_contact_point: + grafana_url: http://grafana:3000 + grafana_user: admin + grafana_password: admin + tasks: + - ansible.builtin.include_role: + name: ../../grafana_contact_point diff --git a/tests/integration/targets/grafana_contact_point/tasks/alertmanager.yml b/tests/integration/targets/grafana_contact_point/tasks/alertmanager.yml new file mode 100644 index 00000000..6ca4b934 --- /dev/null +++ b/tests/integration/targets/grafana_contact_point/tasks/alertmanager.yml @@ -0,0 +1,56 @@ +--- +- name: Create alertmanager contact point + register: result + community.grafana.grafana_contact_point: + uid: alertmanager + name: alertmanager + type: alertmanager + alertmanager_url: https://example.org + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == True + +- name: Create alertmanager contact point (idempotency) + register: result + community.grafana.grafana_contact_point: + uid: alertmanager + name: alertmanager + type: alertmanager + alertmanager_url: https://example.org + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == False + +- name: Delete alertmanager contact point + register: result + community.grafana.grafana_contact_point: + uid: alertmanager + state: absent + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == True + +- name: Delete alertmanager contact point (idempotency) + register: result + community.grafana.grafana_contact_point: + uid: alertmanager + state: absent + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == False diff --git a/tests/integration/targets/grafana_contact_point/tasks/dingding.yml b/tests/integration/targets/grafana_contact_point/tasks/dingding.yml new file mode 100644 index 00000000..5b14a909 --- /dev/null +++ b/tests/integration/targets/grafana_contact_point/tasks/dingding.yml @@ -0,0 +1,56 @@ +--- +- name: Create dingding contact point + register: result + community.grafana.grafana_contact_point: + uid: dingding + name: dingding + type: dingding + dingding_url: https://example.org + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == True + +- name: Create dingding contact point (idempotency) + register: result + community.grafana.grafana_contact_point: + uid: dingding + name: dingding + type: dingding + dingding_url: https://example.org + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == False + +- name: Delete dingding contact point + register: result + community.grafana.grafana_contact_point: + uid: dingding + state: absent + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == True + +- name: Delete dingding contact point (idempotency) + register: result + community.grafana.grafana_contact_point: + uid: dingding + state: absent + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == False diff --git a/tests/integration/targets/grafana_contact_point/tasks/discord.yml b/tests/integration/targets/grafana_contact_point/tasks/discord.yml new file mode 100644 index 00000000..89e62daa --- /dev/null +++ b/tests/integration/targets/grafana_contact_point/tasks/discord.yml @@ -0,0 +1,56 @@ +--- +- name: Create discord contact point + register: result + community.grafana.grafana_contact_point: + uid: discord + name: discord + type: discord + discord_url: https://example.org + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == True + +- name: Create discord contact point (idempotency) + register: result + community.grafana.grafana_contact_point: + uid: discord + name: discord + type: discord + discord_url: https://example.org + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == False + +- name: Delete discord contact point + register: result + community.grafana.grafana_contact_point: + uid: discord + state: absent + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == True + +- name: Delete discord contact point (idempotency) + register: result + community.grafana.grafana_contact_point: + uid: discord + state: absent + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == False diff --git a/tests/integration/targets/grafana_contact_point/tasks/email.yml b/tests/integration/targets/grafana_contact_point/tasks/email.yml new file mode 100644 index 00000000..91aeba8b --- /dev/null +++ b/tests/integration/targets/grafana_contact_point/tasks/email.yml @@ -0,0 +1,61 @@ +--- +- name: Create email contact point + register: result + community.grafana.grafana_contact_point: + uid: email + name: email + type: email + email_addresses: + - foo@example.org + - bar@example.org + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == True + +- name: Create email contact point (idempotency) + register: result + community.grafana.grafana_contact_point: + uid: email + name: email + type: email + email_addresses: + - foo@example.org + - bar@example.org + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == False + +- name: Delete discord contact point + register: result + community.grafana.grafana_contact_point: + uid: email + state: absent + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == True + - result.state == 'absent' + +- name: Delete discord contact point (idempotency) + register: result + community.grafana.grafana_contact_point: + uid: email + state: absent + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == False diff --git a/tests/integration/targets/grafana_contact_point/tasks/googlechat.yml b/tests/integration/targets/grafana_contact_point/tasks/googlechat.yml new file mode 100644 index 00000000..1243ca04 --- /dev/null +++ b/tests/integration/targets/grafana_contact_point/tasks/googlechat.yml @@ -0,0 +1,56 @@ +--- +- name: Create googlechat contact point + register: result + community.grafana.grafana_contact_point: + uid: googlechat + name: googlechat + type: googlechat + googlechat_url: https://example.org + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == True + +- name: Create googlechat contact point (idempotency) + register: result + community.grafana.grafana_contact_point: + uid: googlechat + name: googlechat + type: googlechat + googlechat_url: https://example.org + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == False + +- name: Delete googlechat contact point + register: result + community.grafana.grafana_contact_point: + uid: googlechat + state: absent + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == True + +- name: Delete googlechat contact point (idempotency) + register: result + community.grafana.grafana_contact_point: + uid: googlechat + state: absent + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == False diff --git a/tests/integration/targets/grafana_contact_point/tasks/kafka.yml b/tests/integration/targets/grafana_contact_point/tasks/kafka.yml new file mode 100644 index 00000000..bef1106c --- /dev/null +++ b/tests/integration/targets/grafana_contact_point/tasks/kafka.yml @@ -0,0 +1,58 @@ +--- +- name: Create kafka contact point + register: result + community.grafana.grafana_contact_point: + uid: kafka + name: kafka + type: kafka + kafka_url: https://example.org + kafka_topic: test + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == True + +- name: Create kafka contact point (idempotentcy) + register: result + community.grafana.grafana_contact_point: + uid: kafka + name: kafka + type: kafka + kafka_url: https://example.org + kafka_topic: test + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == False + +- name: Delete kafka contact point + register: result + community.grafana.grafana_contact_point: + uid: kafka + state: absent + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == True + +- name: Delete kafka contact point (idempotency) + register: result + community.grafana.grafana_contact_point: + uid: kafka + state: absent + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == False diff --git a/tests/integration/targets/grafana_contact_point/tasks/line.yml b/tests/integration/targets/grafana_contact_point/tasks/line.yml new file mode 100644 index 00000000..f1f88de9 --- /dev/null +++ b/tests/integration/targets/grafana_contact_point/tasks/line.yml @@ -0,0 +1,56 @@ +--- +- name: Create line contact point + register: result + community.grafana.grafana_contact_point: + uid: line + name: line + type: line + line_token: xxx + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == True + +- name: Create line contact point (idempotency) + register: result + community.grafana.grafana_contact_point: + uid: line + name: line + type: line + line_token: xxx + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == False + +- name: Delete line contact point + register: result + community.grafana.grafana_contact_point: + uid: line + state: absent + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == True + +- name: Delete line contact point (idempotency) + register: result + community.grafana.grafana_contact_point: + uid: line + state: absent + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == False diff --git a/tests/integration/targets/grafana_contact_point/tasks/main.yml b/tests/integration/targets/grafana_contact_point/tasks/main.yml new file mode 100644 index 00000000..bb94793c --- /dev/null +++ b/tests/integration/targets/grafana_contact_point/tasks/main.yml @@ -0,0 +1,16 @@ +--- +- ansible.builtin.include_tasks: alertmanager.yml +- ansible.builtin.include_tasks: dingding.yml +- ansible.builtin.include_tasks: discord.yml +- ansible.builtin.include_tasks: email.yml +- ansible.builtin.include_tasks: googlechat.yml +- ansible.builtin.include_tasks: kafka.yml +- ansible.builtin.include_tasks: opsgenie.yml +- ansible.builtin.include_tasks: pagerduty.yml +- ansible.builtin.include_tasks: pushover.yml +- ansible.builtin.include_tasks: sensugo.yml +- ansible.builtin.include_tasks: slack.yml +- ansible.builtin.include_tasks: teams.yml +- ansible.builtin.include_tasks: telegram.yml +- ansible.builtin.include_tasks: victorops.yml +- ansible.builtin.include_tasks: webhook.yml diff --git a/tests/integration/targets/grafana_contact_point/tasks/opsgenie.yml b/tests/integration/targets/grafana_contact_point/tasks/opsgenie.yml new file mode 100644 index 00000000..6e810e2a --- /dev/null +++ b/tests/integration/targets/grafana_contact_point/tasks/opsgenie.yml @@ -0,0 +1,59 @@ +--- +- name: Create opsgenie contact point + register: result + community.grafana.grafana_contact_point: + uid: opsgenie + name: opsgenie + type: opsgenie + opsgenie_url: https://example.org + opsgenie_api_key: xxx + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == True + +- name: Create opsgenie contact point (idempotency) + register: result + community.grafana.grafana_contact_point: + uid: opsgenie + name: opsgenie + type: opsgenie + opsgenie_url: https://example.org + opsgenie_api_key: xxx + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == False + +- name: Delete opsgenie contact point + register: result + community.grafana.grafana_contact_point: + uid: opsgenie + state: absent + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == True + - result.state == 'absent' + +- name: Delete opsgenie contact point (idempotency) + register: result + community.grafana.grafana_contact_point: + uid: opsgenie + state: absent + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == False diff --git a/tests/integration/targets/grafana_contact_point/tasks/pagerduty.yml b/tests/integration/targets/grafana_contact_point/tasks/pagerduty.yml new file mode 100644 index 00000000..3c5e65a9 --- /dev/null +++ b/tests/integration/targets/grafana_contact_point/tasks/pagerduty.yml @@ -0,0 +1,56 @@ +--- +- name: Create pagerduty contact point + register: result + community.grafana.grafana_contact_point: + uid: pagerduty + name: pagerduty + type: pagerduty + pagerduty_integration_key: xxx + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == True + +- name: Create pagerduty contact point (idempotency) + register: result + community.grafana.grafana_contact_point: + uid: pagerduty + name: pagerduty + type: pagerduty + pagerduty_integration_key: xxx + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == False + +- name: Delete pagerduty contact point + register: result + community.grafana.grafana_contact_point: + uid: pagerduty + state: absent + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == True + +- name: Delete pagerduty contact point (idempotency) + register: result + community.grafana.grafana_contact_point: + uid: pagerduty + state: absent + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == False diff --git a/tests/integration/targets/grafana_contact_point/tasks/pushover.yml b/tests/integration/targets/grafana_contact_point/tasks/pushover.yml new file mode 100644 index 00000000..27ed69be --- /dev/null +++ b/tests/integration/targets/grafana_contact_point/tasks/pushover.yml @@ -0,0 +1,58 @@ +--- +- name: Create pushover contact point + register: result + community.grafana.grafana_contact_point: + uid: pushover + name: pushover + type: pushover + pushover_api_token: xxx + pushover_user_key: yyy + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == True + +- name: Create pushover contact point (idempotency) + register: result + community.grafana.grafana_contact_point: + uid: pushover + name: pushover + type: pushover + pushover_api_token: xxx + pushover_user_key: yyy + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == False + +- name: Delete pushover contact point + register: result + community.grafana.grafana_contact_point: + uid: pushover + state: absent + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == True + +- name: Delete pushover contact point (idempotency) + register: result + community.grafana.grafana_contact_point: + uid: pushover + state: absent + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == False diff --git a/tests/integration/targets/grafana_contact_point/tasks/sensugo.yml b/tests/integration/targets/grafana_contact_point/tasks/sensugo.yml new file mode 100644 index 00000000..902034e4 --- /dev/null +++ b/tests/integration/targets/grafana_contact_point/tasks/sensugo.yml @@ -0,0 +1,56 @@ +--- +- name: Create sensu contact point + register: result + community.grafana.grafana_contact_point: + uid: sensu + name: sensu + type: sensu + sensu_url: https://example.org + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == True + +- name: Create sensu contact point + register: result + community.grafana.grafana_contact_point: + uid: sensu + name: sensu + type: sensu + sensu_url: https://example.org + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == False + +- name: Delete sensu contact point + register: result + community.grafana.grafana_contact_point: + uid: sensu + state: absent + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == True + +- name: Delete sensu contact point (idempotency) + register: result + community.grafana.grafana_contact_point: + uid: sensu + state: absent + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == False diff --git a/tests/integration/targets/grafana_contact_point/tasks/slack.yml b/tests/integration/targets/grafana_contact_point/tasks/slack.yml new file mode 100644 index 00000000..19c50d21 --- /dev/null +++ b/tests/integration/targets/grafana_contact_point/tasks/slack.yml @@ -0,0 +1,86 @@ +--- +- name: Create slack contact point + register: result + community.grafana.grafana_contact_point: + uid: slack + name: slack + type: slack + slack_url: https://hooks.slack.com/services/xxx/yyy/zzz + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == True + +- name: Create slack contact point + register: result + community.grafana.grafana_contact_point: + uid: slack + name: slack + type: slack + slack_url: https://hooks.slack.com/services/xxx/yyy/zzz + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == False + +- name: Check slack contact point idempotency + register: result + community.grafana.grafana_contact_point: + uid: slack + name: slack + type: slack + slack_url: https://hooks.slack.com/services/xxx/yyy/zzz + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == False + +- name: Update slack contact point + register: result + community.grafana.grafana_contact_point: + uid: slack + name: slack + type: slack + slack_url: https://hooks.slack.com/services/xxx/yyy/fff + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == True + +- name: Delete slack contact point + register: result + community.grafana.grafana_contact_point: + state: absent + uid: slack + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == True + +- name: Delete slack contact point (idempotency) + register: result + community.grafana.grafana_contact_point: + state: absent + uid: slack + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == False diff --git a/tests/integration/targets/grafana_contact_point/tasks/teams.yml b/tests/integration/targets/grafana_contact_point/tasks/teams.yml new file mode 100644 index 00000000..019b407e --- /dev/null +++ b/tests/integration/targets/grafana_contact_point/tasks/teams.yml @@ -0,0 +1,56 @@ +--- +- name: Create teams contact point + register: result + community.grafana.grafana_contact_point: + uid: teams + name: teams + type: teams + teams_url: https://example.org + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == True + +- name: Create teams contact point (idempotency) + register: result + community.grafana.grafana_contact_point: + uid: teams + name: teams + type: teams + teams_url: https://example.org + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == False + +- name: Delete teams contact point + register: result + community.grafana.grafana_contact_point: + uid: teams + state: absent + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == True + +- name: Delete teams contact point (idempotency) + register: result + community.grafana.grafana_contact_point: + uid: teams + state: absent + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == False diff --git a/tests/integration/targets/grafana_contact_point/tasks/telegram.yml b/tests/integration/targets/grafana_contact_point/tasks/telegram.yml new file mode 100644 index 00000000..fbe207e7 --- /dev/null +++ b/tests/integration/targets/grafana_contact_point/tasks/telegram.yml @@ -0,0 +1,58 @@ +--- +- name: Create telegram contact point + register: result + community.grafana.grafana_contact_point: + uid: telegram + name: telegram + type: telegram + telegram_bot_token: xxx + telegram_chat_id: yyy + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == True + +- name: Create telegram contact point + register: result + community.grafana.grafana_contact_point: + uid: telegram + name: telegram + type: telegram + telegram_bot_token: xxx + telegram_chat_id: yyy + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == False + +- name: Delete telegram contact point + register: result + community.grafana.grafana_contact_point: + uid: telegram + state: absent + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == True + +- name: Delete telegram contact point (idempotency) + register: result + community.grafana.grafana_contact_point: + uid: telegram + state: absent + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == False diff --git a/tests/integration/targets/grafana_contact_point/tasks/threema.yml b/tests/integration/targets/grafana_contact_point/tasks/threema.yml new file mode 100644 index 00000000..6e17aa51 --- /dev/null +++ b/tests/integration/targets/grafana_contact_point/tasks/threema.yml @@ -0,0 +1,60 @@ +--- +- name: Create threema contact point + register: result + community.grafana.grafana_contact_point: + uid: threema + name: threema + type: threema + threema_gateway_id: "*xxxxxxx" + threema_recipient_id: yyyyyyyy + threema_api_secret: zzz + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == True + +- name: Create threema contact point (idempotency) + register: result + community.grafana.grafana_contact_point: + uid: threema + name: threema + type: threema + threema_gateway_id: xxx + threema_recepient_id: yyy + threema_api_secret: zzz + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == True + +- name: Delete threema contact point + register: result + community.grafana.grafana_contact_point: + uid: threema + state: absent + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == True + +- name: Delete threema contact point (idempotency) + register: result + community.grafana.grafana_contact_point: + uid: threema + state: absent + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == False diff --git a/tests/integration/targets/grafana_contact_point/tasks/victorops.yml b/tests/integration/targets/grafana_contact_point/tasks/victorops.yml new file mode 100644 index 00000000..8ab40387 --- /dev/null +++ b/tests/integration/targets/grafana_contact_point/tasks/victorops.yml @@ -0,0 +1,56 @@ +--- +- name: Create victorops contact point + register: result + community.grafana.grafana_contact_point: + uid: victorops + name: victorops + type: victorops + victorops_url: https://example.org + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == True + +- name: Create victorops contact point (idempotency) + register: result + community.grafana.grafana_contact_point: + uid: victorops + name: victorops + type: victorops + victorops_url: https://example.org + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == False + +- name: Delete victorops contact point + register: result + community.grafana.grafana_contact_point: + uid: victorops + state: absent + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == True + +- name: Delete victorops contact point (idempotency) + register: result + community.grafana.grafana_contact_point: + uid: victorops + state: absent + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == False diff --git a/tests/integration/targets/grafana_contact_point/tasks/webhook.yml b/tests/integration/targets/grafana_contact_point/tasks/webhook.yml new file mode 100644 index 00000000..8ff8ba4c --- /dev/null +++ b/tests/integration/targets/grafana_contact_point/tasks/webhook.yml @@ -0,0 +1,56 @@ +--- +- name: Create webhook contact point + register: result + community.grafana.grafana_contact_point: + uid: webhook + name: webhook + type: webhook + webhook_url: https://example.org + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == True + +- name: Create webhook contact point (idempotency) + register: result + community.grafana.grafana_contact_point: + uid: webhook + name: webhook + type: webhook + webhook_url: https://example.org + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == False + +- name: Delete webhook contact point + register: result + community.grafana.grafana_contact_point: + uid: webhook + state: absent + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == True + +- name: Delete webhook contact point (idempotency) + register: result + community.grafana.grafana_contact_point: + uid: webhook + state: absent + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed == False From e11c33b09a9c442e9c1ed829acc057494249c2d4 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Mon, 4 Mar 2024 22:24:56 +0100 Subject: [PATCH 24/52] fix: provisioning handling --- plugins/modules/grafana_contact_point.py | 30 ++++++++++++++---------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/plugins/modules/grafana_contact_point.py b/plugins/modules/grafana_contact_point.py index 583a7978..4b41fd69 100644 --- a/plugins/modules/grafana_contact_point.py +++ b/plugins/modules/grafana_contact_point.py @@ -83,7 +83,6 @@ def grafana_contact_point_payload(data): "uid": data["uid"], "name": data["name"], "type": data["type"], - "isDefault": data["is_default"], "disableResolveMessage": data["disable_resolve_message"], "settings": {}, } @@ -300,9 +299,16 @@ def __init__(self, module): self.grafana_switch_organisation(module.params, self.org_id) # }}} - def grafana_api_provisioning(self, data): - if not data["provisioning"]: + def grafana_api_provisioning(self, data, before): + if not before or ( + not before.get("provenance") and not data.get("provisioning") + ): self.headers["X-Disable-Provenance"] = "true" + elif before.get("provenance") and not data.get("provisioning"): + self._module.fail_json( + msg="Unable to update contact point '%s': provisioning cannot be disabled if it's already enabled" + % data["uid"] + ) def grafana_organization_by_name(self, data, org_name): r, info = fetch_url( @@ -329,7 +335,7 @@ def grafana_switch_organisation(self, data, org_id): ) if info["status"] != 200: raise GrafanaAPIException( - "Unable to switch to organization %s : %s" % (org_id, info) + "Unable to switch to organization '%s': %s" % (org_id, info) ) def grafana_check_contact_point_match(self, data): @@ -348,17 +354,17 @@ def grafana_check_contact_point_match(self, data): return self.grafana_handle_contact_point(data, before) else: raise GrafanaAPIException( - "Unable to get contact point %s : %s" % (data["uid"], info) + "Unable to get contact point '%s': %s" % (data["uid"], info) ) def grafana_handle_contact_point(self, data, before): payload = grafana_contact_point_payload(data) if data["state"] == "present": + self.grafana_api_provisioning(data, before) if before: return self.grafana_update_contact_point(data, payload, before) else: - self.grafana_api_provisioning(data) return self.grafana_create_contact_point(data, payload) else: if before: @@ -395,19 +401,20 @@ def grafana_update_contact_point(self, data, payload, before): ) if info["status"] == 202: - contact_point = json.loads(to_text(r.read())) + if before.get("provenance") and data.get("provisioning"): + del before["provenance"] - if before == contact_point: + if before == payload: return {"changed": False} else: return { "changed": True, "diff": {"before": before, "after": payload}, - "contact_point": contact_point, + "contact_point": payload, } else: raise GrafanaAPIException( - "Unable to update contact point %s : %s" % (data["uid"], info) + "Unable to update contact point '%s': %s" % (data["uid"], info) ) def grafana_delete_contact_point(self, data): @@ -424,7 +431,7 @@ def grafana_delete_contact_point(self, data): return {"changed": False} else: raise GrafanaAPIException( - "Unable to delete contact point %s : %s" % (data["uid"], info) + "Unable to delete contact point '%s': %s" % (data["uid"], info) ) @@ -434,7 +441,6 @@ def main(): # general arguments disable_resolve_message=dict(type="bool", default=False), include_image=dict(type="bool", default=False), - is_default=dict(type="bool", default=False), name=dict(type="str"), org_id=dict(type="int", default=1), org_name=dict(type="str"), From 08c87552c0f63e06de57eb4ce53eeb6dcf3d19f5 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Wed, 20 Mar 2024 13:09:24 +0100 Subject: [PATCH 25/52] chore: rename before to contact_point --- plugins/modules/grafana_contact_point.py | 32 ++++++++++++------------ 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/plugins/modules/grafana_contact_point.py b/plugins/modules/grafana_contact_point.py index 4b41fd69..7f71c7df 100644 --- a/plugins/modules/grafana_contact_point.py +++ b/plugins/modules/grafana_contact_point.py @@ -299,12 +299,12 @@ def __init__(self, module): self.grafana_switch_organisation(module.params, self.org_id) # }}} - def grafana_api_provisioning(self, data, before): - if not before or ( - not before.get("provenance") and not data.get("provisioning") + def grafana_api_provisioning(self, data, contact_point): + if not contact_point or ( + not contact_point.get("provenance") and not data.get("provisioning") ): self.headers["X-Disable-Provenance"] = "true" - elif before.get("provenance") and not data.get("provisioning"): + elif contact_point.get("provenance") and not data.get("provisioning"): self._module.fail_json( msg="Unable to update contact point '%s': provisioning cannot be disabled if it's already enabled" % data["uid"] @@ -348,26 +348,26 @@ def grafana_check_contact_point_match(self, data): if info["status"] == 200: contact_points = json.loads(to_text(r.read())) - before = next( + contact_point = next( (cp for cp in contact_points if cp["uid"] == data["uid"]), None ) - return self.grafana_handle_contact_point(data, before) + return self.grafana_handle_contact_point(data, contact_point) else: raise GrafanaAPIException( "Unable to get contact point '%s': %s" % (data["uid"], info) ) - def grafana_handle_contact_point(self, data, before): + def grafana_handle_contact_point(self, data, contact_point): payload = grafana_contact_point_payload(data) if data["state"] == "present": - self.grafana_api_provisioning(data, before) - if before: - return self.grafana_update_contact_point(data, payload, before) + self.grafana_api_provisioning(data, contact_point) + if contact_point: + return self.grafana_update_contact_point(data, payload, contact_point) else: return self.grafana_create_contact_point(data, payload) else: - if before: + if contact_point: return self.grafana_delete_contact_point(data) else: return {"changed": False} @@ -391,7 +391,7 @@ def grafana_create_contact_point(self, data, payload): else: raise GrafanaAPIException("Unable to create contact point: %s" % info) - def grafana_update_contact_point(self, data, payload, before): + def grafana_update_contact_point(self, data, payload, contact_point): r, info = fetch_url( self._module, "%s/api/v1/provisioning/contact-points/%s" % (data["url"], data["uid"]), @@ -401,15 +401,15 @@ def grafana_update_contact_point(self, data, payload, before): ) if info["status"] == 202: - if before.get("provenance") and data.get("provisioning"): - del before["provenance"] + if contact_point.get("provenance") and data.get("provisioning"): + del contact_point["provenance"] - if before == payload: + if contact_point == payload: return {"changed": False} else: return { "changed": True, - "diff": {"before": before, "after": payload}, + "diff": {"before": contact_point, "after": payload}, "contact_point": payload, } else: From b6536b88e1aa997f2b6db1af752e8c44783d2d85 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Wed, 20 Mar 2024 13:34:46 +0100 Subject: [PATCH 26/52] chore: switch entry func and define contact point as self var --- plugins/modules/grafana_contact_point.py | 33 ++++++++++++------------ 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/plugins/modules/grafana_contact_point.py b/plugins/modules/grafana_contact_point.py index 7f71c7df..bcf3dc81 100644 --- a/plugins/modules/grafana_contact_point.py +++ b/plugins/modules/grafana_contact_point.py @@ -298,13 +298,14 @@ def __init__(self, module): ) self.grafana_switch_organisation(module.params, self.org_id) # }}} + self.contact_point = self.grafana_check_contact_point_match(module.params) - def grafana_api_provisioning(self, data, contact_point): - if not contact_point or ( - not contact_point.get("provenance") and not data.get("provisioning") + def grafana_api_provisioning(self, data): + if not self.contact_point or ( + not self.contact_point.get("provenance") and not data.get("provisioning") ): self.headers["X-Disable-Provenance"] = "true" - elif contact_point.get("provenance") and not data.get("provisioning"): + elif self.contact_point.get("provenance") and not data.get("provisioning"): self._module.fail_json( msg="Unable to update contact point '%s': provisioning cannot be disabled if it's already enabled" % data["uid"] @@ -351,23 +352,23 @@ def grafana_check_contact_point_match(self, data): contact_point = next( (cp for cp in contact_points if cp["uid"] == data["uid"]), None ) - return self.grafana_handle_contact_point(data, contact_point) + return contact_point else: raise GrafanaAPIException( "Unable to get contact point '%s': %s" % (data["uid"], info) ) - def grafana_handle_contact_point(self, data, contact_point): + def grafana_handle_contact_point(self, data): payload = grafana_contact_point_payload(data) if data["state"] == "present": - self.grafana_api_provisioning(data, contact_point) - if contact_point: - return self.grafana_update_contact_point(data, payload, contact_point) + self.grafana_api_provisioning(data) + if self.contact_point: + return self.grafana_update_contact_point(data, payload) else: return self.grafana_create_contact_point(data, payload) else: - if contact_point: + if self.contact_point: return self.grafana_delete_contact_point(data) else: return {"changed": False} @@ -391,7 +392,7 @@ def grafana_create_contact_point(self, data, payload): else: raise GrafanaAPIException("Unable to create contact point: %s" % info) - def grafana_update_contact_point(self, data, payload, contact_point): + def grafana_update_contact_point(self, data, payload): r, info = fetch_url( self._module, "%s/api/v1/provisioning/contact-points/%s" % (data["url"], data["uid"]), @@ -401,15 +402,15 @@ def grafana_update_contact_point(self, data, payload, contact_point): ) if info["status"] == 202: - if contact_point.get("provenance") and data.get("provisioning"): - del contact_point["provenance"] + if self.contact_point.get("provenance") and data.get("provisioning"): + del self.contact_point["provenance"] - if contact_point == payload: + if self.contact_point == payload: return {"changed": False} else: return { "changed": True, - "diff": {"before": contact_point, "after": payload}, + "diff": {"before": self.contact_point, "after": payload}, "contact_point": payload, } else: @@ -654,7 +655,7 @@ def main(): module.params["url"] = clean_url(module.params["url"]) grafana_iface = GrafanaContactPointInterface(module) - result = grafana_iface.grafana_check_contact_point_match(module.params) + result = grafana_iface.grafana_handle_contact_point(module.params) module.exit_json(failed=False, **result) From 162e5984daf8fc534fa8c14518c88a3d646dfcc9 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Wed, 20 Mar 2024 15:38:35 +0100 Subject: [PATCH 27/52] fix: change update diff check --- plugins/modules/grafana_contact_point.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/plugins/modules/grafana_contact_point.py b/plugins/modules/grafana_contact_point.py index bcf3dc81..0816cf6d 100644 --- a/plugins/modules/grafana_contact_point.py +++ b/plugins/modules/grafana_contact_point.py @@ -402,16 +402,14 @@ def grafana_update_contact_point(self, data, payload): ) if info["status"] == 202: - if self.contact_point.get("provenance") and data.get("provisioning"): - del self.contact_point["provenance"] - - if self.contact_point == payload: + contact_point = self.grafana_check_contact_point_match(data) + if self.contact_point == contact_point: return {"changed": False} else: return { "changed": True, - "diff": {"before": self.contact_point, "after": payload}, - "contact_point": payload, + "diff": {"before": self.contact_point, "after": contact_point}, + "contact_point": contact_point, } else: raise GrafanaAPIException( From 06490f2fbeb95578261cc6aec0c14c441d94d8a0 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Wed, 20 Mar 2024 16:59:09 +0100 Subject: [PATCH 28/52] fix: payload data handling and update diff check --- plugins/modules/grafana_contact_point.py | 45 +++++++++++++----------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/plugins/modules/grafana_contact_point.py b/plugins/modules/grafana_contact_point.py index 0816cf6d..990a4338 100644 --- a/plugins/modules/grafana_contact_point.py +++ b/plugins/modules/grafana_contact_point.py @@ -254,25 +254,26 @@ def grafana_contact_point_payload(data): type_settings = type_settings_map.get(data["type"]) if type_settings: for setting_key, data_key in type_settings.items(): - if data_key == "pushover_priority": - payload["settings"][setting_key] = { - "emergency": "2", - "high": "1", - "normal": "0", - "low": "-1", - "lowest": "-2", - }[data[data_key]] - elif data_key == "dingding_message_type": - payload["settings"][setting_key] = { - "link": "link", - "action_card": "actionCard", - }[data[data_key]] - elif data_key in ["email_addresses", "pushover_devices"]: - payload["settings"][setting_key] = ";".join(data[data_key]) - elif data_key in ["slack_mention_users", "slack_mention_groups"]: - payload["settings"][setting_key] = ",".join(data[data_key]) - elif data.get(data_key): - payload["settings"][setting_key] = data[data_key] + if data[data_key] is not None: + if data_key == "pushover_priority": + payload["settings"][setting_key] = { + "emergency": "2", + "high": "1", + "normal": "0", + "low": "-1", + "lowest": "-2", + }[data[data_key]] + elif data_key == "dingding_message_type": + payload["settings"][setting_key] = { + "link": "link", + "action_card": "actionCard", + }[data[data_key]] + elif data_key in ["email_addresses", "pushover_devices"]: + payload["settings"][setting_key] = ";".join(data[data_key]) + elif data_key in ["slack_mention_users", "slack_mention_groups"]: + payload["settings"][setting_key] = ",".join(data[data_key]) + elif data.get(data_key): + payload["settings"][setting_key] = data[data_key] return payload @@ -403,6 +404,10 @@ def grafana_update_contact_point(self, data, payload): if info["status"] == 202: contact_point = self.grafana_check_contact_point_match(data) + + if contact_point.get("provenance") and data.get("provisioning"): + del contact_point["provenance"] + if self.contact_point == contact_point: return {"changed": False} else: @@ -452,7 +457,7 @@ def main(): "discord", "email", "googlechat", - "kaska", + "kafka", "line", "opsgenie", "pagerduty", From b9c7655bdb606e7242ce70801c86e19f7a01add2 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Wed, 20 Mar 2024 17:00:20 +0100 Subject: [PATCH 29/52] test: all basic tests successful --- .../grafana_contact_point/tasks/kafka.yml | 4 +-- .../grafana_contact_point/tasks/sensugo.yml | 30 ++++++++++--------- .../grafana_contact_point/tasks/slack.yml | 23 +++++--------- .../grafana_contact_point/tasks/telegram.yml | 4 +-- 4 files changed, 27 insertions(+), 34 deletions(-) diff --git a/tests/integration/targets/grafana_contact_point/tasks/kafka.yml b/tests/integration/targets/grafana_contact_point/tasks/kafka.yml index bef1106c..2503bbdd 100644 --- a/tests/integration/targets/grafana_contact_point/tasks/kafka.yml +++ b/tests/integration/targets/grafana_contact_point/tasks/kafka.yml @@ -5,7 +5,7 @@ uid: kafka name: kafka type: kafka - kafka_url: https://example.org + kafka_rest_proxy_url: https://example.org kafka_topic: test - ansible.builtin.debug: @@ -21,7 +21,7 @@ uid: kafka name: kafka type: kafka - kafka_url: https://example.org + kafka_rest_proxy_url: https://example.org kafka_topic: test - ansible.builtin.debug: diff --git a/tests/integration/targets/grafana_contact_point/tasks/sensugo.yml b/tests/integration/targets/grafana_contact_point/tasks/sensugo.yml index 902034e4..6cdf0478 100644 --- a/tests/integration/targets/grafana_contact_point/tasks/sensugo.yml +++ b/tests/integration/targets/grafana_contact_point/tasks/sensugo.yml @@ -1,11 +1,12 @@ --- -- name: Create sensu contact point +- name: Create sensugo contact point register: result community.grafana.grafana_contact_point: - uid: sensu - name: sensu - type: sensu - sensu_url: https://example.org + uid: sensugo + name: sensugo + type: sensugo + sensugo_url: https://example.org + sensugo_api_key: testapikey - ansible.builtin.debug: var: result @@ -14,13 +15,14 @@ that: - result.changed == True -- name: Create sensu contact point +- name: Create sensugo contact point register: result community.grafana.grafana_contact_point: - uid: sensu - name: sensu - type: sensu - sensu_url: https://example.org + uid: sensugo + name: sensugo + type: sensugo + sensugo_url: https://example.org + sensugo_api_key: testapikey - ansible.builtin.debug: var: result @@ -29,10 +31,10 @@ that: - result.changed == False -- name: Delete sensu contact point +- name: Delete sensugo contact point register: result community.grafana.grafana_contact_point: - uid: sensu + uid: sensugo state: absent - ansible.builtin.debug: @@ -42,10 +44,10 @@ that: - result.changed == True -- name: Delete sensu contact point (idempotency) +- name: Delete sensugo contact point (idempotency) register: result community.grafana.grafana_contact_point: - uid: sensu + uid: sensugo state: absent - ansible.builtin.debug: diff --git a/tests/integration/targets/grafana_contact_point/tasks/slack.yml b/tests/integration/targets/grafana_contact_point/tasks/slack.yml index 19c50d21..110ec963 100644 --- a/tests/integration/targets/grafana_contact_point/tasks/slack.yml +++ b/tests/integration/targets/grafana_contact_point/tasks/slack.yml @@ -6,6 +6,8 @@ name: slack type: slack slack_url: https://hooks.slack.com/services/xxx/yyy/zzz + slack_token: testapitoken + slack_recipient: foo - ansible.builtin.debug: var: result @@ -14,28 +16,15 @@ that: - result.changed == True -- name: Create slack contact point - register: result - community.grafana.grafana_contact_point: - uid: slack - name: slack - type: slack - slack_url: https://hooks.slack.com/services/xxx/yyy/zzz - -- ansible.builtin.debug: - var: result - -- ansible.builtin.assert: - that: - - result.changed == False - -- name: Check slack contact point idempotency +- name: Check slack contact point (idempotency) register: result community.grafana.grafana_contact_point: uid: slack name: slack type: slack slack_url: https://hooks.slack.com/services/xxx/yyy/zzz + slack_token: testapitoken + slack_recipient: foo - ansible.builtin.debug: var: result @@ -51,6 +40,8 @@ name: slack type: slack slack_url: https://hooks.slack.com/services/xxx/yyy/fff + slack_token: testapitoken + slack_recipient: foo - ansible.builtin.debug: var: result diff --git a/tests/integration/targets/grafana_contact_point/tasks/telegram.yml b/tests/integration/targets/grafana_contact_point/tasks/telegram.yml index fbe207e7..2fd38a5a 100644 --- a/tests/integration/targets/grafana_contact_point/tasks/telegram.yml +++ b/tests/integration/targets/grafana_contact_point/tasks/telegram.yml @@ -5,7 +5,7 @@ uid: telegram name: telegram type: telegram - telegram_bot_token: xxx + telegram_token: xxx telegram_chat_id: yyy - ansible.builtin.debug: @@ -21,7 +21,7 @@ uid: telegram name: telegram type: telegram - telegram_bot_token: xxx + telegram_token: xxx telegram_chat_id: yyy - ansible.builtin.debug: From 8bdc6736aee6968b276659276b07699dd87414f8 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Thu, 21 Mar 2024 13:34:39 +0100 Subject: [PATCH 30/52] fix: fail definition for grafana < 9 --- plugins/modules/grafana_contact_point.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/modules/grafana_contact_point.py b/plugins/modules/grafana_contact_point.py index 990a4338..352dcf8b 100644 --- a/plugins/modules/grafana_contact_point.py +++ b/plugins/modules/grafana_contact_point.py @@ -354,6 +354,10 @@ def grafana_check_contact_point_match(self, data): (cp for cp in contact_points if cp["uid"] == data["uid"]), None ) return contact_point + elif info["status"] == 404: + self._module.fail_json( + msg="Unable to get contact point: API endpoint not found" + ) else: raise GrafanaAPIException( "Unable to get contact point '%s': %s" % (data["uid"], info) From baa6415f1de22908b7f134652b2027b8794267af Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Thu, 21 Mar 2024 13:38:20 +0100 Subject: [PATCH 31/52] test: loop cp task files --- .../grafana_contact_point/tasks/main.yml | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/tests/integration/targets/grafana_contact_point/tasks/main.yml b/tests/integration/targets/grafana_contact_point/tasks/main.yml index bb94793c..32a2eb15 100644 --- a/tests/integration/targets/grafana_contact_point/tasks/main.yml +++ b/tests/integration/targets/grafana_contact_point/tasks/main.yml @@ -1,16 +1,20 @@ --- -- ansible.builtin.include_tasks: alertmanager.yml -- ansible.builtin.include_tasks: dingding.yml -- ansible.builtin.include_tasks: discord.yml -- ansible.builtin.include_tasks: email.yml -- ansible.builtin.include_tasks: googlechat.yml -- ansible.builtin.include_tasks: kafka.yml -- ansible.builtin.include_tasks: opsgenie.yml -- ansible.builtin.include_tasks: pagerduty.yml -- ansible.builtin.include_tasks: pushover.yml -- ansible.builtin.include_tasks: sensugo.yml -- ansible.builtin.include_tasks: slack.yml -- ansible.builtin.include_tasks: teams.yml -- ansible.builtin.include_tasks: telegram.yml -- ansible.builtin.include_tasks: victorops.yml -- ansible.builtin.include_tasks: webhook.yml + +- name: Include contact point task files + ansible.builtin.include_tasks: "{{ item }}.yml" + loop: + - alertmanager + - dingding + - discord + - email + - googlechat + - kafka + - opsgenie + - pagerduty + - pushover + - sensugo + - slack + - teams + - telegram + - victorops + - webhook From 6e4cb1064a0838e43228a3a6f99832ed4d99dcbf Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Thu, 21 Mar 2024 15:52:19 +0100 Subject: [PATCH 32/52] test: add api endpoint check --- .../targets/grafana_contact_point/tasks/main.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/integration/targets/grafana_contact_point/tasks/main.yml b/tests/integration/targets/grafana_contact_point/tasks/main.yml index 32a2eb15..060ceab8 100644 --- a/tests/integration/targets/grafana_contact_point/tasks/main.yml +++ b/tests/integration/targets/grafana_contact_point/tasks/main.yml @@ -1,7 +1,15 @@ --- +- name: Check for support of API endpoint + register: result + ignore_errors: true + community.grafana.grafana_contact_point: + uid: apitest + state: absent + - name: Include contact point task files ansible.builtin.include_tasks: "{{ item }}.yml" + when: "result.msg | default('') != 'Unable to get contact point: API endpoint not found'" loop: - alertmanager - dingding From 89c20ff3dca26cb8954f163067b2615433c73e26 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Fri, 5 Apr 2024 11:10:13 +0200 Subject: [PATCH 33/52] chore: todos and rename function --- plugins/modules/grafana_contact_point.py | 8 +++++--- roles/grafana/README.md | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/plugins/modules/grafana_contact_point.py b/plugins/modules/grafana_contact_point.py index 352dcf8b..bd115459 100644 --- a/plugins/modules/grafana_contact_point.py +++ b/plugins/modules/grafana_contact_point.py @@ -301,7 +301,7 @@ def __init__(self, module): # }}} self.contact_point = self.grafana_check_contact_point_match(module.params) - def grafana_api_provisioning(self, data): + def grafana_handle_api_provisioning(self, data): if not self.contact_point or ( not self.contact_point.get("provenance") and not data.get("provisioning") ): @@ -311,6 +311,7 @@ def grafana_api_provisioning(self, data): msg="Unable to update contact point '%s': provisioning cannot be disabled if it's already enabled" % data["uid"] ) + # TODO: else def grafana_organization_by_name(self, data, org_name): r, info = fetch_url( @@ -356,7 +357,7 @@ def grafana_check_contact_point_match(self, data): return contact_point elif info["status"] == 404: self._module.fail_json( - msg="Unable to get contact point: API endpoint not found" + msg="Unable to get contact point: API endpoint not found" # TODO: versionshinweis?! ) else: raise GrafanaAPIException( @@ -367,7 +368,7 @@ def grafana_handle_contact_point(self, data): payload = grafana_contact_point_payload(data) if data["state"] == "present": - self.grafana_api_provisioning(data) + self.grafana_handle_api_provisioning(data) if self.contact_point: return self.grafana_update_contact_point(data, payload) else: @@ -668,3 +669,4 @@ def main(): if __name__ == "__main__": main() +# TODO: check api messages diff --git a/roles/grafana/README.md b/roles/grafana/README.md index ea7eda39..00dfee97 100644 --- a/roles/grafana/README.md +++ b/roles/grafana/README.md @@ -170,7 +170,7 @@ Configure Grafana organizations, dashboards, folders, datasources, teams and use | matchers | yes | | starts_at | yes | | state | no | -| [**grafana_contact_point**](https://docs.ansible.com/ansible/latest/collections/community/grafana/grafana_contact_point_module.html) | +| [**grafana_contact_point**](https://docs.ansible.com/ansible/latest/collections/community/grafana/grafana_contact_point_module.html) | # TODO: parameter | email_addresses | no | | email_single | no | | is_default | no | From 6bc6f1eb29a0d50413824d6e4c7743c2c056cc50 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Thu, 11 Apr 2024 13:28:35 +0200 Subject: [PATCH 34/52] docs: ansible argument docs without desc --- plugins/modules/grafana_contact_point.py | 560 ++++++++++++++++++++++- 1 file changed, 559 insertions(+), 1 deletion(-) diff --git a/plugins/modules/grafana_contact_point.py b/plugins/modules/grafana_contact_point.py index bd115459..dc86a55c 100644 --- a/plugins/modules/grafana_contact_point.py +++ b/plugins/modules/grafana_contact_point.py @@ -28,7 +28,565 @@ short_description: Manage Grafana Contact Points description: - Create/Update/Delete Grafana Contact Points via API. - +options: + disable_resolve_message: + description: + - desc_text + type: bool + default: false + include_image: + description: + - desc_text + type: bool + default: false + name: + description: + - desc_text + type: str + org_id: + description: + - desc_text + type: int + default: 1 + org_name: + description: + - desc_text + type: str + provisioning: + description: + - desc_text + type: bool + default: true + type: + description: + - desc_text + type: str + choices: + - alertmanager + - dingding + - discord + - email + - googlechat + - kafka + - line + - opsgenie + - pagerduty + - pushover + - sensugo + - slack + - teams + - telegram + - threema + - victorops + - webex + - webhook + - wecom + uid: + description: + - desc_text + type: str + alertmanager_password: + description: + - desc_text + type: str + alertmanager_url: + description: + - desc_text + type: str + alertmanager_username: + description: + - desc_text + type: str + dingding_message: + description: + - desc_text + type: str + dingding_message_type: + description: + - desc_text + type: str + dingding_title: + description: + - desc_text + type: str + dingding_url: + description: + - desc_text + type: str + discord_avatar_url: + description: + - desc_text + type: str + discord_message: + description: + - desc_text + type: str + discord_title: + description: + - desc_text + type: str + discord_url: + description: + - desc_text + type: str + discord_use_username: + description: + - desc_text + type: bool + default: false + email_addresses: + description: + - desc_text + type: list + elements: str + email_message: + description: + - desc_text + type: str + email_single: + description: + - desc_text + type: bool + default: false + email_subject: + description: + - desc_text + type: str + googlechat_url: + description: + - desc_text + type: str + googlechat_message: + description: + - desc_text + type: str + googlechat_title: + description: + - desc_text + type: str + kafka_api_version: + description: + - desc_text + type: str + default: v2 + kafka_cluster_id: + description: + - desc_text + type: str + kafka_description: + description: + - desc_text + type: str + kafka_details: + description: + - desc_text + type: str + kafka_password: + description: + - desc_text + type: str + kafka_rest_proxy_url: + description: + - desc_text + type: str + kafka_topic: + description: + - desc_text + type: str + kafka_username: + description: + - desc_text + type: str + line_description: + description: + - desc_text + type: str + line_title: + description: + - desc_text + type: str + line_token: + description: + - desc_text + type: str + opsgenie_api_key: + description: + - desc_text + type: str + opsgenie_auto_close: + description: + - desc_text + type: bool + opsgenie_description: + description: + - desc_text + type: str + opsgenie_message: + description: + - desc_text + type: str + opsgenie_override_priority: + description: + - desc_text + type: bool + opsgenie_responders: + description: + - desc_text + type: list + elements: dict + opsgenie_send_tags_as: + description: + - desc_text + type: str + opsgenie_url: + description: + - desc_text + type: str + pagerduty_class: + description: + - desc_text + type: str + pagerduty_client: + description: + - desc_text + type: str + pagerduty_client_url: + description: + - desc_text + type: str + pagerduty_component: + description: + - desc_text + type: str + pagerduty_details: + description: + - desc_text + type: list + elements: dict + pagerduty_group: + description: + - desc_text + type: str + pagerduty_integration_key: + description: + - desc_text + type: str + pagerduty_severity: + description: + - desc_text + type: str + choices: + - critical + - error + - warning + - info + pagerduty_source: + description: + - desc_text + type: str + pagerduty_summary: + description: + - desc_text + type: str + pushover_api_token: + description: + - desc_text + type: str + pushover_devices: + description: + - desc_text + type: list + elements: str + pushover_expire: + description: + - desc_text + type: int + pushover_message: + description: + - desc_text + type: str + pushover_ok_priority: + description: + - desc_text + type: int + pushover_ok_sound: + description: + - desc_text + type: str + pushover_priority: + description: + - desc_text + type: int + pushover_retry: + description: + - desc_text + type: int + pushover_sound: + description: + - desc_text + type: str + pushover_title: + description: + - desc_text + type: str + pushover_upload_image: + description: + - desc_text + type: bool + default: true + pushover_user_key: + description: + - desc_text + type: str + sensugo_api_key: + description: + - desc_text + type: str + sensugo_url: + description: + - desc_text + type: str + sensugo_check: + description: + - desc_text + type: str + sensugo_entity: + description: + - desc_text + type: str + sensugo_handler: + description: + - desc_text + type: str + sensugo_message: + description: + - desc_text + type: str + sensugo_namespace: + description: + - desc_text + type: str + slack_endpoint_url: + description: + - desc_text + type: str + slack_icon_emoji: + description: + - desc_text + type: str + slack_icon_url: + description: + - desc_text + type: str + slack_mention_channel: + description: + - desc_text + type: str + choices: + - here + - channel + slack_mention_groups: + description: + - desc_text + type: list + slack_mention_users: + description: + - desc_text + type: list + slack_recipient: + description: + - desc_text + type: str + slack_text: + description: + - desc_text + type: str + slack_title: + description: + - desc_text + type: str + slack_token: + description: + - desc_text + type: str + slack_url: + description: + - desc_text + type: str + slack_username: + description: + - desc_text + type: str + teams_message: + description: + - desc_text + type: str + teams_section_title: + description: + - desc_text + type: str + teams_title: + description: + - desc_text + type: str + teams_url: + description: + - desc_text + type: str + telegram_chat_id: + description: + - desc_text + type: str + telegram_disable_notifications: + description: + - desc_text + type: bool + telegram_message: + description: + - desc_text + type: str + telegram_parse_mode: + description: + - desc_text + type: str + telegram_protect_content: + description: + - desc_text + type: bool + telegram_token: + description: + - desc_text + type: str + telegram_web_page_view: + description: + - desc_text + type: bool + threema_api_secret: + description: + - desc_text + type: str + threema_description: + description: + - desc_text + type: str + threema_gateway_id: + description: + - desc_text + type: str + threema_recipient_id: + description: + - desc_text + type: str + threema_title: + description: + - desc_text + type: str + victorops_description: + description: + - desc_text + type: str + victorops_message_type: + description: + - desc_text + type: str + choices: + - CRITICAL + - RECOVERY + victorops_title: + description: + - desc_text + type: str + victorops_url: + description: + - desc_text + type: str + webex_api_url: + description: + - desc_text + type: str + webex_message: + description: + - desc_text + type: str + webex_room_id: + description: + - desc_text + type: str + webex_token: + description: + - desc_text + type: str + webhook_authorization_credentials: + description: + - desc_text + type: str + webhook_authorization_scheme: + description: + - desc_text + type: str + webhook_http_method: + description: + - desc_text + type: str + choices: + - POST + - PUT + webhook_max_alerts: + description: + - desc_text + type: int + webhook_message: + description: + - desc_text + type: str + webhook_password: + description: + - desc_text + type: str + webhook_title: + description: + - desc_text + type: str + webhook_url: + description: + - desc_text + type: str + webhook_username: + description: + - desc_text + type: str + wecom_agent_id: + description: + - desc_text + type: str + wecom_corp_id: + description: + - desc_text + type: str + wecom_message: + description: + - desc_text + type: str + wecom_msg_type: + description: + - desc_text + type: str + wecom_secret: + description: + - desc_text + type: str + wecom_title: + description: + - desc_text + type: str + wecom_to_user: + description: + - desc_text + type: list + wecom_url: + description: + - desc_text + type: str extends_documentation_fragment: - community.grafana.basic_auth - community.grafana.api_key From 5ac22a34982de6866869ee3414e5d000879d1b99 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Thu, 11 Apr 2024 13:39:21 +0200 Subject: [PATCH 35/52] chore: added else for provisioning handling and api version error message --- plugins/modules/grafana_contact_point.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/plugins/modules/grafana_contact_point.py b/plugins/modules/grafana_contact_point.py index dc86a55c..e679086d 100644 --- a/plugins/modules/grafana_contact_point.py +++ b/plugins/modules/grafana_contact_point.py @@ -869,7 +869,11 @@ def grafana_handle_api_provisioning(self, data): msg="Unable to update contact point '%s': provisioning cannot be disabled if it's already enabled" % data["uid"] ) - # TODO: else + else: + self._module.fail_json( + msg="Unable to update contact point '%s': error while handling provisioning" + % data["uid"] + ) def grafana_organization_by_name(self, data, org_name): r, info = fetch_url( @@ -915,7 +919,7 @@ def grafana_check_contact_point_match(self, data): return contact_point elif info["status"] == 404: self._module.fail_json( - msg="Unable to get contact point: API endpoint not found" # TODO: versionshinweis?! + msg="Unable to get contact point: API endpoint not found - please check your Grafana version" ) else: raise GrafanaAPIException( From 41db6228c2419b7c7de14b8bb869c8b631b408f8 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Thu, 11 Apr 2024 13:49:55 +0200 Subject: [PATCH 36/52] fix: pass if provisioning is else --- plugins/modules/grafana_contact_point.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/plugins/modules/grafana_contact_point.py b/plugins/modules/grafana_contact_point.py index e679086d..573908b0 100644 --- a/plugins/modules/grafana_contact_point.py +++ b/plugins/modules/grafana_contact_point.py @@ -870,10 +870,7 @@ def grafana_handle_api_provisioning(self, data): % data["uid"] ) else: - self._module.fail_json( - msg="Unable to update contact point '%s': error while handling provisioning" - % data["uid"] - ) + pass def grafana_organization_by_name(self, data, org_name): r, info = fetch_url( From 52061944f6d2dd60b667d5cd683968290fd4a237 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Thu, 11 Apr 2024 14:04:10 +0200 Subject: [PATCH 37/52] test: fix grafana version check --- tests/integration/targets/grafana_contact_point/tasks/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/targets/grafana_contact_point/tasks/main.yml b/tests/integration/targets/grafana_contact_point/tasks/main.yml index 060ceab8..83a6dc67 100644 --- a/tests/integration/targets/grafana_contact_point/tasks/main.yml +++ b/tests/integration/targets/grafana_contact_point/tasks/main.yml @@ -9,7 +9,7 @@ - name: Include contact point task files ansible.builtin.include_tasks: "{{ item }}.yml" - when: "result.msg | default('') != 'Unable to get contact point: API endpoint not found'" + when: "result.msg | default('') != 'Unable to get contact point: API endpoint not found - please check your Grafana version'" loop: - alertmanager - dingding From c9eebc8cae178deccb4e3857fc65436e7611a4bb Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Thu, 11 Apr 2024 14:13:15 +0200 Subject: [PATCH 38/52] chore: argument section comments --- plugins/modules/grafana_contact_point.py | 38 ++++++++++++------------ 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/plugins/modules/grafana_contact_point.py b/plugins/modules/grafana_contact_point.py index 573908b0..f191cb84 100644 --- a/plugins/modules/grafana_contact_point.py +++ b/plugins/modules/grafana_contact_point.py @@ -1038,31 +1038,31 @@ def main(): ], ), uid=dict(type="str"), - # type: alertmanager + # alertmanager alertmanager_password=dict(type="str", no_log=True), alertmanager_url=dict(type="str"), alertmanager_username=dict(type="str"), - # type: dingding + # dingding dingding_message=dict(type="str"), dingding_message_type=dict(type="str"), dingding_title=dict(type="str"), dingding_url=dict(type="str"), - # type: discord + # discord discord_avatar_url=dict(type="str"), discord_message=dict(type="str"), discord_title=dict(type="str"), discord_url=dict(type="str", no_log=True), discord_use_username=dict(type="bool", default=False), - # type: email + # email email_addresses=dict(type="list", elements="str"), email_message=dict(type="str"), email_single=dict(type="bool", default=False), email_subject=dict(type="str"), - # type: googlechat + # googlechat googlechat_url=dict(type="str", no_log=True), googlechat_message=dict(type="str"), googlechat_title=dict(type="str"), - # type: kafka + # kafka kafka_api_version=dict(type="str", default="v2"), kafka_cluster_id=dict(type="str"), kafka_description=dict(type="str"), @@ -1071,11 +1071,11 @@ def main(): kafka_rest_proxy_url=dict(type="str", no_log=True), kafka_topic=dict(type="str"), kafka_username=dict(type="str"), - # type: line + # line line_description=dict(type="str"), line_title=dict(type="str"), line_token=dict(type="str", no_log=True), - # type: opsgenie + # opsgenie opsgenie_api_key=dict(type="str", no_log=True), opsgenie_auto_close=dict(type="bool"), opsgenie_description=dict(type="str"), @@ -1084,7 +1084,7 @@ def main(): opsgenie_responders=dict(type="list", elements="dict"), opsgenie_send_tags_as=dict(type="str"), opsgenie_url=dict(type="str"), - # type: pagerduty + # pagerduty pagerduty_class=dict(type="str"), pagerduty_client=dict(type="str"), pagerduty_client_url=dict(type="str"), @@ -1097,7 +1097,7 @@ def main(): ), pagerduty_source=dict(type="str"), pagerduty_summary=dict(type="str"), - # type: pushover + # pushover pushover_api_token=dict(type="str", no_log=True), pushover_devices=dict(type="list", elements="str"), pushover_expire=dict(type="int"), @@ -1110,7 +1110,7 @@ def main(): pushover_title=dict(type="str"), pushover_upload_image=dict(type="bool", default=True), pushover_user_key=dict(type="str", no_log=True), - # type: sensugo + # sensugo sensugo_api_key=dict(type="str", no_log=True), sensugo_url=dict(type="str"), sensugo_check=dict(type="str"), @@ -1118,7 +1118,7 @@ def main(): sensugo_handler=dict(type="str"), sensugo_message=dict(type="str"), sensugo_namespace=dict(type="str"), - # type: slack + # slack slack_endpoint_url=dict(type="str"), slack_icon_emoji=dict(type="str"), slack_icon_url=dict(type="str"), @@ -1131,12 +1131,12 @@ def main(): slack_token=dict(type="str", no_log=True), slack_url=dict(type="str", no_log=True), slack_username=dict(type="str"), - # type: teams + # teams teams_message=dict(type="str"), teams_section_title=dict(type="str"), teams_title=dict(type="str"), teams_url=dict(type="str", no_log=True), - # type: telegram + # telegram telegram_chat_id=dict(type="str"), telegram_disable_notifications=dict(type="bool"), telegram_message=dict(type="str"), @@ -1144,23 +1144,23 @@ def main(): telegram_protect_content=dict(type="bool"), telegram_token=dict(type="str", no_log=True), telegram_web_page_view=dict(type="bool"), - # type: threema + # threema threema_api_secret=dict(type="str", no_log=True), threema_description=dict(type="str"), threema_gateway_id=dict(type="str"), threema_recipient_id=dict(type="str"), threema_title=dict(type="str"), - # type: victorops + # victorops victorops_description=dict(type="str"), victorops_message_type=dict(type="str", choices=["CRITICAL", "RECOVERY"]), victorops_title=dict(type="str"), victorops_url=dict(type="str"), - # type: webex + # webex webex_api_url=dict(type="str"), webex_message=dict(type="str"), webex_room_id=dict(type="str"), webex_token=dict(type="str", no_log=True), - # type: webhook + # webhook webhook_authorization_credentials=dict(type="str", no_log=True), webhook_authorization_scheme=dict(type="str"), webhook_http_method=dict(type="str", choices=["POST", "PUT"]), @@ -1170,7 +1170,7 @@ def main(): webhook_title=dict(type="str"), webhook_url=dict(type="str"), webhook_username=dict(type="str"), - # type: wecom + # wecom wecom_agent_id=dict(type="str"), wecom_corp_id=dict(type="str"), wecom_message=dict(type="str"), From fa5ce76010add017df681e4c471b156717d8630c Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Thu, 11 Apr 2024 14:23:15 +0200 Subject: [PATCH 39/52] docs: module docs sanity findings --- plugins/modules/grafana_contact_point.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/plugins/modules/grafana_contact_point.py b/plugins/modules/grafana_contact_point.py index f191cb84..26bbf3db 100644 --- a/plugins/modules/grafana_contact_point.py +++ b/plugins/modules/grafana_contact_point.py @@ -57,6 +57,14 @@ - desc_text type: bool default: true + state: + description: + - Status of the contact point + type: str + default: present + choices: + - present + - absent type: description: - desc_text @@ -389,10 +397,12 @@ description: - desc_text type: list + elements: str slack_mention_users: description: - desc_text type: list + elements: str slack_recipient: description: - desc_text @@ -583,6 +593,7 @@ description: - desc_text type: list + elements: str wecom_url: description: - desc_text @@ -618,6 +629,7 @@ contact_point: description: Contact point created or updated by the module. returned: changed + type: dict """ import json @@ -1123,8 +1135,8 @@ def main(): slack_icon_emoji=dict(type="str"), slack_icon_url=dict(type="str"), slack_mention_channel=dict(type="str", choices=["here", "channel"]), - slack_mention_groups=dict(type="list"), - slack_mention_users=dict(type="list"), + slack_mention_groups=dict(type="list", elements="str"), + slack_mention_users=dict(type="list", elements="str"), slack_recipient=dict(type="str"), slack_text=dict(type="str"), slack_title=dict(type="str"), @@ -1177,7 +1189,7 @@ def main(): wecom_msg_type=dict(type="str"), wecom_secret=dict(type="str", no_log=True), wecom_title=dict(type="str"), - wecom_to_user=dict(type="list"), + wecom_to_user=dict(type="list", elements="str"), wecom_url=dict(type="str", no_log=True), ) From 5b55562aa2ff5a70f459758821e010ea59bfe66f Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Mon, 27 May 2024 11:05:07 +0200 Subject: [PATCH 40/52] docs: role parameters --- roles/grafana/README.md | 130 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 125 insertions(+), 5 deletions(-) diff --git a/roles/grafana/README.md b/roles/grafana/README.md index 00dfee97..6c1e0dd4 100644 --- a/roles/grafana/README.md +++ b/roles/grafana/README.md @@ -170,16 +170,136 @@ Configure Grafana organizations, dashboards, folders, datasources, teams and use | matchers | yes | | starts_at | yes | | state | no | -| [**grafana_contact_point**](https://docs.ansible.com/ansible/latest/collections/community/grafana/grafana_contact_point_module.html) | # TODO: parameter -| email_addresses | no | -| email_single | no | -| is_default | no | -| name | yes | +| [**grafana_contact_point**](https://docs.ansible.com/ansible/latest/collections/community/grafana/grafana_contact_point_module.html) | +| disable_resolve_message | no | +| include_image | no | +| name | no | | org_id | no | | org_name | no | +| provisioning | no | | state | no | | type | yes | | uid | no | +| alertmanager_password | no | +| alertmanager_url | no | +| alertmanager_username | no | +| dingding_message | no | +| dingding_message_type | no | +| dingding_title | no | +| dingding_url | no | +| discord_avatar_url | no | +| discord_message | no | +| discord_title | no | +| discord_url | no | +| discord_use_username | no | +| email_addresses | no | +| email_message | no | +| email_single | no | +| email_subject | no | +| googlechat_url | no | +| googlechat_message | no | +| googlechat_title | no | +| kafka_api_version | no | +| kafka_cluster_id | no | +| kafka_description | no | +| kafka_details | no | +| kafka_password | no | +| kafka_rest_proxy_url | no | +| kafka_topic | no | +| kafka_username | no | +| line_description | no | +| line_title | no | +| line_token | no | +| opsgenie_api_key | no | +| opsgenie_auto_close | no | +| opsgenie_description | no | +| opsgenie_message | no | +| opsgenie_override_priority | no | +| opsgenie_responders | no | +| opsgenie_send_tags_as | no | +| opsgenie_url | no | +| pagerduty_class | no | +| pagerduty_client | no | +| pagerduty_client_url | no | +| pagerduty_component | no | +| pagerduty_details | no | +| pagerduty_group | no | +| pagerduty_integration_key | no | +| pagerduty_severity | no | +| pagerduty_source | no | +| pagerduty_summary | no | +| pushover_api_token | no | +| pushover_devices | no | +| pushover_expire | no | +| pushover_message | no | +| pushover_ok_priority | no | +| pushover_ok_sound | no | +| pushover_priority | no | +| pushover_retry | no | +| pushover_sound | no | +| pushover_title | no | +| pushover_upload_image | no | +| pushover_user_key | no | +| sensugo_api_key | no | +| sensugo_url | no | +| sensugo_check | no | +| sensugo_entity | no | +| sensugo_handler | no | +| sensugo_message | no | +| sensugo_namespace | no | +| slack_endpoint_url | no | +| slack_icon_emoji | no | +| slack_icon_url | no | +| slack_mention_channel | no | +| slack_mention_groups | no | +| slack_mention_users | no | +| slack_recipient | no | +| slack_text | no | +| slack_title | no | +| slack_token | no | +| slack_url | no | +| slack_username | no | +| teams_message | no | +| teams_section_title | no | +| teams_title | no | +| teams_url | no | +| telegram_chat_id | no | +| telegram_disable_notifications | no | +| telegram_message | no | +| telegram_parse_mode | no | +| telegram_protect_content | no | +| telegram_token | no | +| telegram_web_page_view | no | +| threema_api_secret | no | +| threema_description | no | +| threema_gateway_id | no | +| threema_recipient_id | no | +| threema_title | no | +| victorops_description | no | +| victorops_message_type | no | +| victorops_title | no | +| victorops_url | no | +| webex_api_url | no | +| webex_message | no | +| webex_room_id | no | +| webex_token | no | +| webhook_authorization_credentials | no | +| webhook_authorization_scheme | no | +| webhook_http_method | no | +| webhook_max_alerts | no | +| webhook_message | no | +| webhook_password | no | +| webhook_title | no | +| webhook_url | no | +| webhook_username | no | +| wecom_agent_id | no | +| wecom_corp_id | no | +| wecom_message | no | +| wecom_msg_type | no | +| wecom_secret | no | +| wecom_title | no | +| wecom_to_user | no | +| wecom_url | no | ## Example Playbook From f170e997515a60ae83618a955d1bb5436086db31 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Mon, 27 May 2024 11:18:47 +0200 Subject: [PATCH 41/52] docs: module args description texts (thanks chatgpt) --- plugins/modules/grafana_contact_point.py | 258 +++++++++++------------ 1 file changed, 129 insertions(+), 129 deletions(-) diff --git a/plugins/modules/grafana_contact_point.py b/plugins/modules/grafana_contact_point.py index 26bbf3db..2bdb3dea 100644 --- a/plugins/modules/grafana_contact_point.py +++ b/plugins/modules/grafana_contact_point.py @@ -31,35 +31,35 @@ options: disable_resolve_message: description: - - desc_text + - Disables the resolve message. type: bool default: false include_image: description: - - desc_text + - Whether to include an image in the notification. type: bool default: false name: description: - - desc_text + - The name of the contact point. type: str org_id: description: - - desc_text + - The organization ID. type: int default: 1 org_name: description: - - desc_text + - The name of the organization. type: str provisioning: description: - - desc_text + - Indicates if provisioning is enabled. type: bool default: true state: description: - - Status of the contact point + - Status of the contact point. type: str default: present choices: @@ -67,7 +67,7 @@ - absent type: description: - - desc_text + - The type of the contact point. type: str choices: - alertmanager @@ -91,197 +91,197 @@ - wecom uid: description: - - desc_text + - The unique ID of the contact point. type: str alertmanager_password: description: - - desc_text + - Password for accessing Alertmanager. type: str alertmanager_url: description: - - desc_text + - URL for accessing Alertmanager. type: str alertmanager_username: description: - - desc_text + - Username for accessing Alertmanager. type: str dingding_message: description: - - desc_text + - The message to send via DingDing. type: str dingding_message_type: description: - - desc_text + - The type of message to send via DingDing. type: str dingding_title: description: - - desc_text + - The title of the DingDing message. type: str dingding_url: description: - - desc_text + - The URL for DingDing webhook. type: str discord_avatar_url: description: - - desc_text + - The avatar URL for Discord messages. type: str discord_message: description: - - desc_text + - The message to send via Discord. type: str discord_title: description: - - desc_text + - The title of the Discord message. type: str discord_url: description: - - desc_text + - The URL for Discord webhook. type: str discord_use_username: description: - - desc_text + - Whether to use a custom username in Discord. type: bool default: false email_addresses: description: - - desc_text + - List of email addresses to send the message to. type: list elements: str email_message: description: - - desc_text + - The content of the email message. type: str email_single: description: - - desc_text + - Whether to send a single email or individual emails. type: bool default: false email_subject: description: - - desc_text + - The subject of the email. type: str googlechat_url: description: - - desc_text + - The URL for Google Chat webhook. type: str googlechat_message: description: - - desc_text + - The message to send via Google Chat. type: str googlechat_title: description: - - desc_text + - The title of the Google Chat message. type: str kafka_api_version: description: - - desc_text + - The API version for Kafka. type: str default: v2 kafka_cluster_id: description: - - desc_text + - The cluster ID for Kafka. type: str kafka_description: description: - - desc_text + - The description for the Kafka configuration. type: str kafka_details: description: - - desc_text + - Additional details for Kafka. type: str kafka_password: description: - - desc_text + - Password for accessing Kafka. type: str kafka_rest_proxy_url: description: - - desc_text + - URL for Kafka REST Proxy. type: str kafka_topic: description: - - desc_text + - Kafka topic to publish to. type: str kafka_username: description: - - desc_text + - Username for accessing Kafka. type: str line_description: description: - - desc_text + - Description for the Line message. type: str line_title: description: - - desc_text + - Title of the Line message. type: str line_token: description: - - desc_text + - Access token for Line. type: str opsgenie_api_key: description: - - desc_text + - API key for OpsGenie. type: str opsgenie_auto_close: description: - - desc_text + - Whether to enable auto-closing of alerts in OpsGenie. type: bool opsgenie_description: description: - - desc_text + - Description of the OpsGenie alert. type: str opsgenie_message: description: - - desc_text + - Message to send via OpsGenie. type: str opsgenie_override_priority: description: - - desc_text + - Whether to override the priority in OpsGenie. type: bool opsgenie_responders: description: - - desc_text + - List of responders for OpsGenie alerts. type: list elements: dict opsgenie_send_tags_as: description: - - desc_text + - Format for sending tags in OpsGenie. type: str opsgenie_url: description: - - desc_text + - URL for OpsGenie webhook. type: str pagerduty_class: description: - - desc_text + - Class of the PagerDuty alert. type: str pagerduty_client: description: - - desc_text + - Client identifier for PagerDuty. type: str pagerduty_client_url: description: - - desc_text + - Client URL for PagerDuty. type: str pagerduty_component: description: - - desc_text + - Component involved in the PagerDuty alert. type: str pagerduty_details: description: - - desc_text + - List of additional details for PagerDuty. type: list elements: dict pagerduty_group: description: - - desc_text + - Group associated with the PagerDuty alert. type: str pagerduty_integration_key: description: - - desc_text + - Integration key for PagerDuty. type: str pagerduty_severity: description: - - desc_text + - Severity level of the PagerDuty alert. type: str choices: - critical @@ -290,313 +290,313 @@ - info pagerduty_source: description: - - desc_text + - Source of the PagerDuty alert. type: str pagerduty_summary: description: - - desc_text + - Summary of the PagerDuty alert. type: str pushover_api_token: description: - - desc_text + - API token for Pushover. type: str pushover_devices: description: - - desc_text + - List of devices for Pushover notifications. type: list elements: str pushover_expire: description: - - desc_text + - Expiration time for Pushover notifications. type: int pushover_message: description: - - desc_text + - Message to send via Pushover. type: str pushover_ok_priority: description: - - desc_text + - Priority for OK messages in Pushover. type: int pushover_ok_sound: description: - - desc_text + - Sound for OK messages in Pushover. type: str pushover_priority: description: - - desc_text + - Priority for Pushover messages. type: int pushover_retry: description: - - desc_text + - Retry interval for Pushover messages. type: int pushover_sound: description: - - desc_text + - Sound for Pushover notifications. type: str pushover_title: description: - - desc_text + - Title of the Pushover message. type: str pushover_upload_image: description: - - desc_text + - Whether to upload an image with Pushover notification. type: bool default: true pushover_user_key: description: - - desc_text + - User key for Pushover. type: str sensugo_api_key: description: - - desc_text + - API key for Sensu Go. type: str sensugo_url: description: - - desc_text + - URL for Sensu Go. type: str sensugo_check: description: - - desc_text + - Check name for Sensu Go. type: str sensugo_entity: description: - - desc_text + - Entity name for Sensu Go. type: str sensugo_handler: description: - - desc_text + - Handler for Sensu Go. type: str sensugo_message: description: - - desc_text + - Message to send via Sensu Go. type: str sensugo_namespace: description: - - desc_text + - Namespace for Sensu Go. type: str slack_endpoint_url: description: - - desc_text + - Endpoint URL for Slack webhook. type: str slack_icon_emoji: description: - - desc_text + - Icon emoji for Slack messages. type: str slack_icon_url: description: - - desc_text + - Icon URL for Slack messages. type: str slack_mention_channel: description: - - desc_text + - Channel mention for Slack messages. type: str choices: - here - channel slack_mention_groups: description: - - desc_text + - List of groups to mention in Slack messages. type: list elements: str slack_mention_users: description: - - desc_text + - List of users to mention in Slack messages. type: list elements: str slack_recipient: description: - - desc_text + - Recipient for Slack messages. type: str slack_text: description: - - desc_text + - Text content for Slack messages. type: str slack_title: description: - - desc_text + - Title of the Slack message. type: str slack_token: description: - - desc_text + - Token for Slack authentication. type: str slack_url: description: - - desc_text + - URL for Slack webhook. type: str slack_username: description: - - desc_text + - Username to use in Slack messages. type: str teams_message: description: - - desc_text + - Message to send via Microsoft Teams. type: str teams_section_title: description: - - desc_text + - Section title for Microsoft Teams messages. type: str teams_title: description: - - desc_text + - Title of the Microsoft Teams message. type: str teams_url: description: - - desc_text + - URL for Microsoft Teams webhook. type: str telegram_chat_id: description: - - desc_text + - Chat ID for Telegram. type: str telegram_disable_notifications: description: - - desc_text + - Whether to disable notifications for Telegram messages. type: bool telegram_message: description: - - desc_text + - Message to send via Telegram. type: str telegram_parse_mode: description: - - desc_text + - Parse mode for Telegram messages. type: str telegram_protect_content: description: - - desc_text + - Whether to protect content in Telegram messages. type: bool telegram_token: description: - - desc_text + - Token for Telegram authentication. type: str telegram_web_page_view: description: - - desc_text + - Whether to enable web page preview in Telegram messages. type: bool threema_api_secret: description: - - desc_text + - API secret for Threema. type: str threema_description: description: - - desc_text + - Description for Threema messages. type: str threema_gateway_id: description: - - desc_text + - Gateway ID for Threema. type: str threema_recipient_id: description: - - desc_text + - Recipient ID for Threema messages. type: str threema_title: description: - - desc_text + - Title of the Threema message. type: str victorops_description: description: - - desc_text + - Description for VictorOps messages. type: str victorops_message_type: description: - - desc_text + - Message type for VictorOps. type: str choices: - CRITICAL - RECOVERY victorops_title: description: - - desc_text + - Title of the VictorOps message. type: str victorops_url: description: - - desc_text + - URL for VictorOps webhook. type: str webex_api_url: description: - - desc_text + - API URL for Webex. type: str webex_message: description: - - desc_text + - Message to send via Webex. type: str webex_room_id: description: - - desc_text + - Room ID for Webex messages. type: str webex_token: description: - - desc_text + - Token for Webex authentication. type: str webhook_authorization_credentials: description: - - desc_text + - Authorization credentials for webhook. type: str webhook_authorization_scheme: description: - - desc_text + - Authorization scheme for webhook. type: str webhook_http_method: description: - - desc_text + - HTTP method for webhook. type: str choices: - POST - PUT webhook_max_alerts: description: - - desc_text + - Maximum number of alerts for webhook. type: int webhook_message: description: - - desc_text + - Message to send via webhook. type: str webhook_password: description: - - desc_text + - Password for webhook authentication. type: str webhook_title: description: - - desc_text + - Title of the webhook message. type: str webhook_url: description: - - desc_text + - URL for webhook. type: str webhook_username: description: - - desc_text + - Username for webhook authentication. type: str wecom_agent_id: description: - - desc_text + - Agent ID for WeCom. type: str wecom_corp_id: description: - - desc_text + - Corporate ID for WeCom. type: str wecom_message: description: - - desc_text + - Message to send via WeCom. type: str wecom_msg_type: description: - - desc_text + - Message type for WeCom. type: str wecom_secret: description: - - desc_text + - Secret for WeCom authentication. type: str wecom_title: description: - - desc_text + - Title of the WeCom message. type: str wecom_to_user: description: - - desc_text + - List of users to send the WeCom message to. type: list elements: str wecom_url: description: - - desc_text + - URL for WeCom webhook. type: str extends_documentation_fragment: - community.grafana.basic_auth From b42893e4dd229fcae3aa07d352993693ede23b39 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Mon, 27 May 2024 13:23:43 +0200 Subject: [PATCH 42/52] docs: module return values --- plugins/modules/grafana_contact_point.py | 74 ++++++++++++++++++++++-- 1 file changed, 70 insertions(+), 4 deletions(-) diff --git a/plugins/modules/grafana_contact_point.py b/plugins/modules/grafana_contact_point.py index 2bdb3dea..5ec00561 100644 --- a/plugins/modules/grafana_contact_point.py +++ b/plugins/modules/grafana_contact_point.py @@ -628,8 +628,75 @@ RETURN = """ contact_point: description: Contact point created or updated by the module. - returned: changed + returned: success + type: complex + contains: + uid: + description: The uid of the contact point. + returned: success + type: str + sample: + - ddmyrs0f74t8hc + name: + description: The name of the contact point. + returned: success + type: str + sample: + - supportmail + type: + description: The type of the contact point. + returned: success + type: str + sample: + - email + disableResolveMessage: + description: Is the resolve message of the contact point disabled. + returned: success + type: bool + sample: + - false + settings: + description: The type specific settings of the contact point. + returned: success + type: dict + sample: + - addresses: "support@example.com" + singleEmail: false + secureFields: + description: The secure fields config of the contact point. + returned: success + type: dict +diff: + description: Difference between previous and updated contact point. + return: changed type: dict + contains: + before: + description: Previous contact point. + return: changed + type: complex + sample: + - uid: ddmyrs0f74t8hc + name: supportmail + type: email + disableResolveMessage: false + settings: + addresses: support@example.com + singleEmail: false + secureFields: {} + after: + description: Current contact point. + return: changed + type: complex + sample: + - uid: ddmyrs0f74t8hc + name: supportmail + type: email + disableResolveMessage: true + settings: + addresses: support123@example.com + singleEmail: false + secureFields: {} """ import json @@ -963,7 +1030,6 @@ def grafana_create_contact_point(self, data, payload): contact_point = json.loads(to_text(r.read())) return { "changed": True, - "state": data["state"], "contact_point": contact_point, } else: @@ -985,7 +1051,7 @@ def grafana_update_contact_point(self, data, payload): del contact_point["provenance"] if self.contact_point == contact_point: - return {"changed": False} + return {"changed": False, "contact_point": contact_point} else: return { "changed": True, @@ -1006,7 +1072,7 @@ def grafana_delete_contact_point(self, data): ) if info["status"] == 202: - return {"state": "absent", "changed": True} + return {"changed": True, "contact_point": contact_point} elif info["status"] == 404: return {"changed": False} else: From 248d638e429116e632ba5329910fe17520949e51 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Mon, 27 May 2024 14:08:56 +0200 Subject: [PATCH 43/52] fix: contact point var ref --- plugins/modules/grafana_contact_point.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/grafana_contact_point.py b/plugins/modules/grafana_contact_point.py index 5ec00561..fc0a2ad9 100644 --- a/plugins/modules/grafana_contact_point.py +++ b/plugins/modules/grafana_contact_point.py @@ -1072,7 +1072,7 @@ def grafana_delete_contact_point(self, data): ) if info["status"] == 202: - return {"changed": True, "contact_point": contact_point} + return {"changed": True, "contact_point": self.contact_point} elif info["status"] == 404: return {"changed": False} else: From 5478f190939301023e3e5fe6f1f72b76dfc0188a Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Mon, 27 May 2024 14:16:17 +0200 Subject: [PATCH 44/52] docs: return returned type --- plugins/modules/grafana_contact_point.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/modules/grafana_contact_point.py b/plugins/modules/grafana_contact_point.py index fc0a2ad9..62d58767 100644 --- a/plugins/modules/grafana_contact_point.py +++ b/plugins/modules/grafana_contact_point.py @@ -668,12 +668,12 @@ type: dict diff: description: Difference between previous and updated contact point. - return: changed + returned: changed type: dict contains: before: description: Previous contact point. - return: changed + returned: changed type: complex sample: - uid: ddmyrs0f74t8hc @@ -686,7 +686,7 @@ secureFields: {} after: description: Current contact point. - return: changed + returned: changed type: complex sample: - uid: ddmyrs0f74t8hc From 6f10807224bdf39b78fd78c00c1b677d9a869c97 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Tue, 28 May 2024 09:37:48 +0200 Subject: [PATCH 45/52] docs: return values --- plugins/modules/grafana_contact_point.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/modules/grafana_contact_point.py b/plugins/modules/grafana_contact_point.py index 62d58767..464ccd78 100644 --- a/plugins/modules/grafana_contact_point.py +++ b/plugins/modules/grafana_contact_point.py @@ -669,12 +669,12 @@ diff: description: Difference between previous and updated contact point. returned: changed - type: dict + type: complex contains: before: description: Previous contact point. returned: changed - type: complex + type: dict sample: - uid: ddmyrs0f74t8hc name: supportmail @@ -687,7 +687,7 @@ after: description: Current contact point. returned: changed - type: complex + type: dict sample: - uid: ddmyrs0f74t8hc name: supportmail From ee9ff4d1e1b1d457411eae63ff62a63930110fd7 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Tue, 28 May 2024 09:38:09 +0200 Subject: [PATCH 46/52] chore: add state to return --- plugins/modules/grafana_contact_point.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/plugins/modules/grafana_contact_point.py b/plugins/modules/grafana_contact_point.py index 464ccd78..3a85207e 100644 --- a/plugins/modules/grafana_contact_point.py +++ b/plugins/modules/grafana_contact_point.py @@ -1015,7 +1015,7 @@ def grafana_handle_contact_point(self, data): if self.contact_point: return self.grafana_delete_contact_point(data) else: - return {"changed": False} + return {"changed": False, "state": data["state"]} def grafana_create_contact_point(self, data, payload): r, info = fetch_url( @@ -1031,6 +1031,7 @@ def grafana_create_contact_point(self, data, payload): return { "changed": True, "contact_point": contact_point, + "state": data["state"], } else: raise GrafanaAPIException("Unable to create contact point: %s" % info) @@ -1051,12 +1052,13 @@ def grafana_update_contact_point(self, data, payload): del contact_point["provenance"] if self.contact_point == contact_point: - return {"changed": False, "contact_point": contact_point} + return {"changed": False, "contact_point": contact_point, "state": data["state"]} else: return { "changed": True, "diff": {"before": self.contact_point, "after": contact_point}, "contact_point": contact_point, + "state": data["state"], } else: raise GrafanaAPIException( @@ -1072,9 +1074,9 @@ def grafana_delete_contact_point(self, data): ) if info["status"] == 202: - return {"changed": True, "contact_point": self.contact_point} + return {"changed": True, "contact_point": self.contact_point, "state": data["state"]} elif info["status"] == 404: - return {"changed": False} + return {"changed": False, "state": data["state"]} else: raise GrafanaAPIException( "Unable to delete contact point '%s': %s" % (data["uid"], info) From 65c5a7c15d10749b6eca84522708e476fad0f7f2 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Tue, 28 May 2024 09:39:41 +0200 Subject: [PATCH 47/52] style: formatting black --- plugins/modules/grafana_contact_point.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/plugins/modules/grafana_contact_point.py b/plugins/modules/grafana_contact_point.py index 3a85207e..a8e86dbf 100644 --- a/plugins/modules/grafana_contact_point.py +++ b/plugins/modules/grafana_contact_point.py @@ -1052,7 +1052,11 @@ def grafana_update_contact_point(self, data, payload): del contact_point["provenance"] if self.contact_point == contact_point: - return {"changed": False, "contact_point": contact_point, "state": data["state"]} + return { + "changed": False, + "contact_point": contact_point, + "state": data["state"], + } else: return { "changed": True, @@ -1074,7 +1078,11 @@ def grafana_delete_contact_point(self, data): ) if info["status"] == 202: - return {"changed": True, "contact_point": self.contact_point, "state": data["state"]} + return { + "changed": True, + "contact_point": self.contact_point, + "state": data["state"], + } elif info["status"] == 404: return {"changed": False, "state": data["state"]} else: From 669f70d2d82bde9d4acdbbdd27645fd669d67e0d Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Tue, 28 May 2024 10:33:38 +0200 Subject: [PATCH 48/52] docs: required if references --- plugins/modules/grafana_contact_point.py | 34 +++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/plugins/modules/grafana_contact_point.py b/plugins/modules/grafana_contact_point.py index a8e86dbf..368d1e85 100644 --- a/plugins/modules/grafana_contact_point.py +++ b/plugins/modules/grafana_contact_point.py @@ -100,6 +100,7 @@ alertmanager_url: description: - URL for accessing Alertmanager. + - Required when C(type) is C(alertmanager). type: str alertmanager_username: description: @@ -120,6 +121,7 @@ dingding_url: description: - The URL for DingDing webhook. + - Required when C(type) is C(dingding). type: str discord_avatar_url: description: @@ -136,6 +138,7 @@ discord_url: description: - The URL for Discord webhook. + - Required when C(type) is C(discord). type: str discord_use_username: description: @@ -145,6 +148,7 @@ email_addresses: description: - List of email addresses to send the message to. + - Required when C(type) is C(email). type: list elements: str email_message: @@ -163,6 +167,7 @@ googlechat_url: description: - The URL for Google Chat webhook. + - Required when C(type) is C(webhook). type: str googlechat_message: description: @@ -196,10 +201,12 @@ kafka_rest_proxy_url: description: - URL for Kafka REST Proxy. + - Required when C(type) is C(kafka). type: str kafka_topic: description: - Kafka topic to publish to. + - Required when C(type) is C(kafka). type: str kafka_username: description: @@ -216,10 +223,12 @@ line_token: description: - Access token for Line. + - Required when C(type) is C(line). type: str opsgenie_api_key: description: - API key for OpsGenie. + - Required when C(type) is C(opsgenie). type: str opsgenie_auto_close: description: @@ -249,6 +258,7 @@ opsgenie_url: description: - URL for OpsGenie webhook. + - Required when C(type) is C(pagerduty). type: str pagerduty_class: description: @@ -278,6 +288,7 @@ pagerduty_integration_key: description: - Integration key for PagerDuty. + - Required when C(type) is C(pagerduty). type: str pagerduty_severity: description: @@ -299,6 +310,7 @@ pushover_api_token: description: - API token for Pushover. + - Required when C(type) is C(pushover). type: str pushover_devices: description: @@ -345,14 +357,17 @@ pushover_user_key: description: - User key for Pushover. + - Required when C(type) is C(pushover). type: str sensugo_api_key: description: - API key for Sensu Go. + - Required when C(type) is C(pushover). type: str sensugo_url: description: - URL for Sensu Go. + - Required when C(type) is C(sensugo). type: str sensugo_check: description: @@ -406,6 +421,7 @@ slack_recipient: description: - Recipient for Slack messages. + - Required when C(type) is C(slack). type: str slack_text: description: @@ -418,10 +434,12 @@ slack_token: description: - Token for Slack authentication. + - Required when C(type) is C(slack). type: str slack_url: description: - URL for Slack webhook. + - Required when C(type) is C(slack). type: str slack_username: description: @@ -442,10 +460,12 @@ teams_url: description: - URL for Microsoft Teams webhook. + - Required when C(type) is C(teams). type: str telegram_chat_id: description: - Chat ID for Telegram. + - Required when C(type) is C(telegram). type: str telegram_disable_notifications: description: @@ -466,6 +486,7 @@ telegram_token: description: - Token for Telegram authentication. + - Required when C(type) is C(telegram). type: str telegram_web_page_view: description: @@ -474,6 +495,7 @@ threema_api_secret: description: - API secret for Threema. + - Required when C(type) is C(threema). type: str threema_description: description: @@ -482,10 +504,12 @@ threema_gateway_id: description: - Gateway ID for Threema. + - Required when C(type) is C(threema). type: str threema_recipient_id: description: - Recipient ID for Threema messages. + - Required when C(type) is C(threema). type: str threema_title: description: @@ -509,6 +533,7 @@ victorops_url: description: - URL for VictorOps webhook. + - Required when C(type) is C(victorops). type: str webex_api_url: description: @@ -521,10 +546,12 @@ webex_room_id: description: - Room ID for Webex messages. + - Required when C(type) is C(webex). type: str webex_token: description: - Token for Webex authentication. + - Required when C(type) is C(webex). type: str webhook_authorization_credentials: description: @@ -560,6 +587,7 @@ webhook_url: description: - URL for webhook. + - Required when C(type) is C(webhook). type: str webhook_username: description: @@ -568,10 +596,12 @@ wecom_agent_id: description: - Agent ID for WeCom. + - Required when C(type) is C(wecom). type: str wecom_corp_id: description: - Corporate ID for WeCom. + - Required when C(type) is C(wecom). type: str wecom_message: description: @@ -584,6 +614,7 @@ wecom_secret: description: - Secret for WeCom authentication. + - Required when C(type) is C(wecom). type: str wecom_title: description: @@ -597,6 +628,7 @@ wecom_url: description: - URL for WeCom webhook. + - Required when C(type) is C(wecom). type: str extends_documentation_fragment: - community.grafana.basic_auth @@ -1284,7 +1316,7 @@ def main(): ["type", "googlechat", ["googlechat_url"]], ["type", "kafka", ["kafka_rest_proxy_url", "kafka_topic"]], ["type", "line", ["line_token"]], - ["type", "opsgenie", ["opsgenie_api_key"]], + ["type", "opsgenie", ["opsgenie_api_key", "opsgenie_url"]], ["type", "pagerduty", ["pagerduty_integration_key"]], ["type", "pushover", ["pushover_api_token", "pushover_user_key"]], ["type", "sensugo", ["sensugo_api_key", "sensugo_url"]], From 79267fae79544b6c4f94f4d7fe8cdfa37212f8ad Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Tue, 28 May 2024 11:55:22 +0200 Subject: [PATCH 49/52] fix: uid required --- plugins/modules/grafana_contact_point.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/modules/grafana_contact_point.py b/plugins/modules/grafana_contact_point.py index 368d1e85..ef574642 100644 --- a/plugins/modules/grafana_contact_point.py +++ b/plugins/modules/grafana_contact_point.py @@ -42,6 +42,7 @@ name: description: - The name of the contact point. + - Required when C(state) is C(present). type: str org_id: description: @@ -68,6 +69,7 @@ type: description: - The type of the contact point. + - Required when C(state) is C(present). type: str choices: - alertmanager @@ -92,7 +94,9 @@ uid: description: - The unique ID of the contact point. + - Normally the uid is generated randomly, but it is required for handling the contact point via API. type: str + required: true alertmanager_password: description: - Password for accessing Alertmanager. @@ -1307,7 +1311,7 @@ def main(): required_together=[["url_username", "url_password", "org_id"]], mutually_exclusive=[["url_username", "grafana_api_key"]], required_if=[ - ["state", "present", ["name", "type"]], + ["state", "present", ["uid", "name", "type"]], ["state", "absent", ["uid"]], ["type", "alertmanager", ["alertmanager_url"]], ["type", "dingding", ["dingding_url"]], From 0729b3036ccda8135be38d69db93e19d4f51d64c Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Tue, 28 May 2024 11:56:05 +0200 Subject: [PATCH 50/52] test: add update task and extend asserts --- .../tasks/alertmanager.yml | 39 ++++++++++++++-- .../grafana_contact_point/tasks/dingding.yml | 39 ++++++++++++++-- .../grafana_contact_point/tasks/discord.yml | 39 ++++++++++++++-- .../grafana_contact_point/tasks/email.yml | 46 ++++++++++++++++--- .../tasks/googlechat.yml | 39 ++++++++++++++-- .../grafana_contact_point/tasks/kafka.yml | 40 ++++++++++++++-- .../grafana_contact_point/tasks/line.yml | 39 ++++++++++++++-- .../grafana_contact_point/tasks/opsgenie.yml | 41 +++++++++++++++-- .../grafana_contact_point/tasks/pagerduty.yml | 39 ++++++++++++++-- .../grafana_contact_point/tasks/pushover.yml | 40 ++++++++++++++-- .../grafana_contact_point/tasks/sensugo.yml | 40 ++++++++++++++-- .../grafana_contact_point/tasks/slack.yml | 32 +++++++++---- .../grafana_contact_point/tasks/teams.yml | 39 ++++++++++++++-- .../grafana_contact_point/tasks/telegram.yml | 40 ++++++++++++++-- .../grafana_contact_point/tasks/threema.yml | 41 +++++++++++++++-- .../grafana_contact_point/tasks/victorops.yml | 39 ++++++++++++++-- .../grafana_contact_point/tasks/webhook.yml | 39 ++++++++++++++-- 17 files changed, 595 insertions(+), 76 deletions(-) diff --git a/tests/integration/targets/grafana_contact_point/tasks/alertmanager.yml b/tests/integration/targets/grafana_contact_point/tasks/alertmanager.yml index 6ca4b934..9fd23797 100644 --- a/tests/integration/targets/grafana_contact_point/tasks/alertmanager.yml +++ b/tests/integration/targets/grafana_contact_point/tasks/alertmanager.yml @@ -12,7 +12,11 @@ - ansible.builtin.assert: that: - - result.changed == True + - result.changed + - result.state == "present" + - result.contact_point.uid == "alertmanager" + - result.contact_point.name == "alertmanager" + - result.contact_point.type == "prometheus-alertmanager" - name: Create alertmanager contact point (idempotency) register: result @@ -27,7 +31,31 @@ - ansible.builtin.assert: that: - - result.changed == False + - not result.changed + - result.state == "present" + - result.contact_point.uid == "alertmanager" + - result.contact_point.name == "alertmanager" + - result.contact_point.type == "prometheus-alertmanager" + +- name: Update alertmanager contact point + register: result + community.grafana.grafana_contact_point: + uid: alertmanager + name: alertmanager + type: alertmanager + alertmanager_url: https://example-update.org + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed + - result.state == "present" + - result.contact_point.uid == "alertmanager" + - result.contact_point.name == "alertmanager" + - result.contact_point.type == "prometheus-alertmanager" + - result.diff is defined - name: Delete alertmanager contact point register: result @@ -40,7 +68,9 @@ - ansible.builtin.assert: that: - - result.changed == True + - result.changed + - result.state == "absent" + - result.contact_point.uid == "alertmanager" - name: Delete alertmanager contact point (idempotency) register: result @@ -53,4 +83,5 @@ - ansible.builtin.assert: that: - - result.changed == False + - not result.changed + - result.state == "absent" diff --git a/tests/integration/targets/grafana_contact_point/tasks/dingding.yml b/tests/integration/targets/grafana_contact_point/tasks/dingding.yml index 5b14a909..b2fee63c 100644 --- a/tests/integration/targets/grafana_contact_point/tasks/dingding.yml +++ b/tests/integration/targets/grafana_contact_point/tasks/dingding.yml @@ -12,7 +12,11 @@ - ansible.builtin.assert: that: - - result.changed == True + - result.changed + - result.state == "present" + - result.contact_point.uid == "dingding" + - result.contact_point.name == "dingding" + - result.contact_point.type == "dingding" - name: Create dingding contact point (idempotency) register: result @@ -27,7 +31,31 @@ - ansible.builtin.assert: that: - - result.changed == False + - not result.changed + - result.state == "present" + - result.contact_point.uid == "dingding" + - result.contact_point.name == "dingding" + - result.contact_point.type == "dingding" + +- name: Update dingding contact point + register: result + community.grafana.grafana_contact_point: + uid: dingding + name: dingding + type: dingding + dingding_url: https://example-update.org + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed + - result.state == "present" + - result.contact_point.uid == "dingding" + - result.contact_point.name == "dingding" + - result.contact_point.type == "dingding" + - result.diff is defined - name: Delete dingding contact point register: result @@ -40,7 +68,9 @@ - ansible.builtin.assert: that: - - result.changed == True + - result.changed + - result.state == "absent" + - result.contact_point.uid == "dingding" - name: Delete dingding contact point (idempotency) register: result @@ -53,4 +83,5 @@ - ansible.builtin.assert: that: - - result.changed == False + - not result.changed + - result.state == "absent" diff --git a/tests/integration/targets/grafana_contact_point/tasks/discord.yml b/tests/integration/targets/grafana_contact_point/tasks/discord.yml index 89e62daa..a968d440 100644 --- a/tests/integration/targets/grafana_contact_point/tasks/discord.yml +++ b/tests/integration/targets/grafana_contact_point/tasks/discord.yml @@ -12,7 +12,11 @@ - ansible.builtin.assert: that: - - result.changed == True + - result.changed + - result.state == "present" + - result.contact_point.uid == "discord" + - result.contact_point.name == "discord" + - result.contact_point.type == "discord" - name: Create discord contact point (idempotency) register: result @@ -27,7 +31,31 @@ - ansible.builtin.assert: that: - - result.changed == False + - not result.changed + - result.state == "present" + - result.contact_point.uid == "discord" + - result.contact_point.name == "discord" + - result.contact_point.type == "discord" + +- name: Update discord contact point + register: result + community.grafana.grafana_contact_point: + uid: discord + name: discord + type: discord + discord_url: https://example-update.org + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed + - result.state == "present" + - result.contact_point.uid == "discord" + - result.contact_point.name == "discord" + - result.contact_point.type == "discord" + - result.diff is defined - name: Delete discord contact point register: result @@ -40,7 +68,9 @@ - ansible.builtin.assert: that: - - result.changed == True + - result.changed + - result.state == "absent" + - result.contact_point.uid == "discord" - name: Delete discord contact point (idempotency) register: result @@ -53,4 +83,5 @@ - ansible.builtin.assert: that: - - result.changed == False + - not result.changed + - result.state == "absent" diff --git a/tests/integration/targets/grafana_contact_point/tasks/email.yml b/tests/integration/targets/grafana_contact_point/tasks/email.yml index 91aeba8b..1d08ff03 100644 --- a/tests/integration/targets/grafana_contact_point/tasks/email.yml +++ b/tests/integration/targets/grafana_contact_point/tasks/email.yml @@ -14,7 +14,11 @@ - ansible.builtin.assert: that: - - result.changed == True + - result.changed + - result.state == "present" + - result.contact_point.uid == "email" + - result.contact_point.name == "email" + - result.contact_point.type == "email" - name: Create email contact point (idempotency) register: result @@ -31,9 +35,35 @@ - ansible.builtin.assert: that: - - result.changed == False + - not result.changed + - result.state == "present" + - result.contact_point.uid == "email" + - result.contact_point.name == "email" + - result.contact_point.type == "email" -- name: Delete discord contact point +- name: Update email contact point + register: result + community.grafana.grafana_contact_point: + uid: email + name: email + type: email + email_addresses: + - foo@example-update.org + - bar@example-update.org + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed + - result.state == "present" + - result.contact_point.uid == "email" + - result.contact_point.name == "email" + - result.contact_point.type == "email" + - result.diff is defined + +- name: Delete email contact point register: result community.grafana.grafana_contact_point: uid: email @@ -44,10 +74,11 @@ - ansible.builtin.assert: that: - - result.changed == True - - result.state == 'absent' + - result.changed + - result.state == "absent" + - result.contact_point.uid == "email" -- name: Delete discord contact point (idempotency) +- name: Delete email contact point (idempotency) register: result community.grafana.grafana_contact_point: uid: email @@ -58,4 +89,5 @@ - ansible.builtin.assert: that: - - result.changed == False + - not result.changed + - result.state == "absent" diff --git a/tests/integration/targets/grafana_contact_point/tasks/googlechat.yml b/tests/integration/targets/grafana_contact_point/tasks/googlechat.yml index 1243ca04..fdc13b96 100644 --- a/tests/integration/targets/grafana_contact_point/tasks/googlechat.yml +++ b/tests/integration/targets/grafana_contact_point/tasks/googlechat.yml @@ -12,7 +12,11 @@ - ansible.builtin.assert: that: - - result.changed == True + - result.changed + - result.state == "present" + - result.contact_point.uid == "googlechat" + - result.contact_point.name == "googlechat" + - result.contact_point.type == "googlechat" - name: Create googlechat contact point (idempotency) register: result @@ -27,7 +31,31 @@ - ansible.builtin.assert: that: - - result.changed == False + - not result.changed + - result.state == "present" + - result.contact_point.uid == "googlechat" + - result.contact_point.name == "googlechat" + - result.contact_point.type == "googlechat" + +- name: Update googlechat contact point + register: result + community.grafana.grafana_contact_point: + uid: googlechat + name: googlechat + type: googlechat + googlechat_url: https://example-update.org + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed + - result.state == "present" + - result.contact_point.uid == "googlechat" + - result.contact_point.name == "googlechat" + - result.contact_point.type == "googlechat" + - result.diff is defined - name: Delete googlechat contact point register: result @@ -40,7 +68,9 @@ - ansible.builtin.assert: that: - - result.changed == True + - result.changed + - result.state == "absent" + - result.contact_point.uid == "googlechat" - name: Delete googlechat contact point (idempotency) register: result @@ -53,4 +83,5 @@ - ansible.builtin.assert: that: - - result.changed == False + - not result.changed + - result.state == "absent" diff --git a/tests/integration/targets/grafana_contact_point/tasks/kafka.yml b/tests/integration/targets/grafana_contact_point/tasks/kafka.yml index 2503bbdd..3914b6a8 100644 --- a/tests/integration/targets/grafana_contact_point/tasks/kafka.yml +++ b/tests/integration/targets/grafana_contact_point/tasks/kafka.yml @@ -13,7 +13,11 @@ - ansible.builtin.assert: that: - - result.changed == True + - result.changed + - result.state == "present" + - result.contact_point.uid == "kafka" + - result.contact_point.name == "kafka" + - result.contact_point.type == "kafka" - name: Create kafka contact point (idempotentcy) register: result @@ -29,7 +33,32 @@ - ansible.builtin.assert: that: - - result.changed == False + - not result.changed + - result.state == "present" + - result.contact_point.uid == "kafka" + - result.contact_point.name == "kafka" + - result.contact_point.type == "kafka" + +- name: Update kafka contact point + register: result + community.grafana.grafana_contact_point: + uid: kafka + name: kafka + type: kafka + kafka_rest_proxy_url: https://example-update.org + kafka_topic: test-update + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed + - result.state == "present" + - result.contact_point.uid == "kafka" + - result.contact_point.name == "kafka" + - result.contact_point.type == "kafka" + - result.diff is defined - name: Delete kafka contact point register: result @@ -42,7 +71,9 @@ - ansible.builtin.assert: that: - - result.changed == True + - result.changed + - result.state == "absent" + - result.contact_point.uid == "kafka" - name: Delete kafka contact point (idempotency) register: result @@ -55,4 +86,5 @@ - ansible.builtin.assert: that: - - result.changed == False + - not result.changed + - result.state == "absent" diff --git a/tests/integration/targets/grafana_contact_point/tasks/line.yml b/tests/integration/targets/grafana_contact_point/tasks/line.yml index f1f88de9..33c749e8 100644 --- a/tests/integration/targets/grafana_contact_point/tasks/line.yml +++ b/tests/integration/targets/grafana_contact_point/tasks/line.yml @@ -12,7 +12,11 @@ - ansible.builtin.assert: that: - - result.changed == True + - result.changed + - result.state == "present" + - result.contact_point.uid == "line" + - result.contact_point.name == "line" + - result.contact_point.type == "line" - name: Create line contact point (idempotency) register: result @@ -27,7 +31,31 @@ - ansible.builtin.assert: that: - - result.changed == False + - not result.changed + - result.state == "present" + - result.contact_point.uid == "line" + - result.contact_point.name == "line" + - result.contact_point.type == "line" + +- name: Update line contact point + register: result + community.grafana.grafana_contact_point: + uid: line + name: line + type: line + line_token: xxx-update + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed + - result.state == "present" + - result.contact_point.uid == "line" + - result.contact_point.name == "line" + - result.contact_point.type == "line" + - result.diff is defined - name: Delete line contact point register: result @@ -40,7 +68,9 @@ - ansible.builtin.assert: that: - - result.changed == True + - result.changed + - result.state == "absent" + - result.contact_point.uid == "line" - name: Delete line contact point (idempotency) register: result @@ -53,4 +83,5 @@ - ansible.builtin.assert: that: - - result.changed == False + - not result.changed + - result.state == "absent" diff --git a/tests/integration/targets/grafana_contact_point/tasks/opsgenie.yml b/tests/integration/targets/grafana_contact_point/tasks/opsgenie.yml index 6e810e2a..fef2813e 100644 --- a/tests/integration/targets/grafana_contact_point/tasks/opsgenie.yml +++ b/tests/integration/targets/grafana_contact_point/tasks/opsgenie.yml @@ -13,7 +13,11 @@ - ansible.builtin.assert: that: - - result.changed == True + - result.changed + - result.state == "present" + - result.contact_point.uid == "opsgenie" + - result.contact_point.name == "opsgenie" + - result.contact_point.type == "opsgenie" - name: Create opsgenie contact point (idempotency) register: result @@ -29,7 +33,32 @@ - ansible.builtin.assert: that: - - result.changed == False + - not result.changed + - result.state == "present" + - result.contact_point.uid == "opsgenie" + - result.contact_point.name == "opsgenie" + - result.contact_point.type == "opsgenie" + +- name: Update opsgenie contact point + register: result + community.grafana.grafana_contact_point: + uid: opsgenie + name: opsgenie + type: opsgenie + opsgenie_url: https://example-update.org + opsgenie_api_key: xxx-update + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed + - result.state == "present" + - result.contact_point.uid == "opsgenie" + - result.contact_point.name == "opsgenie" + - result.contact_point.type == "opsgenie" + - result.diff is defined - name: Delete opsgenie contact point register: result @@ -42,8 +71,9 @@ - ansible.builtin.assert: that: - - result.changed == True - - result.state == 'absent' + - result.changed + - result.state == "absent" + - result.contact_point.uid == "opsgenie" - name: Delete opsgenie contact point (idempotency) register: result @@ -56,4 +86,5 @@ - ansible.builtin.assert: that: - - result.changed == False + - not result.changed + - result.state == "absent" diff --git a/tests/integration/targets/grafana_contact_point/tasks/pagerduty.yml b/tests/integration/targets/grafana_contact_point/tasks/pagerduty.yml index 3c5e65a9..86c24a6f 100644 --- a/tests/integration/targets/grafana_contact_point/tasks/pagerduty.yml +++ b/tests/integration/targets/grafana_contact_point/tasks/pagerduty.yml @@ -12,7 +12,11 @@ - ansible.builtin.assert: that: - - result.changed == True + - result.changed + - result.state == "present" + - result.contact_point.uid == "pagerduty" + - result.contact_point.name == "pagerduty" + - result.contact_point.type == "pagerduty" - name: Create pagerduty contact point (idempotency) register: result @@ -27,7 +31,31 @@ - ansible.builtin.assert: that: - - result.changed == False + - not result.changed + - result.state == "present" + - result.contact_point.uid == "pagerduty" + - result.contact_point.name == "pagerduty" + - result.contact_point.type == "pagerduty" + +- name: Update pagerduty contact point + register: result + community.grafana.grafana_contact_point: + uid: pagerduty + name: pagerduty + type: pagerduty + pagerduty_integration_key: xxx-update + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed + - result.state == "present" + - result.contact_point.uid == "pagerduty" + - result.contact_point.name == "pagerduty" + - result.contact_point.type == "pagerduty" + - result.diff is defined - name: Delete pagerduty contact point register: result @@ -40,7 +68,9 @@ - ansible.builtin.assert: that: - - result.changed == True + - result.changed + - result.state == "absent" + - result.contact_point.uid == "pagerduty" - name: Delete pagerduty contact point (idempotency) register: result @@ -53,4 +83,5 @@ - ansible.builtin.assert: that: - - result.changed == False + - not result.changed + - result.state == "absent" diff --git a/tests/integration/targets/grafana_contact_point/tasks/pushover.yml b/tests/integration/targets/grafana_contact_point/tasks/pushover.yml index 27ed69be..5a264838 100644 --- a/tests/integration/targets/grafana_contact_point/tasks/pushover.yml +++ b/tests/integration/targets/grafana_contact_point/tasks/pushover.yml @@ -13,7 +13,11 @@ - ansible.builtin.assert: that: - - result.changed == True + - result.changed + - result.state == "present" + - result.contact_point.uid == "pushover" + - result.contact_point.name == "pushover" + - result.contact_point.type == "pushover" - name: Create pushover contact point (idempotency) register: result @@ -29,7 +33,32 @@ - ansible.builtin.assert: that: - - result.changed == False + - not result.changed + - result.state == "present" + - result.contact_point.uid == "pushover" + - result.contact_point.name == "pushover" + - result.contact_point.type == "pushover" + +- name: Update pushover contact point + register: result + community.grafana.grafana_contact_point: + uid: pushover + name: pushover + type: pushover + pushover_api_token: xxx-update + pushover_user_key: yyy-update + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed + - result.state == "present" + - result.contact_point.uid == "pushover" + - result.contact_point.name == "pushover" + - result.contact_point.type == "pushover" + - result.diff is defined - name: Delete pushover contact point register: result @@ -42,7 +71,9 @@ - ansible.builtin.assert: that: - - result.changed == True + - result.changed + - result.state == "absent" + - result.contact_point.uid == "pushover" - name: Delete pushover contact point (idempotency) register: result @@ -55,4 +86,5 @@ - ansible.builtin.assert: that: - - result.changed == False + - not result.changed + - result.state == "absent" diff --git a/tests/integration/targets/grafana_contact_point/tasks/sensugo.yml b/tests/integration/targets/grafana_contact_point/tasks/sensugo.yml index 6cdf0478..16ffab43 100644 --- a/tests/integration/targets/grafana_contact_point/tasks/sensugo.yml +++ b/tests/integration/targets/grafana_contact_point/tasks/sensugo.yml @@ -13,7 +13,11 @@ - ansible.builtin.assert: that: - - result.changed == True + - result.changed + - result.state == "present" + - result.contact_point.uid == "sensugo" + - result.contact_point.name == "sensugo" + - result.contact_point.type == "sensugo" - name: Create sensugo contact point register: result @@ -29,7 +33,32 @@ - ansible.builtin.assert: that: - - result.changed == False + - not result.changed + - result.state == "present" + - result.contact_point.uid == "sensugo" + - result.contact_point.name == "sensugo" + - result.contact_point.type == "sensugo" + +- name: Update sensugo contact point + register: result + community.grafana.grafana_contact_point: + uid: sensugo + name: sensugo + type: sensugo + sensugo_url: https://example-update.org + sensugo_api_key: testapikey-update + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed + - result.state == "present" + - result.contact_point.uid == "sensugo" + - result.contact_point.name == "sensugo" + - result.contact_point.type == "sensugo" + - result.diff is defined - name: Delete sensugo contact point register: result @@ -42,7 +71,9 @@ - ansible.builtin.assert: that: - - result.changed == True + - result.changed + - result.state == "absent" + - result.contact_point.uid == "sensugo" - name: Delete sensugo contact point (idempotency) register: result @@ -55,4 +86,5 @@ - ansible.builtin.assert: that: - - result.changed == False + - not result.changed + - result.state == "absent" diff --git a/tests/integration/targets/grafana_contact_point/tasks/slack.yml b/tests/integration/targets/grafana_contact_point/tasks/slack.yml index 110ec963..b803c041 100644 --- a/tests/integration/targets/grafana_contact_point/tasks/slack.yml +++ b/tests/integration/targets/grafana_contact_point/tasks/slack.yml @@ -14,7 +14,11 @@ - ansible.builtin.assert: that: - - result.changed == True + - result.changed + - result.state == "present" + - result.contact_point.uid == "slack" + - result.contact_point.name == "slack" + - result.contact_point.type == "slack" - name: Check slack contact point (idempotency) register: result @@ -31,7 +35,11 @@ - ansible.builtin.assert: that: - - result.changed == False + - not result.changed + - result.state == "present" + - result.contact_point.uid == "slack" + - result.contact_point.name == "slack" + - result.contact_point.type == "slack" - name: Update slack contact point register: result @@ -39,16 +47,21 @@ uid: slack name: slack type: slack - slack_url: https://hooks.slack.com/services/xxx/yyy/fff - slack_token: testapitoken - slack_recipient: foo + slack_url: https://hooks.slack.com/services/xxx/yyy/update + slack_token: testapitoken-update + slack_recipient: foo-update - ansible.builtin.debug: var: result - ansible.builtin.assert: that: - - result.changed == True + - result.changed + - result.state == "present" + - result.contact_point.uid == "slack" + - result.contact_point.name == "slack" + - result.contact_point.type == "slack" + - result.diff is defined - name: Delete slack contact point register: result @@ -61,7 +74,9 @@ - ansible.builtin.assert: that: - - result.changed == True + - result.changed + - result.state == "absent" + - result.contact_point.uid == "slack" - name: Delete slack contact point (idempotency) register: result @@ -74,4 +89,5 @@ - ansible.builtin.assert: that: - - result.changed == False + - not result.changed + - result.state == "absent" diff --git a/tests/integration/targets/grafana_contact_point/tasks/teams.yml b/tests/integration/targets/grafana_contact_point/tasks/teams.yml index 019b407e..eb0d1e5d 100644 --- a/tests/integration/targets/grafana_contact_point/tasks/teams.yml +++ b/tests/integration/targets/grafana_contact_point/tasks/teams.yml @@ -12,7 +12,11 @@ - ansible.builtin.assert: that: - - result.changed == True + - result.changed + - result.state == "present" + - result.contact_point.uid == "teams" + - result.contact_point.name == "teams" + - result.contact_point.type == "teams" - name: Create teams contact point (idempotency) register: result @@ -27,7 +31,31 @@ - ansible.builtin.assert: that: - - result.changed == False + - not result.changed + - result.state == "present" + - result.contact_point.uid == "teams" + - result.contact_point.name == "teams" + - result.contact_point.type == "teams" + +- name: Update teams contact point + register: result + community.grafana.grafana_contact_point: + uid: teams + name: teams + type: teams + teams_url: https://example-update.org + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed + - result.state == "present" + - result.contact_point.uid == "teams" + - result.contact_point.name == "teams" + - result.contact_point.type == "teams" + - result.diff is defined - name: Delete teams contact point register: result @@ -40,7 +68,9 @@ - ansible.builtin.assert: that: - - result.changed == True + - result.changed + - result.state == "absent" + - result.contact_point.uid == "teams" - name: Delete teams contact point (idempotency) register: result @@ -53,4 +83,5 @@ - ansible.builtin.assert: that: - - result.changed == False + - not result.changed + - result.state == "absent" diff --git a/tests/integration/targets/grafana_contact_point/tasks/telegram.yml b/tests/integration/targets/grafana_contact_point/tasks/telegram.yml index 2fd38a5a..1ff7191b 100644 --- a/tests/integration/targets/grafana_contact_point/tasks/telegram.yml +++ b/tests/integration/targets/grafana_contact_point/tasks/telegram.yml @@ -13,7 +13,11 @@ - ansible.builtin.assert: that: - - result.changed == True + - result.changed + - result.state == "present" + - result.contact_point.uid == "telegram" + - result.contact_point.name == "telegram" + - result.contact_point.type == "telegram" - name: Create telegram contact point register: result @@ -29,7 +33,32 @@ - ansible.builtin.assert: that: - - result.changed == False + - not result.changed + - result.state == "present" + - result.contact_point.uid == "telegram" + - result.contact_point.name == "telegram" + - result.contact_point.type == "telegram" + +- name: Update telegram contact point + register: result + community.grafana.grafana_contact_point: + uid: telegram + name: telegram + type: telegram + telegram_token: xxx-update + telegram_chat_id: yyy-update + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed + - result.state == "present" + - result.contact_point.uid == "telegram" + - result.contact_point.name == "telegram" + - result.contact_point.type == "telegram" + - result.diff is defined - name: Delete telegram contact point register: result @@ -42,7 +71,9 @@ - ansible.builtin.assert: that: - - result.changed == True + - result.changed + - result.state == "absent" + - result.contact_point.uid == "telegram" - name: Delete telegram contact point (idempotency) register: result @@ -55,4 +86,5 @@ - ansible.builtin.assert: that: - - result.changed == False + - not result.changed + - result.state == "absent" diff --git a/tests/integration/targets/grafana_contact_point/tasks/threema.yml b/tests/integration/targets/grafana_contact_point/tasks/threema.yml index 6e17aa51..ac406a4e 100644 --- a/tests/integration/targets/grafana_contact_point/tasks/threema.yml +++ b/tests/integration/targets/grafana_contact_point/tasks/threema.yml @@ -14,7 +14,11 @@ - ansible.builtin.assert: that: - - result.changed == True + - result.changed + - result.state == "present" + - result.contact_point.uid == "threema" + - result.contact_point.name == "threema" + - result.contact_point.type == "threema" - name: Create threema contact point (idempotency) register: result @@ -31,7 +35,33 @@ - ansible.builtin.assert: that: - - result.changed == True + - not result.changed + - result.state == "present" + - result.contact_point.uid == "threema" + - result.contact_point.name == "threema" + - result.contact_point.type == "threema" + +- name: Update threema contact point + register: result + community.grafana.grafana_contact_point: + uid: threema + name: threema + type: threema + threema_gateway_id: "*xxxxxxx-update" + threema_recipient_id: yyyyyyyy-update + threema_api_secret: zzz-update + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed + - result.state == "present" + - result.contact_point.uid == "threema" + - result.contact_point.name == "threema" + - result.contact_point.type == "threema" + - result.diff is defined - name: Delete threema contact point register: result @@ -44,7 +74,9 @@ - ansible.builtin.assert: that: - - result.changed == True + - result.changed + - result.state == "absent" + - result.contact_point.uid == "threema" - name: Delete threema contact point (idempotency) register: result @@ -57,4 +89,5 @@ - ansible.builtin.assert: that: - - result.changed == False + - not result.changed + - result.state == "absent" diff --git a/tests/integration/targets/grafana_contact_point/tasks/victorops.yml b/tests/integration/targets/grafana_contact_point/tasks/victorops.yml index 8ab40387..1ee8c1fa 100644 --- a/tests/integration/targets/grafana_contact_point/tasks/victorops.yml +++ b/tests/integration/targets/grafana_contact_point/tasks/victorops.yml @@ -12,7 +12,11 @@ - ansible.builtin.assert: that: - - result.changed == True + - result.changed + - result.state == "present" + - result.contact_point.uid == "victorops" + - result.contact_point.name == "victorops" + - result.contact_point.type == "victorops" - name: Create victorops contact point (idempotency) register: result @@ -27,7 +31,31 @@ - ansible.builtin.assert: that: - - result.changed == False + - not result.changed + - result.state == "present" + - result.contact_point.uid == "victorops" + - result.contact_point.name == "victorops" + - result.contact_point.type == "victorops" + +- name: Update victorops contact point + register: result + community.grafana.grafana_contact_point: + uid: victorops + name: victorops + type: victorops + victorops_url: https://example-update.org + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed + - result.state == "present" + - result.contact_point.uid == "victorops" + - result.contact_point.name == "victorops" + - result.contact_point.type == "victorops" + - result.diff is defined - name: Delete victorops contact point register: result @@ -40,7 +68,9 @@ - ansible.builtin.assert: that: - - result.changed == True + - result.changed + - result.state == "absent" + - result.contact_point.uid == "victorops" - name: Delete victorops contact point (idempotency) register: result @@ -53,4 +83,5 @@ - ansible.builtin.assert: that: - - result.changed == False + - not result.changed + - result.state == "absent" diff --git a/tests/integration/targets/grafana_contact_point/tasks/webhook.yml b/tests/integration/targets/grafana_contact_point/tasks/webhook.yml index 8ff8ba4c..29dbc246 100644 --- a/tests/integration/targets/grafana_contact_point/tasks/webhook.yml +++ b/tests/integration/targets/grafana_contact_point/tasks/webhook.yml @@ -12,7 +12,11 @@ - ansible.builtin.assert: that: - - result.changed == True + - result.changed + - result.state == "present" + - result.contact_point.uid == "webhook" + - result.contact_point.name == "webhook" + - result.contact_point.type == "webhook" - name: Create webhook contact point (idempotency) register: result @@ -27,7 +31,31 @@ - ansible.builtin.assert: that: - - result.changed == False + - not result.changed + - result.state == "present" + - result.contact_point.uid == "webhook" + - result.contact_point.name == "webhook" + - result.contact_point.type == "webhook" + +- name: Update webhook contact point + register: result + community.grafana.grafana_contact_point: + uid: webhook + name: webhook + type: webhook + webhook_url: https://example-update.org + +- ansible.builtin.debug: + var: result + +- ansible.builtin.assert: + that: + - result.changed + - result.state == "present" + - result.contact_point.uid == "webhook" + - result.contact_point.name == "webhook" + - result.contact_point.type == "webhook" + - result.diff is defined - name: Delete webhook contact point register: result @@ -40,7 +68,9 @@ - ansible.builtin.assert: that: - - result.changed == True + - result.changed + - result.state == "absent" + - result.contact_point.uid == "webhook" - name: Delete webhook contact point (idempotency) register: result @@ -53,4 +83,5 @@ - ansible.builtin.assert: that: - - result.changed == False + - not result.changed + - result.state == "absent" From ae8e157ef8cb97232ed2eefff2260318191e5626 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Tue, 28 May 2024 12:03:03 +0200 Subject: [PATCH 51/52] fix: uid required --- plugins/modules/grafana_contact_point.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/plugins/modules/grafana_contact_point.py b/plugins/modules/grafana_contact_point.py index ef574642..88a7a0c1 100644 --- a/plugins/modules/grafana_contact_point.py +++ b/plugins/modules/grafana_contact_point.py @@ -1161,7 +1161,7 @@ def main(): "wecom", ], ), - uid=dict(type="str"), + uid=dict(required=True, type="str"), # alertmanager alertmanager_password=dict(type="str", no_log=True), alertmanager_url=dict(type="str"), @@ -1311,8 +1311,7 @@ def main(): required_together=[["url_username", "url_password", "org_id"]], mutually_exclusive=[["url_username", "grafana_api_key"]], required_if=[ - ["state", "present", ["uid", "name", "type"]], - ["state", "absent", ["uid"]], + ["state", "present", ["name", "type"]], ["type", "alertmanager", ["alertmanager_url"]], ["type", "dingding", ["dingding_url"]], ["type", "discord", ["discord_url"]], From 83224635b8a02a6740803f2ba98a2df23219efb9 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Tue, 28 May 2024 13:50:16 +0200 Subject: [PATCH 52/52] feat: role task arguments --- roles/grafana/README.md | 8 +-- roles/grafana/tasks/main.yml | 134 +++++++++++++++++++++++++++++++++-- 2 files changed, 131 insertions(+), 11 deletions(-) diff --git a/roles/grafana/README.md b/roles/grafana/README.md index 09901eff..a6ce7507 100644 --- a/roles/grafana/README.md +++ b/roles/grafana/README.md @@ -180,8 +180,8 @@ Configure Grafana organizations, dashboards, folders, datasources, teams and use | org_name | no | | provisioning | no | | state | no | -| type | yes | -| uid | no | +| type | no | +| uid | yes | | alertmanager_password | no | | alertmanager_url | no | | alertmanager_username | no | @@ -198,9 +198,9 @@ Configure Grafana organizations, dashboards, folders, datasources, teams and use | email_message | no | | email_single | no | | email_subject | no | -| googlechat_url | no | | googlechat_message | no | | googlechat_title | no | +| googlechat_url | no | | kafka_api_version | no | | kafka_cluster_id | no | | kafka_description | no | @@ -243,12 +243,12 @@ Configure Grafana organizations, dashboards, folders, datasources, teams and use | pushover_upload_image | no | | pushover_user_key | no | | sensugo_api_key | no | -| sensugo_url | no | | sensugo_check | no | | sensugo_entity | no | | sensugo_handler | no | | sensugo_message | no | | sensugo_namespace | no | +| sensugo_url | no | | slack_endpoint_url | no | | slack_icon_emoji | no | | slack_icon_url | no | diff --git a/roles/grafana/tasks/main.yml b/roles/grafana/tasks/main.yml index 347ef1c6..170d9337 100644 --- a/roles/grafana/tasks/main.yml +++ b/roles/grafana/tasks/main.yml @@ -91,15 +91,135 @@ - name: Manage contact point community.grafana.grafana_contact_point: - email_addresses: "{{ contact_point.email_addresses | default(omit) }}" - email_single: "{{ contact_point.email_single | default(omit) }}" - is_default: "{{ contact_point.is_default | default(omit) }}" - name: "{{ contact_point.name }}" + disable_resolve_message: "{{ contact_point.disable_resolve_message | default(omit) }}" + include_image: "{{ contact_point.include_image | default(omit) }}" + name: "{{ contact_point.name | default(omit) }}" org_id: "{{ contact_point.org_id | default(omit) }}" - org_name: "{{ contact_point.org_id | default(omit) }}" + org_name: "{{ contact_point.org_name | default(omit) }}" + provisioning: "{{ contact_point.provisioning | default(omit) }}" state: "{{ contact_point.state | default(omit) }}" - type: "{{ contact_point.type }}" - uid: "{{ contact_point.uid | default(omit) }}" + type: "{{ contact_point.type | default(omit) }}" + uid: "{{ contact_point.uid }}" + alertmanager_password: "{{ contact_point.alertmanager_password | default(omit) }}" + alertmanager_url: "{{ contact_point.alertmanager_url | default(omit) }}" + alertmanager_username: "{{ contact_point.alertmanager_username | default(omit) }}" + dingding_message: "{{ contact_point.dingding_message | default(omit) }}" + dingding_message_type: "{{ contact_point.dingding_message_type | default(omit) }}" + dingding_title: "{{ contact_point.dingding_title | default(omit) }}" + dingding_url: "{{ contact_point.dingding_url | default(omit) }}" + discord_avatar_url: "{{ contact_point.discord_avatar_url | default(omit) }}" + discord_message: "{{ contact_point.discord_message | default(omit) }}" + discord_title: "{{ contact_point.discord_title | default(omit) }}" + discord_url: "{{ contact_point.discord_url | default(omit) }}" + discord_use_username: "{{ contact_point.discord_use_username | default(omit) }}" + email_addresses: "{{ contact_point.email_addresses | default(omit) }}" + email_message: "{{ contact_point.email_message | default(omit) }}" + email_single: "{{ contact_point.email_single | default(omit) }}" + email_subject: "{{ contact_point.email_subject | default(omit) }}" + googlechat_message: "{{ contact_point.googlechat_message | default(omit) }}" + googlechat_title: "{{ contact_point.googlechat_title | default(omit) }}" + googlechat_url: "{{ contact_point.googlechat_url | default(omit) }}" + kafka_api_version: "{{ contact_point.kafka_api_version | default(omit) }}" + kafka_cluster_id: "{{ contact_point.kafka_cluster_id | default(omit) }}" + kafka_description: "{{ contact_point.kafka_description | default(omit) }}" + kafka_details: "{{ contact_point.kafka_details | default(omit) }}" + kafka_password: "{{ contact_point.kafka_password | default(omit) }}" + kafka_rest_proxy_url: "{{ contact_point.kafka_rest_proxy_url | default(omit) }}" + kafka_topic: "{{ contact_point.kafka_topic | default(omit) }}" + kafka_username: "{{ contact_point.kafka_username | default(omit) }}" + line_description: "{{ contact_point.line_description | default(omit) }}" + line_title: "{{ contact_point.line_title | default(omit) }}" + line_token: "{{ contact_point.line_token | default(omit) }}" + opsgenie_api_key: "{{ contact_point.opsgenie_api_key | default(omit) }}" + opsgenie_auto_close: "{{ contact_point.opsgenie_auto_close | default(omit) }}" + opsgenie_description: "{{ contact_point.opsgenie_description | default(omit) }}" + opsgenie_message: "{{ contact_point.opsgenie_message | default(omit) }}" + opsgenie_override_priority: "{{ contact_point.opsgenie_override_priority | default(omit) }}" + opsgenie_responders: "{{ contact_point.opsgenie_responders | default(omit) }}" + opsgenie_send_tags_as: "{{ contact_point.opsgenie_send_tags_as | default(omit) }}" + opsgenie_url: "{{ contact_point.opsgenie_url | default(omit) }}" + pagerduty_class: "{{ contact_point.pagerduty_class | default(omit) }}" + pagerduty_client: "{{ contact_point.pagerduty_client | default(omit) }}" + pagerduty_client_url: "{{ contact_point.pagerduty_client_url | default(omit) }}" + pagerduty_component: "{{ contact_point.pagerduty_component | default(omit) }}" + pagerduty_details: "{{ contact_point.pagerduty_details | default(omit) }}" + pagerduty_group: "{{ contact_point.pagerduty_group | default(omit) }}" + pagerduty_integration_key: "{{ contact_point.pagerduty_integration_key | default(omit) }}" + pagerduty_severity: "{{ contact_point.pagerduty_severity | default(omit) }}" + pagerduty_source: "{{ contact_point.pagerduty_source | default(omit) }}" + pagerduty_summary: "{{ contact_point.pagerduty_summary | default(omit) }}" + pushover_api_token: "{{ contact_point.pushover_api_token | default(omit) }}" + pushover_devices: "{{ contact_point.pushover_devices | default(omit) }}" + pushover_expire: "{{ contact_point.pushover_expire | default(omit) }}" + pushover_message: "{{ contact_point.pushover_message | default(omit) }}" + pushover_ok_priority: "{{ contact_point.pushover_ok_priority | default(omit) }}" + pushover_ok_sound: "{{ contact_point.pushover_ok_sound | default(omit) }}" + pushover_priority: "{{ contact_point.pushover_priority | default(omit) }}" + pushover_retry: "{{ contact_point.pushover_retry | default(omit) }}" + pushover_sound: "{{ contact_point.pushover_sound | default(omit) }}" + pushover_title: "{{ contact_point.pushover_title | default(omit) }}" + pushover_upload_image: "{{ contact_point.pushover_upload_image | default(omit) }}" + pushover_user_key: "{{ contact_point.pushover_user_key | default(omit) }}" + sensugo_api_key: "{{ contact_point.sensugo_api_key | default(omit) }}" + sensugo_check: "{{ contact_point.sensugo_check | default(omit) }}" + sensugo_entity: "{{ contact_point.sensugo_entity | default(omit) }}" + sensugo_handler: "{{ contact_point.sensugo_handler | default(omit) }}" + sensugo_message: "{{ contact_point.sensugo_message | default(omit) }}" + sensugo_namespace: "{{ contact_point.sensugo_namespace | default(omit) }}" + sensugo_url: "{{ contact_point.sensugo_url | default(omit) }}" + slack_endpoint_url: "{{ contact_point.slack_endpoint_url | default(omit) }}" + slack_icon_emoji: "{{ contact_point.slack_icon_emoji | default(omit) }}" + slack_icon_url: "{{ contact_point.slack_icon_url | default(omit) }}" + slack_mention_channel: "{{ contact_point.slack_mention_channel | default(omit) }}" + slack_mention_groups: "{{ contact_point.slack_mention_groups | default(omit) }}" + slack_mention_users: "{{ contact_point.slack_mention_users | default(omit) }}" + slack_recipient: "{{ contact_point.slack_recipient | default(omit) }}" + slack_text: "{{ contact_point.slack_text | default(omit) }}" + slack_title: "{{ contact_point.slack_title | default(omit) }}" + slack_token: "{{ contact_point.slack_token | default(omit) }}" + slack_url: "{{ contact_point.slack_url | default(omit) }}" + slack_username: "{{ contact_point.slack_username | default(omit) }}" + teams_message: "{{ contact_point.teams_message | default(omit) }}" + teams_section_title: "{{ contact_point.teams_section_title | default(omit) }}" + teams_title: "{{ contact_point.teams_title | default(omit) }}" + teams_url: "{{ contact_point.teams_url | default(omit) }}" + telegram_chat_id: "{{ contact_point.telegram_chat_id | default(omit) }}" + telegram_disable_notifications: "{{ contact_point.telegram_disable_notifications | default(omit) }}" + telegram_message: "{{ contact_point.telegram_message | default(omit) }}" + telegram_parse_mode: "{{ contact_point.telegram_parse_mode | default(omit) }}" + telegram_protect_content: "{{ contact_point.telegram_protect_content | default(omit) }}" + telegram_token: "{{ contact_point.telegram_token | default(omit) }}" + telegram_web_page_view: "{{ contact_point.telegram_web_page_view | default(omit) }}" + threema_api_secret: "{{ contact_point.threema_api_secret | default(omit) }}" + threema_description: "{{ contact_point.threema_description | default(omit) }}" + threema_gateway_id: "{{ contact_point.threema_gateway_id | default(omit) }}" + threema_recipient_id: "{{ contact_point.threema_recipient_id | default(omit) }}" + threema_title: "{{ contact_point.threema_title | default(omit) }}" + victorops_description: "{{ contact_point.victorops_description | default(omit) }}" + victorops_message_type: "{{ contact_point.victorops_message_type | default(omit) }}" + victorops_title: "{{ contact_point.victorops_title | default(omit) }}" + victorops_url: "{{ contact_point.victorops_url | default(omit) }}" + webex_api_url: "{{ contact_point.webex_api_url | default(omit) }}" + webex_message: "{{ contact_point.webex_message | default(omit) }}" + webex_room_id: "{{ contact_point.webex_room_id | default(omit) }}" + webex_token: "{{ contact_point.webex_token | default(omit) }}" + webhook_authorization_credentials: "{{ contact_point.webhook_authorization_credentials | default(omit) }}" + webhook_authorization_scheme: "{{ contact_point.webhook_authorization_scheme | default(omit) }}" + webhook_http_method: "{{ contact_point.webhook_http_method | default(omit) }}" + webhook_max_alerts: "{{ contact_point.webhook_max_alerts | default(omit) }}" + webhook_message: "{{ contact_point.webhook_message | default(omit) }}" + webhook_password: "{{ contact_point.webhook_password | default(omit) }}" + webhook_title: "{{ contact_point.webhook_title | default(omit) }}" + webhook_url: "{{ contact_point.webhook_url | default(omit) }}" + webhook_username: "{{ contact_point.webhook_username | default(omit) }}" + wecom_agent_id: "{{ contact_point.wecom_agent_id | default(omit) }}" + wecom_corp_id: "{{ contact_point.wecom_corp_id | default(omit) }}" + wecom_message: "{{ contact_point.wecom_message | default(omit) }}" + wecom_msg_type: "{{ contact_point.wecom_msg_type | default(omit) }}" + wecom_secret: "{{ contact_point.wecom_secret | default(omit) }}" + wecom_title: "{{ contact_point.wecom_title | default(omit) }}" + wecom_to_user: "{{ contact_point.wecom_to_user | default(omit) }}" + wecom_url: "{{ contact_point.wecom_url | default(omit) }}" loop: "{{ grafana_contact_points }}" loop_control: {loop_var: contact_point} tags: contact_point