From 6e198487c95f87ee122c94b79421dbfe5d18eeae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20Liche=C5=84ski?= Date: Fri, 8 Mar 2019 10:21:03 +0100 Subject: [PATCH] postgresql_privs: Support FOREIGN DATA WRAPPER and FOREIGN SERVER (#38803) * Support FOREIGN DATA WRAPPER and FOREIGN SERVER in postgresql_privs module * Added available from note to fdw and fs object types * Integration tests, examples in documentation * Complete integration tests --- ...postgresql_privs_fdw_and_fs_obj_types.yaml | 3 + .../database/postgresql/postgresql_privs.py | 43 +++- .../targets/postgresql/tasks/main.yml | 3 + .../postgresql/tasks/postgresql_privs.yml | 239 ++++++++++++++++++ 4 files changed, 285 insertions(+), 3 deletions(-) create mode 100644 changelogs/fragments/38803-postgresql_privs_fdw_and_fs_obj_types.yaml create mode 100644 test/integration/targets/postgresql/tasks/postgresql_privs.yml diff --git a/changelogs/fragments/38803-postgresql_privs_fdw_and_fs_obj_types.yaml b/changelogs/fragments/38803-postgresql_privs_fdw_and_fs_obj_types.yaml new file mode 100644 index 00000000000..87f0e0d970f --- /dev/null +++ b/changelogs/fragments/38803-postgresql_privs_fdw_and_fs_obj_types.yaml @@ -0,0 +1,3 @@ +--- +minor_changes: + - "postgresql_privs - introduces support for FOREIGN DATA WRAPPER and FOREIGN SERVER as object types in postgresql_privs module. (https://github.com/ansible/ansible/issues/38801)" diff --git a/lib/ansible/modules/database/postgresql/postgresql_privs.py b/lib/ansible/modules/database/postgresql/postgresql_privs.py index 95f5fca8868..201694ed60f 100644 --- a/lib/ansible/modules/database/postgresql/postgresql_privs.py +++ b/lib/ansible/modules/database/postgresql/postgresql_privs.py @@ -41,10 +41,11 @@ options: description: - Type of database object to set privileges on. - The `default_prives` choice is available starting at version 2.7. + - The 'foreign_data_wrapper' and 'foreign_server' object types are available from Ansible version '2.8'. default: table choices: [table, sequence, function, database, schema, language, tablespace, group, - default_privs] + default_privs, foreign_data_wrapper, foreign_server] objs: description: - Comma separated list of database objects to set privileges on. @@ -272,6 +273,23 @@ EXAMPLES = """ type: default_privs role: reader +# Available since version 2.8 +# GRANT ALL PRIVILEGES ON FOREIGN DATA WRAPPER fdw TO reader +- postgresql_privs: + db: test + objs: fdw + privs: ALL + type: foreign_data_wrapper + role: reader + +# GRANT ALL PRIVILEGES ON FOREIGN SERVER fdw_server TO reader +- postgresql_privs: + db: test + objs: fdw_server + privs: ALL + type: foreign_server + role: reader + """ import traceback @@ -484,6 +502,18 @@ class Connection(object): self.cursor.execute(query, (schema,)) return [t[0] for t in self.cursor.fetchall()] + def get_foreign_data_wrapper_acls(self, fdws): + query = """SELECT fdwacl FROM pg_catalog.pg_foreign_data_wrapper + WHERE fdwname = ANY (%s) ORDER BY fdwname""" + self.cursor.execute(query, (fdws,)) + return [t[0] for t in self.cursor.fetchall()] + + def get_foreign_server_acls(self, fs): + query = """SELECT srvacl FROM pg_catalog.pg_foreign_server + WHERE srvname = ANY (%s) ORDER BY srvname""" + self.cursor.execute(query, (fs,)) + return [t[0] for t in self.cursor.fetchall()] + # Manipulating privileges def manipulate_privs(self, obj_type, privs, objs, roles, @@ -525,6 +555,10 @@ class Connection(object): get_status = self.get_group_memberships elif obj_type == 'default_privs': get_status = partial(self.get_default_privs, schema_qualifier) + elif obj_type == 'foreign_data_wrapper': + get_status = self.get_foreign_data_wrapper_acls + elif obj_type == 'foreign_server': + get_status = self.get_foreign_server_acls else: raise Error('Unsupported database object type "%s".' % obj_type) @@ -559,7 +593,8 @@ class Connection(object): obj_ids = [pg_quote_identifier(i, 'table') for i in obj_ids] # Note: obj_type has been checked against a set of string literals # and privs was escaped when it was parsed - set_what = '%s ON %s %s' % (','.join(privs), obj_type, + # Note: Underscores are replaced with spaces to support multi-word obj_type + set_what = '%s ON %s %s' % (','.join(privs), obj_type.replace('_', ' '), ','.join(obj_ids)) # for_whom: SQL-fragment specifying for whom to set the above @@ -706,7 +741,9 @@ def main(): 'language', 'tablespace', 'group', - 'default_privs']), + 'default_privs', + 'foreign_data_wrapper', + 'foreign_server']), objs=dict(required=False, aliases=['obj']), schema=dict(required=False), roles=dict(required=True, aliases=['role']), diff --git a/test/integration/targets/postgresql/tasks/main.yml b/test/integration/targets/postgresql/tasks/main.yml index 5dd45350a7b..2f35a61fd9b 100644 --- a/test/integration/targets/postgresql/tasks/main.yml +++ b/test/integration/targets/postgresql/tasks/main.yml @@ -777,6 +777,9 @@ # Test postgresql_tablespace module - include: postgresql_tablespace.yml +# Test postgresql_privs +- include: postgresql_privs.yml + # dump/restore tests per format # ============================================================ - include: state_dump_restore.yml test_fixture=user file=dbdata.sql diff --git a/test/integration/targets/postgresql/tasks/postgresql_privs.yml b/test/integration/targets/postgresql/tasks/postgresql_privs.yml new file mode 100644 index 00000000000..f77cd0ea6ff --- /dev/null +++ b/test/integration/targets/postgresql/tasks/postgresql_privs.yml @@ -0,0 +1,239 @@ +--- + +###################################################### +# Test foreign data wrapper and foreign server privs # +###################################################### + +- name: Create DB + become_user: "{{ pg_user }}" + become: True + postgresql_db: + state: present + name: "{{ db_name }}" + login_user: "{{ pg_user }}" + register: result + +- name: Create test role + become: True + become_user: "{{ pg_user }}" + shell: echo "CREATE ROLE fdw_test" | psql -d "{{ db_name }}" + +- name: Create fdw extension + become: True + become_user: "{{ pg_user }}" + shell: echo "CREATE EXTENSION postgres_fdw" | psql -d "{{ db_name }}" + +- name: Create foreign data wrapper + become: True + become_user: "{{ pg_user }}" + shell: echo "CREATE FOREIGN DATA WRAPPER dummy" | psql -d "{{ db_name }}" + +- name: Create foreign server + become: True + become_user: "{{ pg_user }}" + shell: echo "CREATE SERVER dummy_server FOREIGN DATA WRAPPER dummy" | psql -d "{{ db_name }}" + +- name: Grant foreign data wrapper privileges + postgresql_privs: + state: present + type: foreign_data_wrapper + roles: fdw_test + privs: ALL + objs: dummy + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + register: result + ignore_errors: yes + +- assert: + that: + - "result.changed == true" + +- name: Get foreign data wrapper privileges + become: True + become_user: "{{ pg_user }}" + shell: echo "{{ fdw_query }}" | psql -d "{{ db_name }}" + vars: + fdw_query: > + SELECT fdwacl FROM pg_catalog.pg_foreign_data_wrapper + WHERE fdwname = ANY (ARRAY['dummy']) ORDER BY fdwname + register: fdw_result + +- assert: + that: + - "fdw_result.stdout_lines[-1] == '(1 row)'" + - "'fdw_test' in fdw_result.stdout_lines[-2]" + +- name: Grant foreign data wrapper privileges second time + postgresql_privs: + state: present + type: foreign_data_wrapper + roles: fdw_test + privs: ALL + objs: dummy + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + register: result + ignore_errors: yes + +- assert: + that: + - "result.changed == false" + +- name: Revoke foreign data wrapper privileges + postgresql_privs: + state: absent + type: foreign_data_wrapper + roles: fdw_test + privs: ALL + objs: dummy + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + register: result + ignore_errors: yes + +- assert: + that: + - "result.changed == true" + +- name: Get foreign data wrapper privileges + become: True + become_user: "{{ pg_user }}" + shell: echo "{{ fdw_query }}" | psql -d "{{ db_name }}" + vars: + fdw_query: > + SELECT fdwacl FROM pg_catalog.pg_foreign_data_wrapper + WHERE fdwname = ANY (ARRAY['dummy']) ORDER BY fdwname + register: fdw_result + +- assert: + that: + - "fdw_result.stdout_lines[-1] == '(1 row)'" + - "'fdw_test' not in fdw_result.stdout_lines[-2]" + +- name: Revoke foreign data wrapper privileges for second time + postgresql_privs: + state: absent + type: foreign_data_wrapper + roles: fdw_test + privs: ALL + objs: dummy + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + register: result + ignore_errors: yes + +- assert: + that: + - "result.changed == false" + +- name: Grant foreign server privileges + postgresql_privs: + state: present + type: foreign_server + roles: fdw_test + privs: ALL + objs: dummy_server + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + register: result + ignore_errors: yes + +- assert: + that: + - "result.changed == true" + +- name: Get foreign server privileges + become: True + become_user: "{{ pg_user }}" + shell: echo "{{ fdw_query }}" | psql -d "{{ db_name }}" + vars: + fdw_query: > + SELECT srvacl FROM pg_catalog.pg_foreign_server + WHERE srvname = ANY (ARRAY['dummy_server']) ORDER BY srvname + register: fs_result + +- assert: + that: + - "fs_result.stdout_lines[-1] == '(1 row)'" + - "'fdw_test' in fs_result.stdout_lines[-2]" + +- name: Grant foreign server privileges for second time + postgresql_privs: + state: present + type: foreign_server + roles: fdw_test + privs: ALL + objs: dummy_server + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + register: result + ignore_errors: yes + +- assert: + that: + - "result.changed == false" + +- name: Revoke foreign server privileges + postgresql_privs: + state: absent + type: foreign_server + roles: fdw_test + privs: ALL + objs: dummy_server + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + register: result + ignore_errors: yes + +- assert: + that: + - "result.changed == true" + +- name: Get foreign server privileges + become: True + become_user: "{{ pg_user }}" + shell: echo "{{ fdw_query }}" | psql -d "{{ db_name }}" + vars: + fdw_query: > + SELECT srvacl FROM pg_catalog.pg_foreign_server + WHERE srvname = ANY (ARRAY['dummy_server']) ORDER BY srvname + register: fs_result + +- assert: + that: + - "fs_result.stdout_lines[-1] == '(1 row)'" + - "'fdw_test' not in fs_result.stdout_lines[-2]" + +- name: Revoke foreign server privileges for second time + postgresql_privs: + state: absent + type: foreign_server + roles: fdw_test + privs: ALL + objs: dummy_server + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + register: result + ignore_errors: yes + +- assert: + that: + - "result.changed == false" + +- name: Cleanup + become: True + become_user: "{{ pg_user }}" + shell: echo "{{ item }}" | psql -d "{{ db_name }}" + with_items: + - DROP ROLE fdw_test + - DROP FOREIGN DATA WRAPPER dummy + - DROP SERVER dummy_server + +- name: Destroy DB + become_user: "{{ pg_user }}" + become: True + postgresql_db: + state: absent + name: "{{ db_name }}" + login_user: "{{ pg_user }}"