From 54520c468553155f95757bd9cec0bb34f4d84418 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Fri, 31 Jan 2020 18:32:31 +0300 Subject: [PATCH] mysql_user: priv parameter can also be a dictionary (#66801) * mysql_user: add priv_dict parameter * add changelog * priv can be string or dictionary * fix example --- .../66801-mysql_user_priv_can_be_dict.yml | 2 + .../modules/database/mysql/mysql_user.py | 33 ++++++++++++- .../targets/mysql_user/defaults/main.yml | 2 + .../targets/mysql_user/tasks/main.yml | 3 ++ .../mysql_user/tasks/test_priv_dict.yml | 46 +++++++++++++++++++ 5 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 changelogs/fragments/66801-mysql_user_priv_can_be_dict.yml create mode 100644 test/integration/targets/mysql_user/tasks/test_priv_dict.yml diff --git a/changelogs/fragments/66801-mysql_user_priv_can_be_dict.yml b/changelogs/fragments/66801-mysql_user_priv_can_be_dict.yml new file mode 100644 index 00000000000..c4f04e065e8 --- /dev/null +++ b/changelogs/fragments/66801-mysql_user_priv_can_be_dict.yml @@ -0,0 +1,2 @@ +minor_changes: +- mysql_user - ``priv`` parameter can be string or dictionary (https://github.com/ansible/ansible/issues/57533). diff --git a/lib/ansible/modules/database/mysql/mysql_user.py b/lib/ansible/modules/database/mysql/mysql_user.py index f999abb74ae..257f5412d7f 100644 --- a/lib/ansible/modules/database/mysql/mysql_user.py +++ b/lib/ansible/modules/database/mysql/mysql_user.py @@ -60,7 +60,8 @@ options: exactly as returned by a C(SHOW GRANT) statement. If not followed, the module will always report changes. It includes grouping columns by permission (C(SELECT(col1,col2)) instead of C(SELECT(col1),SELECT(col2))). - type: str + - Can be passed as a dictionary (see the examples). + type: raw append_privs: description: - Append the privileges defined by priv to the existing ones for this @@ -167,6 +168,15 @@ EXAMPLES = r''' priv: '*.*:ALL,GRANT' state: present +- name: Create user with password, all database privileges and 'WITH GRANT OPTION' in db1 and db2 + mysql_user: + state: present + name: bob + password: 12345dd + priv: + 'db1.*': 'ALL,GRANT' + 'db2.*': 'ALL,GRANT' + # Note that REQUIRESSL is a special privilege that should only apply to *.* by itself. - name: Modify user to require SSL connections. mysql_user: @@ -651,6 +661,20 @@ def privileges_grant(cursor, user, host, db_table, priv): query = ' '.join(query) cursor.execute(query, (user, host)) + +def convert_priv_dict_to_str(priv): + """Converts privs dictionary to string of certain format. + + Args: + priv (dict): Dict of privileges that needs to be converted to string. + + Returns: + priv (str): String representation of input argument. + """ + priv_list = ['%s:%s' % (key, val) for key, val in iteritems(priv)] + + return '/'.join(priv_list) + # =========================================== # Module execution. # @@ -670,7 +694,7 @@ def main(): host=dict(type='str', default='localhost'), host_all=dict(type="bool", default=False), state=dict(type='str', default='present', choices=['absent', 'present']), - priv=dict(type='str'), + priv=dict(type='raw'), append_privs=dict(type='bool', default=False), check_implicit_admin=dict(type='bool', default=False), update_password=dict(type='str', default='always', choices=['always', 'on_create']), @@ -708,6 +732,11 @@ def main(): plugin = module.params["plugin"] plugin_hash_string = module.params["plugin_hash_string"] plugin_auth_string = module.params["plugin_auth_string"] + if priv and not (isinstance(priv, str) or isinstance(priv, dict)): + module.fail_json(msg="priv parameter must be str or dict but %s was passed" % type(priv)) + + if priv and isinstance(priv, dict): + priv = convert_priv_dict_to_str(priv) if mysql_driver is None: module.fail_json(msg=mysql_driver_fail_msg) diff --git a/test/integration/targets/mysql_user/defaults/main.yml b/test/integration/targets/mysql_user/defaults/main.yml index 9d6865a9190..7522aba14f2 100644 --- a/test/integration/targets/mysql_user/defaults/main.yml +++ b/test/integration/targets/mysql_user/defaults/main.yml @@ -3,9 +3,11 @@ db_name: 'data' user_name_1: 'db_user1' user_name_2: 'db_user2' +user_name_3: 'db_user3' user_password_1: 'gadfFDSdtTU^Sdfuj' user_password_2: 'jkFKUdfhdso78yi&td' +user_password_3: 'jkFKUdfhdso78yi&tk' root_password: 'zevuR6oPh7' diff --git a/test/integration/targets/mysql_user/tasks/main.yml b/test/integration/targets/mysql_user/tasks/main.yml index e399b6db892..7d2a10c0142 100644 --- a/test/integration/targets/mysql_user/tasks/main.yml +++ b/test/integration/targets/mysql_user/tasks/main.yml @@ -208,6 +208,9 @@ # - include: test_privs.yml current_privilege='INSERT,DELETE' current_append_privs=yes +# Tests for the priv parameter with dict value (https://github.com/ansible/ansible/issues/57533) +- include: test_priv_dict.yml + - import_tasks: issue-29511.yaml tags: - issue-29511 diff --git a/test/integration/targets/mysql_user/tasks/test_priv_dict.yml b/test/integration/targets/mysql_user/tasks/test_priv_dict.yml new file mode 100644 index 00000000000..a28cc806f62 --- /dev/null +++ b/test/integration/targets/mysql_user/tasks/test_priv_dict.yml @@ -0,0 +1,46 @@ +# Tests for priv parameter value passed as a dict +- name: Create test databases + mysql_db: + name: '{{ item }}' + state: present + login_unix_socket: '{{ mysql_socket }}' + loop: + - data1 + - data2 + +- name: Create user with privileges + mysql_user: + name: '{{ user_name_3 }}' + password: '{{ user_password_3 }}' + priv: + "data1.*": "SELECT" + "data2.*": "SELECT" + state: present + login_unix_socket: '{{ mysql_socket }}' + +- name: Run command to show privileges for user (expect privileges in stdout) + command: mysql "-e SHOW GRANTS FOR '{{ user_name_3 }}'@'localhost';" + register: result + +- name: Assert user has giving privileges + assert: + that: + - "'GRANT SELECT ON `data1`.*' in result.stdout" + - "'GRANT SELECT ON `data2`.*' in result.stdout" + +########## +# Clean up +- name: Drop test databases + mysql_db: + name: '{{ item }}' + state: present + login_unix_socket: '{{ mysql_socket }}' + loop: + - data1 + - data2 + +- name: Drop test user + mysql_user: + name: '{{ user_name_3 }}' + state: absent + login_unix_socket: '{{ mysql_socket }}'