postgresql_user: add groups parameter (#60638)

* postgresql_user: add groups parameter

* postgresql_user: add groups parameter, fail_on_role default True

* postgresql_user: add groups parameter, add changelog fragment
pull/61212/head
Andrey Klychkov 5 years ago committed by Felix Fontein
parent 4449f3a815
commit e3cf76533c

@ -0,0 +1,2 @@
minor_changes:
- postgresql_user - Add the new parameter ``groups`` (https://github.com/ansible/ansible/pull/60638).

@ -147,6 +147,11 @@ options:
type: str
aliases: [ ssl_rootcert ]
version_added: '2.3'
groups:
description:
- The list of groups (roles) that need to be granted to the user.
type: list
version_added: '2.9'
notes:
- The module creates a user (role) with login privilege by default.
Use NOLOGIN role_attr_flags to change this behaviour.
@ -205,6 +210,13 @@ EXAMPLES = r'''
db: test
user: test
password: ""
- name: Create user test and grant group user_ro and user_rw to it
postgresql_user:
name: test
groups:
- user_ro
- user_rw
'''
RETURN = r'''
@ -233,6 +245,7 @@ from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.database import pg_quote_identifier, SQLParseError
from ansible.module_utils.postgres import (
connect_to_db,
exec_sql,
get_conn_params,
postgres_common_argument_spec,
)
@ -750,6 +763,91 @@ def get_valid_flags_by_version(cursor):
]
class PgMembership():
def __init__(self, module, cursor, target_roles, groups, fail_on_role=True):
self.module = module
self.cursor = cursor
self.target_roles = [r.strip() for r in target_roles]
self.groups = groups
self.granted = {}
self.fail_on_role = fail_on_role
self.non_existent_roles = []
self.changed = False
self.__check_roles_exist()
def grant(self):
for group in self.groups:
self.granted[group] = []
for role in self.target_roles:
# If role is in a group now, pass:
if self.__check_membership(group, role):
continue
query = "GRANT %s TO %s" % ((pg_quote_identifier(group, 'role'),
(pg_quote_identifier(role, 'role'))))
self.changed = exec_sql(self, query, ddl=True, add_to_executed=False)
executed_queries.append(query)
if self.changed:
self.granted[group].append(role)
return self.changed
def __check_membership(self, src_role, dst_role):
query = ("SELECT ARRAY(SELECT b.rolname FROM "
"pg_catalog.pg_auth_members m "
"JOIN pg_catalog.pg_roles b ON (m.roleid = b.oid) "
"WHERE m.member = r.oid) "
"FROM pg_catalog.pg_roles r "
"WHERE r.rolname = '%s'" % dst_role)
res = exec_sql(self, query, add_to_executed=False)
membership = []
if res:
membership = res[0][0]
if not membership:
return False
if src_role in membership:
return True
return False
def __check_roles_exist(self):
for group in self.groups:
if not self.__role_exists(group):
if self.fail_on_role:
self.module.fail_json(msg="Role %s does not exist" % group)
else:
self.module.warn("Role %s does not exist, pass" % group)
self.non_existent_roles.append(group)
for role in self.target_roles:
if not self.__role_exists(role):
if self.fail_on_role:
self.module.fail_json(msg="Role %s does not exist" % role)
else:
self.module.warn("Role %s does not exist, pass" % role)
if role not in self.groups:
self.non_existent_roles.append(role)
else:
if self.fail_on_role:
self.module.exit_json(msg="Role role '%s' is a member of role '%s'" % (role, role))
else:
self.module.warn("Role role '%s' is a member of role '%s', pass" % (role, role))
# Update role lists, excluding non existent roles:
self.groups = [g for g in self.groups if g not in self.non_existent_roles]
self.target_roles = [r for r in self.target_roles if r not in self.non_existent_roles]
def __role_exists(self, role):
return exec_sql(self, "SELECT 1 FROM pg_roles WHERE rolname = '%s'" % role, add_to_executed=False)
# ===========================================
# Module execution.
#
@ -770,6 +868,7 @@ def main():
expires=dict(type='str', default=None),
conn_limit=dict(type='int', default=None),
session_role=dict(type='str'),
groups=dict(type='list'),
)
module = AnsibleModule(
argument_spec=argument_spec,
@ -791,6 +890,9 @@ def main():
expires = module.params["expires"]
conn_limit = module.params["conn_limit"]
role_attr_flags = module.params["role_attr_flags"]
groups = module.params["groups"]
if groups:
groups = [e.strip() for e in groups]
conn_params = get_conn_params(module, module.params, warn_db_default=False)
db_connection = connect_to_db(module, conn_params)
@ -826,6 +928,13 @@ def main():
changed = grant_privileges(cursor, user, privs) or changed
except SQLParseError as e:
module.fail_json(msg=to_native(e), exception=traceback.format_exc())
if groups:
target_roles = []
target_roles.append(user)
pg_membership = PgMembership(module, cursor, target_roles, groups)
changed = pg_membership.grant()
else:
if user_exists(cursor, user):
if module.check_mode:

@ -4,6 +4,8 @@
- vars:
test_user: hello_user
test_group1: group1
test_group2: group2
test_table: test
task_parameters: &task_parameters
become_user: '{{ pg_user }}'
@ -519,6 +521,132 @@
that:
- result is not changed
#
# Test groups parameter
#
- name: Create test group
<<: *task_parameters
postgresql_user:
<<: *pg_parameters
name: '{{ test_group2 }}'
role_attr_flags: NOLOGIN
- name: Create role test_group1 and grant test_group2 to test_group1 in check_mode
<<: *task_parameters
postgresql_user:
<<: *pg_parameters
name: '{{ test_group1 }}'
groups: '{{ test_group2 }}'
role_attr_flags: NOLOGIN
check_mode: yes
- assert:
that:
- result is changed
- result.user == '{{ test_group1 }}'
- result.queries == ['CREATE USER "{{ test_group1 }}" NOLOGIN', 'GRANT "{{ test_group2 }}" TO "{{ test_group1 }}"']
- name: check that the user doesn't exist
<<: *task_parameters
postgresql_query:
<<: *pg_parameters
query: "SELECT rolname FROM pg_roles WHERE rolname = '{{ test_group1 }}'"
- assert:
that:
- result.rowcount == 0
- name: check membership
<<: *task_parameters
postgresql_query:
<<: *pg_parameters
query: "SELECT grolist FROM pg_group WHERE groname = '{{ test_group2 }}' AND grolist != '{}'"
- assert:
that:
- result.rowcount == 0
- name: Create role test_group1 and grant test_group2 to test_group1
<<: *task_parameters
postgresql_user:
<<: *pg_parameters
name: '{{ test_group1 }}'
groups: '{{ test_group2 }}'
role_attr_flags: NOLOGIN
- assert:
that:
- result is changed
- result.user == '{{ test_group1 }}'
- result.queries == ['CREATE USER "{{ test_group1 }}" NOLOGIN', 'GRANT "{{ test_group2 }}" TO "{{ test_group1 }}"']
- name: check that the user exists
<<: *task_parameters
postgresql_query:
<<: *pg_parameters
query: "SELECT rolname FROM pg_roles WHERE rolname = '{{ test_group1 }}'"
- assert:
that:
- result.rowcount == 1
- name: check membership
<<: *task_parameters
postgresql_query:
<<: *pg_parameters
query: "SELECT grolist FROM pg_group WHERE groname = '{{ test_group2 }}' AND grolist != '{}'"
- assert:
that:
- result.rowcount == 1
- name: Grant test_group2 to test_group1 again
<<: *task_parameters
postgresql_user:
<<: *pg_parameters
name: '{{ test_group1 }}'
groups: '{{ test_group2 }}'
- assert:
that:
- result is not changed
- result.user == '{{ test_group1 }}'
- name: check membership
<<: *task_parameters
postgresql_query:
<<: *pg_parameters
query: "SELECT grolist FROM pg_group WHERE groname = '{{ test_group2 }}' AND grolist != '{}'"
- assert:
that:
- result.rowcount == 1
- name: Grant groups to existent role
<<: *task_parameters
postgresql_user:
<<: *pg_parameters
name: '{{ test_user }}'
groups:
- '{{ test_group1 }}'
- '{{ test_group2 }}'
- assert:
that:
- result is changed
- result.user == '{{ test_user }}'
- result.queries == ['GRANT "{{ test_group1 }}" TO "{{ test_user }}"', 'GRANT "{{ test_group2 }}" TO "{{ test_user }}"']
- name: check membership
<<: *task_parameters
postgresql_query:
<<: *pg_parameters
query: "SELECT * FROM pg_group WHERE groname in ('{{ test_group1 }}', '{{ test_group2 }}') AND grolist != '{}'"
- assert:
that:
- result.rowcount == 2
always:
#
# Clean up
@ -534,5 +662,9 @@
<<: *task_parameters
postgresql_user:
<<: *pg_parameters
name: '{{ test_user }}'
name: '{{ item }}'
state: absent
loop:
- '{{ test_user }}'
- '{{ test_group1 }}'
- '{{ test_group2 }}'

Loading…
Cancel
Save