diff --git a/plugins/module_utils/user.py b/plugins/module_utils/user.py index 59a3ef4d..f6a6259e 100644 --- a/plugins/module_utils/user.py +++ b/plugins/module_utils/user.py @@ -211,7 +211,7 @@ def user_add(cursor, user, host, host_all, password, encrypted, if tls_requires is not None: privileges_grant(cursor, user, host, "*.*", get_grants(cursor, user, host), tls_requires) - final_attributes = {} + final_attributes = None if attributes: cursor.execute("ALTER USER %s@%s ATTRIBUTE %s", (user, host, json.dumps(attributes))) @@ -434,6 +434,10 @@ def user_mod(cursor, user, host, host_all, password, encrypted, module.fail_json(msg="user attributes were specified but the server does not support user attributes") else: current_attributes = attributes_get(cursor, user, host) + + if current_attributes is None: + current_attributes = {} + attributes_to_change = {} for key, value in attributes.items(): @@ -974,7 +978,6 @@ def get_attribute_support(cursor): Returns: True if attributes are supported, False if they are not. """ - try: # information_schema.tables does not hold the tables within information_schema itself cursor.execute("SELECT attribute FROM INFORMATION_SCHEMA.USER_ATTRIBUTES LIMIT 0") @@ -994,21 +997,16 @@ def attributes_get(cursor, user, host): host (str): User host name. Returns: - None if the user does not exist, otherwise a dict of attributes set on the user + None if the user does not exist or the user has no attributes set, otherwise a dict of attributes set on the user """ cursor.execute("SELECT attribute FROM INFORMATION_SCHEMA.USER_ATTRIBUTES WHERE user = %s AND host = %s", (user, host)) r = cursor.fetchone() + # convert JSON string stored in row into a dict - mysql enforces that user_attributes entires are in JSON format + j = json.loads(r[0]) if r and r[0] else None - if r: - attributes = r[0] - # convert JSON string stored in row into a dict - mysql enforces that user_attributes entires are in JSON format - if attributes: - return json.loads(attributes) - else: - return {} - - return None + # if the attributes dict is empty, return None instead + return j if j else None def get_impl(cursor): diff --git a/plugins/modules/mysql_user.py b/plugins/modules/mysql_user.py index b9cbc8ba..c6a02fc1 100644 --- a/plugins/modules/mysql_user.py +++ b/plugins/modules/mysql_user.py @@ -515,7 +515,7 @@ def main(): priv = privileges_unpack(priv, mode, column_case_sensitive, ensure_usage=not subtract_privs) password_changed = False - final_attributes = {} + final_attributes = None if state == "present": if user_exists(cursor, user, host, host_all): try: diff --git a/tests/integration/targets/test_mysql_user/tasks/test_user_attributes.yml b/tests/integration/targets/test_mysql_user/tasks/test_user_attributes.yml index 1932a621..c050c6eb 100644 --- a/tests/integration/targets/test_mysql_user/tasks/test_user_attributes.yml +++ b/tests/integration/targets/test_mysql_user/tasks/test_user_attributes.yml @@ -68,6 +68,60 @@ - when: db_engine == 'mysql' block: + # ============================================================ + # Create user with no attributes (test attributes return type) + # + + # Check mode + - name: Attributes | Test creating a user with no attributes in check mode + mysql_user: + <<: *mysql_params + name: '{{ user_name_2 }}' + host: '%' + password: '{{ user_password_2 }}' + register: result_module + check_mode: yes + + - name: Attributes | Run query to verify user creation with no attributes did not take place in check mode + mysql_query: + <<: *mysql_params + query: 'SELECT user FROM mysql.user WHERE user = "{{ user_name_2 }}" AND host = "%"' + register: result_query + + - name: Attributes | Assert that user would have been created without attributes + assert: + that: + - result_module is changed + - result_module.attributes is none + - not result_query.query_result[0] + + # Real mode + - name: Attributes | Test creating a user with no attributes + mysql_user: + <<: *mysql_params + name: '{{ user_name_2 }}' + host: '%' + password: '{{ user_password_2 }}' + register: result_module + + - name: Attributes | Run query to verify created user without attributes + mysql_query: + <<: *mysql_params + query: 'SELECT attribute FROM INFORMATION_SCHEMA.USER_ATTRIBUTES WHERE user = "{{ user_name_2 }}" AND host = "%"' + register: result_query + + - name: Attributes | Assert that user was created without attributes + assert: + that: + - result_module is changed + - result_module.attributes is none + - result_query.query_result[0][0]['ATTRIBUTE'] is none + + # Clean up user to allow it to be recreated with attributes + - include_tasks: utils/remove_user.yml + vars: + user_name: "{{ user_name_2 }}" + # ============================================================ # Create user with attributes # @@ -227,78 +281,76 @@ - (result_query.query_result[0][0]['ATTRIBUTE'] | from_yaml)['key2'] == "new_value2" # ============================================================ - # Test deleting attributes + # Test attribute idempotency when specifying attributes # # Check mode - - name: Attributes | Test deleting attributes on an existing user in check mode + - name: Attributes | Test attribute idempotency by trying to change an already correct attribute in check mode mysql_user: <<: *mysql_params name: '{{ user_name_2 }}' host: '%' attributes: - key2: null + key1: "value1" register: result_module check_mode: yes - - name: Attributes | Run query to verify deleted attribute in check mode + - name: Attributes | Run query to verify idempotency of already correct attribute in check mode mysql_query: <<: *mysql_params query: 'SELECT attribute FROM INFORMATION_SCHEMA.USER_ATTRIBUTES WHERE user = "{{ user_name_2 }}" AND host = "%"' register: result_query - - name: Attributes | Assert that attribute would have been deleted + - name: Attributes | Assert that attribute would not have been updated assert: that: - - result_module is changed - - "'key2' not in result_module.attributes" - - (result_query.query_result[0][0]['ATTRIBUTE'] | from_yaml)['key2'] == "new_value2" + - result_module is not changed + - result_module.attributes.key1 == "value1" + - (result_query.query_result[0][0]['ATTRIBUTE'] | from_yaml)['key1'] == "value1" # Real mode - - name: Attributes | Test deleting attributes on an existing user + - name: Attributes | Test attribute idempotency by trying to change an already correct attribute mysql_user: <<: *mysql_params name: '{{ user_name_2 }}' host: '%' attributes: - key2: null + key1: "value1" register: result_module - - name: Attributes | Run query to verify deleted attribute + - name: Attributes | Run query to verify idempotency of already correct attribute mysql_query: <<: *mysql_params query: 'SELECT attribute FROM INFORMATION_SCHEMA.USER_ATTRIBUTES WHERE user = "{{ user_name_2 }}" AND host = "%"' register: result_query - - name: Attributes | Assert that attribute was deleted + - name: Attributes | Assert that attribute was not updated assert: that: - - result_module is changed - - "'key2' not in result_module.attributes" - - "'key2' not in result_query.query_result[0][0]['ATTRIBUTE'] | from_yaml" + - result_module is not changed + - result_module.attributes.key1 == "value1" + - (result_query.query_result[0][0]['ATTRIBUTE'] | from_yaml)['key1'] == "value1" # ============================================================ - # Test attribute idempotency when specifying attributes + # Test attribute idempotency when not specifying attribute parameter # # Check mode - - name: Attributes | Test attribute idempotency by trying to change an already correct attribute in check mode + - name: Attributes | Test attribute idempotency by not specifying attribute parameter in check mode mysql_user: <<: *mysql_params name: '{{ user_name_2 }}' host: '%' - attributes: - key1: "value1" register: result_module check_mode: yes - - name: Attributes | Run query to verify idempotency of already correct attribute in check mode + - name: Attributes | Run query to verify idempotency when not specifying attribute parameter in check mode mysql_query: <<: *mysql_params query: 'SELECT attribute FROM INFORMATION_SCHEMA.USER_ATTRIBUTES WHERE user = "{{ user_name_2 }}" AND host = "%"' register: result_query - - name: Attributes | Assert that attribute would not have been updated + - name: Attributes | Assert that attribute is returned in check mode assert: that: - result_module is not changed @@ -306,22 +358,20 @@ - (result_query.query_result[0][0]['ATTRIBUTE'] | from_yaml)['key1'] == "value1" # Real mode - - name: Attributes | Test attribute idempotency by trying to change an already correct attribute + - name: Attributes | Test attribute idempotency by not specifying attribute parameter mysql_user: <<: *mysql_params name: '{{ user_name_2 }}' host: '%' - attributes: - key1: "value1" register: result_module - - name: Attributes | Run query to verify idempotency of already correct attribute + - name: Attributes | Run query to verify idempotency when not specifying attribute parameter mysql_query: <<: *mysql_params query: 'SELECT attribute FROM INFORMATION_SCHEMA.USER_ATTRIBUTES WHERE user = "{{ user_name_2 }}" AND host = "%"' register: result_query - - name: Attributes | Assert that attribute was not updated + - name: Attributes | Assert that attribute is returned assert: that: - result_module is not changed @@ -329,52 +379,95 @@ - (result_query.query_result[0][0]['ATTRIBUTE'] | from_yaml)['key1'] == "value1" # ============================================================ - # Test attribute idempotency when not specifying attribute parameter + # Test deleting attributes # # Check mode - - name: Attributes | Test attribute idempotency by not specifying attribute parameter in check mode + - name: Attributes | Test deleting attributes on an existing user in check mode mysql_user: <<: *mysql_params name: '{{ user_name_2 }}' host: '%' + attributes: + key2: null register: result_module check_mode: yes - - name: Attributes | Run query to verify idempotency when not specifying attribute parameter in check mode + - name: Attributes | Run query to verify deleted attribute in check mode mysql_query: <<: *mysql_params query: 'SELECT attribute FROM INFORMATION_SCHEMA.USER_ATTRIBUTES WHERE user = "{{ user_name_2 }}" AND host = "%"' register: result_query - - name: Attributes | Assert that attribute is returned in check mode + - name: Attributes | Assert that attribute would have been deleted assert: that: - - result_module is not changed - - result_module.attributes.key1 == "value1" - - (result_query.query_result[0][0]['ATTRIBUTE'] | from_yaml)['key1'] == "value1" + - result_module is changed + - "'key2' not in result_module.attributes" + - (result_query.query_result[0][0]['ATTRIBUTE'] | from_yaml)['key2'] == "new_value2" # Real mode - - name: Attributes | Test attribute idempotency by not specifying attribute parameter + - name: Attributes | Test deleting attributes on an existing user mysql_user: <<: *mysql_params name: '{{ user_name_2 }}' host: '%' + attributes: + key2: null register: result_module - - name: Attributes | Run query to verify idempotency when not specifying attribute parameter + - name: Attributes | Run query to verify deleted attribute mysql_query: <<: *mysql_params query: 'SELECT attribute FROM INFORMATION_SCHEMA.USER_ATTRIBUTES WHERE user = "{{ user_name_2 }}" AND host = "%"' register: result_query - - name: Attributes | Assert that attribute is returned + - name: Attributes | Assert that attribute was deleted assert: that: - - result_module is not changed - - result_module.attributes.key1 == "value1" + - result_module is changed + - "'key2' not in result_module.attributes" + - "'key2' not in result_query.query_result[0][0]['ATTRIBUTE'] | from_yaml" + + # ============================================================ + # Test attribute return value when no attributes exist + # + + # Check mode + - name: Attributes | Test attributes return value when no attributes exist in check mode + mysql_user: + <<: *mysql_params + name: '{{ user_name_2 }}' + host: '%' + attributes: + key1: null + register: result_module + check_mode: yes + + - name: Attributes | Assert attributes return value when no attributes exist in check mode + assert: + that: + - result_module is changed + - result_module.attributes is none - (result_query.query_result[0][0]['ATTRIBUTE'] | from_yaml)['key1'] == "value1" + # Real mode + - name: Attributes | Test attributes return value when no attributes exist + mysql_user: + <<: *mysql_params + name: '{{ user_name_2 }}' + host: '%' + attributes: + key1: null + register: result_module + + - name: Attributes | Assert attributes return value when no attributes exist + assert: + that: + - result_module is changed + - result_module.attributes is none + - not result_query.query_result[0][0]['ATTRIBUTE'] + # ============================================================ # Cleanup #