mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
* postgresql_privs: add procedure type support
* add CI tests
* add changelog fragment
* change
* improve doc formatting
(cherry picked from commit 08c96d94e6
)
Co-authored-by: Andrew Klychkov <aaklychkov@mail.ru>
This commit is contained in:
parent
0b28f5d9e4
commit
c0971e41b0
3 changed files with 192 additions and 14 deletions
|
@ -0,0 +1,2 @@
|
||||||
|
minor_changes:
|
||||||
|
- postgresql_privs - add ``procedure`` type support (https://github.com/ansible-collections/community.general/issues/1002).
|
|
@ -42,31 +42,33 @@ options:
|
||||||
description:
|
description:
|
||||||
- Type of database object to set privileges on.
|
- Type of database object to set privileges on.
|
||||||
- The C(default_privs) choice is available starting at version 2.7.
|
- 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 since Ansible version '2.8'.
|
- The C(foreign_data_wrapper) and C(foreign_server) object types are available since Ansible version 2.8.
|
||||||
- The C(type) choice is available since Ansible version '2.10'.
|
- The C(type) choice is available since Ansible version 2.10.
|
||||||
|
- The C(procedure) is supported since collection version 1.3.0 and PostgreSQL 11.
|
||||||
type: str
|
type: str
|
||||||
default: table
|
default: table
|
||||||
choices: [ database, default_privs, foreign_data_wrapper, foreign_server, function,
|
choices: [ database, default_privs, foreign_data_wrapper, foreign_server, function,
|
||||||
group, language, table, tablespace, schema, sequence, type ]
|
group, language, table, tablespace, schema, sequence, type , procedure]
|
||||||
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), C(partition table), C(sequence) or C(function),
|
- If I(type) is C(table), C(partition table), C(sequence), C(function) or C(procedure),
|
||||||
the special valueC(ALL_IN_SCHEMA) can be provided instead to specify all
|
the special valueC(ALL_IN_SCHEMA) can be provided instead to specify all
|
||||||
database objects of type I(type) in the schema specified via I(schema).
|
database objects of type I(type) in the schema specified via I(schema).
|
||||||
(This also works with PostgreSQL < 9.0.) (C(ALL_IN_SCHEMA) is available
|
(This also works with PostgreSQL < 9.0.) (C(ALL_IN_SCHEMA) is available
|
||||||
for C(function) and C(partition table) since Ansible 2.8)
|
for C(function) and C(partition table) since Ansible 2.8)
|
||||||
|
- C(procedure) is supported since PostgreSQL 11 and M(community.general) collection 1.3.0.
|
||||||
- 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) or I(procedure), colons (":") in object names will be
|
||||||
replaced with commas (needed to specify function signatures, see examples)'
|
replaced with commas (needed to specify signatures, see examples).
|
||||||
type: str
|
type: str
|
||||||
aliases:
|
aliases:
|
||||||
- obj
|
- obj
|
||||||
schema:
|
schema:
|
||||||
description:
|
description:
|
||||||
- Schema that contains the database objects specified via I(objs).
|
- Schema that contains the database objects specified via I(objs).
|
||||||
- May only be provided if I(type) is C(table), C(sequence), C(function), C(type),
|
- May only be provided if I(type) is C(table), C(sequence), C(function), C(procedure), C(type),
|
||||||
or C(default_privs). Defaults to C(public) in these cases.
|
or C(default_privs). Defaults to C(public) in these cases.
|
||||||
- Pay attention, for embedded types when I(type=type)
|
- Pay attention, for embedded types when I(type=type)
|
||||||
I(schema) can be C(pg_catalog) or C(information_schema) respectively.
|
I(schema) can be C(pg_catalog) or C(information_schema) respectively.
|
||||||
|
@ -369,7 +371,19 @@ EXAMPLES = r'''
|
||||||
objs: ALL_IN_SCHEMA
|
objs: ALL_IN_SCHEMA
|
||||||
schema: common
|
schema: common
|
||||||
|
|
||||||
# Available since Ansible 2.8
|
# Available since collection version 1.3.0
|
||||||
|
# Grant 'execute' permissions on all procedures in schema 'common' to role 'caller'
|
||||||
|
# Needs PostreSQL 11 or higher and community.general 1.3.0 or higher
|
||||||
|
- name: GRANT EXECUTE ON ALL PROCEDURES IN SCHEMA common TO caller
|
||||||
|
community.general.postgresql_privs:
|
||||||
|
type: prucedure
|
||||||
|
state: present
|
||||||
|
privs: EXECUTE
|
||||||
|
roles: caller
|
||||||
|
objs: ALL_IN_SCHEMA
|
||||||
|
schema: common
|
||||||
|
|
||||||
|
# Available since version 2.8
|
||||||
# ALTER DEFAULT PRIVILEGES FOR ROLE librarian IN SCHEMA library GRANT SELECT ON TABLES TO reader
|
# ALTER DEFAULT PRIVILEGES FOR ROLE librarian IN SCHEMA library GRANT SELECT ON TABLES TO reader
|
||||||
# GRANT SELECT privileges for new TABLES objects created by librarian as
|
# GRANT SELECT privileges for new TABLES objects created by librarian as
|
||||||
# default to the role reader.
|
# default to the role reader.
|
||||||
|
@ -574,6 +588,21 @@ class Connection(object):
|
||||||
self.cursor.execute(query, (schema,))
|
self.cursor.execute(query, (schema,))
|
||||||
return ["%s(%s)" % (t[0], t[1]) for t in self.cursor.fetchall()]
|
return ["%s(%s)" % (t[0], t[1]) for t in self.cursor.fetchall()]
|
||||||
|
|
||||||
|
def get_all_procedures_in_schema(self, schema):
|
||||||
|
if self.pg_version < 110000:
|
||||||
|
raise Error("PostgreSQL verion must be >= 11 for type=procedure. Exit")
|
||||||
|
|
||||||
|
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 and p.prokind = 'p'")
|
||||||
|
|
||||||
|
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
|
||||||
|
@ -699,7 +728,7 @@ class Connection(object):
|
||||||
get_status = partial(self.get_table_acls, schema_qualifier)
|
get_status = partial(self.get_table_acls, schema_qualifier)
|
||||||
elif obj_type == 'sequence':
|
elif obj_type == 'sequence':
|
||||||
get_status = partial(self.get_sequence_acls, schema_qualifier)
|
get_status = partial(self.get_sequence_acls, schema_qualifier)
|
||||||
elif obj_type == 'function':
|
elif obj_type in ('function', 'procedure'):
|
||||||
get_status = partial(self.get_function_acls, schema_qualifier)
|
get_status = partial(self.get_function_acls, schema_qualifier)
|
||||||
elif obj_type == 'schema':
|
elif obj_type == 'schema':
|
||||||
get_status = self.get_schema_acls
|
get_status = self.get_schema_acls
|
||||||
|
@ -727,13 +756,13 @@ class Connection(object):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# obj_ids: quoted db object identifiers (sometimes schema-qualified)
|
# obj_ids: quoted db object identifiers (sometimes schema-qualified)
|
||||||
if obj_type == 'function':
|
if obj_type in ('function', 'procedure'):
|
||||||
obj_ids = []
|
obj_ids = []
|
||||||
for obj in objs:
|
for obj in objs:
|
||||||
try:
|
try:
|
||||||
f, args = obj.split('(', 1)
|
f, args = obj.split('(', 1)
|
||||||
except Exception:
|
except Exception:
|
||||||
raise Error('Illegal function signature: "%s".' % obj)
|
raise Error('Illegal function / procedure signature: "%s".' % obj)
|
||||||
obj_ids.append('"%s"."%s"(%s' % (schema_qualifier, f, args))
|
obj_ids.append('"%s"."%s"(%s' % (schema_qualifier, f, args))
|
||||||
elif obj_type in ['table', 'sequence', 'type']:
|
elif obj_type in ['table', 'sequence', 'type']:
|
||||||
obj_ids = ['"%s"."%s"' % (schema_qualifier, o) for o in objs]
|
obj_ids = ['"%s"."%s"' % (schema_qualifier, o) for o in objs]
|
||||||
|
@ -749,7 +778,7 @@ class Connection(object):
|
||||||
set_what = ','.join(privs)
|
set_what = ','.join(privs)
|
||||||
else:
|
else:
|
||||||
# function types are already quoted above
|
# function types are already quoted above
|
||||||
if obj_type != 'function':
|
if obj_type not in ('function', 'procedure'):
|
||||||
obj_ids = [pg_quote_identifier(i, 'table') for i in obj_ids]
|
obj_ids = [pg_quote_identifier(i, 'table') for i in obj_ids]
|
||||||
# Note: obj_type has been checked against a set of string literals
|
# Note: obj_type has been checked against a set of string literals
|
||||||
# and privs was escaped when it was parsed
|
# and privs was escaped when it was parsed
|
||||||
|
@ -961,6 +990,7 @@ def main():
|
||||||
choices=['table',
|
choices=['table',
|
||||||
'sequence',
|
'sequence',
|
||||||
'function',
|
'function',
|
||||||
|
'procedure',
|
||||||
'database',
|
'database',
|
||||||
'schema',
|
'schema',
|
||||||
'language',
|
'language',
|
||||||
|
@ -997,7 +1027,7 @@ def main():
|
||||||
# Create type object as namespace for module params
|
# Create type object as namespace for module params
|
||||||
p = type('Params', (), module.params)
|
p = type('Params', (), module.params)
|
||||||
# param "schema": default, allowed depends on param "type"
|
# param "schema": default, allowed depends on param "type"
|
||||||
if p.type in ['table', 'sequence', 'function', 'type', 'default_privs']:
|
if p.type in ['table', 'sequence', 'function', 'procedure', 'type', 'default_privs']:
|
||||||
p.schema = p.schema or 'public'
|
p.schema = p.schema or 'public'
|
||||||
elif p.schema:
|
elif p.schema:
|
||||||
module.fail_json(msg='Argument "schema" is not allowed '
|
module.fail_json(msg='Argument "schema" is not allowed '
|
||||||
|
@ -1060,6 +1090,8 @@ def main():
|
||||||
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':
|
elif p.type == 'function' and p.objs == 'ALL_IN_SCHEMA':
|
||||||
objs = conn.get_all_functions_in_schema(p.schema)
|
objs = conn.get_all_functions_in_schema(p.schema)
|
||||||
|
elif p.type == 'procedure' and p.objs == 'ALL_IN_SCHEMA':
|
||||||
|
objs = conn.get_all_procedures_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())
|
||||||
|
@ -1078,7 +1110,7 @@ def main():
|
||||||
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 in ('function', 'procedure'):
|
||||||
objs = [obj.replace(':', ',') for obj in objs]
|
objs = [obj.replace(':', ',') for obj in objs]
|
||||||
|
|
||||||
# roles
|
# roles
|
||||||
|
|
|
@ -682,6 +682,150 @@
|
||||||
- result is not changed
|
- result is not changed
|
||||||
when: postgres_version_resp.stdout is version('11', '>=')
|
when: postgres_version_resp.stdout is version('11', '>=')
|
||||||
|
|
||||||
|
###########################
|
||||||
|
# Test for procedure type #
|
||||||
|
###########################
|
||||||
|
- name: Create another procedure for tests
|
||||||
|
postgresql_query:
|
||||||
|
query: "CREATE PROCEDURE mock_procedure1(int, int) LANGUAGE SQL AS $$ SELECT 1; $$;"
|
||||||
|
db: "{{ db_name }}"
|
||||||
|
login_user: "{{ db_user3 }}"
|
||||||
|
login_password: password
|
||||||
|
when: postgres_version_resp.stdout is version('11', '>=')
|
||||||
|
|
||||||
|
- name: Grant privs on procedure
|
||||||
|
postgresql_privs:
|
||||||
|
type: procedure
|
||||||
|
state: present
|
||||||
|
privs: EXECUTE
|
||||||
|
roles: "{{ db_user2 }}"
|
||||||
|
objs: 'mock_procedure1(int:int)'
|
||||||
|
schema: public
|
||||||
|
db: "{{ db_name }}"
|
||||||
|
login_user: "{{ db_user3 }}"
|
||||||
|
login_password: password
|
||||||
|
register: result
|
||||||
|
when: postgres_version_resp.stdout is version('11', '>=')
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result is changed
|
||||||
|
when: postgres_version_resp.stdout is version('11', '>=')
|
||||||
|
|
||||||
|
- name: Grant privs on procedure again
|
||||||
|
postgresql_privs:
|
||||||
|
type: procedure
|
||||||
|
state: present
|
||||||
|
privs: EXECUTE
|
||||||
|
roles: "{{ db_user2 }}"
|
||||||
|
objs: 'mock_procedure1(int:int)'
|
||||||
|
schema: public
|
||||||
|
db: "{{ db_name }}"
|
||||||
|
login_user: "{{ db_user3 }}"
|
||||||
|
login_password: password
|
||||||
|
register: result
|
||||||
|
when: postgres_version_resp.stdout is version('11', '>=')
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result is not changed
|
||||||
|
when: postgres_version_resp.stdout is version('11', '>=')
|
||||||
|
|
||||||
|
- name: Revoke procedure privs
|
||||||
|
postgresql_privs:
|
||||||
|
type: procedure
|
||||||
|
state: absent
|
||||||
|
privs: EXECUTE
|
||||||
|
roles: "{{ db_user2 }}"
|
||||||
|
objs: 'mock_procedure1(int:int)'
|
||||||
|
schema: public
|
||||||
|
db: "{{ db_name }}"
|
||||||
|
login_user: "{{ db_user3 }}"
|
||||||
|
login_password: password
|
||||||
|
register: result
|
||||||
|
when: postgres_version_resp.stdout is version('11', '>=')
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result is changed
|
||||||
|
when: postgres_version_resp.stdout is version('11', '>=')
|
||||||
|
|
||||||
|
- name: Revoke procedure privs again
|
||||||
|
postgresql_privs:
|
||||||
|
type: procedure
|
||||||
|
state: absent
|
||||||
|
privs: EXECUTE
|
||||||
|
roles: "{{ db_user2 }}"
|
||||||
|
objs: 'mock_procedure1(int:int)'
|
||||||
|
schema: public
|
||||||
|
db: "{{ db_name }}"
|
||||||
|
login_user: "{{ db_user3 }}"
|
||||||
|
login_password: password
|
||||||
|
register: result
|
||||||
|
when: postgres_version_resp.stdout is version('11', '>=')
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result is not changed
|
||||||
|
when: postgres_version_resp.stdout is version('11', '>=')
|
||||||
|
|
||||||
|
- name: Grant procedure privs for all object in schema
|
||||||
|
postgresql_privs:
|
||||||
|
type: procedure
|
||||||
|
state: present
|
||||||
|
privs: ALL
|
||||||
|
roles: "{{ db_user2 }}"
|
||||||
|
objs: ALL_IN_SCHEMA
|
||||||
|
schema: public
|
||||||
|
db: "{{ db_name }}"
|
||||||
|
login_user: "{{ db_user3 }}"
|
||||||
|
login_password: password
|
||||||
|
register: result
|
||||||
|
when: postgres_version_resp.stdout is version('11', '>=')
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result is changed
|
||||||
|
when: postgres_version_resp.stdout is version('11', '>=')
|
||||||
|
|
||||||
|
- name: Grant procedure privs for all object in schema again
|
||||||
|
postgresql_privs:
|
||||||
|
type: procedure
|
||||||
|
state: present
|
||||||
|
privs: ALL
|
||||||
|
roles: "{{ db_user2 }}"
|
||||||
|
objs: ALL_IN_SCHEMA
|
||||||
|
schema: public
|
||||||
|
db: "{{ db_name }}"
|
||||||
|
login_user: "{{ db_user3 }}"
|
||||||
|
login_password: password
|
||||||
|
register: result
|
||||||
|
when: postgres_version_resp.stdout is version('11', '>=')
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result is not changed
|
||||||
|
when: postgres_version_resp.stdout is version('11', '>=')
|
||||||
|
|
||||||
|
- name: Revoke procedure privs for all object in schema
|
||||||
|
postgresql_privs:
|
||||||
|
type: procedure
|
||||||
|
state: absent
|
||||||
|
privs: ALL
|
||||||
|
roles: "{{ db_user2 }}"
|
||||||
|
objs: ALL_IN_SCHEMA
|
||||||
|
schema: public
|
||||||
|
db: "{{ db_name }}"
|
||||||
|
login_user: "{{ db_user3 }}"
|
||||||
|
login_password: password
|
||||||
|
register: result
|
||||||
|
when: postgres_version_resp.stdout is version('11', '>=')
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result is changed
|
||||||
|
when: postgres_version_resp.stdout is version('11', '>=')
|
||||||
|
|
||||||
#################################################
|
#################################################
|
||||||
# Test ALL_IN_SCHEMA for 'partioned tables type #
|
# Test ALL_IN_SCHEMA for 'partioned tables type #
|
||||||
#################################################
|
#################################################
|
||||||
|
|
Loading…
Reference in a new issue