(postgresql_privs) accept 'ALL_IN_SCHEMA' objs for 'function' type (#35331)

* avoid using Postgres formatting function
* add tests for ALL FUNCTIONS IN SCHEMA
* documentation and changelog
* requested changes in tests
* fixed changelog
This commit is contained in:
Matteo Ferrando 2019-03-14 14:51:05 +00:00 committed by Abhijeet Kasurde
parent 0ed7484216
commit 86405b8fe4
3 changed files with 231 additions and 49 deletions

View file

@ -0,0 +1,2 @@
minor_changes:
- postgres_privs now accepts 'ALL_IN_SCHEMA' objs for 'function' type (https://github.com/ansible/ansible/pull/35331).

View file

@ -49,10 +49,11 @@ options:
objs: objs:
description: description:
- Comma separated list of database objects to set privileges on. - Comma separated list of database objects to set privileges on.
- If I(type) is C(table) or C(sequence), the special value - If I(type) is C(table), C(sequence) or C(function), the special value
C(ALL_IN_SCHEMA) can be provided instead to specify all database C(ALL_IN_SCHEMA) can be provided instead to specify all database
objects of type I(type) in the schema specified via I(schema). (This objects of type I(type) in the schema specified via I(schema). (This
also works with PostgreSQL < 9.0.) also works with PostgreSQL < 9.0.) (C(ALL_IN_SCHEMA) is available for
C(function) from version 2.8)
- If I(type) is C(database), this parameter can be omitted, in which case - If I(type) is C(database), this parameter can be omitted, in which case
privileges are set for the database specified via I(database). privileges are set for the database specified via I(database).
- 'If I(type) is I(function), colons (":") in object names will be - 'If I(type) is I(function), colons (":") in object names will be
@ -282,6 +283,7 @@ EXAMPLES = """
type: foreign_data_wrapper type: foreign_data_wrapper
role: reader role: reader
# Available since version 2.8
# GRANT ALL PRIVILEGES ON FOREIGN SERVER fdw_server TO reader # GRANT ALL PRIVILEGES ON FOREIGN SERVER fdw_server TO reader
- postgresql_privs: - postgresql_privs:
db: test db: test
@ -290,6 +292,16 @@ EXAMPLES = """
type: foreign_server type: foreign_server
role: reader role: reader
# Available since version 2.8
# GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA common TO caller
# Grant 'execute' permissions on all functions in schema 'common' to role 'caller'
- postgresql_privs:
type: function
state: present
privs: EXECUTE
roles: caller
objs: ALL_IN_SCHEMA
schema: common
""" """
import traceback import traceback
@ -425,6 +437,16 @@ class Connection(object):
self.cursor.execute(query, (schema,)) self.cursor.execute(query, (schema,))
return [t[0] for t in self.cursor.fetchall()] return [t[0] for t in self.cursor.fetchall()]
def get_all_functions_in_schema(self, schema):
if not self.schema_exists(schema):
raise Error('Schema "%s" does not exist.' % schema)
query = """SELECT p.proname, oidvectortypes(p.proargtypes)
FROM pg_catalog.pg_proc p
JOIN pg_namespace n ON n.oid = p.pronamespace
WHERE nspname = %s"""
self.cursor.execute(query, (schema,))
return ["%s(%s)" % (t[0], t[1]) for t in self.cursor.fetchall()]
# Methods for getting access control lists and group membership info # Methods for getting access control lists and group membership info
# To determine whether anything has changed after granting/revoking # To determine whether anything has changed after granting/revoking
@ -824,6 +846,8 @@ def main():
objs = conn.get_all_tables_in_schema(p.schema) objs = conn.get_all_tables_in_schema(p.schema)
elif p.type == 'sequence' and p.objs == 'ALL_IN_SCHEMA': elif p.type == 'sequence' and p.objs == 'ALL_IN_SCHEMA':
objs = conn.get_all_sequences_in_schema(p.schema) objs = conn.get_all_sequences_in_schema(p.schema)
elif p.type == 'function' and p.objs == 'ALL_IN_SCHEMA':
objs = conn.get_all_functions_in_schema(p.schema)
elif p.type == 'default_privs': elif p.type == 'default_privs':
if p.objs == 'ALL_DEFAULT': if p.objs == 'ALL_DEFAULT':
objs = frozenset(VALID_DEFAULT_OBJS.keys()) objs = frozenset(VALID_DEFAULT_OBJS.keys())
@ -841,9 +865,9 @@ def main():
else: else:
objs = p.objs.split(',') objs = p.objs.split(',')
# function signatures are encoded using ':' to separate args # function signatures are encoded using ':' to separate args
if p.type == 'function': if p.type == 'function':
objs = [obj.replace(':', ',') for obj in objs] objs = [obj.replace(':', ',') for obj in objs]
# roles # roles
if p.roles == 'PUBLIC': if p.roles == 'PUBLIC':

View file

@ -1,43 +1,58 @@
--- # Setup
- name: Create DB
become_user: "{{ pg_user }}"
become: yes
postgresql_db:
state: present
name: "{{ db_name }}"
login_user: "{{ pg_user }}"
- name: Create a user to be owner of objects
postgresql_user:
name: "{{ db_user3 }}"
state: present
encrypted: yes
password: password
role_attr_flags: CREATEDB,LOGIN
db: "{{ db_name }}"
login_user: "{{ pg_user }}"
- name: Create a user to be given permissions and other tests
postgresql_user:
name: "{{ db_user2 }}"
state: present
encrypted: yes
password: password
role_attr_flags: LOGIN
db: "{{ db_name }}"
login_user: "{{ pg_user }}"
###################################################### ######################################################
# Test foreign data wrapper and foreign server privs # # Test foreign data wrapper and foreign server privs #
###################################################### ######################################################
- name: Create DB # Foreign data wrapper setup
become_user: "{{ pg_user }}" - name: Create foreign data wrapper extension
become: True become: yes
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 }}" become_user: "{{ pg_user }}"
shell: echo "CREATE EXTENSION postgres_fdw" | psql -d "{{ db_name }}" shell: echo "CREATE EXTENSION postgres_fdw" | psql -d "{{ db_name }}"
- name: Create foreign data wrapper - name: Create dummy foreign data wrapper
become: True become: yes
become_user: "{{ pg_user }}" become_user: "{{ pg_user }}"
shell: echo "CREATE FOREIGN DATA WRAPPER dummy" | psql -d "{{ db_name }}" shell: echo "CREATE FOREIGN DATA WRAPPER dummy" | psql -d "{{ db_name }}"
- name: Create foreign server - name: Create foreign server
become: True become: yes
become_user: "{{ pg_user }}" become_user: "{{ pg_user }}"
shell: echo "CREATE SERVER dummy_server FOREIGN DATA WRAPPER dummy" | psql -d "{{ db_name }}" shell: echo "CREATE SERVER dummy_server FOREIGN DATA WRAPPER dummy" | psql -d "{{ db_name }}"
# Test
- name: Grant foreign data wrapper privileges - name: Grant foreign data wrapper privileges
postgresql_privs: postgresql_privs:
state: present state: present
type: foreign_data_wrapper type: foreign_data_wrapper
roles: fdw_test roles: "{{ db_user2 }}"
privs: ALL privs: ALL
objs: dummy objs: dummy
db: "{{ db_name }}" db: "{{ db_name }}"
@ -45,12 +60,13 @@
register: result register: result
ignore_errors: yes ignore_errors: yes
# Checks
- assert: - assert:
that: that:
- "result.changed == true" - "result.changed == true"
- name: Get foreign data wrapper privileges - name: Get foreign data wrapper privileges
become: True become: yes
become_user: "{{ pg_user }}" become_user: "{{ pg_user }}"
shell: echo "{{ fdw_query }}" | psql -d "{{ db_name }}" shell: echo "{{ fdw_query }}" | psql -d "{{ db_name }}"
vars: vars:
@ -62,13 +78,14 @@
- assert: - assert:
that: that:
- "fdw_result.stdout_lines[-1] == '(1 row)'" - "fdw_result.stdout_lines[-1] == '(1 row)'"
- "'fdw_test' in fdw_result.stdout_lines[-2]" - "'{{ db_user2 }}' in fdw_result.stdout_lines[-2]"
# Test
- name: Grant foreign data wrapper privileges second time - name: Grant foreign data wrapper privileges second time
postgresql_privs: postgresql_privs:
state: present state: present
type: foreign_data_wrapper type: foreign_data_wrapper
roles: fdw_test roles: "{{ db_user2 }}"
privs: ALL privs: ALL
objs: dummy objs: dummy
db: "{{ db_name }}" db: "{{ db_name }}"
@ -76,15 +93,17 @@
register: result register: result
ignore_errors: yes ignore_errors: yes
# Checks
- assert: - assert:
that: that:
- "result.changed == false" - "result.changed == false"
# Test
- name: Revoke foreign data wrapper privileges - name: Revoke foreign data wrapper privileges
postgresql_privs: postgresql_privs:
state: absent state: absent
type: foreign_data_wrapper type: foreign_data_wrapper
roles: fdw_test roles: "{{ db_user2 }}"
privs: ALL privs: ALL
objs: dummy objs: dummy
db: "{{ db_name }}" db: "{{ db_name }}"
@ -92,12 +111,13 @@
register: result register: result
ignore_errors: yes ignore_errors: yes
# Checks
- assert: - assert:
that: that:
- "result.changed == true" - "result.changed == true"
- name: Get foreign data wrapper privileges - name: Get foreign data wrapper privileges
become: True become: yes
become_user: "{{ pg_user }}" become_user: "{{ pg_user }}"
shell: echo "{{ fdw_query }}" | psql -d "{{ db_name }}" shell: echo "{{ fdw_query }}" | psql -d "{{ db_name }}"
vars: vars:
@ -109,13 +129,14 @@
- assert: - assert:
that: that:
- "fdw_result.stdout_lines[-1] == '(1 row)'" - "fdw_result.stdout_lines[-1] == '(1 row)'"
- "'fdw_test' not in fdw_result.stdout_lines[-2]" - "'{{ db_user2 }}' not in fdw_result.stdout_lines[-2]"
# Test
- name: Revoke foreign data wrapper privileges for second time - name: Revoke foreign data wrapper privileges for second time
postgresql_privs: postgresql_privs:
state: absent state: absent
type: foreign_data_wrapper type: foreign_data_wrapper
roles: fdw_test roles: "{{ db_user2 }}"
privs: ALL privs: ALL
objs: dummy objs: dummy
db: "{{ db_name }}" db: "{{ db_name }}"
@ -123,15 +144,17 @@
register: result register: result
ignore_errors: yes ignore_errors: yes
# Checks
- assert: - assert:
that: that:
- "result.changed == false" - "result.changed == false"
# Test
- name: Grant foreign server privileges - name: Grant foreign server privileges
postgresql_privs: postgresql_privs:
state: present state: present
type: foreign_server type: foreign_server
roles: fdw_test roles: "{{ db_user2 }}"
privs: ALL privs: ALL
objs: dummy_server objs: dummy_server
db: "{{ db_name }}" db: "{{ db_name }}"
@ -139,12 +162,13 @@
register: result register: result
ignore_errors: yes ignore_errors: yes
# Checks
- assert: - assert:
that: that:
- "result.changed == true" - "result.changed == true"
- name: Get foreign server privileges - name: Get foreign server privileges
become: True become: yes
become_user: "{{ pg_user }}" become_user: "{{ pg_user }}"
shell: echo "{{ fdw_query }}" | psql -d "{{ db_name }}" shell: echo "{{ fdw_query }}" | psql -d "{{ db_name }}"
vars: vars:
@ -156,13 +180,14 @@
- assert: - assert:
that: that:
- "fs_result.stdout_lines[-1] == '(1 row)'" - "fs_result.stdout_lines[-1] == '(1 row)'"
- "'fdw_test' in fs_result.stdout_lines[-2]" - "'{{ db_user2 }}' in fs_result.stdout_lines[-2]"
# Test
- name: Grant foreign server privileges for second time - name: Grant foreign server privileges for second time
postgresql_privs: postgresql_privs:
state: present state: present
type: foreign_server type: foreign_server
roles: fdw_test roles: "{{ db_user2 }}"
privs: ALL privs: ALL
objs: dummy_server objs: dummy_server
db: "{{ db_name }}" db: "{{ db_name }}"
@ -170,15 +195,17 @@
register: result register: result
ignore_errors: yes ignore_errors: yes
# Checks
- assert: - assert:
that: that:
- "result.changed == false" - "result.changed == false"
# Test
- name: Revoke foreign server privileges - name: Revoke foreign server privileges
postgresql_privs: postgresql_privs:
state: absent state: absent
type: foreign_server type: foreign_server
roles: fdw_test roles: "{{ db_user2 }}"
privs: ALL privs: ALL
objs: dummy_server objs: dummy_server
db: "{{ db_name }}" db: "{{ db_name }}"
@ -186,12 +213,13 @@
register: result register: result
ignore_errors: yes ignore_errors: yes
# Checks
- assert: - assert:
that: that:
- "result.changed == true" - "result.changed == true"
- name: Get foreign server privileges - name: Get foreign server privileges
become: True become: yes
become_user: "{{ pg_user }}" become_user: "{{ pg_user }}"
shell: echo "{{ fdw_query }}" | psql -d "{{ db_name }}" shell: echo "{{ fdw_query }}" | psql -d "{{ db_name }}"
vars: vars:
@ -203,13 +231,14 @@
- assert: - assert:
that: that:
- "fs_result.stdout_lines[-1] == '(1 row)'" - "fs_result.stdout_lines[-1] == '(1 row)'"
- "'fdw_test' not in fs_result.stdout_lines[-2]" - "'{{ db_user2 }}' not in fs_result.stdout_lines[-2]"
# Test
- name: Revoke foreign server privileges for second time - name: Revoke foreign server privileges for second time
postgresql_privs: postgresql_privs:
state: absent state: absent
type: foreign_server type: foreign_server
roles: fdw_test roles: "{{ db_user2 }}"
privs: ALL privs: ALL
objs: dummy_server objs: dummy_server
db: "{{ db_name }}" db: "{{ db_name }}"
@ -217,22 +246,149 @@
register: result register: result
ignore_errors: yes ignore_errors: yes
# Checks
- assert: - assert:
that: that:
- "result.changed == false" - "result.changed == false"
- name: Cleanup # Foreign data wrapper cleanup
become: True - name: Drop foreign server
become: yes
become_user: "{{ pg_user }}" become_user: "{{ pg_user }}"
shell: echo "{{ item }}" | psql -d "{{ db_name }}" shell: echo "DROP SERVER dummy_server" | psql -d "{{ db_name }}"
with_items:
- DROP ROLE fdw_test - name: Drop dummy foreign data wrapper
- DROP FOREIGN DATA WRAPPER dummy become: yes
- DROP SERVER dummy_server become_user: "{{ pg_user }}"
shell: echo "DROP FOREIGN DATA WRAPPER dummy" | psql -d "{{ db_name }}"
- name: Drop foreign data wrapper extension
become: yes
become_user: "{{ pg_user }}"
shell: echo "DROP EXTENSION postgres_fdw" | psql -d "{{ db_name }}"
##########################################
# Test ALL_IN_SCHEMA for 'function' type #
##########################################
# Function ALL_IN_SCHEMA Setup
- name: Create function for test
postgresql_query:
query: CREATE FUNCTION public.a() RETURNS integer LANGUAGE SQL AS 'SELECT 2';
db: "{{ db_name }}"
login_user: "{{ db_user3 }}"
login_password: password
# Test
- name: Grant execute to all functions
postgresql_privs:
type: function
state: present
privs: EXECUTE
roles: "{{ db_user2 }}"
objs: ALL_IN_SCHEMA
schema: public
db: "{{ db_name }}"
login_user: "{{ db_user3 }}"
login_password: password
register: result
ignore_errors: yes
# Checks
- assert:
that: result.changed == true
- name: Check that all functions have execute privileges
become: yes
become_user: "{{ pg_user }}"
shell: psql {{ db_name }} -c "SELECT proacl FROM pg_proc WHERE proname = 'a'" -t
register: result
- assert:
that: "'{{ db_user2 }}=X/{{ db_user3 }}' in '{{ result.stdout_lines[0] }}'"
# Test
- name: Grant execute to all functions again
postgresql_privs:
type: function
state: present
privs: EXECUTE
roles: "{{ db_user2 }}"
objs: ALL_IN_SCHEMA
schema: public
db: "{{ db_name }}"
login_user: "{{ db_user3 }}"
login_password: password
register: result
ignore_errors: yes
# Checks
- assert:
that: result.changed == false
# Test
- name: Revoke execute to all functions
postgresql_privs:
type: function
state: absent
privs: EXECUTE
roles: "{{ db_user2 }}"
objs: ALL_IN_SCHEMA
schema: public
db: "{{ db_name }}"
login_user: "{{ db_user3 }}"
login_password: password
register: result
ignore_errors: yes
# Checks
- assert:
that: result.changed == true
# Test
- name: Revoke execute to all functions again
postgresql_privs:
type: function
state: absent
privs: EXECUTE
roles: "{{ db_user2 }}"
objs: ALL_IN_SCHEMA
schema: public
db: "{{ db_name }}"
login_user: "{{ db_user3 }}"
login_password: password
register: result
ignore_errors: yes
- assert:
that: result.changed == false
# Function ALL_IN_SCHEMA cleanup
- name: Remove function for test
postgresql_query:
query: DROP FUNCTION public.a();
db: "{{ db_name }}"
login_user: "{{ db_user3 }}"
login_password: password
# Cleanup
- name: Remove user given permissions
postgresql_user:
name: "{{ db_user2 }}"
state: absent
db: "{{ db_name }}"
login_user: "{{ pg_user }}"
- name: Remove user owner of objects
postgresql_user:
name: "{{ db_user3 }}"
state: absent
db: "{{ db_name }}"
login_user: "{{ pg_user }}"
- name: Destroy DB - name: Destroy DB
become_user: "{{ pg_user }}" become_user: "{{ pg_user }}"
become: True become: yes
postgresql_db: postgresql_db:
state: absent state: absent
name: "{{ db_name }}" name: "{{ db_name }}"