Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(grafana_silence): add org switch #371

Merged
merged 10 commits into from
May 27, 2024
3 changes: 3 additions & 0 deletions changelogs/fragments/371-silence-org-switch.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
minor_changes:
- add org switch by `org_id` and `org_name` in `grafana_silence`
50 changes: 43 additions & 7 deletions plugins/modules/grafana_silence.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,19 @@
requirements:
- The Alertmanager API is only available starting Grafana 8 and the module will fail if the server version is lower than version 8.
options:
org_id:
description:
- The Grafana organization ID where the silence will be created or deleted.
- Not used when I(grafana_api_key) is set, because the grafana_api_key only belongs to one organization.
- Mutually exclusive with C(org_name).
default: 1
type: int
org_name:
description:
- The Grafana organization name where the silence will be created or deleted.
- Not used when I(grafana_api_key) is set, because the grafana_api_key only belongs to one organization.
- Mutually exclusive with C(org_id).
type: str
comment:
description:
- The comment that describes the silence.
Expand Down Expand Up @@ -181,9 +194,10 @@ class GrafanaError(Exception):
class GrafanaSilenceInterface(object):
def __init__(self, module):
self._module = module
self.grafana_url = base.clean_url(module.params.get("url"))
self.org_id = None
# {{{ Authentication header
self.headers = {"Content-Type": "application/json"}
module.params["force_basic_auth"] = True
if module.params.get("grafana_api_key", None):
self.headers["Authorization"] = (
"Bearer %s" % module.params["grafana_api_key"]
Expand All @@ -192,8 +206,14 @@ def __init__(self, module):
self.headers["Authorization"] = basic_auth_header(
module.params["url_username"], module.params["url_password"]
)
self.org_id = (
self.organization_by_name(module.params["org_name"])
if module.params["org_name"]
else module.params["org_id"]
)
self.switch_organization(self.org_id)
# }}}
self.grafana_url = base.clean_url(module.params.get("url"))

if module.params.get("skip_version_check") is False:
try:
grafana_version = self.get_version()
Expand Down Expand Up @@ -233,6 +253,21 @@ def _send_request(self, url, data=None, headers=None, method="GET"):
failed=True, msg="Grafana Silences API answered with HTTP %d" % status_code
)

def switch_organization(self, org_id):
url = "/api/user/using/%d" % org_id
self._send_request(url, headers=self.headers, method="POST")

def organization_by_name(self, org_name):
url = "/api/user/orgs"
organizations = self._send_request(url, headers=self.headers, method="GET")
orga = next((org for org in organizations if org["name"] == org_name))
if orga:
return orga["orgId"]

return self._module.fail_json(
failed=True, msg="Current user isn't member of organization: %s" % org_name
)

def get_version(self):
url = "/api/health"
response = self._send_request(
Expand Down Expand Up @@ -310,23 +345,24 @@ def setup_module_object():
argument_spec = base.grafana_argument_spec()
argument_spec.update(
comment=dict(type="str", required=True),
state=dict(type="str", choices=["present", "absent"], default="present"),
created_by=dict(type="str", required=True),
starts_at=dict(type="str", required=True),
ends_at=dict(type="str", required=True),
matchers=dict(type="list", elements="dict", required=True),
org_id=dict(default=1, type="int"),
org_name=dict(type="str"),
skip_version_check=dict(type="bool", default=False),
starts_at=dict(type="str", required=True),
state=dict(type="str", choices=["present", "absent"], default="present"),
)


def main():

module = setup_module_object()
comment = module.params["comment"]
created_by = module.params["created_by"]
starts_at = module.params["starts_at"]
ends_at = module.params["ends_at"]
matchers = module.params["matchers"]
starts_at = module.params["starts_at"]
state = module.params["state"]

changed = False
Expand All @@ -338,7 +374,6 @@ def main():
)

if state == "present":

if not silence:
silence = grafana_iface.create_silence(
comment, created_by, starts_at, ends_at, matchers
Expand All @@ -362,6 +397,7 @@ def main():
changed=changed,
msg="Silence does not exist",
)

module.exit_json(failed=failed, changed=changed, silence=silence)


Expand Down
2 changes: 2 additions & 0 deletions roles/grafana/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,8 @@ Configure Grafana organizations, dashboards, folders, datasources, teams and use
| created_by | yes |
| ends_at | yes |
| matchers | yes |
| org_id | no |
| org_name | no |
| starts_at | yes |
| state | no |

Expand Down
4 changes: 3 additions & 1 deletion roles/grafana/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -206,9 +206,11 @@
community.grafana.grafana_silence:
comment: "{{ silence.comment }}"
created_by: "{{ silence.created_by }}"
starts_at: "{{ silence.starts_at }}"
ends_at: "{{ silence.ends_at }}"
matchers: "{{ silence.matchers }}"
org_id: "{{ datasource.org_id | default(omit) }}"
org_name: "{{ datasource.org_name | default(omit) }}"
starts_at: "{{ silence.starts_at }}"
state: "{{ silence.state | default(omit) }}"
loop: "{{ grafana_silences }}"
loop_control: {loop_var: silence}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
---
- module_defaults:
community.grafana.grafana_silence:
url: "{{ grafana_url }}"
url_username: "{{ grafana_username }}"
url_password: "{{ grafana_password }}"
block:
- name: Create new silence
community.grafana.grafana_silence:
comment: "a testcomment"
created_by: "me"
starts_at: "2029-07-29T08:45:45.000Z"
ends_at: "2029-07-29T08:55:45.000Z"
matchers:
- isEqual: true
isRegex: true
name: environment
value: test
state: present
register: result
- assert:
that:
- "result.changed == true"
- "result.failed == false"
- "result.silence.id != ''"

