Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

clickhouse_user: set default role #70

Merged
merged 18 commits into from
Aug 22, 2024
Merged
5 changes: 5 additions & 0 deletions changelogs/fragments/0-clickhouse_user.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
minor_changes:
- clickhouse_user - add the ``roles`` argument to grant roles (https://github.com/ansible-collections/community.clickhouse/pull/70).
- clickhouse_user - add the ``default_roles`` argument to set default roles (https://github.com/ansible-collections/community.clickhouse/pull/70).
- clickhouse_user - add the ``append_roles`` argument to append roles passed through ``roles`` argument (https://github.com/ansible-collections/community.clickhouse/pull/70).
- clickhouse_user - add the ``append_default_roles`` argument to append roles passed through ``default_roles`` argument (https://github.com/ansible-collections/community.clickhouse/pull/70).
188 changes: 185 additions & 3 deletions plugins/modules/clickhouse_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,47 @@
type: list
elements: str
version_added: '0.5.0'
roles:
description:
- Grants specified roles for the user.
- To append specified roles to existing ones, also add I(append_roles=true) to your task.
- To revoke all roles, pass an empty list and I(append_roles=false).
type: list
elements: str
version_added: '0.6.0'
default_roles:
description:
- Sets specified roles as default for the user.
- The roles must be explicitly granted to the user whether manually
before using this argument or by using the I(roles)
argument in the same task.
- To append specified roles to existing ones, also add I(append_default_roles=true) to your task.
- To unset all roles as default, pass an empty list and I(append_default_roles=false).
type: list
elements: str
version_added: '0.6.0'
append_roles:
description:
- When set to C(true), appends roles specified in I(roles) to existing
user roles instead of removing the user from not specified roles.
- The default is C(false), which will remove the user from all not specified roles.
- Ignored without I(roles) set.
type: bool
default: false
version_added: '0.6.0'
append_default_roles:
description:
- When set to C(true), appends roles specified in I(default_roles) to existing
default roles instead of unsetting not specified ones.
- The default is C(false), which will unset all not specified roles.
- Ignored without I(default_roles) set.
type: bool
default: false
version_added: '0.6.0'
'''

EXAMPLES = r'''
- name: Create user
- name: Create user granting roles and setting default role
community.clickhouse.clickhouse_user:
login_host: localhost
login_user: alice
Expand All @@ -94,6 +131,41 @@
name: test_user
password: qwerty
type_password: sha256_password
roles:
- accountant
- manager
default_roles:
- accountant
append_roles: true

- name: Append the sales role to alice's roles
community.clickhouse.clickhouse_user:
login_host: localhost
login_user: alice
login_db: foo
login_password: my_password
name: test_user
roles:
- sales
append_roles: true

- name: Unset all alice's default roles
community.clickhouse.clickhouse_user:
login_host: localhost
login_user: alice
login_db: foo
login_password: my_password
name: test_user
default_roles: []

- name: Revoke all roles from alice
community.clickhouse.clickhouse_user:
login_host: localhost
login_user: alice
login_db: foo
login_password: my_password
name: test_user
roles: []
Andersson007 marked this conversation as resolved.
Show resolved Hide resolved

- name: If user exists, update password
community.clickhouse.clickhouse_user:
Expand Down Expand Up @@ -155,6 +227,7 @@

class ClickHouseUser():
def __init__(self, module, client, name, password, type_password, cluster):
self.changed = False
self.module = module
self.client = client
self.name = name
Expand All @@ -163,11 +236,15 @@ def __init__(self, module, client, name, password, type_password, cluster):
self.cluster = cluster
# Set default values, then update
self.user_exists = False
self.current_default_roles = []
self.current_roles = []
# Fetch actual values from DB and
# update the attributes with them
self.__populate_info()

def __populate_info(self):
# Collecting user information
query = ("SELECT name, storage, auth_type "
query = ("SELECT name, storage, auth_type, default_roles_list "
"FROM system.users "
"WHERE name = '%s'" % self.name)

Expand All @@ -180,6 +257,16 @@ def __populate_info(self):

if result != []:
self.user_exists = True
self.current_default_roles = result[0][3]

if self.user_exists:
self.current_roles = self.__fetch_user_groups()

def __fetch_user_groups(self):
query = ("SELECT granted_role_name FROM system.role_grants "
"WHERE user_name = '%s'" % self.name)
result = execute_query(self.module, self.client, query)
return [row[0] for row in result]

def create(self):
list_settings = self.module.params['settings']
Expand All @@ -204,11 +291,61 @@ def create(self):
if not self.module.check_mode:
execute_query(self.module, self.client, query)

if self.module.params['roles']:
self.__grant_role(self.module.params['roles'])

if self.module.params['default_roles']:
self.__set_default_roles(self.module.params['default_roles'])

return True

def update(self, update_password):
if self.module.params['roles'] is not None:
desired_roles = self.module.params['roles']

roles_to_grant = []
for role in desired_roles:
if role not in self.current_roles:
roles_to_grant.append(role)

if roles_to_grant:
self.__grant_roles(roles_to_grant)

if not self.module.params['append_roles']:
roles_to_revoke = []
for role in self.current_roles:
if role not in desired_roles:
roles_to_revoke.append(role)

if roles_to_revoke:
self.__revoke_roles(roles_to_revoke)

if self.module.params['default_roles'] is not None:
default_roles = self.module.params['default_roles']
cur_def_roles_set = set(self.current_default_roles)
req_def_roles_set = set(default_roles)

if self.module.params['append_roles'] is False:
if not req_def_roles_set:
# Update roles info in case all roles were revoked
# in the same task and then unset if the roles list
# is not empty
self.current_roles = self.__fetch_user_groups()
if self.current_roles:
self.__unset_default_roles()

elif cur_def_roles_set != req_def_roles_set:
self.__set_default_roles(default_roles)

else:
if cur_def_roles_set != req_def_roles_set:
# Append roles to default roles.
# Use set union to make a list of unique roles
roles_to_set = list(cur_def_roles_set.union(req_def_roles_set))
self.__set_default_roles(roles_to_set)

if update_password == 'on_create':
return False
return False or self.changed

# If update_password is always
# TODO: When ClickHouse will allow to retrieve password hashes,
Expand All @@ -235,6 +372,47 @@ def drop(self):

return True

def __grant_roles(self, roles_to_set):
query = "GRANT %s TO %s" % (', '.join(roles_to_set), self.name)
executed_statements.append(query)

if not self.module.check_mode:
execute_query(self.module, self.client, query)

self.changed = True

def __revoke_roles(self, roles_to_revoke):
query = "REVOKE %s FROM %s" % (', '.join(roles_to_revoke), self.name)
executed_statements.append(query)

if not self.module.check_mode:
execute_query(self.module, self.client, query)

self.changed = True

def __set_default_roles(self, roles_to_set):
self.current_roles = self.__fetch_user_groups()
for role in roles_to_set:
if role not in self.current_roles and role not in self.module.params["roles"]:
self.module.fail_json("User %s is not in %s role. Grant it explicitly first." % (self.name, role))

query = "ALTER USER %s DEFAULT ROLE %s" % (self.name, ', '.join(roles_to_set))
executed_statements.append(query)

if not self.module.check_mode:
execute_query(self.module, self.client, query)

self.changed = True

def __unset_default_roles(self):
query = "SET DEFAULT ROLE NONE TO %s" % self.name
executed_statements.append(query)

if not self.module.check_mode:
execute_query(self.module, self.client, query)

self.changed = True


def main():
argument_spec = client_common_argument_spec()
Expand All @@ -249,6 +427,10 @@ def main():
default='on_create', no_log=False
),
settings=dict(type='list', elements='str'),
roles=dict(type='list', elements='str', default=None),
default_roles=dict(type='list', elements='str', default=None),
append_roles=dict(type='bool', default=False),
append_default_roles=dict(type='bool', default=False),
)

# Instantiate an object of module class
Expand Down
Loading
Loading