From f410d3cb79b572166f0c2f23f979f676acd7bc0b Mon Sep 17 00:00:00 2001 From: CWollinger Date: Mon, 4 Apr 2022 20:30:42 +0200 Subject: [PATCH 1/7] add create_folder_permission --- plugins/modules/grafana_folder.py | 36 +++++++++++++++++++ .../targets/grafana_folder/tasks/main.yml | 19 ++++++++++ 2 files changed, 55 insertions(+) diff --git a/plugins/modules/grafana_folder.py b/plugins/modules/grafana_folder.py index d39e56e4..cd06cb76 100644 --- a/plugins/modules/grafana_folder.py +++ b/plugins/modules/grafana_folder.py @@ -52,6 +52,14 @@ type: bool default: False version_added: "1.2.0" + permissions: + description: + - Set the permissions of the folder. + - Able to define it for a I(role), I(teamId) and I(userId) + - Values for I(permission) are C(1) = View, C(2) = Edit, C(4) = Admin + - This will remove existing permissions if they are not included. + type: list + elements: dict extends_documentation_fragment: - community.grafana.basic_auth - community.grafana.api_key @@ -66,6 +74,20 @@ title: "grafana_working_group" state: present +- name: Create a folder and set permissions + community.grafana.grafana_folder: + url: "https://grafana.example.com" + grafana_api_key: "{{ some_api_token_value }}" + title: "grafana_working_group" + state: present + permissions: + - role: "Editor" + permission: 2 + - role: "Viewer" + permission: 1 + - userId: 2 + permission: 4 + - name: Delete a folder community.grafana.grafana_folder: url: "https://grafana.example.com" @@ -246,6 +268,15 @@ def delete_folder(self, folder_uid): response = self._send_request(url, headers=self.headers, method="DELETE") return response + def create_folder_permission(self, folder_uid, perm): + url = "/api/folders/{folder_uid}/permissions".format(folder_uid=folder_uid) + items = { + "items": [] + } + items["items"].extend(perm) + response = self._send_request(url, data=items, headers=self.headers, method="POST") + return response + def setup_module_object(): module = AnsibleModule( @@ -262,6 +293,7 @@ def setup_module_object(): name=dict(type='str', aliases=['title'], required=True), state=dict(type='str', default='present', choices=['present', 'absent']), skip_version_check=dict(type='bool', default=False), + permissions=dict(type='list', elements='dict'), ) @@ -270,6 +302,7 @@ def main(): module = setup_module_object() state = module.params['state'] title = module.params['name'] + permissions = module.params['permissions'] grafana_iface = GrafanaFolderInterface(module) @@ -281,6 +314,9 @@ def main(): folder = grafana_iface.get_folder(title) changed = True folder = grafana_iface.get_folder(title) + if permissions is not None: + grafana_iface.create_folder_permission(folder.get("uid"), permissions) + changed = True module.exit_json(changed=changed, folder=folder) elif state == 'absent': folder = grafana_iface.get_folder(title) diff --git a/tests/integration/targets/grafana_folder/tasks/main.yml b/tests/integration/targets/grafana_folder/tasks/main.yml index c6a52056..699a9aed 100644 --- a/tests/integration/targets/grafana_folder/tasks/main.yml +++ b/tests/integration/targets/grafana_folder/tasks/main.yml @@ -14,6 +14,25 @@ - "result.changed == true" - "result.folder.title == 'grafana_working_group'" +- name: Set permissions of a Folder + grafana_folder: + url: "{{ grafana_url }}" + url_username: "{{ grafana_username }}" + url_password: "{{ grafana_password }}" + title: "grafana_working_group" + state: present + permissions: + - role: "Editor" + permission: 2 + - role: "Viewer" + permission: 1 + register: result + +- assert: + that: + - "result.changed == true" + - "result.folder.title == 'grafana_working_group'" + - name: Test folder creation idempotency grafana_folder: url: "{{ grafana_url }}" From 5de9896ce09c311167a24c52daf71d464fbbfdbc Mon Sep 17 00:00:00 2001 From: CWollinger Date: Tue, 5 Apr 2022 19:30:17 +0200 Subject: [PATCH 2/7] ensure idempotency --- plugins/modules/grafana_folder.py | 125 ++++++++++++++++-- .../targets/grafana_folder/tasks/main.yml | 47 ++++++- 2 files changed, 155 insertions(+), 17 deletions(-) diff --git a/plugins/modules/grafana_folder.py b/plugins/modules/grafana_folder.py index cd06cb76..cb48f9fb 100644 --- a/plugins/modules/grafana_folder.py +++ b/plugins/modules/grafana_folder.py @@ -56,10 +56,34 @@ description: - Set the permissions of the folder. - Able to define it for a I(role), I(teamId) and I(userId) - - Values for I(permission) are C(1) = View, C(2) = Edit, C(4) = Admin - This will remove existing permissions if they are not included. type: list elements: dict + suboptions: + role: + description: + - The name of the role. + choices: + - Viewer + - Editor + type: str + username: + description: + - The name of the user. + type: str + team: + description: + - The name of the team. + type: str + permission: + description: + - The permission to be added. + choices: + - view + - edit + - admin + required: true + type: str extends_documentation_fragment: - community.grafana.basic_auth - community.grafana.api_key @@ -80,13 +104,12 @@ grafana_api_key: "{{ some_api_token_value }}" title: "grafana_working_group" state: present - permissions: - role: "Editor" - permission: 2 - - role: "Viewer" - permission: 1 - - userId: 2 - permission: 4 + permission: "admin" + - username: "myuser" + permission: "edit" + - team: "myteam" + permission: "view" - name: Delete a folder community.grafana.grafana_folder: @@ -268,15 +291,82 @@ def delete_folder(self, folder_uid): response = self._send_request(url, headers=self.headers, method="DELETE") return response - def create_folder_permission(self, folder_uid, perm): + def get_folder_permission(self, folder_uid): + url = "/api/folders/{folder_uid}/permissions".format(folder_uid=folder_uid) + response = self._send_request(url, headers=self.headers, method="GET") + for items in response: + del items['created'] + del items['inherited'] + del items['slug'] + del items['teamAvatarUrl'] + del items['teamEmail'] + del items['title'] + del items['uid'] + del items['updated'] + del items['url'] + del items['userAvatarUrl'] + del items['userEmail'] + del items['userLogin'] + del items['folderId'] + del items['permissionName'] + del items['team'] + del items['isFolder'] + if items['userId'] == 0: + del items['userId'] + if items['teamId'] == 0: + del items['teamId'] + return response + + def create_folder_permission(self, folder_uid, permissions): url = "/api/folders/{folder_uid}/permissions".format(folder_uid=folder_uid) items = { "items": [] } - items["items"].extend(perm) + items["items"].extend(permissions) response = self._send_request(url, data=items, headers=self.headers, method="POST") return response + def get_user_id_from_mail(self, email): + url = "/api/users/lookup?loginOrEmail={email}".format(email=email) + user = self._send_request(url, headers=self.headers, method="GET") + if user is None: + self._module.fail_json(failed=True, msg="User '%s' does not exists" % email) + return user.get("id") + + def get_team(self, name): + url = "/api/teams/search?name={team}".format(team=quote(name)) + response = self._send_request(url, headers=self.headers, method="GET") + if len(response.get("teams")) == 0: + self._module.fail_json(failed=True, msg="Team '%s' does not exists" % name) + return response.get("teams")[0]["id"] + + def process_permission_input(self, input): + for i in input: + if i["username"] is None: + del i["username"] + if i["team"] is None: + del i["team"] + if i["role"] is None: + del i["role"] + + if "username" in i: + i["userId"] = i.pop("username") + i["userId"] = self.get_user_id_from_mail(i["userId"]) + + if "team" in i: + i["teamId"] = i.pop("team") + i["teamId"] = self.get_team(i["teamId"]) + + if i["permission"] == "view": + i["permission"] = 1 + elif i["permission"] == "edit": + i["permission"] = 2 + elif i["permission"] == "admin": + i["permission"] = 4 + else: + self._module.fail_json(failed=True, msg="Use view, edit or admin as value for permission") + return input + def setup_module_object(): module = AnsibleModule( @@ -288,12 +378,19 @@ def setup_module_object(): return module +permissions_spec = dict( + role=dict(type='str', choices=['Viewer', 'Editor']), + team=dict(type='str'), + username=dict(type='str'), + permission=dict(type='str', choices=['view', 'edit', 'admin']), +) + argument_spec = base.grafana_argument_spec() argument_spec.update( name=dict(type='str', aliases=['title'], required=True), state=dict(type='str', default='present', choices=['present', 'absent']), skip_version_check=dict(type='bool', default=False), - permissions=dict(type='list', elements='dict'), + permissions=dict(type='list', elements='dict', options=permissions_spec), ) @@ -315,8 +412,12 @@ def main(): changed = True folder = grafana_iface.get_folder(title) if permissions is not None: - grafana_iface.create_folder_permission(folder.get("uid"), permissions) - changed = True + current_permissions = grafana_iface.get_folder_permission(folder.get("uid")) + new_permissions = grafana_iface.process_permission_input(permissions) + if current_permissions != new_permissions: + grafana_iface.create_folder_permission(folder.get("uid"), permissions) + changed = True + module.exit_json(changed=changed, folder=folder) elif state == 'absent': folder = grafana_iface.get_folder(title) diff --git a/tests/integration/targets/grafana_folder/tasks/main.yml b/tests/integration/targets/grafana_folder/tasks/main.yml index 699a9aed..0fcc4aff 100644 --- a/tests/integration/targets/grafana_folder/tasks/main.yml +++ b/tests/integration/targets/grafana_folder/tasks/main.yml @@ -14,7 +14,22 @@ - "result.changed == true" - "result.folder.title == 'grafana_working_group'" -- name: Set permissions of a Folder +- name: Test folder creation idempotency + grafana_folder: + url: "{{ grafana_url }}" + url_username: "{{ grafana_username }}" + url_password: "{{ grafana_password }}" + title: "grafana_working_group" + state: present + register: result + +- assert: + that: + - "result.changed == false" + - "result.folder.title == 'grafana_working_group'" + + +- name: Change folder permissions grafana_folder: url: "{{ grafana_url }}" url_username: "{{ grafana_username }}" @@ -23,9 +38,7 @@ state: present permissions: - role: "Editor" - permission: 2 - - role: "Viewer" - permission: 1 + permission: "admin" register: result - assert: @@ -33,13 +46,16 @@ - "result.changed == true" - "result.folder.title == 'grafana_working_group'" -- name: Test folder creation idempotency +- name: Test folder permission idempotency grafana_folder: url: "{{ grafana_url }}" url_username: "{{ grafana_username }}" url_password: "{{ grafana_password }}" title: "grafana_working_group" state: present + permissions: + - role: "Editor" + permission: "admin" register: result - assert: @@ -47,6 +63,27 @@ - "result.changed == false" - "result.folder.title == 'grafana_working_group'" +- name: Test folder permission user lookup + grafana_folder: + url: "{{ grafana_url }}" + url_username: "{{ grafana_username }}" + url_password: "{{ grafana_password }}" + title: "grafana_working_group" + state: present + permissions: + - role: "Editor" + permission: "admin" + - role: "unknown" + permission: "edit" + register: result + ignore_errors: true + +- assert: + that: + - "result.changed == false" + - "result.failed == true" + - "result.msg == 'value of role must be one of: Viewer, Editor, got: unknown found in permissions'" + - name: Delete a Folder grafana_folder: url: "{{ grafana_url }}" From 2c6fc2e6c5f0c368e698c9b8a68886866270fc86 Mon Sep 17 00:00:00 2001 From: CWollinger Date: Tue, 5 Apr 2022 23:32:27 +0200 Subject: [PATCH 3/7] add more tests --- .../targets/grafana_folder/tasks/main.yml | 33 +++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/tests/integration/targets/grafana_folder/tasks/main.yml b/tests/integration/targets/grafana_folder/tasks/main.yml index 0fcc4aff..6c4bedfb 100644 --- a/tests/integration/targets/grafana_folder/tasks/main.yml +++ b/tests/integration/targets/grafana_folder/tasks/main.yml @@ -56,6 +56,8 @@ permissions: - role: "Editor" permission: "admin" + - role: "Viewer" + permission: "view" register: result - assert: @@ -73,16 +75,43 @@ permissions: - role: "Editor" permission: "admin" - - role: "unknown" + - role: "Viewer" + permission: "view" + - username: "unknown" permission: "edit" register: result ignore_errors: true +- set_fact: + expected_error: "User 'unknown' does not exists" + +- assert: + that: + - "result.changed == false" + - "result.failed == true" + - "result.msg == expected_error" + +- name: Test folder permission team lookup + grafana_folder: + url: "{{ grafana_url }}" + url_username: "{{ grafana_username }}" + url_password: "{{ grafana_password }}" + title: "grafana_working_group" + state: present + permissions: + - team: "unknown" + permission: "edit" + register: result + ignore_errors: true + +- set_fact: + expected_error: "Team 'unknown' does not exists" + - assert: that: - "result.changed == false" - "result.failed == true" - - "result.msg == 'value of role must be one of: Viewer, Editor, got: unknown found in permissions'" + - "result.msg == expected_error" - name: Delete a Folder grafana_folder: From d52a99f189841328e82839431f294122ade2c331 Mon Sep 17 00:00:00 2001 From: CWollinger Date: Tue, 5 Apr 2022 23:43:33 +0200 Subject: [PATCH 4/7] fix test --- tests/integration/targets/grafana_folder/tasks/main.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/integration/targets/grafana_folder/tasks/main.yml b/tests/integration/targets/grafana_folder/tasks/main.yml index 6c4bedfb..f73c3ef1 100644 --- a/tests/integration/targets/grafana_folder/tasks/main.yml +++ b/tests/integration/targets/grafana_folder/tasks/main.yml @@ -39,6 +39,8 @@ permissions: - role: "Editor" permission: "admin" + - role: "Viewer" + permission: "view" register: result - assert: From 8a12b5fe442bf96dbb0fabe5d35016e1927c71da Mon Sep 17 00:00:00 2001 From: CWollinger Date: Tue, 5 Apr 2022 23:56:03 +0200 Subject: [PATCH 5/7] add missing permissions key in example --- plugins/modules/grafana_folder.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/modules/grafana_folder.py b/plugins/modules/grafana_folder.py index cb48f9fb..c63d0202 100644 --- a/plugins/modules/grafana_folder.py +++ b/plugins/modules/grafana_folder.py @@ -104,6 +104,7 @@ grafana_api_key: "{{ some_api_token_value }}" title: "grafana_working_group" state: present + permissions: - role: "Editor" permission: "admin" - username: "myuser" From d4af77378609e82dd0c9a90f18fd70ee92516299 Mon Sep 17 00:00:00 2001 From: CWollinger Date: Wed, 6 Apr 2022 16:57:43 +0200 Subject: [PATCH 6/7] suboption permission always needed --- plugins/modules/grafana_folder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/grafana_folder.py b/plugins/modules/grafana_folder.py index c63d0202..fbba4244 100644 --- a/plugins/modules/grafana_folder.py +++ b/plugins/modules/grafana_folder.py @@ -383,7 +383,7 @@ def setup_module_object(): role=dict(type='str', choices=['Viewer', 'Editor']), team=dict(type='str'), username=dict(type='str'), - permission=dict(type='str', choices=['view', 'edit', 'admin']), + permission=dict(type='str', choices=['view', 'edit', 'admin'], required=True), ) argument_spec = base.grafana_argument_spec() From d4349c3b26241a74b1617fa8907330df99e745c3 Mon Sep 17 00:00:00 2001 From: CWollinger Date: Tue, 19 Apr 2022 17:56:27 +0200 Subject: [PATCH 7/7] use loop to remove items --- plugins/modules/grafana_folder.py | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/plugins/modules/grafana_folder.py b/plugins/modules/grafana_folder.py index fbba4244..f330b992 100644 --- a/plugins/modules/grafana_folder.py +++ b/plugins/modules/grafana_folder.py @@ -295,23 +295,11 @@ def delete_folder(self, folder_uid): def get_folder_permission(self, folder_uid): url = "/api/folders/{folder_uid}/permissions".format(folder_uid=folder_uid) response = self._send_request(url, headers=self.headers, method="GET") + KEYS_TO_REMOVE = ['created', 'inherited', 'slug', 'teamAvatarUrl', 'teamEmail', 'title', 'uid', 'updated', 'url', + 'userAvatarUrl', 'userEmail', 'userLogin', 'folderId', 'permissionName', 'team', 'isFolder'] for items in response: - del items['created'] - del items['inherited'] - del items['slug'] - del items['teamAvatarUrl'] - del items['teamEmail'] - del items['title'] - del items['uid'] - del items['updated'] - del items['url'] - del items['userAvatarUrl'] - del items['userEmail'] - del items['userLogin'] - del items['folderId'] - del items['permissionName'] - del items['team'] - del items['isFolder'] + for k in KEYS_TO_REMOVE: + del items[k] if items['userId'] == 0: del items['userId'] if items['teamId'] == 0: