From 7547432236c77088fb04db1d24e1dab973e88483 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Mon, 27 May 2024 09:47:23 +0200 Subject: [PATCH 001/120] docs: role readme org_id and org_name --- roles/grafana/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/roles/grafana/README.md b/roles/grafana/README.md index f76e687f..b26a9044 100644 --- a/roles/grafana/README.md +++ b/roles/grafana/README.md @@ -168,6 +168,8 @@ Configure Grafana organizations, dashboards, folders, datasources, teams and use | created_by | yes | | ends_at | yes | | matchers | yes | +| org_id | no | +| org_name | no | | starts_at | yes | | state | no | From 8ce66da0e0d5e455bdeac5428df9e3c804ab1fda Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Mon, 27 May 2024 09:48:06 +0200 Subject: [PATCH 002/120] feat: role task org_id and org_name --- roles/grafana/tasks/main.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/roles/grafana/tasks/main.yml b/roles/grafana/tasks/main.yml index 1665a856..f5e41021 100644 --- a/roles/grafana/tasks/main.yml +++ b/roles/grafana/tasks/main.yml @@ -206,9 +206,11 @@ community.grafana.grafana_silence: comment: "{{ silence.comment }}" created_by: "{{ silence.created_by }}" - starts_at: "{{ silence.starts_at }}" ends_at: "{{ silence.ends_at }}" matchers: "{{ silence.matchers }}" + org_id: "{{ datasource.org_id | default(omit) }}" + org_name: "{{ datasource.org_name | default(omit) }}" + starts_at: "{{ silence.starts_at }}" state: "{{ silence.state | default(omit) }}" loop: "{{ grafana_silences }}" loop_control: {loop_var: silence} From 9d88bb0df548d1843670fe59651e578bdf57ccea Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Mon, 27 May 2024 09:48:41 +0200 Subject: [PATCH 003/120] docs: module args org_id and org_name --- plugins/modules/grafana_silence.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/plugins/modules/grafana_silence.py b/plugins/modules/grafana_silence.py index 35f54a3d..2b2897cb 100644 --- a/plugins/modules/grafana_silence.py +++ b/plugins/modules/grafana_silence.py @@ -30,6 +30,18 @@ requirements: - The Alertmanager API is only available starting Grafana 8 and the module will fail if the server version is lower than version 8. options: + org_id: + description: + - The Grafana organization ID where the silence will be created or deleted. + - Not used when I(grafana_api_key) is set, because the grafana_api_key only belongs to one organization. + - Mutually exclusive with C(org_name). + default: 1 + type: int + org_name: + description: + - The Grafana organization name where the silence will be created or deleted. + - Not used when I(grafana_api_key) is set, because the grafana_api_key only belongs to one organization. + - Mutually exclusive with C(org_id). comment: description: - The comment that describes the silence. From 213f3fd6343db811b449a2c8092ca48d4a6bae9f Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Mon, 27 May 2024 09:49:56 +0200 Subject: [PATCH 004/120] feat: class init switch org --- plugins/modules/grafana_silence.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/plugins/modules/grafana_silence.py b/plugins/modules/grafana_silence.py index 2b2897cb..19932be9 100644 --- a/plugins/modules/grafana_silence.py +++ b/plugins/modules/grafana_silence.py @@ -193,9 +193,10 @@ class GrafanaError(Exception): class GrafanaSilenceInterface(object): def __init__(self, module): self._module = module + self.grafana_url = base.clean_url(module.params.get("url")) + self.org_id = None # {{{ Authentication header self.headers = {"Content-Type": "application/json"} - module.params["force_basic_auth"] = True if module.params.get("grafana_api_key", None): self.headers["Authorization"] = ( "Bearer %s" % module.params["grafana_api_key"] @@ -204,8 +205,14 @@ 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.switch_organization(self.org_id) # }}} - self.grafana_url = base.clean_url(module.params.get("url")) + if module.params.get("skip_version_check") is False: try: grafana_version = self.get_version() From d59bec9153b526dea83a492804ef70e1f83b9689 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Mon, 27 May 2024 09:50:27 +0200 Subject: [PATCH 005/120] feat: switch org and get org functions --- plugins/modules/grafana_silence.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/plugins/modules/grafana_silence.py b/plugins/modules/grafana_silence.py index 19932be9..fd8d1701 100644 --- a/plugins/modules/grafana_silence.py +++ b/plugins/modules/grafana_silence.py @@ -252,6 +252,21 @@ def _send_request(self, url, data=None, headers=None, method="GET"): failed=True, msg="Grafana Silences API answered with HTTP %d" % status_code ) + def switch_organization(self, org_id): + url = "/api/user/using/%d" % org_id + self._send_request(url, headers=self.headers, method="POST") + + def organization_by_name(self, org_name): + url = "/api/user/orgs" + organizations = self._send_request(url, headers=self.headers, method="GET") + orga = next((org for org in organizations if org["name"] == org_name)) + if orga: + return orga["orgId"] + + return self._module.fail_json( + failed=True, msg="Current user isn't member of organization: %s" % org_name + ) + def get_version(self): url = "/api/health" response = self._send_request( From 0eb1ea58a18351c1654b6636823a14654ca02da8 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Mon, 27 May 2024 09:50:46 +0200 Subject: [PATCH 006/120] feat: module args --- plugins/modules/grafana_silence.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/plugins/modules/grafana_silence.py b/plugins/modules/grafana_silence.py index fd8d1701..53dea9cc 100644 --- a/plugins/modules/grafana_silence.py +++ b/plugins/modules/grafana_silence.py @@ -344,23 +344,24 @@ def setup_module_object(): argument_spec = base.grafana_argument_spec() argument_spec.update( comment=dict(type="str", required=True), - state=dict(type="str", choices=["present", "absent"], default="present"), created_by=dict(type="str", required=True), - starts_at=dict(type="str", required=True), ends_at=dict(type="str", required=True), matchers=dict(type="list", elements="dict", required=True), + org_id=dict(default=1, type="int"), + org_name=dict(type="str"), skip_version_check=dict(type="bool", default=False), + starts_at=dict(type="str", required=True), + state=dict(type="str", choices=["present", "absent"], default="present"), ) def main(): - module = setup_module_object() comment = module.params["comment"] created_by = module.params["created_by"] - starts_at = module.params["starts_at"] ends_at = module.params["ends_at"] matchers = module.params["matchers"] + starts_at = module.params["starts_at"] state = module.params["state"] changed = False @@ -372,7 +373,6 @@ def main(): ) if state == "present": - if not silence: silence = grafana_iface.create_silence( comment, created_by, starts_at, ends_at, matchers @@ -396,6 +396,7 @@ def main(): changed=changed, msg="Silence does not exist", ) + module.exit_json(failed=failed, changed=changed, silence=silence) From f5d059313672c3abab68e83f6119fdf5f769db7e Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Mon, 27 May 2024 09:53:41 +0200 Subject: [PATCH 007/120] docs: changelog fragment --- changelogs/fragments/371-silence-org-switch.yml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 changelogs/fragments/371-silence-org-switch.yml diff --git a/changelogs/fragments/371-silence-org-switch.yml b/changelogs/fragments/371-silence-org-switch.yml new file mode 100644 index 00000000..140237ad --- /dev/null +++ b/changelogs/fragments/371-silence-org-switch.yml @@ -0,0 +1,3 @@ +--- +minor_changes: + - add org switch by `org_id` and `org_name` in `grafana_silence` From c1946f9076b8a9be38f4d6c537accb96fd2344e4 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Mon, 27 May 2024 10:03:04 +0200 Subject: [PATCH 008/120] test: recode silence tests and add org tests --- .../grafana_silence/tasks/create-delete.yml | 83 +++++++++++++++++ .../targets/grafana_silence/tasks/main.yml | 91 +------------------ .../targets/grafana_silence/tasks/org.yml | 7 ++ 3 files changed, 94 insertions(+), 87 deletions(-) create mode 100644 tests/integration/targets/grafana_silence/tasks/create-delete.yml create mode 100644 tests/integration/targets/grafana_silence/tasks/org.yml diff --git a/tests/integration/targets/grafana_silence/tasks/create-delete.yml b/tests/integration/targets/grafana_silence/tasks/create-delete.yml new file mode 100644 index 00000000..1561eb59 --- /dev/null +++ b/tests/integration/targets/grafana_silence/tasks/create-delete.yml @@ -0,0 +1,83 @@ +--- +- module_defaults: + community.grafana.grafana_silence: + url: "{{ grafana_url }}" + url_username: "{{ grafana_username }}" + url_password: "{{ grafana_password }}" + block: + - name: Create new silence + community.grafana.grafana_silence: + comment: "a testcomment" + created_by: "me" + starts_at: "2029-07-29T08:45:45.000Z" + ends_at: "2029-07-29T08:55:45.000Z" + matchers: + - isEqual: true + isRegex: true + name: environment + value: test + state: present + register: result + - assert: + that: + - "result.changed == true" + - "result.failed == false" + - "result.silence.id != ''" + + - name: Check idempotency on silence creation + community.grafana.grafana_silence: + comment: "a testcomment" + created_by: "me" + starts_at: "2029-07-29T08:45:45.000Z" + ends_at: "2029-07-29T08:55:45.000Z" + matchers: + - isEqual: true + isRegex: true + name: environment + value: test + state: present + register: result + - assert: + that: + - "result.changed == false" + - "result.msg != ''" + + - name: Delete the silence + community.grafana.grafana_silence: + comment: "a testcomment" + created_by: "me" + starts_at: "2029-07-29T08:45:45.000Z" + ends_at: "2029-07-29T08:55:45.000Z" + matchers: + - isEqual: true + isRegex: true + name: environment + value: test + state: absent + register: result + - assert: + that: + - "result.changed == true" + - "result.failed == false" + - "result.silence.id != ''" + - - "result.silence.createdBy != 'me'" + + - name: Check idempotency on silence deletion + community.grafana.grafana_silence: + comment: "a testcomment" + created_by: "me" + starts_at: "2029-07-29T08:45:45.000Z" + ends_at: "2029-07-29T08:55:45.000Z" + matchers: + - isEqual: true + isRegex: true + name: environment + value: test + state: absent + register: result + ignore_errors: yes + - assert: + that: + - "result.changed == false" + - "result.failed == false" + - "result.msg == 'Silence does not exist'" diff --git a/tests/integration/targets/grafana_silence/tasks/main.yml b/tests/integration/targets/grafana_silence/tasks/main.yml index ad4cfa25..f8756077 100644 --- a/tests/integration/targets/grafana_silence/tasks/main.yml +++ b/tests/integration/targets/grafana_silence/tasks/main.yml @@ -1,89 +1,6 @@ --- -- name: Create new silence - community.grafana.grafana_silence: - url: "{{ grafana_url }}" - url_username: "{{ grafana_username }}" - url_password: "{{ grafana_password }}" - comment: "a testcomment" - created_by: "me" - starts_at: "2029-07-29T08:45:45.000Z" - ends_at: "2029-07-29T08:55:45.000Z" - matchers: - - isEqual: true - isRegex: true - name: environment - value: test - state: present - register: result -- assert: - that: - - "result.changed == true" - - "result.failed == false" - - "result.silence.id != ''" +- name: Silence creation and deletion + ansible.builtin.include_tasks: create-delete.yml -- name: Check idempotency on silence creation - community.grafana.grafana_silence: - url: "{{ grafana_url }}" - url_username: "{{ grafana_username }}" - url_password: "{{ grafana_password }}" - comment: "a testcomment" - created_by: "me" - starts_at: "2029-07-29T08:45:45.000Z" - ends_at: "2029-07-29T08:55:45.000Z" - matchers: - - isEqual: true - isRegex: true - name: environment - value: test - state: present - register: result -- assert: - that: - - "result.changed == false" - - "result.msg != ''" - -- name: Delete the silence - community.grafana.grafana_silence: - url: "{{ grafana_url }}" - url_username: "{{ grafana_username }}" - url_password: "{{ grafana_password }}" - comment: "a testcomment" - created_by: "me" - starts_at: "2029-07-29T08:45:45.000Z" - ends_at: "2029-07-29T08:55:45.000Z" - matchers: - - isEqual: true - isRegex: true - name: environment - value: test - state: absent - register: result -- assert: - that: - - "result.changed == true" - - "result.failed == false" - - "result.silence.id != ''" - - - "result.silence.createdBy != 'me'" - -- name: Check idempotency on silence deletion - community.grafana.grafana_silence: - url: "{{ grafana_url }}" - url_username: "{{ grafana_username }}" - url_password: "{{ grafana_password }}" - comment: "a testcomment" - created_by: "me" - starts_at: "2029-07-29T08:45:45.000Z" - ends_at: "2029-07-29T08:55:45.000Z" - matchers: - - isEqual: true - isRegex: true - name: environment - value: test - state: absent - register: result - ignore_errors: yes -- assert: - that: - - "result.changed == false" - - "result.failed == false" - - "result.msg == 'Silence does not exist'" \ No newline at end of file +- name: Silence creation and deletion for organization + ansible.builtin.include_tasks: org.yml diff --git a/tests/integration/targets/grafana_silence/tasks/org.yml b/tests/integration/targets/grafana_silence/tasks/org.yml new file mode 100644 index 00000000..514232f9 --- /dev/null +++ b/tests/integration/targets/grafana_silence/tasks/org.yml @@ -0,0 +1,7 @@ +--- +- module_defaults: + community.grafana.grafana_silence: + org_name: Main Org. + block: + - name: Silence creation and deletion + ansible.builtin.include_tasks: create-delete.yml From 747034109bd0a6853331c604875c708c794ec42b Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Mon, 27 May 2024 10:09:09 +0200 Subject: [PATCH 009/120] docs: org name type --- plugins/modules/grafana_silence.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/modules/grafana_silence.py b/plugins/modules/grafana_silence.py index 53dea9cc..7d0421ec 100644 --- a/plugins/modules/grafana_silence.py +++ b/plugins/modules/grafana_silence.py @@ -42,6 +42,7 @@ - The Grafana organization name where the silence will be created or deleted. - Not used when I(grafana_api_key) is set, because the grafana_api_key only belongs to one organization. - Mutually exclusive with C(org_id). + type: str comment: description: - The comment that describes the silence. From 2a031d0e91dd2403fb5b63addee0fa8926ae704d Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Mon, 27 May 2024 10:24:45 +0200 Subject: [PATCH 010/120] test: mock test called once with to with --- .../modules/grafana/grafana_silence/test_grafana_silence.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/modules/grafana/grafana_silence/test_grafana_silence.py b/tests/unit/modules/grafana/grafana_silence/test_grafana_silence.py index 96522c2a..c2bb1df3 100644 --- a/tests/unit/modules/grafana/grafana_silence/test_grafana_silence.py +++ b/tests/unit/modules/grafana/grafana_silence/test_grafana_silence.py @@ -136,7 +136,7 @@ def test_create_silence_new_silence( } ], ) - mock_fetch_url.assert_called_once_with( + mock_fetch_url.assert_called_with( module, "https://grafana.example.com/api/alertmanager/grafana/api/v2/silences", data=json.dumps( @@ -198,7 +198,7 @@ def test_delete_silence(self, mock_fetch_url, mock_get_version): grafana_iface = grafana_silence.GrafanaSilenceInterface(module) silence_id = "470b7116-8f06-4bb6-9e6c-6258aa92218e" result = grafana_iface.delete_silence(silence_id) - mock_fetch_url.assert_called_once_with( + mock_fetch_url.assert_called_with( module, "https://grafana.example.com/api/alertmanager/grafana/api/v2/silence/470b7116-8f06-4bb6-9e6c-6258aa92218e", data=None, From 1aca3d8159aa8e7ebc10f903fbd9b81361cbab0d Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Thu, 11 Apr 2024 10:32:30 +0200 Subject: [PATCH 011/120] fix: remove message argument --- plugins/modules/grafana_dashboard.py | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/plugins/modules/grafana_dashboard.py b/plugins/modules/grafana_dashboard.py index 77a5a756..60cd5b86 100644 --- a/plugins/modules/grafana_dashboard.py +++ b/plugins/modules/grafana_dashboard.py @@ -81,8 +81,6 @@ description: - Set a commit message for the version history. - Only used when C(state) is C(present). - - C(message) alias is deprecated in Ansible 2.10, since it is used internally by Ansible Core Engine. - aliases: [ 'message' ] type: str extends_documentation_fragment: - community.grafana.basic_auth @@ -620,15 +618,7 @@ def main(): dashboard_id=dict(type="str"), dashboard_revision=dict(type="str", default="1"), overwrite=dict(type="bool", default=False), - commit_message=dict( - type="str", - aliases=["message"], - deprecated_aliases=[ - dict( - name="message", version="2.0.0", collection_name="community.grafana" - ) - ], - ), + commit_message=dict(type="str"), ) module = AnsibleModule( argument_spec=argument_spec, @@ -647,11 +637,6 @@ def main(): module.params["url"] = clean_url(module.params["url"]) - if "message" in module.params: - module.fail_json( - msg="'message' is reserved keyword, please change this parameter to 'commit_message'" - ) - try: if module.params["state"] == "present": result = grafana_create_dashboard(module, module.params) From 3ea35d0de8358216f181322fda61760a17d4029a Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Tue, 28 May 2024 13:01:06 +0200 Subject: [PATCH 012/120] docs: changelog fragment --- changelogs/fragments/372-rm-dashboard-message-argument.yml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 changelogs/fragments/372-rm-dashboard-message-argument.yml diff --git a/changelogs/fragments/372-rm-dashboard-message-argument.yml b/changelogs/fragments/372-rm-dashboard-message-argument.yml new file mode 100644 index 00000000..3f54afa5 --- /dev/null +++ b/changelogs/fragments/372-rm-dashboard-message-argument.yml @@ -0,0 +1,3 @@ +--- +removed_features: + - removed deprecated `message` argument in `grafana_dashboard` From 9874869c6ae126829b02b5a06ee4583df052f512 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Tue, 28 May 2024 13:21:17 +0200 Subject: [PATCH 013/120] ci: remove sanity ignore files active tested versions --- tests/sanity/ignore-2.15.txt | 1 - tests/sanity/ignore-2.16.txt | 1 - tests/sanity/ignore-2.17.txt | 4 ---- 3 files changed, 6 deletions(-) delete mode 100644 tests/sanity/ignore-2.15.txt delete mode 100644 tests/sanity/ignore-2.16.txt delete mode 100644 tests/sanity/ignore-2.17.txt diff --git a/tests/sanity/ignore-2.15.txt b/tests/sanity/ignore-2.15.txt deleted file mode 100644 index 0a40a23d..00000000 --- a/tests/sanity/ignore-2.15.txt +++ /dev/null @@ -1 +0,0 @@ -plugins/modules/grafana_dashboard.py validate-modules:invalid-argument-name diff --git a/tests/sanity/ignore-2.16.txt b/tests/sanity/ignore-2.16.txt deleted file mode 100644 index 0a40a23d..00000000 --- a/tests/sanity/ignore-2.16.txt +++ /dev/null @@ -1 +0,0 @@ -plugins/modules/grafana_dashboard.py validate-modules:invalid-argument-name diff --git a/tests/sanity/ignore-2.17.txt b/tests/sanity/ignore-2.17.txt deleted file mode 100644 index 5c82494f..00000000 --- a/tests/sanity/ignore-2.17.txt +++ /dev/null @@ -1,4 +0,0 @@ -plugins/modules/grafana_dashboard.py validate-modules:invalid-argument-name -tests/unit/modules/grafana/grafana_plugin/test_grafana_plugin.py pep8:W291 -hacking/check_fragment.sh shebang -hacking/find_grafana_versions.py shebang From 125308ef45f5998633364c19e84cbf628b7403b5 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Tue, 28 May 2024 13:25:55 +0200 Subject: [PATCH 014/120] ci: remove sanity ignore files active tested versions --- tests/sanity/ignore-2.18.txt | 1 - 1 file changed, 1 deletion(-) delete mode 100644 tests/sanity/ignore-2.18.txt diff --git a/tests/sanity/ignore-2.18.txt b/tests/sanity/ignore-2.18.txt deleted file mode 100644 index 0a40a23d..00000000 --- a/tests/sanity/ignore-2.18.txt +++ /dev/null @@ -1 +0,0 @@ -plugins/modules/grafana_dashboard.py validate-modules:invalid-argument-name From 31876c1c0a16f0f5cd65925d811689841f5016fb Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Tue, 28 May 2024 13:22:40 +0200 Subject: [PATCH 015/120] test: remove all sanity ignore files --- tests/sanity/ignore-2.10.txt | 4 ---- tests/sanity/ignore-2.11.txt | 6 ------ tests/sanity/ignore-2.12.txt | 4 ---- tests/sanity/ignore-2.13.txt | 4 ---- tests/sanity/ignore-2.14.txt | 4 ---- tests/sanity/ignore-2.9.txt | 3 --- 6 files changed, 25 deletions(-) delete mode 100644 tests/sanity/ignore-2.10.txt delete mode 100644 tests/sanity/ignore-2.11.txt delete mode 100644 tests/sanity/ignore-2.12.txt delete mode 100644 tests/sanity/ignore-2.13.txt delete mode 100644 tests/sanity/ignore-2.14.txt delete mode 100644 tests/sanity/ignore-2.9.txt diff --git a/tests/sanity/ignore-2.10.txt b/tests/sanity/ignore-2.10.txt deleted file mode 100644 index 5c82494f..00000000 --- a/tests/sanity/ignore-2.10.txt +++ /dev/null @@ -1,4 +0,0 @@ -plugins/modules/grafana_dashboard.py validate-modules:invalid-argument-name -tests/unit/modules/grafana/grafana_plugin/test_grafana_plugin.py pep8:W291 -hacking/check_fragment.sh shebang -hacking/find_grafana_versions.py shebang diff --git a/tests/sanity/ignore-2.11.txt b/tests/sanity/ignore-2.11.txt deleted file mode 100644 index 68cb96ab..00000000 --- a/tests/sanity/ignore-2.11.txt +++ /dev/null @@ -1,6 +0,0 @@ -plugins/modules/grafana_dashboard.py validate-modules:invalid-argument-name -tests/unit/modules/grafana/grafana_plugin/test_grafana_plugin.py pep8:W291 -hacking/check_fragment.sh shebang -hacking/find_grafana_versions.py shebang -hacking/find_grafana_versions.py future-import-boilerplate!skip -hacking/find_grafana_versions.py metaclass-boilerplate!skip diff --git a/tests/sanity/ignore-2.12.txt b/tests/sanity/ignore-2.12.txt deleted file mode 100644 index 5c82494f..00000000 --- a/tests/sanity/ignore-2.12.txt +++ /dev/null @@ -1,4 +0,0 @@ -plugins/modules/grafana_dashboard.py validate-modules:invalid-argument-name -tests/unit/modules/grafana/grafana_plugin/test_grafana_plugin.py pep8:W291 -hacking/check_fragment.sh shebang -hacking/find_grafana_versions.py shebang diff --git a/tests/sanity/ignore-2.13.txt b/tests/sanity/ignore-2.13.txt deleted file mode 100644 index 5c82494f..00000000 --- a/tests/sanity/ignore-2.13.txt +++ /dev/null @@ -1,4 +0,0 @@ -plugins/modules/grafana_dashboard.py validate-modules:invalid-argument-name -tests/unit/modules/grafana/grafana_plugin/test_grafana_plugin.py pep8:W291 -hacking/check_fragment.sh shebang -hacking/find_grafana_versions.py shebang diff --git a/tests/sanity/ignore-2.14.txt b/tests/sanity/ignore-2.14.txt deleted file mode 100644 index 5c82494f..00000000 --- a/tests/sanity/ignore-2.14.txt +++ /dev/null @@ -1,4 +0,0 @@ -plugins/modules/grafana_dashboard.py validate-modules:invalid-argument-name -tests/unit/modules/grafana/grafana_plugin/test_grafana_plugin.py pep8:W291 -hacking/check_fragment.sh shebang -hacking/find_grafana_versions.py shebang diff --git a/tests/sanity/ignore-2.9.txt b/tests/sanity/ignore-2.9.txt deleted file mode 100644 index 476a7a42..00000000 --- a/tests/sanity/ignore-2.9.txt +++ /dev/null @@ -1,3 +0,0 @@ -tests/unit/modules/grafana/grafana_plugin/test_grafana_plugin.py pep8:W291 -hacking/check_fragment.sh shebang -hacking/find_grafana_versions.py shebang From 3a2f6de470d3f80887141745e73772a9cd332eae Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Tue, 28 May 2024 13:23:48 +0200 Subject: [PATCH 016/120] ci: bump python to 3.12 --- .github/workflows/ansible-test.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ansible-test.yml b/.github/workflows/ansible-test.yml index f32caf98..acf315dc 100644 --- a/.github/workflows/ansible-test.yml +++ b/.github/workflows/ansible-test.yml @@ -12,7 +12,7 @@ jobs: timeout-minutes: 30 strategy: matrix: - python_version: ["3.10"] + python_version: ["3.12"] ansible_version: ["stable-2.15", "stable-2.16", "devel"] steps: - name: Perform testing @@ -29,7 +29,7 @@ jobs: timeout-minutes: 30 strategy: matrix: - python_version: ["3.10"] + python_version: ["3.12"] ansible_version: ["stable-2.15", "stable-2.16", "devel"] steps: - name: Perform testing @@ -47,7 +47,7 @@ jobs: matrix: grafana_version: ["8.5.27", "9.5.19", "10.4.3"] ansible_version: ["stable-2.15", "stable-2.16", "devel"] - python_version: ["3.10"] + python_version: ["3.12"] services: grafana: image: grafana/grafana:${{ matrix.grafana_version }} @@ -69,7 +69,7 @@ jobs: matrix: grafana_version: ["8.5.27", "9.5.19", "10.4.3"] ansible_version: ["stable-2.15", "stable-2.16", "devel"] - python_version: ["3.10"] + python_version: ["3.12"] services: grafana: image: grafana/grafana:${{ matrix.grafana_version }} From 211d32474e9cbe3fc6d143db153fd08dde24e1fa Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Tue, 28 May 2024 13:28:35 +0200 Subject: [PATCH 017/120] docs: changelog fragment --- changelogs/fragments/373-cleanup-and-update-sanity.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelogs/fragments/373-cleanup-and-update-sanity.yml diff --git a/changelogs/fragments/373-cleanup-and-update-sanity.yml b/changelogs/fragments/373-cleanup-and-update-sanity.yml new file mode 100644 index 00000000..c20a1253 --- /dev/null +++ b/changelogs/fragments/373-cleanup-and-update-sanity.yml @@ -0,0 +1,4 @@ +--- +trivial: + - bump python to 3.12 in ansible tests + - remove sanity ignore files From 0498790af53fe658943d1d9c9a5fbfe990a3537d Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Tue, 28 May 2024 13:39:43 +0200 Subject: [PATCH 018/120] ci: python to 3.11 --- .github/workflows/ansible-test.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ansible-test.yml b/.github/workflows/ansible-test.yml index acf315dc..58405817 100644 --- a/.github/workflows/ansible-test.yml +++ b/.github/workflows/ansible-test.yml @@ -12,7 +12,7 @@ jobs: timeout-minutes: 30 strategy: matrix: - python_version: ["3.12"] + python_version: ["3.11"] ansible_version: ["stable-2.15", "stable-2.16", "devel"] steps: - name: Perform testing @@ -29,7 +29,7 @@ jobs: timeout-minutes: 30 strategy: matrix: - python_version: ["3.12"] + python_version: ["3.11"] ansible_version: ["stable-2.15", "stable-2.16", "devel"] steps: - name: Perform testing @@ -47,7 +47,7 @@ jobs: matrix: grafana_version: ["8.5.27", "9.5.19", "10.4.3"] ansible_version: ["stable-2.15", "stable-2.16", "devel"] - python_version: ["3.12"] + python_version: ["3.11"] services: grafana: image: grafana/grafana:${{ matrix.grafana_version }} @@ -69,7 +69,7 @@ jobs: matrix: grafana_version: ["8.5.27", "9.5.19", "10.4.3"] ansible_version: ["stable-2.15", "stable-2.16", "devel"] - python_version: ["3.12"] + python_version: ["3.11"] services: grafana: image: grafana/grafana:${{ matrix.grafana_version }} From 849f4dfc448f5b837e512077bce7e14cbf4bb62e Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Tue, 28 May 2024 13:40:05 +0200 Subject: [PATCH 019/120] docs: update fragment --- changelogs/fragments/373-cleanup-and-update-sanity.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelogs/fragments/373-cleanup-and-update-sanity.yml b/changelogs/fragments/373-cleanup-and-update-sanity.yml index c20a1253..e5236485 100644 --- a/changelogs/fragments/373-cleanup-and-update-sanity.yml +++ b/changelogs/fragments/373-cleanup-and-update-sanity.yml @@ -1,4 +1,4 @@ --- trivial: - - bump python to 3.12 in ansible tests + - bump python to 3.11 in ansible tests - remove sanity ignore files From 3f91a7e81a57e7e4e39c362c371176d8db740372 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 020/120] 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 a393b26ffd6b6cdc921a5de9b6d23e615c6b87d9 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 021/120] 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 e5de86a4c55282a16cc4ebbe2e53a71fc1cbde9e 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 022/120] 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 37db4425bc8f70dc7201fee63f5ef355e71d812c 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 023/120] 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 56d38490a8fc970d3c8753c22695d616fb9d8796 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 024/120] 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 2fcd37608809358a60e9e56a20db945b509b5ecb 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 025/120] 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 792e07008927780c975d7bdb6f3ed8f4b416d9ba 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 026/120] 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 7a9bfd5ea406af20b9ad14cde4fdf51edcce86da 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 027/120] 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 4d9e165d144e758e1ba35cab2257871f989ec855 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 028/120] 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 3b4f04f4047c0492bc4a72f59b9d09292ff8b901 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 029/120] 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 a1310ea2e1fae2e25c276132b99b582b8ecb4ab3 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 030/120] 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 0e260fafdd2eb32173fe2409113ead6a06d51038 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 031/120] 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 e6168f0ca55d0cd7eb651d403bf3fdcd1693e9f3 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 032/120] 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 e3bb6048c6450576963ad54d7320c88264f45153 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 033/120] 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 45a2170755b6c7377ec206c320a0c68aadebaf2b 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 034/120] 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 65e92b5245daeb44fed9c7efd9abbd34cc38ef04 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 035/120] 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 b26a9044..61de4fc5 100644 --- a/roles/grafana/README.md +++ b/roles/grafana/README.md @@ -172,6 +172,16 @@ Configure Grafana organizations, dashboards, folders, datasources, teams and use | org_name | no | | 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 f5e41021..347ef1c6 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 75167fc6186b4c1ec6a6a939fe10fe92d3c72ab0 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 036/120] 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 fbca2c4b656db2838fb2f430297291238a1000b0 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 037/120] 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 8672a474b6d86e833959ed67154c4ac440cfebef 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 038/120] 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 cdbe594071f79ece8b22cff8a98571f58b7e59f3 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 039/120] 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 8a9234dfcff10363f959146dadfd4d4260bed09a 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 040/120] 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 bd48aabce275d12cd84862c75aae654ca006ae32 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 041/120] 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 c3352e8582d2748de2dc28d4ba4da01819f94591 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 042/120] 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 3345e9a060a11270efe765873bd3c6658585e893 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 043/120] 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 fc12309b43f929181ca49e222c4d02f7b455450f 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 044/120] 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 8fe1aa7776bf69715f3c65dc4d222ad9755eef39 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 045/120] 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 2801a5733ccb89be76c383386f8425f8d1ad1717 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 046/120] 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 483edd7de6d95063f2d1e76b60b200ebf0fc03e3 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 047/120] 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 e55a7395f7a0ea8e71c1d47ca1206c849016be36 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 048/120] 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 8835e888ba06afc78845c7c8a42487cbc0ddab80 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 049/120] 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 c57708622f520dd3be4174fd8d00d21ca6ba1441 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 050/120] 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 5f00dd781e3b14e21406e4abbcc30163e5b55763 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 051/120] 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 cb5cc0b52f18d878a394030c10b55c26b7c59295 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 052/120] 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 61de4fc5..6baa5b37 100644 --- a/roles/grafana/README.md +++ b/roles/grafana/README.md @@ -172,7 +172,7 @@ Configure Grafana organizations, dashboards, folders, datasources, teams and use | org_name | no | | 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 55ded9129b07bb3b8209d4447399dda4def14020 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 053/120] 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 d229b0103023335f4adf4dbb2ee5ad4e9c984e4c 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 054/120] 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 fb996b754bd744aae342d3e3503c58fb9b02c7ed 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 055/120] 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 5ea99a50ca089e1cf2e8beb0b0368408994778b5 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 056/120] 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 8a00cd0d43538dee8f34755d1b300fbfffa41992 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 057/120] 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 3f29357542efafd5c3ccb84be68f5560a7903fa1 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 058/120] 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 dfd918d6c28257be59d4cbff3e9b100355d3a1cb 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 059/120] 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 6baa5b37..09901eff 100644 --- a/roles/grafana/README.md +++ b/roles/grafana/README.md @@ -172,16 +172,136 @@ Configure Grafana organizations, dashboards, folders, datasources, teams and use | org_name | no | | 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 242aa554e13dcc6279ce517a5e25ad9a29713d22 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 060/120] 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 d1e9674e4ce565d36e452d2c1488eb40ebef4d9a 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 061/120] 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 6fc012f7b5086fb8e294135041d759c087c3ce9c 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 062/120] 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 45e7798f29f3ea99bad53712eb8ebcc34881b735 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 063/120] 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 b11720cab21ddd87820e2d94037296e817388c40 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 064/120] 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 2a393b629832ca806521055b48c7f0d50343eb05 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 065/120] 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 c4510df0b851a7410914a16632cd2fbc40b27b46 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 066/120] 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 11a3f75c15e9be76ce94e0654df8fedd279402f0 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 067/120] 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 2d552cb953ae09c6024817902a4d787cc8f2073c 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 068/120] 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 8f6e14d9e73e03f078b849ed781d8d0fbbbc541e 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 069/120] 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 d0044e2bcb9c0254af5d88307d94d3f6ed6b09fd 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 070/120] 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 fff95583bda563ae4356900d888b07d491d454c6 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 071/120] 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 From bffae2a6d485e314699369dcc8f9db4b5d3ab46f Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Fri, 31 May 2024 09:34:49 +0200 Subject: [PATCH 072/120] fix: bump required ansible version --- meta/runtime.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta/runtime.yml b/meta/runtime.yml index 71243ba1..529b9ea0 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -1,5 +1,5 @@ --- -requires_ansible: ">=2.14.0" +requires_ansible: ">=2.15.0" action_groups: grafana: - grafana_dashboard From 224d306d12a39d976b591b6a25f8bcecfd0bed05 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Fri, 31 May 2024 09:35:05 +0200 Subject: [PATCH 073/120] docs: changelog fragment --- changelogs/fragments/374-ansible-required-version.yml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 changelogs/fragments/374-ansible-required-version.yml diff --git a/changelogs/fragments/374-ansible-required-version.yml b/changelogs/fragments/374-ansible-required-version.yml new file mode 100644 index 00000000..b3ea6347 --- /dev/null +++ b/changelogs/fragments/374-ansible-required-version.yml @@ -0,0 +1,3 @@ +--- +trivial: + - bump required ansible version to 2.15.0 From 3bdd20a51d96315a676c3ffeb04698b8dce25dfc Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Fri, 31 May 2024 10:01:25 +0200 Subject: [PATCH 074/120] fix: bump required ansible version in role --- roles/grafana/meta/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/roles/grafana/meta/main.yml b/roles/grafana/meta/main.yml index b5df4219..a9a4ae36 100644 --- a/roles/grafana/meta/main.yml +++ b/roles/grafana/meta/main.yml @@ -4,7 +4,7 @@ galaxy_info: author: community description: Configure Grafana organizations, dashboards, folders, datasources, silences, teams and users license: GPLv3 - min_ansible_version: "2.14" + min_ansible_version: "2.15" galaxy_tags: [grafana, monitoring] platforms: - {name: EL, versions: [all]} From a9f4b80022f9bf04888b1a8ce4d1b548ca21aa06 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Fri, 31 May 2024 11:29:00 +0200 Subject: [PATCH 075/120] fix: remove deprecation check for api_key version --- plugins/lookup/grafana_dashboard.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/plugins/lookup/grafana_dashboard.py b/plugins/lookup/grafana_dashboard.py index 83ee250f..0a1baa43 100644 --- a/plugins/lookup/grafana_dashboard.py +++ b/plugins/lookup/grafana_dashboard.py @@ -152,16 +152,10 @@ def grafana_switch_organisation(self, headers): def grafana_headers(self): headers = {"content-type": "application/json; charset=utf8"} - if self.grafana_api_key: - api_key = self.grafana_api_key - if len(api_key) % 4 == 2: - display.deprecated( - "Passing a mangled version of the API key to the grafana_dashboard lookup is no longer necessary and should not be done.", - "2.0.0", - collection_name="community.grafana", - ) - api_key += "==" - headers["Authorization"] = "Bearer %s" % api_key + if module.params.get("grafana_api_key", None): + self.headers["Authorization"] = ( + "Bearer %s" % module.params["grafana_api_key"] + ) else: headers["Authorization"] = basic_auth_header( self.grafana_user, self.grafana_password From 74495f3af99c869f3350e8df8d92342920f01cad Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Fri, 31 May 2024 11:31:37 +0200 Subject: [PATCH 076/120] docs: changelog fragment --- .../fragments/376-dashboard-lookup-api-key-deprecation.yml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 changelogs/fragments/376-dashboard-lookup-api-key-deprecation.yml diff --git a/changelogs/fragments/376-dashboard-lookup-api-key-deprecation.yml b/changelogs/fragments/376-dashboard-lookup-api-key-deprecation.yml new file mode 100644 index 00000000..a462ffd3 --- /dev/null +++ b/changelogs/fragments/376-dashboard-lookup-api-key-deprecation.yml @@ -0,0 +1,3 @@ +--- +removed_features: + - removed check and handling of mangled api key in `grafana_dashboard` lookup From 5e76ab7fcea23b181bacb343f8824eba868aca1d Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Fri, 31 May 2024 11:37:09 +0200 Subject: [PATCH 077/120] fix: grafana api key variable ref --- plugins/lookup/grafana_dashboard.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/plugins/lookup/grafana_dashboard.py b/plugins/lookup/grafana_dashboard.py index 0a1baa43..514d4fd5 100644 --- a/plugins/lookup/grafana_dashboard.py +++ b/plugins/lookup/grafana_dashboard.py @@ -152,10 +152,8 @@ def grafana_switch_organisation(self, headers): def grafana_headers(self): headers = {"content-type": "application/json; charset=utf8"} - if module.params.get("grafana_api_key", None): - self.headers["Authorization"] = ( - "Bearer %s" % module.params["grafana_api_key"] - ) + if self.grafana_api_key.get("grafana_api_key", None): + headers["Authorization"] = "Bearer %s" % self.grafana_api_key else: headers["Authorization"] = basic_auth_header( self.grafana_user, self.grafana_password From 3f9bcae6341b12d32dbe63438da5654644b692e3 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Fri, 31 May 2024 09:23:50 +0200 Subject: [PATCH 078/120] build(2.0.0): release version --- CHANGELOG.rst | 15 +++++++++++++++ changelogs/changelog.yaml | 14 ++++++++++++++ galaxy.yml | 2 +- 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 9449bdc5..57dae08c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,21 @@ Grafana Collection Release Notes .. contents:: Topics +v2.0.0 +====== + +Minor Changes +------------- + +- Add `grafana_contact_point` module +- Add support of `grafana_contact_point` in grafana role +- add org switch by `org_id` and `org_name` in `grafana_silence` + +Removed Features (previously deprecated) +---------------------------------------- + +- removed deprecated `message` argument in `grafana_dashboard` + v1.9.1 ====== diff --git a/changelogs/changelog.yaml b/changelogs/changelog.yaml index f8d3f898..2b78d2c9 100644 --- a/changelogs/changelog.yaml +++ b/changelogs/changelog.yaml @@ -312,3 +312,17 @@ releases: - 367-dashboard-undo-breaing-change-message.yml - 368-molecule-pin-requests.yml release_date: '2024-05-21' + 2.0.0: + changes: + minor_changes: + - Add `grafana_contact_point` module + - Add support of `grafana_contact_point` in grafana role + - add org switch by `org_id` and `org_name` in `grafana_silence` + removed_features: + - removed deprecated `message` argument in `grafana_dashboard` + fragments: + - 352-module-contact-point.yml + - 371-silence-org-switch.yml + - 372-rm-dashboard-message-argument.yml + - 373-cleanup-and-update-sanity.yml + release_date: '2024-05-31' diff --git a/galaxy.yml b/galaxy.yml index 83df2d7d..0577f6c2 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -1,7 +1,7 @@ --- namespace: community name: grafana -version: 1.9.1 +version: 2.0.0 readme: README.md authors: - Rémi REY (@rrey) From f7207b9e64c79ca004c770c3da74991c936aed4f Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Fri, 31 May 2024 09:59:34 +0200 Subject: [PATCH 079/120] docs: fix some version_added values --- plugins/modules/grafana_contact_point.py | 2 +- plugins/modules/grafana_silence.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/modules/grafana_contact_point.py b/plugins/modules/grafana_contact_point.py index 88a7a0c1..500c64f6 100644 --- a/plugins/modules/grafana_contact_point.py +++ b/plugins/modules/grafana_contact_point.py @@ -24,7 +24,7 @@ module: grafana_contact_point author: - Moritz Pötschk (@nemental) -version_added: "1.9.0" +version_added: "2.0.0" short_description: Manage Grafana Contact Points description: - Create/Update/Delete Grafana Contact Points via API. diff --git a/plugins/modules/grafana_silence.py b/plugins/modules/grafana_silence.py index 7d0421ec..1b2b7091 100644 --- a/plugins/modules/grafana_silence.py +++ b/plugins/modules/grafana_silence.py @@ -23,7 +23,7 @@ module: grafana_silence author: - flkhndlr (@flkhndlr) -version_added: "1.8.0" +version_added: "1.9.0" short_description: Manage Grafana Silences description: - Create/delete Grafana Silences through the Alertmanager Silence API. From f16555e84535b93a55777253ea7f267503b0d57c Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Fri, 31 May 2024 12:24:26 +0200 Subject: [PATCH 080/120] docs: update changelog --- CHANGELOG.rst | 6 ++++++ changelogs/changelog.yaml | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 57dae08c..bf836e6e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -17,8 +17,14 @@ Minor Changes Removed Features (previously deprecated) ---------------------------------------- +- removed check and handling of mangled api key in `grafana_dashboard` lookup - removed deprecated `message` argument in `grafana_dashboard` +New Modules +----------- + +- community.grafana.grafana_contact_point - Manage Grafana Contact Points + v1.9.1 ====== diff --git a/changelogs/changelog.yaml b/changelogs/changelog.yaml index 2b78d2c9..d9be1049 100644 --- a/changelogs/changelog.yaml +++ b/changelogs/changelog.yaml @@ -319,10 +319,17 @@ releases: - Add support of `grafana_contact_point` in grafana role - add org switch by `org_id` and `org_name` in `grafana_silence` removed_features: + - removed check and handling of mangled api key in `grafana_dashboard` lookup - removed deprecated `message` argument in `grafana_dashboard` fragments: - 352-module-contact-point.yml - 371-silence-org-switch.yml - 372-rm-dashboard-message-argument.yml - 373-cleanup-and-update-sanity.yml + - 374-ansible-required-version.yml + - 376-dashboard-lookup-api-key-deprecation.yml + modules: + - description: Manage Grafana Contact Points + name: grafana_contact_point + namespace: '' release_date: '2024-05-31' From c30717b3fa3a593fede31366c04eb86b033e5465 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Sun, 16 Jun 2024 11:23:40 +0200 Subject: [PATCH 081/120] fix: grafana version sorting --- hacking/find_grafana_versions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hacking/find_grafana_versions.py b/hacking/find_grafana_versions.py index 716f877a..c54f18ca 100644 --- a/hacking/find_grafana_versions.py +++ b/hacking/find_grafana_versions.py @@ -33,7 +33,7 @@ def get_grafana_releases(): if major not in by_major.keys() or by_major[major]["as_tuple"] < as_tuple: by_major[major] = {"version": version, "as_tuple": as_tuple} - latest_3_majors = sorted(list(by_major.keys()))[:3] + latest_3_majors = sorted(list(by_major.keys()), reverse=True)[:3] latest_releases = [by_major[idx]["version"] for idx in latest_3_majors] print(json.dumps(latest_releases)) From 793df2c59a69f766ff3582cabbe6763889efd185 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Sun, 16 Jun 2024 13:04:04 +0200 Subject: [PATCH 082/120] docs: chabgelog fragment --- changelogs/fragments/378-grafana-version-sorting.yml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 changelogs/fragments/378-grafana-version-sorting.yml diff --git a/changelogs/fragments/378-grafana-version-sorting.yml b/changelogs/fragments/378-grafana-version-sorting.yml new file mode 100644 index 00000000..46c74655 --- /dev/null +++ b/changelogs/fragments/378-grafana-version-sorting.yml @@ -0,0 +1,3 @@ +--- +trivial: + - Fix grafana version sorting From 8f1eefa1c07d2dc1be2670fa19b0bc1b1e94109b Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Wed, 19 Jun 2024 09:55:14 +0200 Subject: [PATCH 083/120] fix(grafana_contact_point): get org by name func args --- plugins/modules/grafana_contact_point.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/modules/grafana_contact_point.py b/plugins/modules/grafana_contact_point.py index 500c64f6..b7633f3d 100644 --- a/plugins/modules/grafana_contact_point.py +++ b/plugins/modules/grafana_contact_point.py @@ -966,7 +966,9 @@ def __init__(self, module): module.params["url_username"], module.params["url_password"] ) self.org_id = ( - self.grafana_organization_by_name(module.params["org_name"]) + self.grafana_organization_by_name( + module.params, module.params["org_name"] + ) if module.params["org_name"] else module.params["org_id"] ) From 5339481cc962356a4a709611e5503ac8e968d5c5 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Wed, 19 Jun 2024 09:58:54 +0200 Subject: [PATCH 084/120] docs: changelog fragment --- changelogs/fragments/379-contact-points-org-name-func-args.yml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 changelogs/fragments/379-contact-points-org-name-func-args.yml diff --git a/changelogs/fragments/379-contact-points-org-name-func-args.yml b/changelogs/fragments/379-contact-points-org-name-func-args.yml new file mode 100644 index 00000000..5e8eea45 --- /dev/null +++ b/changelogs/fragments/379-contact-points-org-name-func-args.yml @@ -0,0 +1,3 @@ +--- +bugfixes: + - Add missing function argument in `grafana_contact_point` for org handling From eeeba8ec39146a529c0d2eda5adc168925b764e9 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Thu, 27 Jun 2024 10:44:12 +0200 Subject: [PATCH 085/120] docs: meta runtime deprecation notification channel --- meta/runtime.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/meta/runtime.yml b/meta/runtime.yml index 529b9ea0..7f45047b 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -13,3 +13,9 @@ action_groups: - grafana_team - grafana_user - grafana_silence +plugin_routing: + modules: + grafana_notification_channel: + deprecation: + removal_version: 3.0.0 + warning_text: Legacy alerting is removed in Grafana version 11, use community.grafana.grafana_contact_point instead. From 729f2f231a387458d9efcc92e7e9232d4da07654 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Thu, 27 Jun 2024 10:44:41 +0200 Subject: [PATCH 086/120] docs: plugin module deprecation notification_channel --- plugins/modules/grafana_notification_channel.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/modules/grafana_notification_channel.py b/plugins/modules/grafana_notification_channel.py index 30b5b112..42ea442d 100644 --- a/plugins/modules/grafana_notification_channel.py +++ b/plugins/modules/grafana_notification_channel.py @@ -33,6 +33,10 @@ short_description: Manage Grafana Notification Channels description: - Create/Update/Delete Grafana Notification Channels via API. +deprecated: + removed_in: "3.0.0" + why: Legacy alerting is removed in Grafana version 11. + alternative: Use M(community.grafana.grafana_contact_point) instead. options: org_id: description: From 3c2cad9fc75b47d7afd50ef79b1da39aa60c0465 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Thu, 27 Jun 2024 10:48:43 +0200 Subject: [PATCH 087/120] docs: changelog fragment --- changelogs/fragments/382-notification-channel-deprecation.yml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 changelogs/fragments/382-notification-channel-deprecation.yml diff --git a/changelogs/fragments/382-notification-channel-deprecation.yml b/changelogs/fragments/382-notification-channel-deprecation.yml new file mode 100644 index 00000000..3410b084 --- /dev/null +++ b/changelogs/fragments/382-notification-channel-deprecation.yml @@ -0,0 +1,3 @@ +--- +deprecated_features: + - Deprecate `grafana_notification_channel` with removal in version 3.0.0 From 260a4f361aac5e8ebbfb470bfba8feb805c8e875 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Thu, 27 Jun 2024 14:21:15 +0200 Subject: [PATCH 088/120] feat: notification channel get version with skip version check --- .../modules/grafana_notification_channel.py | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/plugins/modules/grafana_notification_channel.py b/plugins/modules/grafana_notification_channel.py index 42ea442d..324836ed 100644 --- a/plugins/modules/grafana_notification_channel.py +++ b/plugins/modules/grafana_notification_channel.py @@ -100,6 +100,11 @@ default: false description: - Disable the resolve message. + skip_version_check: + description: + - Skip Grafana version check and try to reach api endpoint anyway. + type: bool + default: false reminder_frequency: type: str description: @@ -627,6 +632,29 @@ def __init__(self, module): ) # }}} self.grafana_url = clean_url(module.params.get("url")) + if module.params.get("skip_version_check") is False: + try: + grafana_version = self.get_version() + except GrafanaAPIException as e: + self._module.fail_json(failed=True, msg=to_text(e)) + if grafana_version["major"] >= 11: + self._module.fail_json( + failed=True, + msg="Legacy Alerting API is available up to Grafana v11", + ) + + def get_version(self): + r, info = fetch_url( + self._module, + "%s/api/health" % self.grafana_url, + headers=self.headers, + method="GET", + ) + version = r.get("version") + if version is not None: + major, minor, rev = version.split(".") + return {"major": int(major), "minor": int(minor), "rev": int(rev)} + raise GrafanaAPIException("Failed to retrieve version: %s" % info) def grafana_switch_organisation(self, grafana_url, org_id): r, info = fetch_url( @@ -761,6 +789,7 @@ def main(): is_default=dict(type="bool", default=False), include_image=dict(type="bool", default=False), disable_resolve_message=dict(type="bool", default=False), + skip_version_check=dict(type="bool", default=False), reminder_frequency=dict(type="str"), dingding_url=dict(type="str"), dingding_message_type=dict( From bd719fa8a62abef3157d6838d5cf7d08a3df221e Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Thu, 27 Jun 2024 14:28:23 +0200 Subject: [PATCH 089/120] chore: version ref --- 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 324836ed..c092e9cf 100644 --- a/plugins/modules/grafana_notification_channel.py +++ b/plugins/modules/grafana_notification_channel.py @@ -640,7 +640,7 @@ def __init__(self, module): if grafana_version["major"] >= 11: self._module.fail_json( failed=True, - msg="Legacy Alerting API is available up to Grafana v11", + msg="Legacy Alerting API is available up to Grafana v10", ) def get_version(self): From 95ea959d93aa3eb60160f6bcc7a1495587741b87 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Thu, 27 Jun 2024 14:28:51 +0200 Subject: [PATCH 090/120] test: notification channel check for legacy api --- .../tasks/main.yml | 61 +++++++++---------- 1 file changed, 28 insertions(+), 33 deletions(-) diff --git a/tests/integration/targets/grafana_notification_channel/tasks/main.yml b/tests/integration/targets/grafana_notification_channel/tasks/main.yml index af0b2f86..4684926a 100644 --- a/tests/integration/targets/grafana_notification_channel/tasks/main.yml +++ b/tests/integration/targets/grafana_notification_channel/tasks/main.yml @@ -1,34 +1,29 @@ --- -- block: - - ansible.builtin.include_tasks: - file: dingding.yml - - ansible.builtin.include_tasks: - file: discord.yml - - ansible.builtin.include_tasks: - file: email.yml - - ansible.builtin.include_tasks: - file: googlechat.yml - - ansible.builtin.include_tasks: - file: hipchat.yml - - ansible.builtin.include_tasks: - file: kafka.yml - - ansible.builtin.include_tasks: - file: teams.yml - - ansible.builtin.include_tasks: - file: opsgenie.yml - - ansible.builtin.include_tasks: - file: pagerduty.yml - - ansible.builtin.include_tasks: - file: prometheus.yml - - ansible.builtin.include_tasks: - file: pushover.yml - - ansible.builtin.include_tasks: - file: sensu.yml - - ansible.builtin.include_tasks: - file: slack-and-beyond.yml - - ansible.builtin.include_tasks: - file: telegram.yml - - ansible.builtin.include_tasks: - file: victorops.yml - - ansible.builtin.include_tasks: - file: webhook.yml + +- name: Check for support of API endpoint + register: result + ignore_errors: true + community.grafana.grafana_notification_channel: + title: apitest + state: absent + +- name: Include notification channel task files + ansible.builtin.include_tasks: "{{ item }}.yml" + when: "result.msg | default('') != 'Legacy Alerting API is available up to Grafana v10'" + loop: + - dingding + - discord + - email + - googlechat + - hipchat + - kafka + - teams + - opsgenie + - pagerduty + - prometheus + - pushover + - sensu + - slack-and-beyond + - telegram + - victorops + - webhook From 48810322a0303435fa80ae536207ad6f49140ff0 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Thu, 27 Jun 2024 14:33:19 +0200 Subject: [PATCH 091/120] test: grafana attributes --- .../targets/grafana_notification_channel/tasks/main.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/integration/targets/grafana_notification_channel/tasks/main.yml b/tests/integration/targets/grafana_notification_channel/tasks/main.yml index 4684926a..7161088b 100644 --- a/tests/integration/targets/grafana_notification_channel/tasks/main.yml +++ b/tests/integration/targets/grafana_notification_channel/tasks/main.yml @@ -4,6 +4,9 @@ register: result ignore_errors: true community.grafana.grafana_notification_channel: + grafana_url: "{{ grafana_url }}" + grafana_user: "{{ grafana_username }}" + grafana_password: "{{ grafana_password }}" title: apitest state: absent From 6b6d955c01386711a46a0906efd85631715cfdd8 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Fri, 28 Jun 2024 12:19:55 +0200 Subject: [PATCH 092/120] fix: read output of health api if ok --- plugins/modules/grafana_notification_channel.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/plugins/modules/grafana_notification_channel.py b/plugins/modules/grafana_notification_channel.py index c092e9cf..24e4e3c0 100644 --- a/plugins/modules/grafana_notification_channel.py +++ b/plugins/modules/grafana_notification_channel.py @@ -650,11 +650,13 @@ def get_version(self): headers=self.headers, method="GET", ) - version = r.get("version") - if version is not None: - major, minor, rev = version.split(".") - return {"major": int(major), "minor": int(minor), "rev": int(rev)} - raise GrafanaAPIException("Failed to retrieve version: %s" % info) + if info["status"] == 200: + version = json.loads(to_text(r.read())).get("version") + if version is not None: + major, minor, rev = version.split(".") + return {"major": int(major), "minor": int(minor), "rev": int(rev)} + else: + raise GrafanaAPIException("Failed to retrieve version: %s" % info) def grafana_switch_organisation(self, grafana_url, org_id): r, info = fetch_url( From 11ae84f1527362c2f5394fa3852510f868e66692 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Fri, 28 Jun 2024 12:20:31 +0200 Subject: [PATCH 093/120] test: wrong arg --- .../targets/grafana_notification_channel/tasks/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/targets/grafana_notification_channel/tasks/main.yml b/tests/integration/targets/grafana_notification_channel/tasks/main.yml index 7161088b..7d1dec69 100644 --- a/tests/integration/targets/grafana_notification_channel/tasks/main.yml +++ b/tests/integration/targets/grafana_notification_channel/tasks/main.yml @@ -7,7 +7,7 @@ grafana_url: "{{ grafana_url }}" grafana_user: "{{ grafana_username }}" grafana_password: "{{ grafana_password }}" - title: apitest + name: apitest state: absent - name: Include notification channel task files From 6bb5266a69e404c9a8fdbea9e7ba17a7556d5692 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Fri, 28 Jun 2024 12:23:37 +0200 Subject: [PATCH 094/120] docs: changelog fragment --- changelogs/fragments/382-notification-channel-deprecation.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/changelogs/fragments/382-notification-channel-deprecation.yml b/changelogs/fragments/382-notification-channel-deprecation.yml index 3410b084..9af1cf01 100644 --- a/changelogs/fragments/382-notification-channel-deprecation.yml +++ b/changelogs/fragments/382-notification-channel-deprecation.yml @@ -1,3 +1,5 @@ --- deprecated_features: - Deprecate `grafana_notification_channel` with removal in version 3.0.0 +trivial: + - Check Grafana version for `grafana_notification_channel` integration tests From 7c1d1e7fe7251ada5cd7f840efc58a99b4b650a8 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Wed, 26 Jun 2024 13:38:11 +0200 Subject: [PATCH 095/120] refactor: folder finding loop --- plugins/modules/grafana_folder.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/plugins/modules/grafana_folder.py b/plugins/modules/grafana_folder.py index 73c437db..a6372fad 100644 --- a/plugins/modules/grafana_folder.py +++ b/plugins/modules/grafana_folder.py @@ -290,15 +290,16 @@ def create_folder(self, title): return response def get_folder(self, title): - url = "/api/search?type=dash-folder&query={title}".format(title=quote(title)) + url = "/api/search?type=dash-folder&query=%s" % quote(title) response = self._send_request(url, headers=self.headers, method="GET") - for item in response: - if item.get("title") == to_text(title): - return item + folder = next((item for item in response if item["title"] == title)) + if folder: + return folder + return None def delete_folder(self, folder_uid): - url = "/api/folders/{folder_uid}".format(folder_uid=folder_uid) + url = "/api/folders/%s" % folder_uid response = self._send_request(url, headers=self.headers, method="DELETE") return response From 4ea2afbeb379f8ad0c0e9b5b99fded159ea7ce31 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Wed, 26 Jun 2024 13:38:31 +0200 Subject: [PATCH 096/120] chore: add uid and parent_uid arg --- plugins/modules/grafana_folder.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/modules/grafana_folder.py b/plugins/modules/grafana_folder.py index a6372fad..9f34ed8d 100644 --- a/plugins/modules/grafana_folder.py +++ b/plugins/modules/grafana_folder.py @@ -308,10 +308,12 @@ def main(): argument_spec = base.grafana_argument_spec() argument_spec.update( name=dict(type="str", aliases=["title"], required=True), - state=dict(type="str", default="present", choices=["present", "absent"]), - skip_version_check=dict(type="bool", default=False), org_id=dict(default=1, type="int"), org_name=dict(type="str"), + parent_uid=dict(type="str"), + skip_version_check=dict(type="bool", default=False), + state=dict(type="str", default="present", choices=["present", "absent"]), + uid=dict(type="str"), ) module = AnsibleModule( argument_spec=argument_spec, From b2d155fd72d1581cc91b234cb51a1ca66ef384a0 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Wed, 26 Jun 2024 14:16:22 +0200 Subject: [PATCH 097/120] fix: check if multiple folder found by name --- plugins/modules/grafana_folder.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/plugins/modules/grafana_folder.py b/plugins/modules/grafana_folder.py index 9f34ed8d..4af28c0e 100644 --- a/plugins/modules/grafana_folder.py +++ b/plugins/modules/grafana_folder.py @@ -292,9 +292,14 @@ def create_folder(self, title): def get_folder(self, title): url = "/api/search?type=dash-folder&query=%s" % quote(title) response = self._send_request(url, headers=self.headers, method="GET") - folder = next((item for item in response if item["title"] == title)) - if folder: - return folder + folders = [item for item in response if item.get("title") == to_text(title)] + + if len(folders) == 1: + return folders[0] + elif len(folders) > 1: + raise GrafanaError( + f"Multiple folders found for name {title}. Please use uid." + ) return None From 2729a74beafea93fa32722b36231add73a9fe81c Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Wed, 26 Jun 2024 15:00:04 +0200 Subject: [PATCH 098/120] feat: change get folder api and use parent_uid if defined and check against uid first --- plugins/modules/grafana_folder.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/plugins/modules/grafana_folder.py b/plugins/modules/grafana_folder.py index 4af28c0e..978c8bd1 100644 --- a/plugins/modules/grafana_folder.py +++ b/plugins/modules/grafana_folder.py @@ -289,17 +289,16 @@ def create_folder(self, title): ) return response - def get_folder(self, title): - url = "/api/search?type=dash-folder&query=%s" % quote(title) + def get_folder(self, title, uid=None, parent_uid=None): + url = "/api/folders%s" % ("?parentUid=%s" % parent_uid if parent_uid else "") response = self._send_request(url, headers=self.headers, method="GET") - folders = [item for item in response if item.get("title") == to_text(title)] + if uid: + folders = [item for item in response if item.get("uid") == uid] + else: + folders = [item for item in response if item.get("title") == to_text(title)] - if len(folders) == 1: + if folders: return folders[0] - elif len(folders) > 1: - raise GrafanaError( - f"Multiple folders found for name {title}. Please use uid." - ) return None From 34ae5b9a0f0ad2dc760c25e6225929b88b73195f Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Wed, 26 Jun 2024 15:00:29 +0200 Subject: [PATCH 099/120] feat: get folders by uid and parent_uid if defined --- plugins/modules/grafana_folder.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/plugins/modules/grafana_folder.py b/plugins/modules/grafana_folder.py index 978c8bd1..33ad4fcc 100644 --- a/plugins/modules/grafana_folder.py +++ b/plugins/modules/grafana_folder.py @@ -331,13 +331,24 @@ def main(): ) state = module.params["state"] title = module.params["name"] + parent_uid = module.params["parent_uid"] + uid = module.params["uid"] module.params["url"] = base.clean_url(module.params["url"]) grafana_iface = GrafanaFolderInterface(module) changed = False - if state == "present": + + if uid and parent_uid: + folder = grafana_iface.get_folder(title, uid, parent_uid) + elif uid: + folder = grafana_iface.get_folder(title, uid) + elif parent_uid: + folder = grafana_iface.get_folder(title, parent_uid=parent_uid) + else: folder = grafana_iface.get_folder(title) + + if state == "present": if folder is None: grafana_iface.create_folder(title) folder = grafana_iface.get_folder(title) @@ -345,7 +356,6 @@ def main(): folder = grafana_iface.get_folder(title) module.exit_json(changed=changed, folder=folder) elif state == "absent": - folder = grafana_iface.get_folder(title) if folder is None: module.exit_json(changed=False, message="No folder found") result = grafana_iface.delete_folder(folder.get("uid")) From 8743bd9ace19a6e6de17ef64765239640a37f0d5 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Wed, 26 Jun 2024 15:12:16 +0200 Subject: [PATCH 100/120] chore: simplify --- plugins/modules/grafana_folder.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/plugins/modules/grafana_folder.py b/plugins/modules/grafana_folder.py index 33ad4fcc..8f190a38 100644 --- a/plugins/modules/grafana_folder.py +++ b/plugins/modules/grafana_folder.py @@ -339,14 +339,7 @@ def main(): changed = False - if uid and parent_uid: - folder = grafana_iface.get_folder(title, uid, parent_uid) - elif uid: - folder = grafana_iface.get_folder(title, uid) - elif parent_uid: - folder = grafana_iface.get_folder(title, parent_uid=parent_uid) - else: - folder = grafana_iface.get_folder(title) + folder = grafana_iface.get_folder(title, uid, parent_uid) if state == "present": if folder is None: From a73039057f9b8d4850f0eaf0d179c1475c19f19c Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Wed, 26 Jun 2024 15:19:31 +0200 Subject: [PATCH 101/120] feat: create folder with uid and parent_uid --- plugins/modules/grafana_folder.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/plugins/modules/grafana_folder.py b/plugins/modules/grafana_folder.py index 8f190a38..44142078 100644 --- a/plugins/modules/grafana_folder.py +++ b/plugins/modules/grafana_folder.py @@ -281,9 +281,9 @@ def get_version(self): return {"major": int(major), "minor": int(minor), "rev": int(rev)} raise GrafanaError("Failed to retrieve version from '%s'" % url) - def create_folder(self, title): + def create_folder(self, title, uid=None, parent_uid=None): url = "/api/folders" - folder = dict(title=title) + folder = dict(title=title, uid=uid, parentUid=parent_uid) response = self._send_request( url, data=folder, headers=self.headers, method="POST" ) @@ -343,10 +343,9 @@ def main(): if state == "present": if folder is None: - grafana_iface.create_folder(title) - folder = grafana_iface.get_folder(title) + grafana_iface.create_folder(title, uid, parent_uid) + folder = grafana_iface.get_folder(title, uid, parent_uid) changed = True - folder = grafana_iface.get_folder(title) module.exit_json(changed=changed, folder=folder) elif state == "absent": if folder is None: From 453b68ac366fd0fc3c4e29f93803314c3ee24a5e Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Wed, 26 Jun 2024 15:23:06 +0200 Subject: [PATCH 102/120] docs: parent_uid and uid --- plugins/modules/grafana_folder.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/plugins/modules/grafana_folder.py b/plugins/modules/grafana_folder.py index 44142078..db5d9e90 100644 --- a/plugins/modules/grafana_folder.py +++ b/plugins/modules/grafana_folder.py @@ -37,10 +37,18 @@ required: true type: str aliases: [ title ] + uid: + description: + - The folder UID. + type: str + parent_uid: + description: + - The parent folder UID. + type: str state: description: - Delete the members not found in the C(members) parameters from the - - list of members found on the Folder. + - list of members found on the folder. default: present type: str choices: ["present", "absent"] From dfca09b1abcd863e9429ab25dee54ba102a32999 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Thu, 27 Jun 2024 09:45:02 +0200 Subject: [PATCH 103/120] docs: module return values --- plugins/modules/grafana_folder.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/plugins/modules/grafana_folder.py b/plugins/modules/grafana_folder.py index db5d9e90..61f9f4a9 100644 --- a/plugins/modules/grafana_folder.py +++ b/plugins/modules/grafana_folder.py @@ -116,6 +116,12 @@ type: str sample: - "nErXDvCkzz" + orgId: + description: The organization id + returned: always + type: int + sample: + - 1 title: description: The Folder title returned: always @@ -182,6 +188,12 @@ type: int sample: - 1 + parentUid: + description: The parent folders uid + returned: always as subfolder + type: str + sample: + - "76HjcBH2" """ import json @@ -189,7 +201,6 @@ from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.urls import fetch_url, basic_auth_header from ansible_collections.community.grafana.plugins.module_utils import base -from ansible.module_utils.six.moves.urllib.parse import quote from ansible.module_utils._text import to_text __metaclass__ = type From ca04523caaec602a43e22a08df621c0c4560df41 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Thu, 27 Jun 2024 09:45:44 +0200 Subject: [PATCH 104/120] test: use module defaults --- .../grafana_folder/tasks/create-delete.yml | 94 +++++++++---------- 1 file changed, 44 insertions(+), 50 deletions(-) diff --git a/tests/integration/targets/grafana_folder/tasks/create-delete.yml b/tests/integration/targets/grafana_folder/tasks/create-delete.yml index c818c2ef..e1ee756c 100644 --- a/tests/integration/targets/grafana_folder/tasks/create-delete.yml +++ b/tests/integration/targets/grafana_folder/tasks/create-delete.yml @@ -1,58 +1,52 @@ --- -- name: Create a Folder - community.grafana.grafana_folder: - url: "{{ grafana_url }}" - url_username: "{{ grafana_username }}" - url_password: "{{ grafana_password }}" - title: grafana_working_group - state: present - register: result +- module_defaults: + community.grafana.grafana_folder: + url: "{{ grafana_url }}" + url_username: "{{ grafana_username }}" + url_password: "{{ grafana_password }}" + block: + - name: Create a Folder + community.grafana.grafana_folder: + title: grafana_working_group + state: present + register: result -- ansible.builtin.assert: - that: - - result.changed == true - - result.folder.title == 'grafana_working_group' - when: not ansible_check_mode + - ansible.builtin.assert: + that: + - result.changed == true + - result.folder.title == 'grafana_working_group' + when: not ansible_check_mode -- name: Test folder creation idempotency - community.grafana.grafana_folder: - url: "{{ grafana_url }}" - url_username: "{{ grafana_username }}" - url_password: "{{ grafana_password }}" - title: grafana_working_group - state: present - register: result + - name: Test folder creation idempotency + community.grafana.grafana_folder: + title: grafana_working_group + state: present + register: result -- ansible.builtin.assert: - that: - - result.changed == false - - result.folder.title == 'grafana_working_group' - when: not ansible_check_mode + - ansible.builtin.assert: + that: + - result.changed == false + - result.folder.title == 'grafana_working_group' + when: not ansible_check_mode -- name: Delete a Folder - community.grafana.grafana_folder: - url: "{{ grafana_url }}" - url_username: "{{ grafana_username }}" - url_password: "{{ grafana_password }}" - title: grafana_working_group - state: absent - register: result + - name: Delete a Folder + community.grafana.grafana_folder: + title: grafana_working_group + state: absent + register: result -- ansible.builtin.assert: - that: - - result.changed == true - when: not ansible_check_mode + - ansible.builtin.assert: + that: + - result.changed == true + when: not ansible_check_mode -- name: Test folder deletion idempotency - community.grafana.grafana_folder: - url: "{{ grafana_url }}" - url_username: "{{ grafana_username }}" - url_password: "{{ grafana_password }}" - title: grafana_working_group - state: absent - register: result + - name: Test folder deletion idempotency + community.grafana.grafana_folder: + title: grafana_working_group + state: absent + register: result -- ansible.builtin.assert: - that: - - result.changed == false - when: not ansible_check_mode + - ansible.builtin.assert: + that: + - result.changed == false + when: not ansible_check_mode From bca4d7d10d9152014c85a29db6420cfa53714cc0 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Thu, 27 Jun 2024 09:46:00 +0200 Subject: [PATCH 105/120] test: add sub folder tests --- .../targets/grafana_folder/tasks/sub.yml | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 tests/integration/targets/grafana_folder/tasks/sub.yml diff --git a/tests/integration/targets/grafana_folder/tasks/sub.yml b/tests/integration/targets/grafana_folder/tasks/sub.yml new file mode 100644 index 00000000..d9f95ac5 --- /dev/null +++ b/tests/integration/targets/grafana_folder/tasks/sub.yml @@ -0,0 +1,110 @@ +--- +- module_defaults: + community.grafana.grafana_folder: + url: "{{ grafana_url }}" + url_username: "{{ grafana_username }}" + url_password: "{{ grafana_password }}" + uid: "parent" + block: + - name: Create a parent Folder + community.grafana.grafana_folder: + title: grafana_working_group + state: present + register: result + + - ansible.builtin.assert: + that: + - result.changed == true + - result.folder.title == 'grafana_working_group' + - result.folder.uid == 'parent' + when: not ansible_check_mode + + - name: Test folder parent creation idempotency + community.grafana.grafana_folder: + title: grafana_working_group + state: present + register: result + + - ansible.builtin.assert: + that: + - result.changed == false + - result.folder.title == 'grafana_working_group' + - result.folder.uid == 'parent' + when: not ansible_check_mode + + - module_defaults: + community.grafana.grafana_folder: + uid: "sub" + parent_uid: "parent" + block: + - name: Create a sub Folder + community.grafana.grafana_folder: + title: grafana_working_group + state: present + register: result + + - ansible.builtin.assert: + that: + - result.changed == true + - result.folder.title == 'grafana_working_group' + - result.folder.uid == 'sub' + - result.folder.parentUid == 'parent' + when: not ansible_check_mode + + - name: Test sub folder creation idempotency + community.grafana.grafana_folder: + title: grafana_working_group + state: present + register: result + + - ansible.builtin.assert: + that: + - result.changed == false + - result.folder.title == 'grafana_working_group' + - result.folder.uid == 'sub' + - result.folder.parentUid == 'parent' + when: not ansible_check_mode + + - name: Delete sub Folder + community.grafana.grafana_folder: + title: grafana_working_group + state: absent + register: result + + - ansible.builtin.assert: + that: + - result.changed == true + when: not ansible_check_mode + + - name: Test sub folder deletion idempotency + community.grafana.grafana_folder: + title: grafana_working_group + state: absent + register: result + + - ansible.builtin.assert: + that: + - result.changed == false + when: not ansible_check_mode + + - name: Delete a Folder + community.grafana.grafana_folder: + title: grafana_working_group + state: absent + register: result + + - ansible.builtin.assert: + that: + - result.changed == true + when: not ansible_check_mode + + - name: Test folder deletion idempotency + community.grafana.grafana_folder: + title: grafana_working_group + state: absent + register: result + + - ansible.builtin.assert: + that: + - result.changed == false + when: not ansible_check_mode From 2f1d435948cbbab40b8d7a6f3cacfc400e9670e2 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Thu, 27 Jun 2024 09:54:28 +0200 Subject: [PATCH 106/120] test: add sub folder tests to main --- tests/integration/targets/grafana_folder/tasks/main.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/integration/targets/grafana_folder/tasks/main.yml b/tests/integration/targets/grafana_folder/tasks/main.yml index 0ac91f37..7880e8dc 100644 --- a/tests/integration/targets/grafana_folder/tasks/main.yml +++ b/tests/integration/targets/grafana_folder/tasks/main.yml @@ -4,3 +4,6 @@ - name: Folder creation and deletion for organization ansible.builtin.include_tasks: org.yml + +- name: Folder creation and deletion for subfolders + ansible.builtin.include_tasks: sub.yml From d09dd8da4a33a0da811164118c9f44b9ad711244 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Thu, 27 Jun 2024 09:54:56 +0200 Subject: [PATCH 107/120] test: fix org name test --- .../targets/grafana_folder/tasks/org.yml | 52 +++++++++++++++++-- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/tests/integration/targets/grafana_folder/tasks/org.yml b/tests/integration/targets/grafana_folder/tasks/org.yml index f30d48c1..7a628914 100644 --- a/tests/integration/targets/grafana_folder/tasks/org.yml +++ b/tests/integration/targets/grafana_folder/tasks/org.yml @@ -1,7 +1,53 @@ --- - module_defaults: community.grafana.grafana_folder: - org_name: Main Org. + url: "{{ grafana_url }}" + url_username: "{{ grafana_username }}" + url_password: "{{ grafana_password }}" + org_name: "Main Org." block: - - name: Folder creation and deletion - ansible.builtin.include_tasks: create-delete.yml + - name: Create a Folder + community.grafana.grafana_folder: + title: grafana_working_group + state: present + register: result + + - ansible.builtin.assert: + that: + - result.changed == true + - result.folder.title == 'grafana_working_group' + when: not ansible_check_mode + + - name: Test folder creation idempotency + community.grafana.grafana_folder: + title: grafana_working_group + state: present + register: result + + - ansible.builtin.assert: + that: + - result.changed == false + - result.folder.title == 'grafana_working_group' + when: not ansible_check_mode + + - name: Delete a Folder + community.grafana.grafana_folder: + title: grafana_working_group + state: absent + register: result + + - ansible.builtin.assert: + that: + - result.changed == true + when: not ansible_check_mode + + - name: Test folder deletion idempotency + community.grafana.grafana_folder: + title: grafana_working_group + state: absent + register: result + + - ansible.builtin.assert: + that: + - result.changed == false + when: not ansible_check_mode From 2bc3d8f423e0e63d3589d14371f913f40eaa3ffb Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Thu, 27 Jun 2024 09:55:11 +0200 Subject: [PATCH 108/120] test: sub folder tests module defaults --- tests/integration/targets/grafana_folder/tasks/sub.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/integration/targets/grafana_folder/tasks/sub.yml b/tests/integration/targets/grafana_folder/tasks/sub.yml index d9f95ac5..21adb236 100644 --- a/tests/integration/targets/grafana_folder/tasks/sub.yml +++ b/tests/integration/targets/grafana_folder/tasks/sub.yml @@ -34,6 +34,9 @@ - module_defaults: community.grafana.grafana_folder: + url: "{{ grafana_url }}" + url_username: "{{ grafana_username }}" + url_password: "{{ grafana_password }}" uid: "sub" parent_uid: "parent" block: From 0cffd21140c04620f23a20153440bd167735c612 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Thu, 27 Jun 2024 10:00:06 +0200 Subject: [PATCH 109/120] docs: changelog fragment --- changelogs/fragments/381-sub-folders.yml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 changelogs/fragments/381-sub-folders.yml diff --git a/changelogs/fragments/381-sub-folders.yml b/changelogs/fragments/381-sub-folders.yml new file mode 100644 index 00000000..36e46667 --- /dev/null +++ b/changelogs/fragments/381-sub-folders.yml @@ -0,0 +1,3 @@ +--- +minor_changes: + - `grafana_folder` manage subfolders and specify uid From 46d21d996f92a421511025f7d64b74b64d1db0c6 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Thu, 27 Jun 2024 10:04:34 +0200 Subject: [PATCH 110/120] feat: role uid and parent_uid --- roles/grafana/tasks/main.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/roles/grafana/tasks/main.yml b/roles/grafana/tasks/main.yml index 170d9337..d0ddab40 100644 --- a/roles/grafana/tasks/main.yml +++ b/roles/grafana/tasks/main.yml @@ -277,10 +277,12 @@ - name: Manage folder # noqa: args[module] community.grafana.grafana_folder: name: "{{ folder.name }}" - skip_version_check: "{{ folder.skip_version_check | default(omit) }}" - state: "{{ folder.state | default(omit) }}" org_id: "{{ folder.org_id | default(omit) }}" org_name: "{{ folder.org_name | default(omit) }}" + parent_uid: "{{ folder.parent_uid | default(omit) }}" + skip_version_check: "{{ folder.skip_version_check | default(omit) }}" + state: "{{ folder.state | default(omit) }}" + uid: "{{ folder.uid | default(omit) }}" loop: "{{ grafana_folders }}" loop_control: {loop_var: folder} tags: folder From b6160be362252300476c06e265067b82aa62404b Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Thu, 27 Jun 2024 10:04:50 +0200 Subject: [PATCH 111/120] docs: role new subfolder parameters --- roles/grafana/README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/roles/grafana/README.md b/roles/grafana/README.md index a6ce7507..5c76497c 100644 --- a/roles/grafana/README.md +++ b/roles/grafana/README.md @@ -73,10 +73,12 @@ Configure Grafana organizations, dashboards, folders, datasources, teams and use | zabbix_user | no | | [**grafana_folders**](https://docs.ansible.com/ansible/latest/collections/community/grafana/grafana_folder_module.html) | | name | yes | -| skip_version_check | no | -| state | no | | org_id | no | | org_name | no | +| parent_uid | no | +| skip_version_check | no | +| state | no | +| uid | no | | [**grafana_dashboards**](https://docs.ansible.com/ansible/latest/collections/community/grafana/grafana_dashboard_module.html) | | commit_message | no | | dashboard_id | no | From 196cb0925e4822b7f47658dd5ce6053535a4c9ce Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Fri, 28 Jun 2024 13:00:58 +0200 Subject: [PATCH 112/120] fix: check for api support --- plugins/modules/grafana_folder.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/plugins/modules/grafana_folder.py b/plugins/modules/grafana_folder.py index 61f9f4a9..c9fa2364 100644 --- a/plugins/modules/grafana_folder.py +++ b/plugins/modules/grafana_folder.py @@ -241,6 +241,10 @@ def __init__(self, module): self._module.fail_json( failed=True, msg="Folders API is available starting Grafana v5" ) + if grafana_version["major"] < 11 and module.params["parent_uid"]: + self._module.fail_json( + failed=True, msg="Subfolder API is available starting Grafana v11" + ) def _send_request(self, url, data=None, headers=None, method="GET"): if data is not None: @@ -311,13 +315,16 @@ def create_folder(self, title, uid=None, parent_uid=None): def get_folder(self, title, uid=None, parent_uid=None): url = "/api/folders%s" % ("?parentUid=%s" % parent_uid if parent_uid else "") response = self._send_request(url, headers=self.headers, method="GET") - if uid: - folders = [item for item in response if item.get("uid") == uid] - else: - folders = [item for item in response if item.get("title") == to_text(title)] + if response is not None: + if uid: + folders = [item for item in response if item.get("uid") == uid] + else: + folders = [ + item for item in response if item.get("title") == to_text(title) + ] - if folders: - return folders[0] + if folders: + return folders[0] return None From 6decf1c990ee8f36c00d9a66555ffa96c90f819e Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Fri, 28 Jun 2024 13:01:28 +0200 Subject: [PATCH 113/120] test: separate sub folder test file --- .../targets/grafana_folder/tasks/main.yml | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/tests/integration/targets/grafana_folder/tasks/main.yml b/tests/integration/targets/grafana_folder/tasks/main.yml index 7880e8dc..ca3bd168 100644 --- a/tests/integration/targets/grafana_folder/tasks/main.yml +++ b/tests/integration/targets/grafana_folder/tasks/main.yml @@ -1,9 +1,21 @@ --- -- name: Folder creation and deletion - ansible.builtin.include_tasks: create-delete.yml +- name: Include folder task files + ansible.builtin.include_tasks: "{{ item }}.yml" + loop: + - create-delete + - org -- name: Folder creation and deletion for organization - ansible.builtin.include_tasks: org.yml +- name: Check for support of API endpoint + register: result + ignore_errors: true + community.grafana.grafana_folder: + url: "{{ grafana_url }}" + url_username: "{{ grafana_username }}" + url_password: "{{ grafana_password }}" + title: apitest + parent_uid: "parent" + state: absent -- name: Folder creation and deletion for subfolders +- name: Include folder task file for subfolders ansible.builtin.include_tasks: sub.yml + when: "result.msg | default('') != 'Subfolder API is available starting Grafana v11'" From 57fc59268da2b1bd6ba27addce21f433c20c7a75 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Fri, 28 Jun 2024 13:04:21 +0200 Subject: [PATCH 114/120] docs: version notice for parent_uid arg --- plugins/modules/grafana_folder.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/modules/grafana_folder.py b/plugins/modules/grafana_folder.py index c9fa2364..054386d2 100644 --- a/plugins/modules/grafana_folder.py +++ b/plugins/modules/grafana_folder.py @@ -44,6 +44,7 @@ parent_uid: description: - The parent folder UID. + - Available with subfolder feature of Grafana 11. type: str state: description: From 23ff04f28abc8905e89eadd3b2c590390b29d4e2 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Fri, 28 Jun 2024 13:05:27 +0200 Subject: [PATCH 115/120] docs: changelog fragment format fix --- changelogs/fragments/381-sub-folders.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelogs/fragments/381-sub-folders.yml b/changelogs/fragments/381-sub-folders.yml index 36e46667..bb6f2506 100644 --- a/changelogs/fragments/381-sub-folders.yml +++ b/changelogs/fragments/381-sub-folders.yml @@ -1,3 +1,3 @@ --- minor_changes: - - `grafana_folder` manage subfolders and specify uid + - Manage subfolders for `grafana_folder` and specify uid From 89e5cfa37322d1af54ec4e4a3fcf417ebed489c4 Mon Sep 17 00:00:00 2001 From: Nemental <15136847+Nemental@users.noreply.github.com> Date: Fri, 28 Jun 2024 13:11:19 +0200 Subject: [PATCH 116/120] chore: lower folder --- plugins/modules/grafana_folder.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/plugins/modules/grafana_folder.py b/plugins/modules/grafana_folder.py index 054386d2..dc7e1b6d 100644 --- a/plugins/modules/grafana_folder.py +++ b/plugins/modules/grafana_folder.py @@ -25,15 +25,15 @@ author: - Rémi REY (@rrey) version_added: "1.0.0" -short_description: Manage Grafana Folders +short_description: Manage Grafana folders description: - - Create/update/delete Grafana Folders through the Folders API. + - Create/update/delete Grafana folders through the folders API. requirements: - - The Folders API is only available starting Grafana 5 and the module will fail if the server version is lower than version 5. + - The folders API is only available starting Grafana 5 and the module will fail if the server version is lower than version 5. options: name: description: - - The title of the Grafana Folder. + - The title of the Grafana folder. required: true type: str aliases: [ title ] @@ -101,18 +101,18 @@ RETURN = """ --- folder: - description: Information about the Folder + description: Information about the folder returned: On success type: complex contains: id: - description: The Folder identifier + description: The folder identifier returned: always type: int sample: - 42 uid: - description: The Folder uid + description: The folder uid returned: always type: str sample: @@ -124,13 +124,13 @@ sample: - 1 title: - description: The Folder title + description: The folder title returned: always type: str sample: - "Department ABC" url: - description: The Folder url + description: The folder url returned: always type: str sample: @@ -240,7 +240,7 @@ def __init__(self, module): self._module.fail_json(failed=True, msg=to_text(e)) if grafana_version["major"] < 5: self._module.fail_json( - failed=True, msg="Folders API is available starting Grafana v5" + failed=True, msg="folders API is available starting Grafana v5" ) if grafana_version["major"] < 11 and module.params["parent_uid"]: self._module.fail_json( @@ -276,7 +276,7 @@ def _send_request(self, url, data=None, headers=None, method="GET"): response = resp.read() or "{}" return self._module.from_json(response) self._module.fail_json( - failed=True, msg="Grafana Folders API answered with HTTP %d" % status_code + failed=True, msg="Grafana folders API answered with HTTP %d" % status_code ) def switch_organization(self, org_id): From 19218c4bfb201b6b6b2e3727fffa9eab84ca6be8 Mon Sep 17 00:00:00 2001 From: Moritz <15136847+Nemental@users.noreply.github.com> Date: Wed, 3 Jul 2024 10:32:14 +0200 Subject: [PATCH 117/120] chore: simplify if statement Co-authored-by: Sebastian Gumprich --- plugins/modules/grafana_folder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/grafana_folder.py b/plugins/modules/grafana_folder.py index dc7e1b6d..9e7cea92 100644 --- a/plugins/modules/grafana_folder.py +++ b/plugins/modules/grafana_folder.py @@ -316,7 +316,7 @@ def create_folder(self, title, uid=None, parent_uid=None): def get_folder(self, title, uid=None, parent_uid=None): url = "/api/folders%s" % ("?parentUid=%s" % parent_uid if parent_uid else "") response = self._send_request(url, headers=self.headers, method="GET") - if response is not None: + if response: if uid: folders = [item for item in response if item.get("uid") == uid] else: From 2e15a7ebf04889a52d1ceaee20e859b29f7bed8b Mon Sep 17 00:00:00 2001 From: sfhl <59044937+sfhl@users.noreply.github.com> Date: Tue, 9 Jul 2024 13:22:49 +0200 Subject: [PATCH 118/120] fix wrong vars --- roles/grafana/tasks/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/roles/grafana/tasks/main.yml b/roles/grafana/tasks/main.yml index d0ddab40..80704fa7 100644 --- a/roles/grafana/tasks/main.yml +++ b/roles/grafana/tasks/main.yml @@ -345,8 +345,8 @@ created_by: "{{ silence.created_by }}" ends_at: "{{ silence.ends_at }}" matchers: "{{ silence.matchers }}" - org_id: "{{ datasource.org_id | default(omit) }}" - org_name: "{{ datasource.org_name | default(omit) }}" + org_id: "{{ silence.org_id | default(omit) }}" + org_name: "{{ silence.org_name | default(omit) }}" starts_at: "{{ silence.starts_at }}" state: "{{ silence.state | default(omit) }}" loop: "{{ grafana_silences }}" From 9bc0280a75644b3877f3cf259af99f5eb90699cc Mon Sep 17 00:00:00 2001 From: sfhl <59044937+sfhl@users.noreply.github.com> Date: Tue, 9 Jul 2024 13:58:48 +0200 Subject: [PATCH 119/120] Create patch-1.yaml --- changelogs/fragments/patch-1.yaml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 changelogs/fragments/patch-1.yaml diff --git a/changelogs/fragments/patch-1.yaml b/changelogs/fragments/patch-1.yaml new file mode 100644 index 00000000..1b2c3d60 --- /dev/null +++ b/changelogs/fragments/patch-1.yaml @@ -0,0 +1,2 @@ +bugfixes: + - Fix var prefixes From 0f3ed379c2bac3c3322501baf33b4f17ec18853a Mon Sep 17 00:00:00 2001 From: Sebastian Gumprich Date: Tue, 9 Jul 2024 14:38:16 +0200 Subject: [PATCH 120/120] Update patch-1.yaml --- changelogs/fragments/patch-1.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelogs/fragments/patch-1.yaml b/changelogs/fragments/patch-1.yaml index 1b2c3d60..84e78c92 100644 --- a/changelogs/fragments/patch-1.yaml +++ b/changelogs/fragments/patch-1.yaml @@ -1,2 +1,2 @@ bugfixes: - - Fix var prefixes + - Fix var prefixes in silence-task in role