- name: Check idempotency on silence creation
community.grafana.grafana_silence:
comment: "a testcomment"
created_by: "me"
starts_at: "2029-07-29T08:45:45.000Z"
ends_at: "2029-07-29T08:55:45.000Z"
matchers:
- isEqual: true
isRegex: true
name: environment
value: test
state: present
register: result
- assert:
that:
- "result.changed == false"
- "result.msg != ''"

- name: Delete the silence
community.grafana.grafana_silence:
comment: "a testcomment"
created_by: "me"
starts_at: "2029-07-29T08:45:45.000Z"
ends_at: "2029-07-29T08:55:45.000Z"
matchers:
- isEqual: true
isRegex: true
name: environment
value: test
state: absent
register: result
- assert:
that:
- "result.changed == true"
- "result.failed == false"
- "result.silence.id != ''"
- - "result.silence.createdBy != 'me'"

- name: Check idempotency on silence deletion
community.grafana.grafana_silence:
comment: "a testcomment"
created_by: "me"
starts_at: "2029-07-29T08:45:45.000Z"
ends_at: "2029-07-29T08:55:45.000Z"
matchers:
- isEqual: true
isRegex: true
name: environment
value: test
state: absent
register: result
ignore_errors: yes
- assert:
that:
- "result.changed == false"
- "result.failed == false"
- "result.msg == 'Silence does not exist'"
91 changes: 4 additions & 87 deletions tests/integration/targets/grafana_silence/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -1,89 +1,6 @@
---
- name: Create new silence
community.grafana.grafana_silence:
url: "{{ grafana_url }}"
url_username: "{{ grafana_username }}"
url_password: "{{ grafana_password }}"
comment: "a testcomment"
created_by: "me"
starts_at: "2029-07-29T08:45:45.000Z"
ends_at: "2029-07-29T08:55:45.000Z"
matchers:
- isEqual: true
isRegex: true
name: environment
value: test
state: present
register: result
- assert:
that:
- "result.changed == true"
- "result.failed == false"
- "result.silence.id != ''"
- name: Silence creation and deletion
ansible.builtin.include_tasks: create-delete.yml

- name: Check idempotency on silence creation
community.grafana.grafana_silence:
url: "{{ grafana_url }}"
url_username: "{{ grafana_username }}"
url_password: "{{ grafana_password }}"
comment: "a testcomment"
created_by: "me"
starts_at: "2029-07-29T08:45:45.000Z"
ends_at: "2029-07-29T08:55:45.000Z"
matchers:
- isEqual: true
isRegex: true
name: environment
value: test
state: present
register: result
- assert:
that:
- "result.changed == false"
- "result.msg != ''"

- name: Delete the silence
community.grafana.grafana_silence:
url: "{{ grafana_url }}"
url_username: "{{ grafana_username }}"
url_password: "{{ grafana_password }}"
comment: "a testcomment"
created_by: "me"
starts_at: "2029-07-29T08:45:45.000Z"
ends_at: "2029-07-29T08:55:45.000Z"
matchers:
- isEqual: true
isRegex: true
name: environment
value: test
state: absent
register: result
- assert:
that:
- "result.changed == true"
- "result.failed == false"
- "result.silence.id != ''"
- - "result.silence.createdBy != 'me'"

- name: Check idempotency on silence deletion
community.grafana.grafana_silence:
url: "{{ grafana_url }}"
url_username: "{{ grafana_username }}"
url_password: "{{ grafana_password }}"
comment: "a testcomment"
created_by: "me"
starts_at: "2029-07-29T08:45:45.000Z"
ends_at: "2029-07-29T08:55:45.000Z"
matchers:
- isEqual: true
isRegex: true
name: environment
value: test
state: absent
register: result
ignore_errors: yes
- assert:
that:
- "result.changed == false"
- "result.failed == false"
- "result.msg == 'Silence does not exist'"
- name: Silence creation and deletion for organization
ansible.builtin.include_tasks: org.yml
7 changes: 7 additions & 0 deletions tests/integration/targets/grafana_silence/tasks/org.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
- module_defaults:
community.grafana.grafana_silence:
org_name: Main Org.
block:
- name: Silence creation and deletion
ansible.builtin.include_tasks: create-delete.yml
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ def test_create_silence_new_silence(
}
],
)
mock_fetch_url.assert_called_once_with(
mock_fetch_url.assert_called_with(
module,
"https://grafana.example.com/api/alertmanager/grafana/api/v2/silences",
data=json.dumps(
Expand Down Expand Up @@ -198,7 +198,7 @@ def test_delete_silence(self, mock_fetch_url, mock_get_version):
grafana_iface = grafana_silence.GrafanaSilenceInterface(module)
silence_id = "470b7116-8f06-4bb6-9e6c-6258aa92218e"
result = grafana_iface.delete_silence(silence_id)
mock_fetch_url.assert_called_once_with(
mock_fetch_url.assert_called_with(
module,
"https://grafana.example.com/api/alertmanager/grafana/api/v2/silence/470b7116-8f06-4bb6-9e6c-6258aa92218e",
data=None,
Expand Down
Loading