diff --git a/.github/workflows/ansible-test.yml b/.github/workflows/ansible-test.yml index bce8ac1..3d5c6da 100644 --- a/.github/workflows/ansible-test.yml +++ b/.github/workflows/ansible-test.yml @@ -1,40 +1,20 @@ -# README FIRST -# 1. replace "NAMESPACE" and "COLLECTION_NAME" with the correct name in the env section (e.g. with 'community' and 'mycollection') -# 2. If you don't have unit tests remove that section -# 3. If your collection depends on other collections ensure they are installed, see "Install collection dependencies" -# If you need help please ask in #ansible-community on the Libera.chat IRC network - name: CI on: - # Run CI against all pushes (direct commits, also merged PRs), Pull Requests push: - branches: - - main - - stable-* pull_request: - # Run CI once per day (at 06:00 UTC) - # This ensures that even if there haven't been commits that we are still testing against latest version of ansible-test for each ansible-base version schedule: - cron: '0 6 * * *' env: - NAMESPACE: NAMESPACE - COLLECTION_NAME: COLLECTION_NAME + NAMESPACE: grafana + COLLECTION_NAME: grafana jobs: -### -# Sanity tests (REQUIRED) -# -# https://docs.ansible.com/ansible/latest/dev_guide/testing_sanity.html - sanity: name: Sanity (Ⓐ${{ matrix.ansible }}) strategy: matrix: ansible: - # It's important that Sanity is tested against all stable-X.Y branches - # Testing against `devel` may fail as new tests are added. - # - stable-2.9 # Only if your collection supports Ansible 2.9 - stable-2.10 - stable-2.11 - stable-2.12 @@ -42,9 +22,6 @@ jobs: runs-on: ubuntu-latest steps: - # ansible-test requires the collection to be in a directory in the form - # .../ansible_collections/${{env.NAMESPACE}}/${{env.COLLECTION_NAME}}/ - - name: Check out code uses: actions/checkout@v3 with: @@ -53,162 +30,19 @@ jobs: - name: Set up Python uses: actions/setup-python@v3 with: - # it is just required to run that once as "ansible-test sanity" in the docker image - # will run on all python versions it supports. - python-version: '3.10' + python-version: '3.9' - # Install the head of the given branch (devel, stable-2.10) - name: Install ansible-base (${{ matrix.ansible }}) run: pip install https://github.com/ansible/ansible/archive/${{ matrix.ansible }}.tar.gz --disable-pip-version-check - # run ansible-test sanity inside of Docker. - # The docker container has all the pinned dependencies that are required - # and all python versions ansible supports. - name: Run sanity tests run: ansible-test sanity --docker -v --color --coverage working-directory: ./ansible_collections/${{env.NAMESPACE}}/${{env.COLLECTION_NAME}} - # ansible-test support producing code coverage date - name: Generate coverage report run: ansible-test coverage xml -v --requirements --group-by command --group-by version working-directory: ./ansible_collections/${{env.NAMESPACE}}/${{env.COLLECTION_NAME}} - # See the reports at https://codecov.io/gh/GITHUBORG/REPONAME - uses: codecov/codecov-action@v3 with: fail_ci_if_error: false - -### -# Unit tests (OPTIONAL) -# -# https://docs.ansible.com/ansible/latest/dev_guide/testing_units.html - - units: - runs-on: ubuntu-latest - name: Units (Ⓐ${{ matrix.ansible }}) - strategy: - # As soon as the first unit test fails, cancel the others to free up the CI queue - fail-fast: true - matrix: - ansible: - # - stable-2.9 # Only if your collection supports Ansible 2.9 - - stable-2.10 - - stable-2.11 - - stable-2.12 - - devel - - steps: - - name: Check out code - uses: actions/checkout@v3 - with: - path: ansible_collections/${{env.NAMESPACE}}/${{env.COLLECTION_NAME}} - - - name: Set up Python - uses: actions/setup-python@v3 - with: - # it is just required to run that once as "ansible-test units" in the docker image - # will run on all python versions it supports. - python-version: '3.10' - - - name: Install ansible-base (${{ matrix.ansible }}) - run: pip install https://github.com/ansible/ansible/archive/${{ matrix.ansible }}.tar.gz --disable-pip-version-check - - # OPTIONAL If your unit test requires Python libraries from other collections - # Install them like this - - name: Install collection dependencies - run: ansible-galaxy collection install ansible.netcommon ansible.utils -p . - - # Run the unit tests - - name: Run unit test - run: ansible-test units -v --color --docker --coverage - working-directory: ./ansible_collections/${{env.NAMESPACE}}/${{env.COLLECTION_NAME}} - - # ansible-test support producing code coverage date - - name: Generate coverage report - run: ansible-test coverage xml -v --requirements --group-by command --group-by version - working-directory: ./ansible_collections/${{env.NAMESPACE}}/${{env.COLLECTION_NAME}} - - # See the reports at https://codecov.io/gh/GITHUBORG/REPONAME - - uses: codecov/codecov-action@v3 - with: - fail_ci_if_error: false - -### -# Integration tests (RECOMMENDED) -# -# https://docs.ansible.com/ansible/latest/dev_guide/testing_integration.html - - -# If the application you are testing is available as a docker container and you want to test -# multiple versions see the following for an example: -# https://github.com/ansible-collections/community.zabbix/tree/master/.github/workflows - - integration: - runs-on: ubuntu-latest - name: I (Ⓐ${{ matrix.ansible }}+py${{ matrix.python }}) - strategy: - fail-fast: false - matrix: - ansible: - # - stable-2.9 # Only if your collection supports Ansible 2.9 - - stable-2.10 - - stable-2.11 - - stable-2.12 - - devel - python: - - '2.6' - - '2.7' - - '3.5' - - '3.6' - - '3.7' - - '3.8' - - '3.9' - - '3.10' - exclude: - # Because ansible-test doesn't support Python 3.9 for Ansible 2.9 - # and Python 3.10 is supported in 2.12 or later. - - ansible: stable-2.9 - python: '3.9' - - ansible: stable-2.9 - python: '3.10' - - ansible: stable-2.10 - python: '3.10' - - ansible: stable-2.11 - python: '3.10' - - - steps: - - name: Check out code - uses: actions/checkout@v3 - with: - path: ansible_collections/${{env.NAMESPACE}}/${{env.COLLECTION_NAME}} - - - name: Set up Python - uses: actions/setup-python@v3 - with: - # it is just required to run that once as "ansible-test integration" in the docker image - # will run on all python versions it supports. - python-version: '3.10' - - - name: Install ansible-base (${{ matrix.ansible }}) - run: pip install https://github.com/ansible/ansible/archive/${{ matrix.ansible }}.tar.gz --disable-pip-version-check - - # OPTIONAL If your integration test requires Python libraries or modules from other collections - # Install them like this - - name: Install collection dependencies - run: ansible-galaxy collection install ansible.netcommon -p . - - # Run the integration tests - - name: Run integration test - run: ansible-test integration -v --color --retry-on-error --continue-on-error --diff --python ${{ matrix.python }} --docker --coverage - working-directory: ./ansible_collections/${{env.NAMESPACE}}/${{env.COLLECTION_NAME}} - - # ansible-test support producing code coverage date - - name: Generate coverage report - run: ansible-test coverage xml -v --requirements --group-by command --group-by version - working-directory: ./ansible_collections/${{env.NAMESPACE}}/${{env.COLLECTION_NAME}} - - # See the reports at https://codecov.io/gh/GITHUBORG/REPONAME - - uses: codecov/codecov-action@v3 - with: - fail_ci_if_error: false \ No newline at end of file diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 02cbcb9..72d4503 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -5,20 +5,5 @@ Grafana.Grafana Release Notes .. contents:: Topics -v0.0.2 +v0.0.7 ====== - -v0.0.1 -====== - -New Modules ------------ - -- grafana.grafana.alert_contact_point - Manage Alerting Contact points in Grafana -- grafana.grafana.alert_notification_policy - Sets the notification policy tree in Grafana Alerting -- grafana.grafana.cloud_api_key - Manage Grafana Cloud API keys -- grafana.grafana.cloud_plugin - Manage Grafana Cloud Plugins -- grafana.grafana.cloud_stack - Manage Grafana Cloud stack -- grafana.grafana.dashboard - Manage Dashboards in Grafana -- grafana.grafana.datasource - Manage Data sources in Grafana -- grafana.grafana.folder - Manage Folders in Grafana diff --git a/changelogs/.plugin-cache.yaml b/changelogs/.plugin-cache.yaml index c8bb3ba..c8e27e4 100644 --- a/changelogs/.plugin-cache.yaml +++ b/changelogs/.plugin-cache.yaml @@ -54,4 +54,4 @@ plugins: shell: {} strategy: {} vars: {} -version: 0.0.2 +version: 0.0.7 diff --git a/changelogs/changelog.yaml b/changelogs/changelog.yaml index 7b2b957..1f97212 100644 --- a/changelogs/changelog.yaml +++ b/changelogs/changelog.yaml @@ -1,31 +1,4 @@ ancestor: null releases: - 0.0.1: - modules: - - description: Manage Alerting Contact points in Grafana - name: alert_contact_point - namespace: '' - - description: Sets the notification policy tree in Grafana Alerting - name: alert_notification_policy - namespace: '' - - description: Manage Grafana Cloud API keys - name: cloud_api_key - namespace: '' - - description: Manage Grafana Cloud Plugins - name: cloud_plugin - namespace: '' - - description: Manage Grafana Cloud stack - name: cloud_stack - namespace: '' - - description: Manage Dashboards in Grafana - name: dashboard - namespace: '' - - description: Manage Data sources in Grafana - name: datasource - namespace: '' - - description: Manage Folders in Grafana - name: folder - namespace: '' - release_date: '2022-08-11' - 0.0.2: + 0.0.7: release_date: '2022-08-11' diff --git a/galaxy.yml b/galaxy.yml index 7bb2681..ddd06f8 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -1,26 +1,13 @@ namespace: grafana - name: grafana - -version: 0.0.6 - +version: 0.0.7 readme: README.md - authors: - Grafana Labs - Ishan Jain - description: Ansible collection to manage Grafana resources - license: - GPL-3.0-or-later - tags: [grafana, observability, monitoring] - repository: https://github.com/grafana/grafana-ansible-collection - -issues: https://github.com/grafana/grafana-ansible-collection/issues - -build_ignore: -- .idea - +issues: https://github.com/grafana/grafana-ansible-collection/issues \ No newline at end of file diff --git a/plugins/modules/alert_contact_point.py b/plugins/modules/alert_contact_point.py index db30f71..8b2961b 100644 --- a/plugins/modules/alert_contact_point.py +++ b/plugins/modules/alert_contact_point.py @@ -1,14 +1,21 @@ #!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2021, Rainer Leber +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) DOCUMENTATION = ''' --- -module: grafana.grafana.alert_contact_point +module: alert_contact_point author: - Ishan Jain (@ishanjainn) version_added: "0.0.1" short_description: Manage Alerting Contact points in Grafana description: - Create, Update and delete Contact points using Ansible. +requirements: [ "requests >= 1.0.0" ] options: name: description: @@ -40,6 +47,11 @@ options: - Grafana API Key used to authenticate with Grafana. type: str required : true + stack_slug: + description: + - Name of the Grafana Cloud stack to which the contact points will be added + type: str + required: true state: description: - State for the Grafana CLoud stack. @@ -103,7 +115,13 @@ output: ''' from ansible.module_utils.basic import AnsibleModule -import requests +try: + import requests + HAS_REQUESTS = True +except ImportError: + HAS_REQUESTS = False + +__metaclass__ = type def present_alert_contact_point(module): @@ -172,7 +190,7 @@ def main(): settings=dict(type='dict', required=True), DisableResolveMessage=dict(type='bool', required=False, default=False), stack_slug=dict(type='str', required=True), - grafana_api_key=dict(type='str', required=True), + grafana_api_key=dict(type='str', required=True, no_log=True), state=dict(type='str', required=False, default='present', choices=['present', 'absent']) ) @@ -196,4 +214,4 @@ def main(): if __name__ == '__main__': - main() \ No newline at end of file + main() diff --git a/plugins/modules/alert_notification_policy.py b/plugins/modules/alert_notification_policy.py index c505713..80a4ddc 100644 --- a/plugins/modules/alert_notification_policy.py +++ b/plugins/modules/alert_notification_policy.py @@ -1,43 +1,53 @@ #!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2021, Rainer Leber +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) DOCUMENTATION = ''' --- -module: grafana.grafana.alert_notification_policy +module: alert_notification_policy author: - Ishan Jain (@ishanjainn) version_added: "0.0.1" short_description: Sets the notification policy tree in Grafana Alerting description: - Set the notification policy tree using Ansible +requirements: [ "requests >= 1.0.0" ] options: Continue: description: - Continue matching subsequent sibling nodes if set to `True`. type: bool default: false - GroupByStr: + groupByStr: description: - List of string. - Group alerts when you receive a notification based on labels. If empty it will be inherited from the parent policy. type: list default: [] - MuteTimeIntervals: + elements: str + muteTimeIntervals: description: - List of string. - Add mute timing to policy type: list default: [] + elements: str root_policy_receiver: description: - Name of the contact point to set as the default receiver type: str default: grafana-default-email - Routes: + routes: description: - List of objects - A Route is a node that contains definitions of how to handle alerts. type: list required: true + elements: dict groupInterval: description: - The wait time to send a batch of new alerts for that group after the first notification was sent. Inherited from the parent policy if empty. @@ -50,10 +60,10 @@ options: default: 30s objectMatchers: description: - - State for the Grafana CLoud stack. - type: str - default: present - choices: [ present, absent ] + - Matchers is a slice of Matchers that is sortable, implements Stringer, and provides a Matches method to match a LabelSet. + type: list + default: [] + elements: dict repeatInterval: description: - The waiting time to resend an alert after they have successfully been sent. @@ -119,10 +129,6 @@ output: description: The waiting time until the initial notification is sent for a new group created by an incoming alert. This is of the parent policy. returned: on success type: str - provenance: - description: - returned: on success - type: str receiver: description: The name of the default contact point returned: state is present and on success @@ -138,7 +144,13 @@ output: ''' from ansible.module_utils.basic import AnsibleModule -import requests +try: + import requests + HAS_REQUESTS = True +except ImportError: + HAS_REQUESTS = False + +__metaclass__ = type def alert_notification_policy(module): @@ -163,16 +175,16 @@ def alert_notification_policy(module): def main(): module_args = dict(Continue=dict(type='bool', required=False, default=False), - groupByStr=dict(type='list', required=False, default=[]), - muteTimeIntervals=dict(type='list', required=False, default=[]), + groupByStr=dict(type='list', required=False, default=[], elements='str'), + muteTimeIntervals=dict(type='list', required=False, default=[], elements='str'), root_policy_receiver=dict(type='str', required=False, default='grafana-default-email'), - routes=dict(type='list', required=True), + routes=dict(type='list', required=True, elements='dict'), groupInterval=dict(type='str', required=False, default='5m'), groupWait=dict(type='str', required=False, default='30s'), repeatInterval=dict(type='str', required=False, default='4h'), - objectMatchers=dict(type='list', required=False, default=[]), + objectMatchers=dict(type='list', required=False, default=[], elements='dict'), stack_slug=dict(type='str', required=True), - grafana_api_key=dict(type='str', required=True), ) + grafana_api_key=dict(type='str', required=True, no_log=True), ) module = AnsibleModule(argument_spec=module_args, supports_check_mode=True) @@ -185,4 +197,4 @@ def main(): if __name__ == '__main__': - main() \ No newline at end of file + main() diff --git a/plugins/modules/cloud_api_key.py b/plugins/modules/cloud_api_key.py index cee53c5..f6d2d1b 100644 --- a/plugins/modules/cloud_api_key.py +++ b/plugins/modules/cloud_api_key.py @@ -1,14 +1,21 @@ #!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2021, Rainer Leber +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) DOCUMENTATION = ''' --- -module: grafana.grafana.cloud_api_key +module: cloud_api_key author: - Ishan Jain (@ishanjainn) version_added: "0.0.1" short_description: Manage Grafana Cloud API keys description: - Create and delete Grafana Cloud API keys using Ansible. +requirements: [ "requests >= 1.0.0" ] options: name: description: @@ -20,6 +27,7 @@ options: - Role to be associated with the CLoud API key. type: str required: true + choices: [Admin, Viewer, Editor, MetricsPublisher] org_slug: description: - Name of the Grafana Cloud organization in which Cloud API key will be created @@ -62,7 +70,13 @@ EXAMPLES = ''' ''' from ansible.module_utils.basic import AnsibleModule -import requests +try: + import requests + HAS_REQUESTS = True +except ImportError: + HAS_REQUESTS = False + +__metaclass__ = type def present_cloud_api_key(module): @@ -100,7 +114,7 @@ def main(): name=dict(type='str', required=True), role=dict(type='str', required=True, choices=['Admin', 'Viewer', 'Editor', 'MetricsPublisher']), org_slug=dict(type='str', required=True), - existing_cloud_api_key=dict(type='str', required=True), + existing_cloud_api_key=dict(type='str', required=True, no_log=True), fail_if_already_created=dict(type='bool', required=False, default='True'), state=dict(type='str', required=False, default='present', choices=['present', 'absent']) ) @@ -125,4 +139,4 @@ def main(): if __name__ == '__main__': - main() \ No newline at end of file + main() diff --git a/plugins/modules/cloud_plugin.py b/plugins/modules/cloud_plugin.py index 3b700bb..3cd542e 100644 --- a/plugins/modules/cloud_plugin.py +++ b/plugins/modules/cloud_plugin.py @@ -1,14 +1,21 @@ #!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2021, Rainer Leber +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) DOCUMENTATION = ''' --- -module: grafana.grafana.cloud_plugin +module: cloud_plugin author: - Ishan Jain (@ishanjainn) version_added: "0.0.1" short_description: Manage Grafana Cloud Plugins description: - Create, Update and delete Grafana Cloud stacks using Ansible. +requirements: [ "requests >= 1.0.0" ] options: name: description: @@ -79,7 +86,13 @@ RETURN = r''' ''' from ansible.module_utils.basic import AnsibleModule -import requests +try: + import requests + HAS_REQUESTS = True +except ImportError: + HAS_REQUESTS = False + +__metaclass__ = type def present_cloud_plugin(module): @@ -121,7 +134,7 @@ def main(): name=dict(type='str', required=True), version=dict(type='str', required=False, default='latest'), stack_slug=dict(type='str', required=True), - cloud_api_key=dict(type='str', required=True), + cloud_api_key=dict(type='str', required=True, no_log=True), state=dict(type='str', required=False, default='present', choices=['present', 'absent']) ) @@ -150,4 +163,4 @@ def main(): if __name__ == '__main__': - main() \ No newline at end of file + main() diff --git a/plugins/modules/cloud_stack.py b/plugins/modules/cloud_stack.py index b76bfd5..581bcb3 100644 --- a/plugins/modules/cloud_stack.py +++ b/plugins/modules/cloud_stack.py @@ -1,14 +1,21 @@ #!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2021, Rainer Leber +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) DOCUMENTATION = ''' --- -module: grafana.grafana.cloud_stack +module: cloud_stack author: - Ishan Jain (@ishanjainn) version_added: "0.0.1" short_description: Manage Grafana Cloud stack description: - Create and delete Grafana Cloud stacks using Ansible. +requirements: [ "requests >= 1.0.0" ] options: name: description: @@ -33,14 +40,13 @@ options: choices: [ us, us-azure, eu, au, eu-azure, prod-ap-southeast-0, prod-gb-south-0, prod-eu-west-3] url: description: - - If you use a custom domain for the instance, you can provide it here. For example, “https://grafana.yourdoman.io”. + - If you use a custom domain for the instance, you can provide it here. Will be set to https://.grafana.net if not provided. type: str - default: https://.grafana.net org_slug: description: - Name of the organization under which Cloud stack is created. type: str - required: false + required: true state: description: - State for the Grafana CLoud stack. @@ -107,7 +113,13 @@ RETURN = r''' ''' from ansible.module_utils.basic import AnsibleModule -import requests +try: + import requests + HAS_REQUESTS = True +except ImportError: + HAS_REQUESTS = False + +__metaclass__ = type def present_cloud_stack(module): @@ -127,7 +139,7 @@ def present_cloud_stack(module): if result.status_code == 200: return False, True, result.json() - elif (result.status_code == 409 and result.json()['message'] == "That url is not available") or (result.status_code == 403 and result.json()['message'] == "Hosted instance limit reached"): + elif result.status_code in [409, 403] and result.json()['message'] in ["That url is not available", "Hosted instance limit reached"]: api_url = 'https://grafana.com/api/orgs/' + module.params['org_slug'] + '/instances' result = requests.get(api_url, headers={"Authorization": 'Bearer ' + module.params['cloud_api_key']}) @@ -154,7 +166,7 @@ def main(): module_args = dict( name=dict(type='str', required=True), stack_slug=dict(type='str', required=True), - cloud_api_key=dict(type='str', required=True), + cloud_api_key=dict(type='str', required=True, no_log=True), region=dict(type='str', required=False, default='us', choices=['us', 'us-azure', 'eu', 'au', 'eu-azure', 'prod-ap-southeast-0', 'prod-gb-south-0', 'prod-eu-west-3']), @@ -191,4 +203,4 @@ def main(): if __name__ == '__main__': - main() \ No newline at end of file + main() diff --git a/plugins/modules/dashboard.py b/plugins/modules/dashboard.py index 0a1411f..c405548 100644 --- a/plugins/modules/dashboard.py +++ b/plugins/modules/dashboard.py @@ -1,14 +1,21 @@ #!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2021, Rainer Leber +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) DOCUMENTATION = ''' --- -module: grafana.grafana.dashboard +module: dashboard author: - Ishan Jain (@ishanjainn) version_added: "0.0.1" short_description: Manage Dashboards in Grafana description: - Create, Update and delete Dashboards using Ansible. +requirements: [ "requests >= 1.0.0" ] options: dashboard: description: @@ -17,10 +24,10 @@ options: required: true stack_slug: description: - - Name of the Grafana Cloud stack to which the notification policies will be added + - Name of the Grafana Cloud stack to which the dashboard will be added type: str required: true - cloud_api_key: + grafana_api_key: description: - CLoud API Key to authenticate with Grafana Cloud. type: str @@ -38,14 +45,14 @@ EXAMPLES = ''' grafana.grafana.dashboard: datasource: "{{ lookup('ansible.builtin.file', 'dashboard.json') }}" stack_slug: "{{ stack_slug }}" - cloud_api_key: "{{ grafana_cloud_api_key }}" + grafana_api_key: "{{ grafana_api_key }}" state: present - name: Delete dashboard grafana.grafana.dashboard: datasource: "{{ lookup('ansible.builtin.file', 'dashboard.json') }}" stack_slug: "{{ stack_slug }}" - cloud_api_key: "{{ grafana_cloud_api_key }}" + grafana_api_key: "{{ grafana_api_key }}" state: absent ''' @@ -90,14 +97,21 @@ output: ''' from ansible.module_utils.basic import AnsibleModule -import requests +try: + import requests + HAS_REQUESTS = True +except ImportError: + HAS_REQUESTS = False + + +__metaclass__ = type def present_dashboard(module): api_url = 'https://' + module.params['stack_slug'] + '.grafana.net/api/dashboards/db' - result = requests.post(api_url, json=module.params['dashboard'], headers={"Authorization": 'Bearer ' + module.params['cloud_api_key']}) + result = requests.post(api_url, json=module.params['dashboard'], headers={"Authorization": 'Bearer ' + module.params['grafana_api_key']}) if result.status_code == 200: return False, True, result.json() @@ -111,7 +125,7 @@ def absent_dashboard(module): api_url = api_url = 'https://' + module.params['stack_slug'] + '.grafana.net/api/dashboards/uid/' + module.params['dashboard']['dashboard']['uid'] - result = requests.delete(api_url, headers={"Authorization": 'Bearer ' + module.params['cloud_api_key']}) + result = requests.delete(api_url, headers={"Authorization": 'Bearer ' + module.params['grafana_api_key']}) if result.status_code == 200: return False, True, result.json() @@ -123,7 +137,7 @@ def main(): module_args = dict( dashboard=dict(type='dict', required=True), stack_slug=dict(type='str', required=True), - cloud_api_key=dict(type='str', required=True), + grafana_api_key=dict(type='str', required=True, no_log=True), state=dict(type='str', required=False, default='present', choices=['present', 'absent']) ) @@ -147,4 +161,4 @@ def main(): if __name__ == '__main__': - main() \ No newline at end of file + main() diff --git a/plugins/modules/datasource.py b/plugins/modules/datasource.py index 3cc1dd9..6081bf7 100644 --- a/plugins/modules/datasource.py +++ b/plugins/modules/datasource.py @@ -1,14 +1,21 @@ #!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2021, Rainer Leber +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) DOCUMENTATION = ''' --- -module: grafana.grafana.datasource +module: datasource author: - Ishan Jain (@ishanjainn) version_added: "0.0.1" short_description: Manage Data sources in Grafana description: - Create, Update and delete Data sources using Ansible. +requirements: [ "requests >= 1.0.0" ] options: datasource: description: @@ -17,10 +24,10 @@ options: required: true stack_slug: description: - - Name of the Grafana Cloud stack to which the notification policies will be added + - Name of the Grafana Cloud stack to which the data source will be added type: str required: true - cloud_api_key: + grafana_api_key: description: - CLoud API Key to authenticate with Grafana Cloud. type: str @@ -38,14 +45,14 @@ EXAMPLES = ''' grafana.grafana.datasource: datasource: "{{ lookup('ansible.builtin.file', 'datasource.json') }}" stack_slug: "{{ stack_slug }}" - cloud_api_key: "{{ grafana_cloud_api_key }}" + grafana_api_key: "{{ grafana_api_key }}" state: present - name: Delete Data sources grafana.grafana.datasource: datasource: "{{ lookup('ansible.builtin.file', 'datasource.json') }}" stack_slug: "{{ stack_slug }}" - cloud_api_key: "{{ grafana_cloud_api_key }}" + grafana_api_key: "{{ grafana_api_key }}" state: absent ''' @@ -74,23 +81,29 @@ output: ''' from ansible.module_utils.basic import AnsibleModule -import requests +try: + import requests + HAS_REQUESTS = True +except ImportError: + HAS_REQUESTS = False + +__metaclass__ = type def present_datasource(module): api_url = 'https://' + module.params['stack_slug'] + '.grafana.net/api/datasources' - result = requests.post(api_url, json=module.params['datasource'], headers={"Authorization": 'Bearer ' + module.params['cloud_api_key']}) + result = requests.post(api_url, json=module.params['datasource'], headers={"Authorization": 'Bearer ' + module.params['grafana_api_key']}) if result.status_code == 200: return False, True, result.json() elif result.status_code == 409: get_id_url = requests.get('https://' + module.params['stack_slug'] + '.grafana.net/api/datasources/id/' + module.params['datasource']['name'], - headers={"Authorization": 'Bearer ' + module.params['cloud_api_key']}) + headers={"Authorization": 'Bearer ' + module.params['grafana_api_key']}) api_url = 'https://' + module.params['stack_slug'] + '.grafana.net/api/datasources/' + str(get_id_url.json()['id']) - result = requests.put(api_url, json=module.params['datasource'], headers={"Authorization": 'Bearer ' + module.params['cloud_api_key']}) + result = requests.put(api_url, json=module.params['datasource'], headers={"Authorization": 'Bearer ' + module.params['grafana_api_key']}) if result.status_code == 200: return False, True, result.json() @@ -104,7 +117,7 @@ def present_datasource(module): def absent_datasource(module): api_url = 'https://' + module.params['stack_slug'] + '.grafana.net/api/datasources/' + module.params['datasource']['name'] - result = requests.delete(api_url, headers={"Authorization": 'Bearer ' + module.params['cloud_api_key']}) + result = requests.delete(api_url, headers={"Authorization": 'Bearer ' + module.params['grafana_api_key']}) if result.status_code == 200: return False, True, result.json() @@ -116,7 +129,7 @@ def main(): module_args = dict( datasource=dict(type='dict', required=True), stack_slug=dict(type='str', required=True), - cloud_api_key=dict(type='str', required=True), + grafana_api_key=dict(type='str', required=True, no_log=True), state=dict(type='str', required=False, default='present', choices=['present', 'absent']) ) @@ -140,4 +153,4 @@ def main(): if __name__ == '__main__': - main() \ No newline at end of file + main() diff --git a/plugins/modules/folder.py b/plugins/modules/folder.py index fe6cd53..0871167 100644 --- a/plugins/modules/folder.py +++ b/plugins/modules/folder.py @@ -1,14 +1,21 @@ #!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2021, Rainer Leber +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) DOCUMENTATION = ''' --- -module: grafana.grafana.folder +module: folder author: - Ishan Jain (@ishanjainn) version_added: "0.0.1" short_description: Manage Folders in Grafana description: - Create, Update and delete Folders via Ansible. +requirements: [ "requests >= 1.0.0" ] options: title: description: @@ -23,14 +30,19 @@ options: overwrite: description: - Set to false if you dont want to overwrite existing folder with newer version. - type: str + type: bool required: false default: true - cloud_api_key: + grafana_api_key: description: - - CLoud API Key to authenticate with Grafana Cloud. + - Grafana API Key to authenticate with Grafana. type: str required : true + stack_slug: + description: + - Name of the Grafana Cloud stack to which the folder will be added + type: str + required: true state: description: - State for the Grafana CLoud stack. @@ -46,14 +58,14 @@ EXAMPLES = ''' uid: folder_name overwrite: true stack_slug: "{{ stack_slug }}" - cloud_api_key: "{{ grafana_cloud_api_key }}" + grafana_api_key: "{{ grafana_api_key }}" state: present - name: Delete a Folder in Grafana grafana.grafana.folder: uid: folder_name stack_slug: "{{ stack_slug }}" - cloud_api_key: "{{ grafana_cloud_api_key }}" + grafana_api_key: "{{ grafana_api_key }}" state: absent ''' @@ -126,7 +138,13 @@ output: ''' from ansible.module_utils.basic import AnsibleModule -import requests +try: + import requests + HAS_REQUESTS = True +except ImportError: + HAS_REQUESTS = False + +__metaclass__ = type def present_folder(module): @@ -136,7 +154,7 @@ def present_folder(module): } api_url = 'https://' + module.params['stack_slug'] + '.grafana.net/api/folders' - result = requests.post(api_url, json=body, headers={"Authorization": 'Bearer ' + module.params['cloud_api_key']}) + result = requests.post(api_url, json=body, headers={"Authorization": 'Bearer ' + module.params['grafana_api_key']}) if result.status_code == 200: return False, True, result.json() @@ -148,7 +166,7 @@ def present_folder(module): } api_url = 'https://' + module.params['stack_slug'] + '.grafana.net/api/folders/' + module.params['uid'] - result = requests.put(api_url, json=body, headers={"Authorization": 'Bearer ' + module.params['cloud_api_key']}) + result = requests.put(api_url, json=body, headers={"Authorization": 'Bearer ' + module.params['grafana_api_key']}) if result.status_code == 200: return False, True, result.json() @@ -162,7 +180,7 @@ def present_folder(module): def absent_folder(module): api_url = 'https://' + module.params['stack_slug'] + '.grafana.net/api/folders/' + module.params['uid'] - result = requests.delete(api_url, headers={"Authorization": 'Bearer ' + module.params['cloud_api_key']}) + result = requests.delete(api_url, headers={"Authorization": 'Bearer ' + module.params['grafana_api_key']}) if result.status_code == 200: return False, True, result.json() @@ -176,7 +194,7 @@ def main(): uid=dict(type='str', required=True), overwrite=dict(type='bool', required=False, default=True), stack_slug=dict(type='str', required=True), - cloud_api_key=dict(type='str', required=True), + grafana_api_key=dict(type='str', required=True, no_log=True), state=dict(type='str', required=False, default='present', choices=['present', 'absent']) ) @@ -200,4 +218,4 @@ def main(): if __name__ == '__main__': - main() \ No newline at end of file + main() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..663bd1f --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +requests \ No newline at end of file