diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-05 16:18:34 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-05 16:18:34 +0000 |
commit | 3667197efb7b18ec842efd504785965911f8ac4b (patch) | |
tree | 0b986a4bc6879d080b100666a97cdabbc9ca1f28 /ansible_collections/community/grafana | |
parent | Adding upstream version 9.5.1+dfsg. (diff) | |
download | ansible-upstream/10.0.0+dfsg.tar.xz ansible-upstream/10.0.0+dfsg.zip |
Adding upstream version 10.0.0+dfsg.upstream/10.0.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/community/grafana')
39 files changed, 1100 insertions, 59 deletions
diff --git a/ansible_collections/community/grafana/.github/workflows/ansible-test.yml b/ansible_collections/community/grafana/.github/workflows/ansible-test.yml index a5717781f..7d474372b 100644 --- a/ansible_collections/community/grafana/.github/workflows/ansible-test.yml +++ b/ansible_collections/community/grafana/.github/workflows/ansible-test.yml @@ -45,7 +45,7 @@ jobs: strategy: fail-fast: false matrix: - grafana_version: ["9.5.14", "8.5.27", "10.2.2"] + grafana_version: ["8.5.27", "9.5.18", "10.4.2"] ansible_version: ["stable-2.15", "stable-2.16", "devel"] python_version: ["3.10"] services: @@ -67,7 +67,7 @@ jobs: strategy: fail-fast: false matrix: - grafana_version: ["9.5.14", "8.5.27", "10.2.2"] + grafana_version: ["8.5.27", "9.5.18", "10.4.2"] ansible_version: ["stable-2.15", "stable-2.16", "devel"] python_version: ["3.10"] services: @@ -86,7 +86,7 @@ jobs: - name: Install dependencies run: | python -m pip install --no-cache-dir --upgrade pip - pip install "git+https://github.com/ansible/ansible@${{ matrix.ansible_version }}" molecule molecule-docker + pip install "git+https://github.com/ansible/ansible@${{ matrix.ansible_version }}" molecule molecule-plugins[docker] 'requests<2.32.0' - name: Test with molecule run: | diff --git a/ansible_collections/community/grafana/CHANGELOG.rst b/ansible_collections/community/grafana/CHANGELOG.rst index 8a40ac18f..9449bdc5c 100644 --- a/ansible_collections/community/grafana/CHANGELOG.rst +++ b/ansible_collections/community/grafana/CHANGELOG.rst @@ -4,6 +4,35 @@ Grafana Collection Release Notes .. contents:: Topics +v1.9.1 +====== + +Bugfixes +-------- + +- undo removed deprecated `message` argument in `grafana_dashboard` + +v1.9.0 +====== + +Minor Changes +------------- + +- Add new module `grafana_silence` to create and delete silences through the API +- Add role components for `grafana_silence` module +- lookup - grafana_dashboards - add `validate_certs` and `ca_path` options to plugin for custom certs validation + +Removed Features (previously deprecated) +---------------------------------------- + +- removed deprecated `message` argument in `grafana_dashboard` + +Bugfixes +-------- + +- Handling of desired default state for first `grafana_datasource` +- Ignore `type` argument for diff comparison if `grafana-postgresq-datasource` alias `postgres` is used +- Set umask for `grafana_plugin` command v1.8.0 ====== @@ -288,4 +317,3 @@ Release Summary --------------- Initial migration of Grafana content from Ansible core (2.9/devel) - diff --git a/ansible_collections/community/grafana/FILES.json b/ansible_collections/community/grafana/FILES.json index be95fa2bc..037433bd7 100644 --- a/ansible_collections/community/grafana/FILES.json +++ b/ansible_collections/community/grafana/FILES.json @@ -53,7 +53,7 @@ "name": ".github/workflows/ansible-test.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "e3e774ec33a3dc2a5ea1da40931ab5e6b96d3b67e57454fc867f9009cbc9797f", + "chksum_sha256": "cd6239f3c1bd2e8e47e65c2e1504273adabd0debe5034b52c0b3d1817d5ece75", "format": 1 }, { @@ -470,6 +470,76 @@ "format": 1 }, { + "name": "changelogs/fragments/353-docs-add-influxdb-flux-example.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a07b0c46777ec3102120e2d1374c24f11fa49b643f9ee598697d7cb7931a6fb0", + "format": 1 + }, + { + "name": "changelogs/fragments/354-fix-find-grafana-versions.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "988704ddd40d7a4e6c20a045fa813de4fff2db0ad722f64f97c08af0ebcc3ee6", + "format": 1 + }, + { + "name": "changelogs/fragments/355-rm-dashboard-message-argument.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1f9b81756d099816b1ef211ed9420406256f31e44a1af8e21025d1781ec8c452", + "format": 1 + }, + { + "name": "changelogs/fragments/356-lookup-dashboards-add-custom-certs-verification-logic.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "45e0bb6e524aecb31ef2ed642a981a964164da2871ec3b5167ce79205ef77d3e", + "format": 1 + }, + { + "name": "changelogs/fragments/357-module-grafana-silence.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d4302eaaf80c43b0e4b8d9b919579ec6fbf2156966696b17342ff3caaa4b39a8", + "format": 1 + }, + { + "name": "changelogs/fragments/358-role-grafana-silence.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "44f72091e352d9f720a2c69be372a251ae80371c9bbb3d046a1c92e3169597f9", + "format": 1 + }, + { + "name": "changelogs/fragments/362-plugin-umask.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a8e15e87758848e90e4798e0d8bbf4ae3d69c8df49f36d3f6f4665b7bac175dc", + "format": 1 + }, + { + "name": "changelogs/fragments/364-first-datasource-default.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1706a7f2ab349d835f22e173fc4b1f1abd30d08ddb6a24054a12999254d5f816", + "format": 1 + }, + { + "name": "changelogs/fragments/367-dashboard-undo-breaing-change-message.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d9379b41ea1756af06746c98636b2f29c04b3dcb3d1c05d131d30d84d569e75f", + "format": 1 + }, + { + "name": "changelogs/fragments/368-molecule-pin-requests.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "cf41eb78ee4dc1c0b4e70f18040bf236de5764067de76a46c2ca60bb787a0801", + "format": 1 + }, + { "name": "changelogs/fragments/add-units-datasource.yml", "ftype": "file", "chksum_type": "sha256", @@ -543,7 +613,7 @@ "name": "changelogs/changelog.yaml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "641de48bd2e1d3ab8daeace7b522413299bd4d2521204d1f707e8c23bb445480", + "chksum_sha256": "58b3c24d8492d4052650bd4595af4f4150984005e7e2cf1fa45d86c6710e3632", "format": 1 }, { @@ -571,14 +641,14 @@ "name": "hacking/check_fragment.sh", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "89b848ffbab17ebb115443ad4d599db0238a1f35a95d52ab4eedf990eef097a8", + "chksum_sha256": "911f382f8cb1de3f26d67809bba3a950ad75cd0a95cd39de13c2eb24131bc9aa", "format": 1 }, { "name": "hacking/find_grafana_versions.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "39acc81ec9733183bbb4eeae0c10c3aeecdeedd1ffc2dabcc675b84e16565f3e", + "chksum_sha256": "72518bc80cd4e6c1e6c28aa057bfb05e96231cffd71fb481dba53b76fcb86786", "format": 1 }, { @@ -599,7 +669,7 @@ "name": "meta/runtime.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "6872a5ea5042e0a7a43f2b60fe6c02a24e3e76b25deb50a46567df781345e41b", + "chksum_sha256": "0ccfa35f79e37b4162e45d083ec7170e8dd3b3011e506101f58ccbc5d183adbb", "format": 1 }, { @@ -620,7 +690,7 @@ "name": "molecule/default/converge.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "c1e0e02c807ceafdf7bd707e0def1e9d2118e6389af9af44dc832dfc0483dab9", + "chksum_sha256": "707ee0f0231e6bfb2f467ec7d5089125b0b7d77ea85560921ad4eb179453c922", "format": 1 }, { @@ -711,7 +781,7 @@ "name": "plugins/lookup/grafana_dashboard.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "7483dabbd84c95d3cd603e2d9d905b51f00e9f6ab4974164f31387c3ee63535d", + "chksum_sha256": "7e46eff65f8eda5fd1fbd4de9790d018acac261fa5e747dbdc1c9bc6062608cf", "format": 1 }, { @@ -753,7 +823,7 @@ "name": "plugins/modules/grafana_datasource.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "23081add13f2aba994b4cddb264972d219bee823ac1e4f454931d5a6a374bd94", + "chksum_sha256": "108ff562f0e37620ddcf6751f144aad05432bd7f3369bfbfc957e9df49310001", "format": 1 }, { @@ -788,7 +858,14 @@ "name": "plugins/modules/grafana_plugin.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "fb0ee164a2936bc35381cf79026b6725c7a1df139e71d58989f8912432043c00", + "chksum_sha256": "a6e739390d7afc946f656dc2080e96ab0c2e630e016fc20aaa2351ec909b5d64", + "format": 1 + }, + { + "name": "plugins/modules/grafana_silence.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1a90d5e29905a481dfed1fbd573a342984cf1e891c3643be5c13541302810cc5", "format": 1 }, { @@ -830,7 +907,7 @@ "name": "roles/grafana/defaults/main.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "ee6026b2dd62777a731f2214949124f45d73309102123cad31ad55489a90ca45", + "chksum_sha256": "bce677aac146e75eee5d57c22d7ad1bdff128abb52d4bf05f8cd30f47d6c4105", "format": 1 }, { @@ -844,7 +921,7 @@ "name": "roles/grafana/meta/main.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "192ef2ac687cd5594febffbc0ab6f3d184cceed34dc66c11ff217db19f4ccfc2", + "chksum_sha256": "840995ce8972789e894e6c9be6d2fcd030fd3c478da292e7e363cb40c6f462c9", "format": 1 }, { @@ -858,14 +935,14 @@ "name": "roles/grafana/tasks/main.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "0de2cd11feb7e45f3427d063c3123024f94401bc5c1b48eaecea9716efcc03d5", + "chksum_sha256": "beb75cb00b20421fd4f4c81d72d32d13986005e809dd2c550a15290f09f1b9a2", "format": 1 }, { "name": "roles/grafana/README.md", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "ab4f69856777b7b0c208c3c3d06a9771c98f81a729618dc94df80ea981b1b6ab", + "chksum_sha256": "f04e735d327e90a7bf9b51ce09c4834f8ed6c6e5ba6d635cdd4e92d807171794", "format": 1 }, { @@ -1054,7 +1131,7 @@ "name": "tests/integration/targets/grafana_datasource/tasks/elastic.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "69fba433ef869f3bb1916f2036a43985af48d21118f08e969f6020532a963d43", + "chksum_sha256": "c2db61ab9613c93dc35ed793aca09451ae672ab5e24c43b456a970ad0ff63f56", "format": 1 }, { @@ -1096,7 +1173,7 @@ "name": "tests/integration/targets/grafana_datasource/tasks/postgres.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "4ed316aab332c0dfcc005213ef28087ea49ed011c0a73bdd0e5f8481e9403de4", + "chksum_sha256": "bdd63b0df46202a010b8d06b6658c303ed3a8893733e807ff79dd13ca042d8b7", "format": 1 }, { @@ -1499,6 +1576,55 @@ "format": 1 }, { + "name": "tests/integration/targets/grafana_silence", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/grafana_silence/defaults", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/grafana_silence/defaults/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d7d3a06f224d1576e39fcc2083e33261f30e3ceac18506f17772d8bcb72315c2", + "format": 1 + }, + { + "name": "tests/integration/targets/grafana_silence/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/grafana_silence/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "330c281b5f69223f9f6292aab612dc3ebe4511c87b2f9748b386d1aaca2f5a89", + "format": 1 + }, + { + "name": "tests/integration/targets/grafana_silence/runme.sh", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b9aa6c8dadef0b4ce9cb08a05d0cf6c40f882d04f9e698bff795b26896e8853c", + "format": 1 + }, + { + "name": "tests/integration/targets/grafana_silence/site.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9c2b6df4d70c00df0765796d40ef1dbaa673984ce30a06c0e89569c1a7bcaea2", + "format": 1 + }, + { "name": "tests/integration/targets/grafana_team", "ftype": "dir", "chksum_type": null, @@ -1649,14 +1775,14 @@ "name": "tests/sanity/ignore-2.15.txt", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "0908af1f4e86722688b3f158cfa9cc81c931ef40c0a166c398f47a4af69a93f9", + "chksum_sha256": "743d0edd0ef882d4dca19699436e4ea09d2acb4e3418385c8e7b89cfaf6e415e", "format": 1 }, { "name": "tests/sanity/ignore-2.16.txt", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "0908af1f4e86722688b3f158cfa9cc81c931ef40c0a166c398f47a4af69a93f9", + "chksum_sha256": "743d0edd0ef882d4dca19699436e4ea09d2acb4e3418385c8e7b89cfaf6e415e", "format": 1 }, { @@ -1667,6 +1793,13 @@ "format": 1 }, { + "name": "tests/sanity/ignore-2.18.txt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "743d0edd0ef882d4dca19699436e4ea09d2acb4e3418385c8e7b89cfaf6e415e", + "format": 1 + }, + { "name": "tests/sanity/ignore-2.9.txt", "ftype": "file", "chksum_type": "sha256", @@ -1719,7 +1852,21 @@ "name": "tests/unit/modules/grafana/grafana_plugin/test_grafana_plugin.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "ada20cb8aef9852aee7b79afddd78f144f813c9df55baf3b8adc1b3863a8e9db", + "chksum_sha256": "9564a8a336726addac97b13fb19813c1d97824d5ca65e6cdc55b9c940554c255", + "format": 1 + }, + { + "name": "tests/unit/modules/grafana/grafana_silence", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/unit/modules/grafana/grafana_silence/test_grafana_silence.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7b6a2c19e99236c08e318b25fb9ee100ee79f6ce07abd9853254f17c64d4c1c8", "format": 1 }, { @@ -1782,7 +1929,7 @@ "name": "CHANGELOG.rst", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "e20df0ad2408ce9c311cf3bf18c99b2362c6eee388db48286b13be28ecedd8db", + "chksum_sha256": "70056dd2063eee72103302580afc77a58435eca179f53de3a2fefb3ad1798b09", "format": 1 }, { @@ -1796,7 +1943,7 @@ "name": "README.md", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "8e93c6a3ce656865252643666a94835e98d904107d7a678f0a74774445f91125", + "chksum_sha256": "f7151290ee62498487eb8bbb7fb07383ac448d6f192b519f069bd99f453d2385", "format": 1 }, { diff --git a/ansible_collections/community/grafana/MANIFEST.json b/ansible_collections/community/grafana/MANIFEST.json index 71f9afdc1..2c1f7f1a0 100644 --- a/ansible_collections/community/grafana/MANIFEST.json +++ b/ansible_collections/community/grafana/MANIFEST.json @@ -2,7 +2,7 @@ "collection_info": { "namespace": "community", "name": "grafana", - "version": "1.8.0", + "version": "1.9.1", "authors": [ "R\u00e9mi REY (@rrey)", "Thierry Sall\u00e9 (@seuf)" @@ -25,7 +25,7 @@ "name": "FILES.json", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "98a10c2378e1ad46d7031d895c557d33af10809314832b9e3af5c2e607099e0b", + "chksum_sha256": "4ff85c9d6729fe479fe05c2e56a41833f87286a8ee1af5b2b730ccd876c04ebb", "format": 1 }, "format": 1 diff --git a/ansible_collections/community/grafana/README.md b/ansible_collections/community/grafana/README.md index 6d23f1b96..9ba981ae7 100644 --- a/ansible_collections/community/grafana/README.md +++ b/ansible_collections/community/grafana/README.md @@ -31,13 +31,14 @@ Click on the name of a plugin or module to view that content's documentation: - [grafana_plugin](https://docs.ansible.com/ansible/latest/collections/community/grafana/grafana_plugin_module.html) - [grafana_team](https://docs.ansible.com/ansible/latest/collections/community/grafana/grafana_team_module.html) - [grafana_user](https://docs.ansible.com/ansible/latest/collections/community/grafana/grafana_user_module.html) + - [grafana_silence](https://docs.ansible.com/ansible/latest/collections/community/grafana/grafana_silence_module.html) ## Supported Grafana versions We aim at keeping the last 3 Major versions of Grafana tested. This collection is currently testing the modules against following versions of Grafana: ``` -grafana_version: ["9.5.14", "8.5.27", "10.2.2"] +grafana_version: ["8.5.27", "9.5.18", "10.4.2"] ``` ## Installation and Usage diff --git a/ansible_collections/community/grafana/changelogs/changelog.yaml b/ansible_collections/community/grafana/changelogs/changelog.yaml index 465b42bc3..f8d3f898b 100644 --- a/ansible_collections/community/grafana/changelogs/changelog.yaml +++ b/ansible_collections/community/grafana/changelogs/changelog.yaml @@ -279,3 +279,36 @@ releases: - 349-role-notification-channel.yml - 350-python3.12.yml release_date: '2024-02-21' + 1.9.0: + changes: + bugfixes: + - Handling of desired default state for first `grafana_datasource` + - Ignore `type` argument for diff comparison if `grafana-postgresq-datasource` + alias `postgres` is used + - Set umask for `grafana_plugin` command + minor_changes: + - Add new module `grafana_silence` to create and delete silences through the + API + - Add role components for `grafana_silence` module + - lookup - grafana_dashboards - add `validate_certs` and `ca_path` options to + plugin for custom certs validation + removed_features: + - removed deprecated `message` argument in `grafana_dashboard` + fragments: + - 353-docs-add-influxdb-flux-example.yml + - 354-fix-find-grafana-versions.yml + - 355-rm-dashboard-message-argument.yml + - 356-lookup-dashboards-add-custom-certs-verification-logic.yml + - 357-module-grafana-silence.yml + - 358-role-grafana-silence.yml + - 362-plugin-umask.yml + - 364-first-datasource-default.yml + release_date: '2024-04-30' + 1.9.1: + changes: + bugfixes: + - undo removed deprecated `message` argument in `grafana_dashboard` + fragments: + - 367-dashboard-undo-breaing-change-message.yml + - 368-molecule-pin-requests.yml + release_date: '2024-05-21' diff --git a/ansible_collections/community/grafana/changelogs/fragments/353-docs-add-influxdb-flux-example.yml b/ansible_collections/community/grafana/changelogs/fragments/353-docs-add-influxdb-flux-example.yml new file mode 100644 index 000000000..b3baa1bc5 --- /dev/null +++ b/ansible_collections/community/grafana/changelogs/fragments/353-docs-add-influxdb-flux-example.yml @@ -0,0 +1,3 @@ +--- +trivial: + - grafana_datasource - (docs) add influxdb v2 flux example
\ No newline at end of file diff --git a/ansible_collections/community/grafana/changelogs/fragments/354-fix-find-grafana-versions.yml b/ansible_collections/community/grafana/changelogs/fragments/354-fix-find-grafana-versions.yml new file mode 100644 index 000000000..4ba22771a --- /dev/null +++ b/ansible_collections/community/grafana/changelogs/fragments/354-fix-find-grafana-versions.yml @@ -0,0 +1,3 @@ +--- +trivial: + - fix find grafana versions python script comparation diff --git a/ansible_collections/community/grafana/changelogs/fragments/355-rm-dashboard-message-argument.yml b/ansible_collections/community/grafana/changelogs/fragments/355-rm-dashboard-message-argument.yml new file mode 100644 index 000000000..3f54afa5e --- /dev/null +++ b/ansible_collections/community/grafana/changelogs/fragments/355-rm-dashboard-message-argument.yml @@ -0,0 +1,3 @@ +--- +removed_features: + - removed deprecated `message` argument in `grafana_dashboard` diff --git a/ansible_collections/community/grafana/changelogs/fragments/356-lookup-dashboards-add-custom-certs-verification-logic.yml b/ansible_collections/community/grafana/changelogs/fragments/356-lookup-dashboards-add-custom-certs-verification-logic.yml new file mode 100644 index 000000000..182822d9b --- /dev/null +++ b/ansible_collections/community/grafana/changelogs/fragments/356-lookup-dashboards-add-custom-certs-verification-logic.yml @@ -0,0 +1,2 @@ +minor_changes: + - lookup - grafana_dashboards - add `validate_certs` and `ca_path` options to plugin for custom certs validation diff --git a/ansible_collections/community/grafana/changelogs/fragments/357-module-grafana-silence.yml b/ansible_collections/community/grafana/changelogs/fragments/357-module-grafana-silence.yml new file mode 100644 index 000000000..a9ff03703 --- /dev/null +++ b/ansible_collections/community/grafana/changelogs/fragments/357-module-grafana-silence.yml @@ -0,0 +1,2 @@ +minor_changes: + - Add new module `grafana_silence` to create and delete silences through the API diff --git a/ansible_collections/community/grafana/changelogs/fragments/358-role-grafana-silence.yml b/ansible_collections/community/grafana/changelogs/fragments/358-role-grafana-silence.yml new file mode 100644 index 000000000..a72161f04 --- /dev/null +++ b/ansible_collections/community/grafana/changelogs/fragments/358-role-grafana-silence.yml @@ -0,0 +1,2 @@ +minor_changes: + - Add role components for `grafana_silence` module
\ No newline at end of file diff --git a/ansible_collections/community/grafana/changelogs/fragments/362-plugin-umask.yml b/ansible_collections/community/grafana/changelogs/fragments/362-plugin-umask.yml new file mode 100644 index 000000000..cbef21ffd --- /dev/null +++ b/ansible_collections/community/grafana/changelogs/fragments/362-plugin-umask.yml @@ -0,0 +1,2 @@ +bugfixes: + - Set umask for `grafana_plugin` command diff --git a/ansible_collections/community/grafana/changelogs/fragments/364-first-datasource-default.yml b/ansible_collections/community/grafana/changelogs/fragments/364-first-datasource-default.yml new file mode 100644 index 000000000..3d39e9a0a --- /dev/null +++ b/ansible_collections/community/grafana/changelogs/fragments/364-first-datasource-default.yml @@ -0,0 +1,3 @@ +bugfixes: + - Handling of desired default state for first `grafana_datasource` + - Ignore `type` argument for diff comparison if `grafana-postgresq-datasource` alias `postgres` is used diff --git a/ansible_collections/community/grafana/changelogs/fragments/367-dashboard-undo-breaing-change-message.yml b/ansible_collections/community/grafana/changelogs/fragments/367-dashboard-undo-breaing-change-message.yml new file mode 100644 index 000000000..085ae0cb8 --- /dev/null +++ b/ansible_collections/community/grafana/changelogs/fragments/367-dashboard-undo-breaing-change-message.yml @@ -0,0 +1,3 @@ +--- +bugfixes: + - undo removed deprecated `message` argument in `grafana_dashboard` diff --git a/ansible_collections/community/grafana/changelogs/fragments/368-molecule-pin-requests.yml b/ansible_collections/community/grafana/changelogs/fragments/368-molecule-pin-requests.yml new file mode 100644 index 000000000..6e60e0348 --- /dev/null +++ b/ansible_collections/community/grafana/changelogs/fragments/368-molecule-pin-requests.yml @@ -0,0 +1,3 @@ +--- +trivial: + - pin version of requests package for molecule tests diff --git a/ansible_collections/community/grafana/hacking/check_fragment.sh b/ansible_collections/community/grafana/hacking/check_fragment.sh index bf90cae65..3173b342f 100755 --- a/ansible_collections/community/grafana/hacking/check_fragment.sh +++ b/ansible_collections/community/grafana/hacking/check_fragment.sh @@ -1,7 +1,7 @@ -#!/bin/bash +#!/usr/bin/env bash function fail() { - cat << EOF + cat <<EOF Dear contributor, Thank you for you Pull Request ! @@ -10,7 +10,7 @@ function fail() { It will help your change be released faster ! Thank you ! EOF - exit 1 + exit 1 } FRAGMENTS=$(git fetch && git diff --name-only --diff-filter=ACMRT origin/main..HEAD | grep "changelogs") diff --git a/ansible_collections/community/grafana/hacking/find_grafana_versions.py b/ansible_collections/community/grafana/hacking/find_grafana_versions.py index b31fc530a..716f877a8 100644 --- a/ansible_collections/community/grafana/hacking/find_grafana_versions.py +++ b/ansible_collections/community/grafana/hacking/find_grafana_versions.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python import json import requests @@ -7,12 +7,12 @@ import requests def get_by_major(version): if version.startswith("v"): version = version[1:] - return (version[0], version, int(version.replace(".", ""))) + return int(version.split(".")[0]), version, tuple(map(int, version.split("."))) def get_grafana_releases(): r = requests.get( - "https://api.github.com/repos/grafana/grafana/releases?per_page=50", + "https://api.github.com/repos/grafana/grafana/releases?per_page=100", headers={"Accept": "application/vnd.github.v3+json"}, ) if r.status_code != 200: @@ -20,19 +20,20 @@ def get_grafana_releases(): return r.json() -by_major = {} - if __name__ == "__main__": releases = get_grafana_releases() - for item in releases: - if item.get("prerelease"): + by_major = {} + + for release in releases: + if release.get("prerelease") or any( + char in release.get("tag_name") for char in "-+" + ): continue - major, version, as_int = get_by_major(item.get("tag_name")) - if major not in by_major.keys() or by_major[major]["as_int"] < as_int: - by_major[major] = {"version": version, "as_int": as_int} - latest_3_majors = sorted(list(by_major.keys()), reverse=True)[:3] - - latest_releases = [] - for idx in latest_3_majors: - latest_releases.append(by_major[idx]["version"]) + major, version, as_tuple = get_by_major(release.get("tag_name")) + 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_releases = [by_major[idx]["version"] for idx in latest_3_majors] + print(json.dumps(latest_releases)) diff --git a/ansible_collections/community/grafana/meta/runtime.yml b/ansible_collections/community/grafana/meta/runtime.yml index 15f5554fc..d29f1dda8 100644 --- a/ansible_collections/community/grafana/meta/runtime.yml +++ b/ansible_collections/community/grafana/meta/runtime.yml @@ -11,3 +11,4 @@ action_groups: - grafana_plugin - grafana_team - grafana_user + - grafana_silence diff --git a/ansible_collections/community/grafana/molecule/default/converge.yml b/ansible_collections/community/grafana/molecule/default/converge.yml index b1e1cf20b..4046e3369 100644 --- a/ansible_collections/community/grafana/molecule/default/converge.yml +++ b/ansible_collections/community/grafana/molecule/default/converge.yml @@ -45,4 +45,15 @@ path: test_dashboard.json overwrite: true + grafana_silences: + - comment: molecule test + created_by: ansible + ends_at: "2030-01-01T00:00:00.000Z" + matchers: + - isEqual: true + isRegex: false + name: environment + value: test + starts_at: "2029-12-31T23:23:59.000Z" + roles: [{role: community.grafana.grafana}] diff --git a/ansible_collections/community/grafana/plugins/lookup/grafana_dashboard.py b/ansible_collections/community/grafana/plugins/lookup/grafana_dashboard.py index c7fa31574..83ee250f7 100644 --- a/ansible_collections/community/grafana/plugins/lookup/grafana_dashboard.py +++ b/ansible_collections/community/grafana/plugins/lookup/grafana_dashboard.py @@ -41,6 +41,13 @@ options: description: optional filter for dashboard search. env: - name: GRAFANA_DASHBOARD_SEARCH + validate_certs: + description: flag to control SSL certificate validation + type: boolean + default: true + ca_path: + description: string of the file system path to CA cert bundle to use for validation + type: string """ EXAMPLES = """ @@ -51,13 +58,25 @@ EXAMPLES = """ - name: get all grafana dashboards set_fact: grafana_dashboards: "{{ lookup('grafana_dashboard', 'grafana_url=http://grafana.company.com grafana_api_key=' ~ grafana_api_key) }}" + +- name: get project foo grafana dashboards (validate SSL certificates of the instance with custom CA Certificate Bundle) + set_fact: + grafana_dashboards: | + {{ + lookup( + 'grafana_dashboard', + 'grafana_url=https://grafana.company.com grafana_user=admin grafana_password=admin search=foo', + validate_certs=true, + ca_path='/path/to/chain.crt' + ) + }} """ import json import os from ansible.errors import AnsibleError from ansible.plugins.lookup import LookupBase -from ansible.module_utils.urls import basic_auth_header, open_url +from ansible.module_utils.urls import basic_auth_header, open_url, SSLValidationError from ansible.module_utils._text import to_native from ansible.module_utils.six.moves.urllib.error import HTTPError from ansible.utils.display import Display @@ -96,13 +115,15 @@ class GrafanaAPIException(Exception): class GrafanaAPI: - def __init__(self, **kwargs): + def __init__(self, validate_certs, ca_path, **kwargs): self.grafana_url = kwargs.get("grafana_url", ANSIBLE_GRAFANA_URL) self.grafana_api_key = kwargs.get("grafana_api_key", ANSIBLE_GRAFANA_API_KEY) self.grafana_user = kwargs.get("grafana_user", ANSIBLE_GRAFANA_USER) self.grafana_password = kwargs.get("grafana_password", ANSIBLE_GRAFANA_PASSWORD) self.grafana_org_id = kwargs.get("grafana_org_id", ANSIBLE_GRAFANA_ORG_ID) self.search = kwargs.get("search", ANSIBLE_GRAFANA_DASHBOARD_SEARCH) + self.validate_certs = validate_certs + self.ca_path = ca_path def grafana_switch_organisation(self, headers): try: @@ -110,12 +131,19 @@ class GrafanaAPI: "%s/api/user/using/%s" % (self.grafana_url, self.grafana_org_id), headers=headers, method="POST", + validate_certs=self.validate_certs, + ca_path=self.ca_path, ) except HTTPError as e: raise GrafanaAPIException( "Unable to switch to organization %s : %s" % (self.grafana_org_id, to_native(e)) ) + except SSLValidationError as e: + raise GrafanaAPIException( + "Unable to validate server's certificate with %s: %s" + % (self.ca_path, to_native(e)) + ) if r.getcode() != 200: raise GrafanaAPIException( "Unable to switch to organization %s : %s" @@ -153,13 +181,24 @@ class GrafanaAPI: "%s/api/search?query=%s" % (self.grafana_url, self.search), headers=headers, method="GET", + validate_certs=self.validate_certs, + ca_path=self.ca_path, ) else: r = open_url( - "%s/api/search/" % self.grafana_url, headers=headers, method="GET" + "%s/api/search/" % self.grafana_url, + headers=headers, + method="GET", + validate_certs=self.validate_certs, + ca_path=self.ca_path, ) except HTTPError as e: raise GrafanaAPIException("Unable to search dashboards : %s" % to_native(e)) + except SSLValidationError as e: + raise GrafanaAPIException( + "Unable to validate server's certificate with %s: %s" + % (self.ca_path, to_native(e)) + ) if r.getcode() == 200: try: dashboard_list = json.loads(r.read()) @@ -175,6 +214,7 @@ class GrafanaAPI: class LookupModule(LookupBase): def run(self, terms, variables=None, **kwargs): + self.set_options(var_options=variables, direct=kwargs) grafana_args = terms[0].split(" ") grafana_dict = {} ret = [] @@ -189,7 +229,11 @@ class LookupModule(LookupBase): ) grafana_dict[key] = value - grafana = GrafanaAPI(**grafana_dict) + grafana = GrafanaAPI( + **grafana_dict, + validate_certs=self.get_option("validate_certs"), + ca_path=self.get_option("ca_path"), + ) ret = grafana.grafana_list_dashboards() diff --git a/ansible_collections/community/grafana/plugins/modules/grafana_datasource.py b/ansible_collections/community/grafana/plugins/modules/grafana_datasource.py index 29cdbea7c..b011e5db1 100644 --- a/ansible_collections/community/grafana/plugins/modules/grafana_datasource.py +++ b/ansible_collections/community/grafana/plugins/modules/grafana_datasource.py @@ -400,6 +400,23 @@ EXAMPLES = """ time_interval: ">10s" tls_ca_cert: "/etc/ssl/certs/ca.pem" +- name: Create influxdbv2 datasource using fluxql + community.grafana.grafana_datasource: + name: "datasource-influxdb-flux" + grafana_url: "https://grafana.company.com" + grafana_user: "admin" + grafana_password: "xxxxxx" + org_id: "1" + ds_type: "influxdb" + ds_url: "https://influx.company.com:8086" + additional_json_data: + version: "Flux" + organization: "organization" + defaultBucket: "bucket" + tlsSkipVerify: false + additional_secure_json_data: + token: "token" + - name: Create postgres datasource community.grafana.grafana_datasource: name: "datasource-postgres" @@ -536,6 +553,9 @@ def compare_datasources(new, current, compareSecureData=True): del current["password"] if "basicAuthPassword" in current: del current["basicAuthPassword"] + if current["type"] == "grafana-postgresql-datasource" and new["type"] == "postgres": + del current["type"] + del new["type"] # check if secureJsonData should be compared if not compareSecureData: @@ -928,6 +948,9 @@ def main(): if ds is None: grafana_iface.create_datasource(payload) ds = grafana_iface.datasource_by_name(name) + if ds.get("isDefault") != module.params["is_default"]: + grafana_iface.update_datasource(ds.get("id"), payload) + ds = grafana_iface.datasource_by_name(name) module.exit_json( changed=True, datasource=ds, msg="Datasource %s created" % name ) diff --git a/ansible_collections/community/grafana/plugins/modules/grafana_plugin.py b/ansible_collections/community/grafana/plugins/modules/grafana_plugin.py index c510f02ba..db1e8553a 100644 --- a/ansible_collections/community/grafana/plugins/modules/grafana_plugin.py +++ b/ansible_collections/community/grafana/plugins/modules/grafana_plugin.py @@ -223,7 +223,7 @@ def grafana_plugin(module, params): else: cmd = "{0} uninstall {1}".format(grafana_cli, params["name"]) - rc, stdout, stderr = module.run_command(cmd) + rc, stdout, stderr = module.run_command(cmd, umask=0o0022) if rc == 0: stdout_lines = stdout.split("\n") for line in stdout_lines: diff --git a/ansible_collections/community/grafana/plugins/modules/grafana_silence.py b/ansible_collections/community/grafana/plugins/modules/grafana_silence.py new file mode 100644 index 000000000..35f54a3d8 --- /dev/null +++ b/ansible_collections/community/grafana/plugins/modules/grafana_silence.py @@ -0,0 +1,369 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +# Copyright: (c) 2023, flkhndlr (@flkhndlr) + +from __future__ import absolute_import, division, print_function + +DOCUMENTATION = """ +module: grafana_silence +author: + - flkhndlr (@flkhndlr) +version_added: "1.8.0" +short_description: Manage Grafana Silences +description: + - Create/delete Grafana Silences through the Alertmanager Silence API. +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: + comment: + description: + - The comment that describes the silence. + required: true + type: str + created_by: + description: + - The author that creates the silence. + required: true + type: str + starts_at: + description: + - ISO 8601 Timestamp with milliseconds e.g. "2029-07-29T08:45:45.000Z" when the silence starts. + type: str + required: true + ends_at: + description: + - ISO 8601 Timestamp with milliseconds e.g. "2029-07-29T08:45:45.000Z" when the silence will end. + type: str + required: true + matchers: + description: + - List of matchers to select which alerts are affected by the silence. + type: list + elements: dict + required: true + state: + description: + - Delete the first occurrence of a silence with the same settings. Can be "absent" or "present". + default: present + type: str + choices: ["present", "absent"] + skip_version_check: + description: + - Skip Grafana version check and try to reach api endpoint anyway. + - This parameter can be useful if you enabled `hide_version` in grafana.ini + required: False + type: bool + default: False +extends_documentation_fragment: +- community.grafana.basic_auth +- community.grafana.api_key +""" + +EXAMPLES = """ +--- +- name: Create a silence + community.grafana.grafana_silence: + grafana_url: "https://grafana.example.com" + grafana_api_key: "{{ some_api_token_value }}" + 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 + +- name: Delete a silence + community.grafana.grafana_silence: + grafana_url: "https://grafana.example.com" + grafana_api_key: "{{ some_api_token_value }}" + 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 +""" + +RETURN = """ +--- +silence: + description: Information about the silence + returned: On success + type: complex + contains: + id: + description: The id of the silence + returned: success + type: str + sample: + - ec27df6b-ac3c-412f-ae0b-6e3e1f41c9c3 + comment: + description: The comment of the silence + returned: success + type: str + sample: + - this is a test + createdBy: + description: The author of the silence + returned: success + type: str + sample: + - me + startsAt: + description: The begin timestamp of the silence + returned: success + type: str + sample: + - "2029-07-29T08:45:45.000Z" + endsAt: + description: The end timestamp of the silence + returned: success + type: str + sample: + - "2029-07-29T08:55:45.000Z" + matchers: + description: The matchers of the silence + returned: success + type: list + sample: + - [{"isEqual": true, "isRegex": true, "name": "environment", "value": "test"}] + status: + description: The status of the silence + returned: success + type: dict + sample: + - {"state": "pending"} + updatedAt: + description: The timestamp of the last update for the silence + returned: success + type: str + sample: + - "2023-07-27T13:27:33.042Z" +""" + +import json + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.urls import fetch_url, basic_auth_header +from ansible.module_utils._text import to_text +from ansible_collections.community.grafana.plugins.module_utils import base + +__metaclass__ = type + + +class GrafanaError(Exception): + pass + + +class GrafanaSilenceInterface(object): + def __init__(self, module): + self._module = module + # {{{ 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"] + ) + else: + self.headers["Authorization"] = basic_auth_header( + module.params["url_username"], module.params["url_password"] + ) + # }}} + self.grafana_url = base.clean_url(module.params.get("url")) + if module.params.get("skip_version_check") is False: + try: + grafana_version = self.get_version() + except GrafanaError as e: + self._module.fail_json(failed=True, msg=to_text(e)) + if grafana_version["major"] < 8: + self._module.fail_json( + failed=True, + msg="Silences API is available starting with Grafana v8", + ) + + def _send_request(self, url, data=None, headers=None, method="GET"): + if data is not None: + data = json.dumps(data) + if not headers: + headers = [] + + full_url = "{grafana_url}{path}".format(grafana_url=self.grafana_url, path=url) + resp, info = fetch_url( + self._module, full_url, data=data, headers=headers, method=method + ) + status_code = info["status"] + if status_code == 404: + return None + elif status_code == 401: + self._module.fail_json( + failed=True, + msg="Unauthorized to perform action '%s' on '%s'" % (method, full_url), + ) + elif status_code == 403: + self._module.fail_json(failed=True, msg="Permission Denied") + elif status_code in [200, 202]: + return self._module.from_json(resp.read()) + elif status_code == 400: + self._module.fail_json(failed=True, msg=info) + self._module.fail_json( + failed=True, msg="Grafana Silences API answered with HTTP %d" % status_code + ) + + def get_version(self): + url = "/api/health" + response = self._send_request( + url, data=None, headers=self.headers, method="GET" + ) + version = response.get("version") + if version is not None: + major, minor, rev = version.split(".") + return {"major": int(major), "minor": int(minor), "rev": int(rev)} + raise GrafanaError("Failed to retrieve version from '%s'" % url) + + def create_silence(self, comment, created_by, starts_at, ends_at, matchers): + url = "/api/alertmanager/grafana/api/v2/silences" + silence = dict( + comment=comment, + createdBy=created_by, + endsAt=ends_at, + matchers=matchers, + startsAt=starts_at, + ) + response = self._send_request( + url, data=silence, headers=self.headers, method="POST" + ) + if self.get_version()["major"] == 8: + response["silenceID"] = response["id"] + response.pop("id", None) + return response + + def get_silence(self, comment, created_by, starts_at, ends_at, matchers): + url = "/api/alertmanager/grafana/api/v2/silences" + + responses = self._send_request(url, headers=self.headers, method="GET") + + for response in responses: + if ( + response["comment"] == comment + and response["createdBy"] == created_by + and response["startsAt"] == starts_at + and response["endsAt"] == ends_at + and response["matchers"] == matchers + ): + return response + return None + + def get_silence_by_id(self, silence_id): + url = "/api/alertmanager/grafana/api/v2/silence/{SilenceId}".format( + SilenceId=silence_id + ) + response = self._send_request(url, headers=self.headers, method="GET") + return response + + def get_silences(self): + url = "/api/alertmanager/grafana/api/v2/silences" + response = self._send_request(url, headers=self.headers, method="GET") + return response + + def delete_silence(self, silence_id): + url = "/api/alertmanager/grafana/api/v2/silence/{SilenceId}".format( + SilenceId=silence_id + ) + response = self._send_request(url, headers=self.headers, method="DELETE") + return response + + +def setup_module_object(): + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=False, + required_together=base.grafana_required_together(), + mutually_exclusive=base.grafana_mutually_exclusive(), + ) + return module + + +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), + skip_version_check=dict(type="bool", default=False), +) + + +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"] + state = module.params["state"] + + changed = False + failed = False + grafana_iface = GrafanaSilenceInterface(module) + + silence = grafana_iface.get_silence( + comment, created_by, starts_at, ends_at, matchers + ) + + if state == "present": + + if not silence: + silence = grafana_iface.create_silence( + comment, created_by, starts_at, ends_at, matchers + ) + silence = grafana_iface.get_silence_by_id(silence["silenceID"]) + changed = True + else: + module.exit_json( + failed=failed, + changed=changed, + msg="Silence with same parameters already exists! eg. '%s'" + % silence["id"], + ) + elif state == "absent": + if silence: + grafana_iface.delete_silence(silence["id"]) + changed = True + else: + module.exit_json( + failed=False, + changed=changed, + msg="Silence does not exist", + ) + module.exit_json(failed=failed, changed=changed, silence=silence) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/community/grafana/roles/grafana/README.md b/ansible_collections/community/grafana/roles/grafana/README.md index f46434edf..f76e687fc 100644 --- a/ansible_collections/community/grafana/roles/grafana/README.md +++ b/ansible_collections/community/grafana/roles/grafana/README.md @@ -163,6 +163,13 @@ Configure Grafana organizations, dashboards, folders, datasources, teams and use | webhook_password | no | | webhook_url | no | | webhook_username | no | +| [**grafana_silence**](https://docs.ansible.com/ansible/latest/collections/community/grafana/grafana_silence_module.html) | +| comment | yes | +| created_by | yes | +| ends_at | yes | +| matchers | yes | +| starts_at | yes | +| state | no | ## Example Playbook diff --git a/ansible_collections/community/grafana/roles/grafana/defaults/main.yml b/ansible_collections/community/grafana/roles/grafana/defaults/main.yml index 6a84370d3..9756a1331 100644 --- a/ansible_collections/community/grafana/roles/grafana/defaults/main.yml +++ b/ansible_collections/community/grafana/roles/grafana/defaults/main.yml @@ -7,3 +7,4 @@ grafana_datasources: [] grafana_folders: [] grafana_dashboards: [] grafana_notification_channels: [] +grafana_silences: [] diff --git a/ansible_collections/community/grafana/roles/grafana/meta/main.yml b/ansible_collections/community/grafana/roles/grafana/meta/main.yml index 47d4af5b7..b5df42193 100644 --- a/ansible_collections/community/grafana/roles/grafana/meta/main.yml +++ b/ansible_collections/community/grafana/roles/grafana/meta/main.yml @@ -2,7 +2,7 @@ galaxy_info: role_name: grafana author: community - description: Configure Grafana organizations, dashboards, folders, datasources, teams and users + description: Configure Grafana organizations, dashboards, folders, datasources, silences, teams and users license: GPLv3 min_ansible_version: "2.14" galaxy_tags: [grafana, monitoring] diff --git a/ansible_collections/community/grafana/roles/grafana/tasks/main.yml b/ansible_collections/community/grafana/roles/grafana/tasks/main.yml index 82bbc633d..1665a8562 100644 --- a/ansible_collections/community/grafana/roles/grafana/tasks/main.yml +++ b/ansible_collections/community/grafana/roles/grafana/tasks/main.yml @@ -201,3 +201,15 @@ loop: "{{ grafana_dashboards }}" loop_control: {loop_var: dashboard} tags: [dashboard, molecule-idempotence-notest] + + - name: Manage silence + 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 }}" + state: "{{ silence.state | default(omit) }}" + loop: "{{ grafana_silences }}" + loop_control: {loop_var: silence} + tags: silence diff --git a/ansible_collections/community/grafana/tests/integration/targets/grafana_datasource/tasks/elastic.yml b/ansible_collections/community/grafana/tests/integration/targets/grafana_datasource/tasks/elastic.yml index ff05b93f5..be5873574 100644 --- a/ansible_collections/community/grafana/tests/integration/targets/grafana_datasource/tasks/elastic.yml +++ b/ansible_collections/community/grafana/tests/integration/targets/grafana_datasource/tasks/elastic.yml @@ -286,6 +286,32 @@ - result.datasource.secureJsonFields.secureTest == true - result.diff.after.secureJsonData is defined +- name: Delete elasticsearch legacy datasource + register: result + community.grafana.grafana_datasource: + name: datasource/elasticLegacy + grafana_url: "{{ grafana_url }}" + grafana_user: "{{ grafana_username }}" + grafana_password: "{{ grafana_password }}" + state: absent + +- ansible.builtin.assert: + that: + - result.changed + +- name: Delete elasticsearch legacy datasource (idempotency) + register: result + community.grafana.grafana_datasource: + name: datasource/elasticLegacy + grafana_url: "{{ grafana_url }}" + grafana_user: "{{ grafana_username }}" + grafana_password: "{{ grafana_password }}" + state: absent + +- ansible.builtin.assert: + that: + - not result.changed + - name: Delete elasticsearch datasource register: result community.grafana.grafana_datasource: diff --git a/ansible_collections/community/grafana/tests/integration/targets/grafana_datasource/tasks/postgres.yml b/ansible_collections/community/grafana/tests/integration/targets/grafana_datasource/tasks/postgres.yml index b07bc752b..8945e2bdd 100644 --- a/ansible_collections/community/grafana/tests/integration/targets/grafana_datasource/tasks/postgres.yml +++ b/ansible_collections/community/grafana/tests/integration/targets/grafana_datasource/tasks/postgres.yml @@ -56,7 +56,7 @@ - result.datasource.jsonData.timescaledb == true - result.datasource.name == 'datasource-postgres' - result.datasource.orgId == 1 - - result.datasource.type == 'postgres' + - result.datasource.type in ['postgres', 'grafana-postgresql-datasource'] - result.datasource.url == 'postgres.company.com:5432' - result.datasource.user == 'postgres' - result.datasource.withCredentials == false diff --git a/ansible_collections/community/grafana/tests/integration/targets/grafana_silence/defaults/main.yml b/ansible_collections/community/grafana/tests/integration/targets/grafana_silence/defaults/main.yml new file mode 100644 index 000000000..4abf9bb43 --- /dev/null +++ b/ansible_collections/community/grafana/tests/integration/targets/grafana_silence/defaults/main.yml @@ -0,0 +1,5 @@ +--- + +grafana_url: http://grafana:3000/ +grafana_username: admin +grafana_password: admin diff --git a/ansible_collections/community/grafana/tests/integration/targets/grafana_silence/runme.sh b/ansible_collections/community/grafana/tests/integration/targets/grafana_silence/runme.sh new file mode 100755 index 000000000..867afb0d3 --- /dev/null +++ b/ansible_collections/community/grafana/tests/integration/targets/grafana_silence/runme.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +set -eux + +ansible-playbook site.yml diff --git a/ansible_collections/community/grafana/tests/integration/targets/grafana_silence/site.yml b/ansible_collections/community/grafana/tests/integration/targets/grafana_silence/site.yml new file mode 100644 index 000000000..791582277 --- /dev/null +++ b/ansible_collections/community/grafana/tests/integration/targets/grafana_silence/site.yml @@ -0,0 +1,6 @@ +--- +- name: Run tests for grafana_silence + hosts: localhost + tasks: + - ansible.builtin.include_role: + name: ../../grafana_silence diff --git a/ansible_collections/community/grafana/tests/integration/targets/grafana_silence/tasks/main.yml b/ansible_collections/community/grafana/tests/integration/targets/grafana_silence/tasks/main.yml new file mode 100644 index 000000000..ad4cfa25d --- /dev/null +++ b/ansible_collections/community/grafana/tests/integration/targets/grafana_silence/tasks/main.yml @@ -0,0 +1,89 @@ +--- +- 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: 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 diff --git a/ansible_collections/community/grafana/tests/sanity/ignore-2.15.txt b/ansible_collections/community/grafana/tests/sanity/ignore-2.15.txt index 5c82494f9..0a40a23d5 100644 --- a/ansible_collections/community/grafana/tests/sanity/ignore-2.15.txt +++ b/ansible_collections/community/grafana/tests/sanity/ignore-2.15.txt @@ -1,4 +1 @@ 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/ansible_collections/community/grafana/tests/sanity/ignore-2.16.txt b/ansible_collections/community/grafana/tests/sanity/ignore-2.16.txt index 5c82494f9..0a40a23d5 100644 --- a/ansible_collections/community/grafana/tests/sanity/ignore-2.16.txt +++ b/ansible_collections/community/grafana/tests/sanity/ignore-2.16.txt @@ -1,4 +1 @@ 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/ansible_collections/community/grafana/tests/sanity/ignore-2.18.txt b/ansible_collections/community/grafana/tests/sanity/ignore-2.18.txt new file mode 100644 index 000000000..0a40a23d5 --- /dev/null +++ b/ansible_collections/community/grafana/tests/sanity/ignore-2.18.txt @@ -0,0 +1 @@ +plugins/modules/grafana_dashboard.py validate-modules:invalid-argument-name diff --git a/ansible_collections/community/grafana/tests/unit/modules/grafana/grafana_plugin/test_grafana_plugin.py b/ansible_collections/community/grafana/tests/unit/modules/grafana/grafana_plugin/test_grafana_plugin.py index ec691a467..7cc04a99a 100644 --- a/ansible_collections/community/grafana/tests/unit/modules/grafana/grafana_plugin/test_grafana_plugin.py +++ b/ansible_collections/community/grafana/tests/unit/modules/grafana/grafana_plugin/test_grafana_plugin.py @@ -21,7 +21,7 @@ Restart grafana after installing plugins . <service grafana-server restart> def run_command_install_zip(): STDERR = "" STDOUT = """ -installing alexanderzobnin-grafana-zabbix @ +installing alexanderzobnin-grafana-zabbix @ from: /home/grafana//alexanderzobnin-grafana-zabbix-v3.10.5-1-g2219691.zip into: /var/lib/grafana/plugins diff --git a/ansible_collections/community/grafana/tests/unit/modules/grafana/grafana_silence/test_grafana_silence.py b/ansible_collections/community/grafana/tests/unit/modules/grafana/grafana_silence/test_grafana_silence.py new file mode 100644 index 000000000..96522c2af --- /dev/null +++ b/ansible_collections/community/grafana/tests/unit/modules/grafana/grafana_silence/test_grafana_silence.py @@ -0,0 +1,211 @@ +from __future__ import absolute_import, division, print_function + +from unittest import TestCase +from unittest.mock import patch +from ansible_collections.community.grafana.plugins.modules import grafana_silence +from ansible.module_utils._text import to_bytes +from ansible.module_utils import basic +from ansible.module_utils.urls import basic_auth_header +import json + +__metaclass__ = type + + +class MockedReponse(object): + def __init__(self, data): + self.data = data + + def read(self): + return self.data + + +def exit_json(*args, **kwargs): + """function to patch over exit_json; package return data into an exception""" + if "changed" not in kwargs: + kwargs["changed"] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): + """function to patch over fail_json; package return data into an exception""" + kwargs["failed"] = True + raise AnsibleFailJson(kwargs) + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + + pass + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({"ANSIBLE_MODULE_ARGS": args}) + basic._ANSIBLE_ARGS = to_bytes(args) + + +def silence_deleted_resp(): + server_response = json.dumps({"message": "silence deleted"}) + return (MockedReponse(server_response), {"status": 200}) + + +def silence_created_resp(): + server_response = json.dumps({"silenceID": "470b7116-8f06-4bb6-9e6c-6258aa92218e"}) + return (MockedReponse(server_response), {"status": 200}) + + +def silence_get_resp(): + server_response = json.dumps([], sort_keys=True) + return (MockedReponse(server_response), {"status": 200}) + + +def get_silence_by_id_resp(): + server_response = json.dumps([], sort_keys=True) + return (MockedReponse(server_response), {"status": 200}) + + +def get_version_resp(): + return {"major": 10, "minor": 0, "rev": 0} + + +class GrafanaSilenceTest(TestCase): + def setUp(self): + self.authorization = basic_auth_header("admin", "changeme") + self.mock_module_helper = patch.multiple( + basic.AnsibleModule, exit_json=exit_json, fail_json=fail_json + ) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + + # create a new silence + @patch( + "ansible_collections.community.grafana.plugins.modules.grafana_silence.GrafanaSilenceInterface.get_silence" + ) + @patch( + "ansible_collections.community.grafana.plugins.modules.grafana_silence.GrafanaSilenceInterface.get_version" + ) + @patch( + "ansible_collections.community.grafana.plugins.modules.grafana_silence.fetch_url" + ) + def test_create_silence_new_silence( + self, mock_fetch_url, mock_get_version, mock_get_silence + ): + set_module_args( + { + "url": "https://grafana.example.com", + "url_username": "admin", + "url_password": "changeme", + "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", + } + ) + module = grafana_silence.setup_module_object() + mock_get_version.return_value = get_version_resp() + mock_fetch_url.return_value = silence_created_resp() + mock_get_silence.return_value = silence_get_resp() + + grafana_iface = grafana_silence.GrafanaSilenceInterface(module) + result = grafana_iface.create_silence( + "a testcomment", + "me", + "2029-07-29T08:45:45.000Z", + "2029-07-29T08:55:45.000Z", + [ + { + "isEqual": True, + "isRegex": True, + "name": "environment", + "value": "test", + } + ], + ) + mock_fetch_url.assert_called_once_with( + module, + "https://grafana.example.com/api/alertmanager/grafana/api/v2/silences", + data=json.dumps( + { + "comment": "a testcomment", + "createdBy": "me", + "startsAt": "2029-07-29T08:45:45.000Z", + "endsAt": "2029-07-29T08:55:45.000Z", + "matchers": [ + { + "isEqual": True, + "isRegex": True, + "name": "environment", + "value": "test", + } + ], + }, + sort_keys=True, + ), + headers={ + "Content-Type": "application/json", + "Authorization": self.authorization, + }, + method="POST", + ) + self.assertEquals(result, {"silenceID": "470b7116-8f06-4bb6-9e6c-6258aa92218e"}) + + @patch( + "ansible_collections.community.grafana.plugins.modules.grafana_silence.GrafanaSilenceInterface.get_version" + ) + @patch( + "ansible_collections.community.grafana.plugins.modules.grafana_silence.fetch_url" + ) + def test_delete_silence(self, mock_fetch_url, mock_get_version): + set_module_args( + { + "url": "https://grafana.example.com", + "url_username": "admin", + "url_password": "changeme", + "comment": "a testcomment", + "created_by": "me", + "ends_at": "2029-07-29T08:55:45.000Z", + "matchers": [ + { + "isEqual": True, + "isRegex": True, + "name": "environment", + "value": "test", + } + ], + "starts_at": "2029-07-29T08:45:45.000Z", + "state": "present", + } + ) + module = grafana_silence.setup_module_object() + mock_fetch_url.return_value = silence_deleted_resp() + mock_get_version.return_value = get_version_resp() + + 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( + module, + "https://grafana.example.com/api/alertmanager/grafana/api/v2/silence/470b7116-8f06-4bb6-9e6c-6258aa92218e", + data=None, + headers={ + "Content-Type": "application/json", + "Authorization": self.authorization, + }, + method="DELETE", + ) + self.assertEquals(result, {"message": "silence deleted"}) |