From 7dd46f7b2ded8585ecb4e26f3c448c426d2008d9 Mon Sep 17 00:00:00 2001 From: Andrey Klychkov Date: Thu, 17 Oct 2019 16:59:06 +0300 Subject: [PATCH] postgresql_privs: add support a type parameter option for types (#63555) * postgresql_privs: add support a type parameter option for types * postgresql_privs: add support a type parameter option for types, add changelog fragment * postgresql_privs: add support a type parameter option for types, add schema handling * postgresql_privs: add support a type parameter option for types, fix typo * postgresql_privs: add support a type parameter option for types, add comment --- ...63555-postgresql_privs_typy_obj_types.yaml | 2 + .../database/postgresql/postgresql_privs.py | 48 +++- .../tasks/postgresql_privs_general.yml | 264 ++++++++++++++++++ 3 files changed, 306 insertions(+), 8 deletions(-) create mode 100644 changelogs/fragments/63555-postgresql_privs_typy_obj_types.yaml diff --git a/changelogs/fragments/63555-postgresql_privs_typy_obj_types.yaml b/changelogs/fragments/63555-postgresql_privs_typy_obj_types.yaml new file mode 100644 index 00000000000..20e08a0baa3 --- /dev/null +++ b/changelogs/fragments/63555-postgresql_privs_typy_obj_types.yaml @@ -0,0 +1,2 @@ +minor_changes: +- postgresql_privs - add support for TYPE as object types in postgresql_privs module (https://github.com/ansible/ansible/issues/62432). diff --git a/lib/ansible/modules/database/postgresql/postgresql_privs.py b/lib/ansible/modules/database/postgresql/postgresql_privs.py index 9a13331b4a6..09443fea26d 100644 --- a/lib/ansible/modules/database/postgresql/postgresql_privs.py +++ b/lib/ansible/modules/database/postgresql/postgresql_privs.py @@ -45,12 +45,13 @@ options: type: description: - Type of database object to set privileges on. - - The `default_privs` choice is available starting at version 2.7. - - The 'foreign_data_wrapper' and 'foreign_server' object types are available from Ansible version '2.8'. + - The C(default_privs) choice is available starting at version 2.7. + - The C(foreign_data_wrapper) and C(foreign_server) object types are available from Ansible version '2.8'. + - The C(type) choice is available from Ansible version '2.10'. type: str default: table choices: [ database, default_privs, foreign_data_wrapper, foreign_server, function, - group, language, table, tablespace, schema, sequence ] + group, language, table, tablespace, schema, sequence, type ] objs: description: - Comma separated list of database objects to set privileges on. @@ -69,8 +70,10 @@ options: schema: description: - Schema that contains the database objects specified via I(objs). - - May only be provided if I(type) is C(table), C(sequence), C(function) - or C(default_privs). Defaults to C(public) in these cases. + - May only be provided if I(type) is C(table), C(sequence), C(function), C(type), + or C(default_privs). Defaults to C(public) in these cases. + - Pay attention, for embedded types when I(type=type) + I(schema) can be C(pg_catalog) or C(information_schema) respectively. type: str roles: description: @@ -326,6 +329,15 @@ EXAMPLES = r''' type: foreign_data_wrapper role: reader +# Available since version 2.10 +- name: GRANT ALL PRIVILEGES ON TYPE customtype TO reader + postgresql_privs: + db: test + objs: customtype + privs: ALL + type: type + role: reader + # Available since version 2.8 - name: GRANT ALL PRIVILEGES ON FOREIGN SERVER fdw_server TO reader postgresql_privs: @@ -376,6 +388,16 @@ EXAMPLES = r''' type: default_privs role: reader target_roles: librarian + +# Available since version 2.10 +- name: Grant type privileges for pg_catalog.numeric type to alice + postgresql_privs: + type: type + roles: alice + privs: ALL + objs: numeric + schema: pg_catalog + db: acme ''' RETURN = r''' @@ -622,6 +644,13 @@ class Connection(object): self.cursor.execute(query, (fs,)) return [t[0] for t in self.cursor.fetchall()] + def get_type_acls(self, schema, types): + query = """SELECT t.typacl FROM pg_catalog.pg_type t + JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace + WHERE n.nspname = %s AND t.typname = ANY (%s) ORDER BY typname""" + self.cursor.execute(query, (schema, types)) + return [t[0] for t in self.cursor.fetchall()] + # Manipulating privileges def manipulate_privs(self, obj_type, privs, objs, roles, target_roles, @@ -669,6 +698,8 @@ class Connection(object): get_status = self.get_foreign_data_wrapper_acls elif obj_type == 'foreign_server': get_status = self.get_foreign_server_acls + elif obj_type == 'type': + get_status = partial(self.get_type_acls, schema_qualifier) else: raise Error('Unsupported database object type "%s".' % obj_type) @@ -685,7 +716,7 @@ class Connection(object): except Exception: raise Error('Illegal function signature: "%s".' % obj) obj_ids.append('"%s"."%s"(%s' % (schema_qualifier, f, args)) - elif obj_type in ['table', 'sequence']: + elif obj_type in ['table', 'sequence', 'type']: obj_ids = ['"%s"."%s"' % (schema_qualifier, o) for o in objs] else: obj_ids = ['"%s"' % o for o in objs] @@ -892,7 +923,8 @@ def main(): 'group', 'default_privs', 'foreign_data_wrapper', - 'foreign_server']), + 'foreign_server', + 'type', ]), objs=dict(required=False, aliases=['obj']), schema=dict(required=False), roles=dict(required=True, aliases=['role']), @@ -917,7 +949,7 @@ def main(): # Create type object as namespace for module params p = type('Params', (), module.params) # param "schema": default, allowed depends on param "type" - if p.type in ['table', 'sequence', 'function', 'default_privs']: + if p.type in ['table', 'sequence', 'function', 'type', 'default_privs']: p.schema = p.schema or 'public' elif p.schema: module.fail_json(msg='Argument "schema" is not allowed ' diff --git a/test/integration/targets/postgresql_privs/tasks/postgresql_privs_general.yml b/test/integration/targets/postgresql_privs/tasks/postgresql_privs_general.yml index 663ea9aaa41..53388be8ef5 100644 --- a/test/integration/targets/postgresql_privs/tasks/postgresql_privs_general.yml +++ b/test/integration/targets/postgresql_privs/tasks/postgresql_privs_general.yml @@ -677,9 +677,271 @@ login_password: password ignore_errors: yes when: postgres_version_resp.stdout is version('10', '>=') + +########################################### +# Test for 'type' value of type parameter # +########################################### + +# Test +- name: Grant type privileges + become: yes + become_user: "{{ pg_user }}" + postgresql_privs: + state: present + type: type + roles: "{{ db_user2 }}" + privs: ALL + objs: numeric + schema: pg_catalog + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + register: result + when: postgres_version_resp.stdout is version('10', '>=') + +# Checks +- assert: + that: + - result is changed + when: postgres_version_resp.stdout is version('10', '>=') + +- name: Get type privileges + become: yes + become_user: "{{ pg_user }}" + postgresql_query: + login_user: "{{ pg_user }}" + login_db: "{{ db_name }}" + query: SELECT typacl FROM pg_catalog.pg_type WHERE typname = 'numeric'; + register: typ_result + when: postgres_version_resp.stdout is version('10', '>=') + +- assert: + that: + - "'{{ db_user2 }}' in typ_result.query_result[0].typacl" + when: postgres_version_resp.stdout is version('10', '>=') + +- name: Grant type privileges again using check_mode + become: yes + become_user: "{{ pg_user }}" + postgresql_privs: + state: present + type: type + roles: "{{ db_user2 }}" + privs: ALL + objs: numeric + schema: pg_catalog + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + register: result + check_mode: yes + when: postgres_version_resp.stdout is version('10', '>=') + +# Checks +- assert: + that: + - result is not changed + when: postgres_version_resp.stdout is version('10', '>=') + +- name: Get type privileges + become: yes + become_user: "{{ pg_user }}" + postgresql_query: + login_user: "{{ pg_user }}" + login_db: "{{ db_name }}" + query: SELECT typacl FROM pg_catalog.pg_type WHERE typname = 'numeric'; + register: typ_result + when: postgres_version_resp.stdout is version('10', '>=') + +- assert: + that: + - "'{{ db_user2 }}' in typ_result.query_result[0].typacl" + when: postgres_version_resp.stdout is version('10', '>=') + +- name: Grant type privileges again + become: yes + become_user: "{{ pg_user }}" + postgresql_privs: + state: present + type: type + roles: "{{ db_user2 }}" + privs: ALL + objs: numeric + schema: pg_catalog + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + register: result + when: postgres_version_resp.stdout is version('10', '>=') + +# Checks +- assert: + that: + - result is not changed + when: postgres_version_resp.stdout is version('10', '>=') + +- name: Get type privileges + become: yes + become_user: "{{ pg_user }}" + postgresql_query: + login_user: "{{ pg_user }}" + login_db: "{{ db_name }}" + query: SELECT typacl FROM pg_catalog.pg_type WHERE typname = 'numeric'; + register: typ_result + when: postgres_version_resp.stdout is version('10', '>=') + +- assert: + that: + - "'{{ db_user2 }}' in typ_result.query_result[0].typacl" + when: postgres_version_resp.stdout is version('10', '>=') +- name: Revoke type privileges in check_mode + become: yes + become_user: "{{ pg_user }}" + postgresql_privs: + state: absent + type: type + roles: "{{ db_user2 }}" + privs: ALL + objs: numeric + schema: pg_catalog + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + register: result + check_mode: yes + when: postgres_version_resp.stdout is version('10', '>=') + +# Checks +- assert: + that: + - result is changed + when: postgres_version_resp.stdout is version('10', '>=') + +- name: Get type privileges + become: yes + become_user: "{{ pg_user }}" + postgresql_query: + login_user: "{{ pg_user }}" + login_db: "{{ db_name }}" + query: SELECT typacl FROM pg_catalog.pg_type WHERE typname = 'numeric'; + register: typ_result + when: postgres_version_resp.stdout is version('10', '>=') + +- assert: + that: + - "'{{ db_user2 }}' in typ_result.query_result[0].typacl" + when: postgres_version_resp.stdout is version('10', '>=') + +- name: Revoke type privileges + become: yes + become_user: "{{ pg_user }}" + postgresql_privs: + state: absent + type: type + roles: "{{ db_user2 }}" + privs: ALL + objs: numeric + schema: pg_catalog + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + register: result + when: postgres_version_resp.stdout is version('10', '>=') + +# Checks +- assert: + that: + - result is changed + when: postgres_version_resp.stdout is version('10', '>=') + +- name: Get type privileges + become: yes + become_user: "{{ pg_user }}" + postgresql_query: + login_user: "{{ pg_user }}" + login_db: "{{ db_name }}" + query: SELECT typacl FROM pg_catalog.pg_type WHERE typname = 'numeric'; + register: typ_result + when: postgres_version_resp.stdout is version('10', '>=') + +- assert: + that: + - "'{{ db_user2 }}' not in typ_result.query_result[0].typacl" + when: postgres_version_resp.stdout is version('10', '>=') + +# type with default schema (public): +- name: Create custom type in schema public + become: yes + become_user: "{{ pg_user }}" + postgresql_query: + login_user: "{{ pg_user }}" + login_db: "{{ db_name }}" + query: "CREATE TYPE compfoo AS (f1 int, f2 text)" + when: postgres_version_resp.stdout is version('10', '>=') + +# Test +- name: Grant type privileges with default schema + become: yes + become_user: "{{ pg_user }}" + postgresql_privs: + state: present + type: type + roles: "{{ db_user2 }}" + privs: ALL + objs: compfoo + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + register: result + when: postgres_version_resp.stdout is version('10', '>=') + +# Checks +- assert: + that: + - result is changed + when: postgres_version_resp.stdout is version('10', '>=') + +- name: Get type privileges + become: yes + become_user: "{{ pg_user }}" + postgresql_query: + login_user: "{{ pg_user }}" + login_db: "{{ db_name }}" + query: > + SELECT t.typacl FROM pg_catalog.pg_type t JOIN pg_catalog.pg_namespace n + ON n.oid = t.typnamespace WHERE t.typname = 'compfoo' AND n.nspname = 'public'; + register: typ_result + when: postgres_version_resp.stdout is version('10', '>=') + +- assert: + that: + - "'{{ db_user2 }}' in typ_result.query_result[0].typacl" + when: postgres_version_resp.stdout is version('10', '>=') + # Cleanup +- name: Remove privs + become: yes + become_user: "{{ pg_user }}" + postgresql_privs: + state: absent + type: type + roles: "{{ db_user2 }}" + privs: ALL + objs: compfoo + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + when: postgres_version_resp.stdout is version('10', '>=') + +- name: Reassign ownership + become_user: "{{ pg_user }}" + become: yes + postgresql_owner: + login_user: "{{ pg_user }}" + db: "{{ db_name }}" + new_owner: "{{ pg_user }}" + reassign_owned_by: "{{ item }}" + loop: + - "{{ db_user2 }}" + - "{{ db_user3 }}" + - name: Remove user given permissions + become: yes + become_user: "{{ pg_user }}" postgresql_user: name: "{{ db_user2 }}" state: absent @@ -687,6 +949,8 @@ login_user: "{{ pg_user }}" - name: Remove user owner of objects + become: yes + become_user: "{{ pg_user }}" postgresql_user: name: "{{ db_user3 }}" state: absent