diff --git a/plugins/module_utils/user.py b/plugins/module_utils/user.py index 87f2572e..64097790 100644 --- a/plugins/module_utils/user.py +++ b/plugins/module_utils/user.py @@ -155,17 +155,15 @@ def user_add(cursor, user, host, host_all, password, encrypted, attributes, tls_requires, reuse_existing_password, module): # we cannot create users without a proper hostname if host_all: - return {'changed': False, 'password_changed': False, 'attributes': {}} + return {'changed': False, 'password_changed': False, 'attributes': attributes} if module.check_mode: - return {'changed': True, 'password_changed': None, 'attributes': {}} + return {'changed': True, 'password_changed': None, 'attributes': attributes} # If attributes are set, perform a sanity check to ensure server supports user attributes before creating user if attributes and not get_attribute_support(cursor): module.fail_json(msg="user attributes were specified but the mysql server does not support user attributes") - final_attributes = {} - # Determine what user management method server uses old_user_mgmt = impl.use_old_user_mgmt(cursor) @@ -210,11 +208,12 @@ def user_add(cursor, user, host, host_all, password, encrypted, if new_priv is not None: for db_table, priv in iteritems(new_priv): privileges_grant(cursor, user, host, db_table, priv, tls_requires) - if attributes is not None: - cursor.execute("ALTER USER %s@%s ATTRIBUTE %s", (user, host, json.dumps(attributes))) - final_attributes = attributes_get(cursor, user, host) if tls_requires is not None: privileges_grant(cursor, user, host, "*.*", get_grants(cursor, user, host), tls_requires) + if attributes is not None: + cursor.execute("ALTER USER %s@%s ATTRIBUTE %s", (user, host, json.dumps(attributes))) + + final_attributes = attributes_get(cursor, user, host) return {'changed': True, 'password_changed': not used_existing_password, 'attributes': final_attributes} @@ -441,12 +440,17 @@ def user_mod(cursor, user, host, host_all, password, encrypted, if attributes_to_change: msg = "Attributes updated: %s" % (", ".join(["%s: %s" % (key, value) for key, value in attributes_to_change.items()])) + if not module.check_mode: cursor.execute("ALTER USER %s@%s ATTRIBUTE %s", (user, host, json.dumps(attributes_to_change))) changed = True + # Calculate final attributes by re-running attributes_get when not in check mode, and merge dictionaries when in check mode if attribute_support: - final_attributes = attributes_get(cursor, user, host) + if module.check_mode: + final_attributes = {**current_attributes, **attributes_to_change} + else: + final_attributes = attributes_get(cursor, user, host) else: final_attributes = {} diff --git a/tests/integration/targets/test_mysql_user/tasks/main.yml b/tests/integration/targets/test_mysql_user/tasks/main.yml index f4247e40..f5e07484 100644 --- a/tests/integration/targets/test_mysql_user/tasks/main.yml +++ b/tests/integration/targets/test_mysql_user/tasks/main.yml @@ -267,6 +267,9 @@ tags: - issue_465 + # Tests for user attributes + - include_tasks: test_user_attributes.yml + # Tests for the TLS requires dictionary - include_tasks: test_tls_requirements.yml 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 new file mode 100644 index 00000000..cd645393 --- /dev/null +++ b/tests/integration/targets/test_mysql_user/tasks/test_user_attributes.yml @@ -0,0 +1,244 @@ +--- +- vars: + mysql_parameters: &mysql_params + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: '{{ mysql_host }}' + login_port: '{{ mysql_primary_port }}' + + block: + + - when: db_engine == 'mariadb' + block: + + - name: Attributes | Attempt to create user with attributes with mariadb in check mode + mysql_user: + <<: *mysql_params + name: '{{ user_name_2 }}' + host: '%' + password: '{{ user_password_2 }}' + attributes: + key1: "value1" + ignore_errors: yes + register: result + check_mode: yes + + - name: Attributes | Assert that creating user with attributes fails with mariadb + assert: + that: + - result is failed + + - name: Attributes | Attempt to create user with attributes with mariadb + mysql_user: + <<: *mysql_params + name: '{{ user_name_2 }}' + host: '%' + password: '{{ user_password_2 }}' + attributes: + key1: "value1" + ignore_errors: yes + register: result + + - name: Attributes | Assert that creating user with attributes fails with mariadb + assert: + that: + - result is failed + + - when: db_engine == 'mysql' + block: + + # Create user with attributes + - name: Attributes | Test creating a user with attributes in check mode + mysql_user: + <<: *mysql_params + name: '{{ user_name_2 }}' + host: '%' + password: '{{ user_password_2 }}' + attributes: + key1: "value1" + register: result + check_mode: yes + + - name: Attributes | Assert that user would have been created with attributes + assert: + that: + - result is changed + - resukt.attributes.key1 == "value1" + + - name: Attributes | Test creating a user with attributes + mysql_user: + <<: *mysql_params + name: '{{ user_name_2 }}' + host: '%' + password: '{{ user_password_2 }}' + attributes: + key1: "value1" + register: result + + - name: Attributes | Assert that user was created with attributes + assert: + that: + - result is changed + - result.attributes.key1 == "value1" + + # Append attributes on an existing user + - name: Attributes | Test appending attributes to an existing user in check mode + mysql_user: + <<: *mysql_params + name: '{{ user_name_2 }}' + host: '%' + attributes: + key2: "value2" + register: result + check_mode: yes + + - name: Attributes | Assert that attribute would have been appended and existing attribute stays + assert: + that: + - result is changed + - result.attributes.key1 == "value1" + - result.attributes.key2 == "value2" + + - name: Attributes | Test appending attributes to an existing user + mysql_user: + <<: *mysql_params + name: '{{ user_name_2 }}' + host: '%' + attributes: + key2: "value2" + register: result + + - name: Attributes | Assert that new attribute was appended and existing attribute stays + assert: + that: + - result is changed + - result.attributes.key1 == "value1" + - result.attributes.key2 == "value2" + + # Test updating existing attributes + - name: Attributes | Test updating attributes on an existing user in check mode + mysql_user: + <<: *mysql_params + name: '{{ user_name_2 }}' + host: '%' + attributes: + key2: "new_value2" + check_mode: yes + register: result + + - name: Attributes | Assert that attribute would have been updated + assert: + that: + - result is changed + - result.attributes.key2 == "new_value2" + + - name: Attributes | Test updating attributes on an existing user + mysql_user: + <<: *mysql_params + name: '{{ user_name_2 }}' + host: '%' + attributes: + key2: "new_value2" + register: result + + - name: Attributes | Assert that attribute was updated + assert: + that: + - result is changed + - result.attributes.key2 == "new_value2" + + # Test deleting attributes + - name: Attributes | Test deleting attributes on an existing user in check mode + mysql_user: + <<: *mysql_params + name: '{{ user_name_2 }}' + host: '%' + attributes: + key2: False + register: result + check_mode: yes + + - name: Attributes | Assert that attribute would have been deleted + assert: + that: + - result is changed + - "'key2' not in result.attributes" + + - name: Attributes | Test deleting attributes on an existing user + mysql_user: + <<: *mysql_params + name: '{{ user_name_2 }}' + host: '%' + attributes: + key2: False + register: result + + - name: Attributes | Assert that attribute was deleted + assert: + that: + - result is changed + - "'key2' not in result.attributes" + + # Test attribute idempotency + - 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: + key1: "value1" + register: result + check_mode: yes + + - name: Attributes | Assert that attribute would not have been updated + assert: + that: + - result is ok + - result.attributes.key1 == "value1" + + - name: Attributes | Test attribute idempotency by trying to change an already correct attribute + mysql_user: + <<: *mysql_params + name: '{{ user_name_2 }}' + host: '%' + attributes: + key1: "value1" + register: result + + - name: Attributes | Assert that attribute was not updated + assert: + that: + - result is ok + - result.attributes.key1 == "value1" + + - name: Attributes | Test attribute idempotency by not specifying attribute parameter in check mode + mysql_user: + <<: *mysql_params + name: '{{ user_name_2 }}' + host: '%' + register: result + check_mode: yes + + - name: Attributes | Assert that attribute is returned in check mode + assert: + that: + - result is ok + - result.attributes.key1 == "value1" + + - name: Attributes | Test attribute idempotency by not specifying attribute parameter + mysql_user: + <<: *mysql_params + name: '{{ user_name_2 }}' + host: '%' + register: result + + - name: Attributes | Assert that attribute is returned + assert: + that: + - result is ok + - result.attributes.key1 == "value1" + + # Cleanup + - include_tasks: utils/remove_user.yml + vars: + user_name: "{{ user_name_2 }}"