mirror of
				https://github.com/ansible-collections/community.mysql.git
				synced 2025-10-25 05:24:01 -07:00 
			
		
		
		
	Merge branch 'main' into password_expiration_mysql_user
This commit is contained in:
		
				commit
				
					
						273fd2cdcc
					
				
			
		
					 21 changed files with 693 additions and 103 deletions
				
			
		|  | @ -1,6 +1,3 @@ | ||||||
| betanummeric | betanummeric | ||||||
| bmalynovytch |  | ||||||
| Jorge-Rodriguez |  | ||||||
| rsicart |  | ||||||
| laurent-indermuehle | laurent-indermuehle | ||||||
| Andersson007 (andersson007_ in #ansible-community IRC/Matrix) | Andersson007 | ||||||
|  |  | ||||||
|  | @ -87,7 +87,7 @@ We maintain each major release (1.x.y, 2.x.y, ...) for two years after the next | ||||||
| Here is the table for the support timeline: | Here is the table for the support timeline: | ||||||
| 
 | 
 | ||||||
| - 1.x.y: released 2020-08-17, EOL | - 1.x.y: released 2020-08-17, EOL | ||||||
| - 2.x.y: released 2021-04-15, supported until 2023-12-01 | - 2.x.y: released 2021-04-15, EOL | ||||||
| - 3.x.y: released 2021-12-01, current | - 3.x.y: released 2021-12-01, current | ||||||
| - 4.x.y: To be released | - 4.x.y: To be released | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										2
									
								
								changelogs/fragments/0-stable-2-eol.yml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								changelogs/fragments/0-stable-2-eol.yml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,2 @@ | ||||||
|  | major_changes: | ||||||
|  | - "Collection version 2.*.* is EOL, no more bugfixes will be backported. Please consider upgrading to the latest version." | ||||||
							
								
								
									
										2
									
								
								changelogs/fragments/602-show-all-slaves-status.yaml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								changelogs/fragments/602-show-all-slaves-status.yaml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,2 @@ | ||||||
|  | bugfixes: | ||||||
|  |   - mysql_info - the ``slave_status`` filter was returning an empty list on MariaDB with multiple replication channels. It now returns all channels by running ``SHOW ALL SLAVES STATUS`` for MariaDB servers (https://github.com/ansible-collections/community.mysql/issues/603). | ||||||
							
								
								
									
										2
									
								
								changelogs/fragments/604-user-attributes.yaml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								changelogs/fragments/604-user-attributes.yaml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,2 @@ | ||||||
|  | minor_changes: | ||||||
|  |   - "mysql_user - add user attribute support via the ``attributes`` parameter and return value (https://github.com/ansible-collections/community.mysql/pull/604)." | ||||||
|  | @ -207,6 +207,13 @@ def get_server_version(cursor): | ||||||
|     return version_str |     return version_str | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | def get_server_implementation(cursor): | ||||||
|  |     if 'mariadb' in get_server_version(cursor).lower(): | ||||||
|  |         return "mariadb" | ||||||
|  |     else: | ||||||
|  |         return "mysql" | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| def set_session_vars(module, cursor, session_vars): | def set_session_vars(module, cursor, session_vars): | ||||||
|     """Set session vars.""" |     """Set session vars.""" | ||||||
|     for var, value in session_vars.items(): |     for var, value in session_vars.items(): | ||||||
|  |  | ||||||
|  | @ -10,6 +10,7 @@ __metaclass__ = type | ||||||
| # Simplified BSD License (see simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause) | # Simplified BSD License (see simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause) | ||||||
| 
 | 
 | ||||||
| import string | import string | ||||||
|  | import json | ||||||
| import re | import re | ||||||
| 
 | 
 | ||||||
| from ansible.module_utils.six import iteritems | from ansible.module_utils.six import iteritems | ||||||
|  | @ -151,14 +152,14 @@ def get_existing_authentication(cursor, user, host): | ||||||
| 
 | 
 | ||||||
| def user_add(cursor, user, host, host_all, password, encrypted, | def user_add(cursor, user, host, host_all, password, encrypted, | ||||||
|              plugin, plugin_hash_string, plugin_auth_string, new_priv, |              plugin, plugin_hash_string, plugin_auth_string, new_priv, | ||||||
|              tls_requires, module, reuse_existing_password, |              attributes, tls_requires, module, reuse_existing_password, | ||||||
|              password_expire, password_expire_interval): |              password_expire, password_expire_interval): | ||||||
|     # we cannot create users without a proper hostname |     # we cannot create users without a proper hostname | ||||||
|     if host_all: |     if host_all: | ||||||
|         return {'changed': False, 'password_changed': False} |         return {'changed': False, 'password_changed': False, 'attributes': attributes} | ||||||
| 
 | 
 | ||||||
|     if module.check_mode: |     if module.check_mode: | ||||||
|         return {'changed': True, 'password_changed': None} |         return {'changed': True, 'password_changed': None, 'attributes': attributes} | ||||||
| 
 | 
 | ||||||
|     # Determine what user management method server uses |     # Determine what user management method server uses | ||||||
|     old_user_mgmt = impl.use_old_user_mgmt(cursor) |     old_user_mgmt = impl.use_old_user_mgmt(cursor) | ||||||
|  | @ -212,7 +213,14 @@ def user_add(cursor, user, host, host_all, password, encrypted, | ||||||
|             privileges_grant(cursor, user, host, db_table, priv, tls_requires) |             privileges_grant(cursor, user, host, db_table, priv, tls_requires) | ||||||
|     if tls_requires is not None: |     if tls_requires is not None: | ||||||
|         privileges_grant(cursor, user, host, "*.*", get_grants(cursor, user, host), tls_requires) |         privileges_grant(cursor, user, host, "*.*", get_grants(cursor, user, host), tls_requires) | ||||||
|     return {'changed': True, 'password_changed': not used_existing_password} | 
 | ||||||
|  |     final_attributes = None | ||||||
|  | 
 | ||||||
|  |     if attributes: | ||||||
|  |         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} | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def is_hash(password): | def is_hash(password): | ||||||
|  | @ -225,7 +233,7 @@ def is_hash(password): | ||||||
| 
 | 
 | ||||||
| def user_mod(cursor, user, host, host_all, password, encrypted, | def user_mod(cursor, user, host, host_all, password, encrypted, | ||||||
|              plugin, plugin_hash_string, plugin_auth_string, new_priv, |              plugin, plugin_hash_string, plugin_auth_string, new_priv, | ||||||
|              append_privs, subtract_privs, tls_requires, module, |              append_privs, subtract_privs, attributes, tls_requires, module, | ||||||
|              password_expire, password_expire_interval, role=False, maria_role=False): |              password_expire, password_expire_interval, role=False, maria_role=False): | ||||||
|     changed = False |     changed = False | ||||||
|     msg = "User unchanged" |     msg = "User unchanged" | ||||||
|  | @ -286,27 +294,26 @@ def user_mod(cursor, user, host, host_all, password, encrypted, | ||||||
|                 if current_pass_hash != encrypted_password: |                 if current_pass_hash != encrypted_password: | ||||||
|                     password_changed = True |                     password_changed = True | ||||||
|                     msg = "Password updated" |                     msg = "Password updated" | ||||||
|                     if module.check_mode: |                     if not module.check_mode: | ||||||
|                         return {'changed': True, 'msg': msg, 'password_changed': password_changed} |                         if old_user_mgmt: | ||||||
|                     if old_user_mgmt: |                             cursor.execute("SET PASSWORD FOR %s@%s = %s", (user, host, encrypted_password)) | ||||||
|                         cursor.execute("SET PASSWORD FOR %s@%s = %s", (user, host, encrypted_password)) |                             msg = "Password updated (old style)" | ||||||
|                         msg = "Password updated (old style)" |                         else: | ||||||
|                     else: |                             try: | ||||||
|                         try: |                                 cursor.execute("ALTER USER %s@%s IDENTIFIED WITH mysql_native_password AS %s", (user, host, encrypted_password)) | ||||||
|                             cursor.execute("ALTER USER %s@%s IDENTIFIED WITH mysql_native_password AS %s", (user, host, encrypted_password)) |                                 msg = "Password updated (new style)" | ||||||
|                             msg = "Password updated (new style)" |                             except (mysql_driver.Error) as e: | ||||||
|                         except (mysql_driver.Error) as e: |                                 # https://stackoverflow.com/questions/51600000/authentication-string-of-root-user-on-mysql | ||||||
|                             # https://stackoverflow.com/questions/51600000/authentication-string-of-root-user-on-mysql |                                 # Replacing empty root password with new authentication mechanisms fails with error 1396 | ||||||
|                             # Replacing empty root password with new authentication mechanisms fails with error 1396 |                                 if e.args[0] == 1396: | ||||||
|                             if e.args[0] == 1396: |                                     cursor.execute( | ||||||
|                                 cursor.execute( |                                         "UPDATE mysql.user SET plugin = %s, authentication_string = %s, Password = '' WHERE User = %s AND Host = %s", | ||||||
|                                     "UPDATE mysql.user SET plugin = %s, authentication_string = %s, Password = '' WHERE User = %s AND Host = %s", |                                         ('mysql_native_password', encrypted_password, user, host) | ||||||
|                                     ('mysql_native_password', encrypted_password, user, host) |                                     ) | ||||||
|                                 ) |                                     cursor.execute("FLUSH PRIVILEGES") | ||||||
|                                 cursor.execute("FLUSH PRIVILEGES") |                                     msg = "Password forced update" | ||||||
|                                 msg = "Password forced update" |                                 else: | ||||||
|                             else: |                                     raise e | ||||||
|                                 raise e |  | ||||||
|                     changed = True |                     changed = True | ||||||
| 
 | 
 | ||||||
|         # Handle password expiration |         # Handle password expiration | ||||||
|  | @ -383,9 +390,8 @@ def user_mod(cursor, user, host, host_all, password, encrypted, | ||||||
|                     if db_table not in new_priv: |                     if db_table not in new_priv: | ||||||
|                         if user != "root" and "PROXY" not in priv: |                         if user != "root" and "PROXY" not in priv: | ||||||
|                             msg = "Privileges updated" |                             msg = "Privileges updated" | ||||||
|                             if module.check_mode: |                             if not module.check_mode: | ||||||
|                                 return {'changed': True, 'msg': msg, 'password_changed': password_changed} |                                 privileges_revoke(cursor, user, host, db_table, priv, grant_option, maria_role) | ||||||
|                             privileges_revoke(cursor, user, host, db_table, priv, grant_option, maria_role) |  | ||||||
|                             changed = True |                             changed = True | ||||||
| 
 | 
 | ||||||
|             # If the user doesn't currently have any privileges on a db.table, then |             # If the user doesn't currently have any privileges on a db.table, then | ||||||
|  | @ -394,9 +400,8 @@ def user_mod(cursor, user, host, host_all, password, encrypted, | ||||||
|                 for db_table, priv in iteritems(new_priv): |                 for db_table, priv in iteritems(new_priv): | ||||||
|                     if db_table not in curr_priv: |                     if db_table not in curr_priv: | ||||||
|                         msg = "New privileges granted" |                         msg = "New privileges granted" | ||||||
|                         if module.check_mode: |                         if not module.check_mode: | ||||||
|                             return {'changed': True, 'msg': msg, 'password_changed': password_changed} |                             privileges_grant(cursor, user, host, db_table, priv, tls_requires, maria_role) | ||||||
|                         privileges_grant(cursor, user, host, db_table, priv, tls_requires, maria_role) |  | ||||||
|                         changed = True |                         changed = True | ||||||
| 
 | 
 | ||||||
|             # If the db.table specification exists in both the user's current privileges |             # If the db.table specification exists in both the user's current privileges | ||||||
|  | @ -435,17 +440,58 @@ def user_mod(cursor, user, host, host_all, password, encrypted, | ||||||
| 
 | 
 | ||||||
|                 if len(grant_privs) + len(revoke_privs) > 0: |                 if len(grant_privs) + len(revoke_privs) > 0: | ||||||
|                     msg = "Privileges updated: granted %s, revoked %s" % (grant_privs, revoke_privs) |                     msg = "Privileges updated: granted %s, revoked %s" % (grant_privs, revoke_privs) | ||||||
|                     if module.check_mode: |                     if not module.check_mode: | ||||||
|                         return {'changed': True, 'msg': msg, 'password_changed': password_changed} |                         if len(revoke_privs) > 0: | ||||||
|                     if len(revoke_privs) > 0: |                             privileges_revoke(cursor, user, host, db_table, revoke_privs, grant_option, maria_role) | ||||||
|                         privileges_revoke(cursor, user, host, db_table, revoke_privs, grant_option, maria_role) |                         if len(grant_privs) > 0: | ||||||
|                     if len(grant_privs) > 0: |                             privileges_grant(cursor, user, host, db_table, grant_privs, tls_requires, maria_role) | ||||||
|                         privileges_grant(cursor, user, host, db_table, grant_privs, tls_requires, maria_role) |                     else: | ||||||
|  |                         changed = True | ||||||
| 
 | 
 | ||||||
|             # after privilege manipulation, compare privileges from before and now |             # after privilege manipulation, compare privileges from before and now | ||||||
|             after_priv = privileges_get(cursor, user, host, maria_role) |             after_priv = privileges_get(cursor, user, host, maria_role) | ||||||
|             changed = changed or (curr_priv != after_priv) |             changed = changed or (curr_priv != after_priv) | ||||||
| 
 | 
 | ||||||
|  |         # Handle attributes | ||||||
|  |         attribute_support = get_attribute_support(cursor) | ||||||
|  |         final_attributes = {} | ||||||
|  | 
 | ||||||
|  |         if attributes: | ||||||
|  |             if not attribute_support: | ||||||
|  |                 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(): | ||||||
|  |                     if key not in current_attributes or current_attributes[key] != value: | ||||||
|  |                         attributes_to_change[key] = value | ||||||
|  | 
 | ||||||
|  |                 if attributes_to_change: | ||||||
|  |                     msg = "Attributes updated: %s" % (", ".join(["%s: %s" % (key, value) for key, value in attributes_to_change.items()])) | ||||||
|  | 
 | ||||||
|  |                     # Calculate final attributes by re-running attributes_get when not in check mode, and merge dictionaries when in check mode | ||||||
|  |                     if not module.check_mode: | ||||||
|  |                         cursor.execute("ALTER USER %s@%s ATTRIBUTE %s", (user, host, json.dumps(attributes_to_change))) | ||||||
|  |                         final_attributes = attributes_get(cursor, user, host) | ||||||
|  |                     else: | ||||||
|  |                         # Final if statements excludes items whose values are None in attributes_to_change, i.e. attributes that will be deleted | ||||||
|  |                         final_attributes = {k: v for d in (current_attributes, attributes_to_change) for k, v in d.items() if k not in attributes_to_change or | ||||||
|  |                                             attributes_to_change[k] is not None} | ||||||
|  | 
 | ||||||
|  |                         # Convert empty dict to None per return value requirements | ||||||
|  |                         final_attributes = final_attributes if final_attributes else None | ||||||
|  |                     changed = True | ||||||
|  |                 else: | ||||||
|  |                     final_attributes = current_attributes | ||||||
|  |         else: | ||||||
|  |             if attribute_support: | ||||||
|  |                 final_attributes = attributes_get(cursor, user, host) | ||||||
|  | 
 | ||||||
|         if role: |         if role: | ||||||
|             continue |             continue | ||||||
| 
 | 
 | ||||||
|  | @ -453,24 +499,23 @@ def user_mod(cursor, user, host, host_all, password, encrypted, | ||||||
|         current_requires = get_tls_requires(cursor, user, host) |         current_requires = get_tls_requires(cursor, user, host) | ||||||
|         if current_requires != tls_requires: |         if current_requires != tls_requires: | ||||||
|             msg = "TLS requires updated" |             msg = "TLS requires updated" | ||||||
|             if module.check_mode: |             if not module.check_mode: | ||||||
|                 return {'changed': True, 'msg': msg, 'password_changed': password_changed} |                 if not old_user_mgmt: | ||||||
|             if not old_user_mgmt: |                     pre_query = "ALTER USER" | ||||||
|                 pre_query = "ALTER USER" |                 else: | ||||||
|             else: |                     pre_query = "GRANT %s ON *.* TO" % ",".join(get_grants(cursor, user, host)) | ||||||
|                 pre_query = "GRANT %s ON *.* TO" % ",".join(get_grants(cursor, user, host)) |  | ||||||
| 
 | 
 | ||||||
|             if tls_requires is not None: |                 if tls_requires is not None: | ||||||
|                 query = " ".join((pre_query, "%s@%s")) |                     query = " ".join((pre_query, "%s@%s")) | ||||||
|                 query_with_args = mogrify_requires(query, (user, host), tls_requires) |                     query_with_args = mogrify_requires(query, (user, host), tls_requires) | ||||||
|             else: |                 else: | ||||||
|                 query = " ".join((pre_query, "%s@%s REQUIRE NONE")) |                     query = " ".join((pre_query, "%s@%s REQUIRE NONE")) | ||||||
|                 query_with_args = query, (user, host) |                     query_with_args = query, (user, host) | ||||||
| 
 | 
 | ||||||
|             cursor.execute(*query_with_args) |                 cursor.execute(*query_with_args) | ||||||
|             changed = True |             changed = True | ||||||
| 
 | 
 | ||||||
|     return {'changed': changed, 'msg': msg, 'password_changed': password_changed} |     return {'changed': changed, 'msg': msg, 'password_changed': password_changed, 'attributes': final_attributes} | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def user_delete(cursor, user, host, host_all, check_mode): | def user_delete(cursor, user, host, host_all, check_mode): | ||||||
|  | @ -1020,6 +1065,41 @@ def is_password_expired(cursor, user, host): | ||||||
|         return True |         return True | ||||||
|     return False |     return False | ||||||
| 
 | 
 | ||||||
|  | def get_attribute_support(cursor): | ||||||
|  |     """Checks if the MySQL server supports user attributes. | ||||||
|  | 
 | ||||||
|  |     Args: | ||||||
|  |         cursor (cursor): DB driver cursor object. | ||||||
|  |     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") | ||||||
|  |         cursor.fetchone() | ||||||
|  |     except mysql_driver.Error: | ||||||
|  |         return False | ||||||
|  | 
 | ||||||
|  |     return True | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def attributes_get(cursor, user, host): | ||||||
|  |     """Get attributes for a given user. | ||||||
|  | 
 | ||||||
|  |         host (str): User host name. | ||||||
|  | 
 | ||||||
|  |     Returns: | ||||||
|  |         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 the attributes dict is empty, return None instead | ||||||
|  |     return j if j else None | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| def get_impl(cursor): | def get_impl(cursor): | ||||||
|     global impl |     global impl | ||||||
|  |  | ||||||
|  | @ -577,14 +577,14 @@ def db_create(cursor, db, encoding, collation): | ||||||
| def main(): | def main(): | ||||||
|     argument_spec = mysql_common_argument_spec() |     argument_spec = mysql_common_argument_spec() | ||||||
|     argument_spec.update( |     argument_spec.update( | ||||||
|         name=dict(type='list', required=True, aliases=['db']), |         name=dict(type='list', elements='str', required=True, aliases=['db']), | ||||||
|         encoding=dict(type='str', default=''), |         encoding=dict(type='str', default=''), | ||||||
|         collation=dict(type='str', default=''), |         collation=dict(type='str', default=''), | ||||||
|         target=dict(type='path'), |         target=dict(type='path'), | ||||||
|         state=dict(type='str', default='present', choices=['absent', 'dump', 'import', 'present']), |         state=dict(type='str', default='present', choices=['absent', 'dump', 'import', 'present']), | ||||||
|         single_transaction=dict(type='bool', default=False), |         single_transaction=dict(type='bool', default=False), | ||||||
|         quick=dict(type='bool', default=True), |         quick=dict(type='bool', default=True), | ||||||
|         ignore_tables=dict(type='list', default=[]), |         ignore_tables=dict(type='list', elements='str', default=[]), | ||||||
|         hex_blob=dict(default=False, type='bool'), |         hex_blob=dict(default=False, type='bool'), | ||||||
|         force=dict(type='bool', default=False), |         force=dict(type='bool', default=False), | ||||||
|         master_data=dict(type='int', default=0, choices=[0, 1, 2]), |         master_data=dict(type='int', default=0, choices=[0, 1, 2]), | ||||||
|  |  | ||||||
|  | @ -5,6 +5,7 @@ | ||||||
| # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) | ||||||
| 
 | 
 | ||||||
| from __future__ import absolute_import, division, print_function | from __future__ import absolute_import, division, print_function | ||||||
|  | 
 | ||||||
| __metaclass__ = type | __metaclass__ = type | ||||||
| 
 | 
 | ||||||
| DOCUMENTATION = r''' | DOCUMENTATION = r''' | ||||||
|  | @ -292,6 +293,7 @@ from ansible_collections.community.mysql.plugins.module_utils.mysql import ( | ||||||
|     mysql_driver_fail_msg, |     mysql_driver_fail_msg, | ||||||
|     get_connector_name, |     get_connector_name, | ||||||
|     get_connector_version, |     get_connector_version, | ||||||
|  |     get_server_implementation, | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| from ansible_collections.community.mysql.plugins.module_utils.user import ( | from ansible_collections.community.mysql.plugins.module_utils.user import ( | ||||||
|  | @ -325,9 +327,10 @@ class MySQL_Info(object): | ||||||
|         5. add info about the new subset with an example to RETURN block |         5. add info about the new subset with an example to RETURN block | ||||||
|     """ |     """ | ||||||
| 
 | 
 | ||||||
|     def __init__(self, module, cursor): |     def __init__(self, module, cursor, server_implementation): | ||||||
|         self.module = module |         self.module = module | ||||||
|         self.cursor = cursor |         self.cursor = cursor | ||||||
|  |         self.server_implementation = server_implementation | ||||||
|         self.info = { |         self.info = { | ||||||
|             'version': {}, |             'version': {}, | ||||||
|             'databases': {}, |             'databases': {}, | ||||||
|  | @ -497,7 +500,10 @@ class MySQL_Info(object): | ||||||
| 
 | 
 | ||||||
|     def __get_slave_status(self): |     def __get_slave_status(self): | ||||||
|         """Get slave status if the instance is a slave.""" |         """Get slave status if the instance is a slave.""" | ||||||
|         res = self.__exec_sql('SHOW SLAVE STATUS') |         if self.server_implementation == "mariadb": | ||||||
|  |             res = self.__exec_sql('SHOW ALL SLAVES STATUS') | ||||||
|  |         else: | ||||||
|  |             res = self.__exec_sql('SHOW SLAVE STATUS') | ||||||
|         if res: |         if res: | ||||||
|             for line in res: |             for line in res: | ||||||
|                 host = line['Master_Host'] |                 host = line['Master_Host'] | ||||||
|  | @ -692,8 +698,8 @@ def main(): | ||||||
|     argument_spec = mysql_common_argument_spec() |     argument_spec = mysql_common_argument_spec() | ||||||
|     argument_spec.update( |     argument_spec.update( | ||||||
|         login_db=dict(type='str'), |         login_db=dict(type='str'), | ||||||
|         filter=dict(type='list'), |         filter=dict(type='list', elements='str'), | ||||||
|         exclude_fields=dict(type='list'), |         exclude_fields=dict(type='list', elements='str'), | ||||||
|         return_empty_dbs=dict(type='bool', default=False), |         return_empty_dbs=dict(type='bool', default=False), | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|  | @ -738,10 +744,12 @@ def main(): | ||||||
|                'Exception message: %s' % (connector_name, connector_version, config_file, to_native(e))) |                'Exception message: %s' % (connector_name, connector_version, config_file, to_native(e))) | ||||||
|         module.fail_json(msg) |         module.fail_json(msg) | ||||||
| 
 | 
 | ||||||
|  |     server_implementation = get_server_implementation(cursor) | ||||||
|  | 
 | ||||||
|     ############################### |     ############################### | ||||||
|     # Create object and do main job |     # Create object and do main job | ||||||
| 
 | 
 | ||||||
|     mysql = MySQL_Info(module, cursor) |     mysql = MySQL_Info(module, cursor, server_implementation) | ||||||
| 
 | 
 | ||||||
|     module.exit_json(changed=False, |     module.exit_json(changed=False, | ||||||
|                      connector_name=connector_name, |                      connector_name=connector_name, | ||||||
|  |  | ||||||
|  | @ -36,6 +36,7 @@ options: | ||||||
|     - List of values to be passed as positional arguments to the query. |     - List of values to be passed as positional arguments to the query. | ||||||
|     - Mutually exclusive with I(named_args). |     - Mutually exclusive with I(named_args). | ||||||
|     type: list |     type: list | ||||||
|  |     elements: raw | ||||||
|   named_args: |   named_args: | ||||||
|     description: |     description: | ||||||
|     - Dictionary of key-value arguments to pass to the query. |     - Dictionary of key-value arguments to pass to the query. | ||||||
|  | @ -141,7 +142,7 @@ def main(): | ||||||
|     argument_spec.update( |     argument_spec.update( | ||||||
|         query=dict(type='raw', required=True), |         query=dict(type='raw', required=True), | ||||||
|         login_db=dict(type='str'), |         login_db=dict(type='str'), | ||||||
|         positional_args=dict(type='list'), |         positional_args=dict(type='list', elements='raw'), | ||||||
|         named_args=dict(type='dict'), |         named_args=dict(type='dict'), | ||||||
|         single_transaction=dict(type='bool', default=False), |         single_transaction=dict(type='bool', default=False), | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  | @ -931,7 +931,7 @@ class Role(): | ||||||
|         if privs: |         if privs: | ||||||
|             result = user_mod(self.cursor, self.name, self.host, |             result = user_mod(self.cursor, self.name, self.host, | ||||||
|                               None, None, None, None, None, None, |                               None, None, None, None, None, None, | ||||||
|                               privs, append_privs, subtract_privs, None, |                               privs, append_privs, subtract_privs, None, None, | ||||||
|                               self.module, None, None, role=True, |                               self.module, None, None, role=True, | ||||||
|                               maria_role=self.is_mariadb) |                               maria_role=self.is_mariadb) | ||||||
|             changed = result['changed'] |             changed = result['changed'] | ||||||
|  |  | ||||||
|  | @ -179,6 +179,13 @@ options: | ||||||
|         fields names in privileges. |         fields names in privileges. | ||||||
|     type: bool |     type: bool | ||||||
|     version_added: '3.8.0' |     version_added: '3.8.0' | ||||||
|  |   attributes: | ||||||
|  |     description: | ||||||
|  |       - "Create, update, or delete user attributes (arbitrary 'key: value' comments) for the user." | ||||||
|  |       - MySQL server must support the INFORMATION_SCHEMA.USER_ATTRIBUTES table. Provided since MySQL 8.0. | ||||||
|  |       - To delete an existing attribute, set its value to null. | ||||||
|  |     type: dict | ||||||
|  |     version_added: '3.9.0' | ||||||
| 
 | 
 | ||||||
| notes: | notes: | ||||||
|    - "MySQL server installs with default I(login_user) of C(root) and no password. |    - "MySQL server installs with default I(login_user) of C(root) and no password. | ||||||
|  | @ -271,6 +278,13 @@ EXAMPLES = r''' | ||||||
|       FUNCTION my_db.my_function: EXECUTE |       FUNCTION my_db.my_function: EXECUTE | ||||||
|     state: present |     state: present | ||||||
| 
 | 
 | ||||||
|  | - name: Modify user attributes, creating the attribute 'foo' and removing the attribute 'bar' | ||||||
|  |   community.mysql.mysql_user: | ||||||
|  |     name: bob | ||||||
|  |     attributes: | ||||||
|  |       foo: "foo" | ||||||
|  |       bar: null | ||||||
|  | 
 | ||||||
| - name: Modify user to require TLS connection with a valid client certificate | - name: Modify user to require TLS connection with a valid client certificate | ||||||
|   community.mysql.mysql_user: |   community.mysql.mysql_user: | ||||||
|     name: bob |     name: bob | ||||||
|  | @ -419,6 +433,7 @@ def main(): | ||||||
|         tls_requires=dict(type='dict'), |         tls_requires=dict(type='dict'), | ||||||
|         append_privs=dict(type='bool', default=False), |         append_privs=dict(type='bool', default=False), | ||||||
|         subtract_privs=dict(type='bool', default=False), |         subtract_privs=dict(type='bool', default=False), | ||||||
|  |         attributes=dict(type='dict'), | ||||||
|         check_implicit_admin=dict(type='bool', default=False), |         check_implicit_admin=dict(type='bool', default=False), | ||||||
|         update_password=dict(type='str', default='always', choices=['always', 'on_create', 'on_new_username'], no_log=False), |         update_password=dict(type='str', default='always', choices=['always', 'on_create', 'on_new_username'], no_log=False), | ||||||
|         sql_log_bin=dict(type='bool', default=True), |         sql_log_bin=dict(type='bool', default=True), | ||||||
|  | @ -453,6 +468,7 @@ def main(): | ||||||
|     append_privs = module.boolean(module.params["append_privs"]) |     append_privs = module.boolean(module.params["append_privs"]) | ||||||
|     subtract_privs = module.boolean(module.params['subtract_privs']) |     subtract_privs = module.boolean(module.params['subtract_privs']) | ||||||
|     update_password = module.params['update_password'] |     update_password = module.params['update_password'] | ||||||
|  |     attributes = module.params['attributes'] | ||||||
|     ssl_cert = module.params["client_cert"] |     ssl_cert = module.params["client_cert"] | ||||||
|     ssl_key = module.params["client_key"] |     ssl_key = module.params["client_key"] | ||||||
|     ssl_ca = module.params["ca_cert"] |     ssl_ca = module.params["ca_cert"] | ||||||
|  | @ -522,23 +538,25 @@ def main(): | ||||||
| 
 | 
 | ||||||
|         priv = privileges_unpack(priv, mode, column_case_sensitive, ensure_usage=not subtract_privs) |         priv = privileges_unpack(priv, mode, column_case_sensitive, ensure_usage=not subtract_privs) | ||||||
|     password_changed = False |     password_changed = False | ||||||
|  |     final_attributes = None | ||||||
|     if state == "present": |     if state == "present": | ||||||
|         if user_exists(cursor, user, host, host_all): |         if user_exists(cursor, user, host, host_all): | ||||||
|             try: |             try: | ||||||
|                 if update_password == "always": |                 if update_password == "always": | ||||||
|                     result = user_mod(cursor, user, host, host_all, password, encrypted, |                     result = user_mod(cursor, user, host, host_all, password, encrypted, | ||||||
|                                       plugin, plugin_hash_string, plugin_auth_string, |                                       plugin, plugin_hash_string, plugin_auth_string, | ||||||
|                                       priv, append_privs, subtract_privs, tls_requires, module, |                                       priv, append_privs, subtract_privs, attributes, tls_requires, module, | ||||||
|                                       password_expire, password_expire_interval) |                                       password_expire, password_expire_interval) | ||||||
| 
 | 
 | ||||||
|                 else: |                 else: | ||||||
|                     result = user_mod(cursor, user, host, host_all, None, encrypted, |                     result = user_mod(cursor, user, host, host_all, None, encrypted, | ||||||
|                                       None, None, None, |                                       None, None, None, | ||||||
|                                       priv, append_privs, subtract_privs, tls_requires, module, |                                       priv, append_privs, subtract_privs, attributes, tls_requires, module, | ||||||
|                                       password_expire, password_expire_interval) |                                       password_expire, password_expire_interval) | ||||||
|                 changed = result['changed'] |                 changed = result['changed'] | ||||||
|                 msg = result['msg'] |                 msg = result['msg'] | ||||||
|                 password_changed = result['password_changed'] |                 password_changed = result['password_changed'] | ||||||
|  |                 final_attributes = result['attributes'] | ||||||
| 
 | 
 | ||||||
|             except (SQLParseError, InvalidPrivsError, mysql_driver.Error) as e: |             except (SQLParseError, InvalidPrivsError, mysql_driver.Error) as e: | ||||||
|                 module.fail_json(msg=to_native(e)) |                 module.fail_json(msg=to_native(e)) | ||||||
|  | @ -551,10 +569,11 @@ def main(): | ||||||
|                 reuse_existing_password = update_password == 'on_new_username' |                 reuse_existing_password = update_password == 'on_new_username' | ||||||
|                 result = user_add(cursor, user, host, host_all, password, encrypted, |                 result = user_add(cursor, user, host, host_all, password, encrypted, | ||||||
|                                   plugin, plugin_hash_string, plugin_auth_string, |                                   plugin, plugin_hash_string, plugin_auth_string, | ||||||
|                                   priv, tls_requires, module, reuse_existing_password, |                                   priv, attributes, tls_requires, module, reuse_existing_password, | ||||||
|                                   password_expire, password_expire_interval) |                                   password_expire, password_expire_interval) | ||||||
|                 changed = result['changed'] |                 changed = result['changed'] | ||||||
|                 password_changed = result['password_changed'] |                 password_changed = result['password_changed'] | ||||||
|  |                 final_attributes = result['attributes'] | ||||||
|                 if changed: |                 if changed: | ||||||
|                     msg = "User added" |                     msg = "User added" | ||||||
| 
 | 
 | ||||||
|  | @ -571,7 +590,7 @@ def main(): | ||||||
|         else: |         else: | ||||||
|             changed = False |             changed = False | ||||||
|             msg = "User doesn't exist" |             msg = "User doesn't exist" | ||||||
|     module.exit_json(changed=changed, user=user, msg=msg, password_changed=password_changed) |     module.exit_json(changed=changed, user=user, msg=msg, password_changed=password_changed, attributes=final_attributes) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| if __name__ == '__main__': | if __name__ == '__main__': | ||||||
|  |  | ||||||
|  | @ -176,7 +176,7 @@ def setvariable(cursor, mysqlvar, value, mode='global'): | ||||||
| def main(): | def main(): | ||||||
|     argument_spec = mysql_common_argument_spec() |     argument_spec = mysql_common_argument_spec() | ||||||
|     argument_spec.update( |     argument_spec.update( | ||||||
|         variable=dict(type='str'), |         variable=dict(type='str', required=True), | ||||||
|         value=dict(type='str'), |         value=dict(type='str'), | ||||||
|         mode=dict(type='str', choices=['global', 'persist', 'persist_only'], default='global'), |         mode=dict(type='str', choices=['global', 'persist', 'persist_only'], default='global'), | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  | @ -269,6 +269,9 @@ | ||||||
|       tags: |       tags: | ||||||
|         - issue_465 |         - issue_465 | ||||||
| 
 | 
 | ||||||
|  |     # Tests for user attributes | ||||||
|  |     - include_tasks: test_user_attributes.yml | ||||||
|  | 
 | ||||||
|     # Tests for the TLS requires dictionary |     # Tests for the TLS requires dictionary | ||||||
|     - include_tasks: test_tls_requirements.yml |     - include_tasks: test_tls_requirements.yml | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -0,0 +1,474 @@ | ||||||
|  | --- | ||||||
|  | - 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: | ||||||
|  | 
 | ||||||
|  |       # ============================================================ | ||||||
|  |       # Fail creating a user with mariadb | ||||||
|  |       # | ||||||
|  | 
 | ||||||
|  |       # Check mode | ||||||
|  |       - 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_module | ||||||
|  |         check_mode: yes | ||||||
|  | 
 | ||||||
|  |       - name: Attributes | Run query to verify user creation with attributes fails with mariadb in check mode | ||||||
|  |         mysql_query: | ||||||
|  |           <<: *mysql_params | ||||||
|  |           query: 'SELECT user FROM mysql.user WHERE user = "{{ user_name_2 }}" AND host = "%"' | ||||||
|  |         ignore_errors: yes | ||||||
|  |         register: result_query | ||||||
|  | 
 | ||||||
|  |       - name: Attributes | Assert that creating user with attributes fails with mariadb in check mode | ||||||
|  |         assert: | ||||||
|  |           that: | ||||||
|  |             - result_module is failed | ||||||
|  |             - not result_query.query_result[0] | ||||||
|  | 
 | ||||||
|  |       # Real mode | ||||||
|  |       - 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_module | ||||||
|  | 
 | ||||||
|  |       - name: Attributes | Run query to verify user creation with attributes fails with mariadb | ||||||
|  |         mysql_query: | ||||||
|  |           <<: *mysql_params | ||||||
|  |           query: 'SELECT user FROM mysql.user WHERE user = "{{ user_name_2 }}" AND host = "%"' | ||||||
|  |         register: result_query | ||||||
|  | 
 | ||||||
|  |       - name: Attributes | Assert that creating user with attributes fails with mariadb | ||||||
|  |         assert: | ||||||
|  |           that: | ||||||
|  |             - result_module is failed | ||||||
|  |             - not result_query.query_result[0] | ||||||
|  | 
 | ||||||
|  |     - 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 | ||||||
|  |       # | ||||||
|  | 
 | ||||||
|  |       # Check mode | ||||||
|  |       - 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_module | ||||||
|  |         check_mode: yes | ||||||
|  | 
 | ||||||
|  |       - name: Attributes | Run query to verify user creation 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 with attributes | ||||||
|  |         assert: | ||||||
|  |           that: | ||||||
|  |             - result_module is changed | ||||||
|  |             - result_module.attributes.key1 == "value1" | ||||||
|  |             - not result_query.query_result[0] | ||||||
|  | 
 | ||||||
|  |       # Real mode | ||||||
|  |       - 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_module | ||||||
|  | 
 | ||||||
|  |       - name: Attributes | Run query to verify created user 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 with attributes | ||||||
|  |         assert: | ||||||
|  |           that: | ||||||
|  |             - result_module is changed | ||||||
|  |             - result_module.attributes.key1 == "value1" | ||||||
|  |             - (result_query.query_result[0][0]['ATTRIBUTE'] | from_yaml)['key1'] == "value1" | ||||||
|  | 
 | ||||||
|  |       # ============================================================ | ||||||
|  |       # Append attributes on an existing user | ||||||
|  |       # | ||||||
|  | 
 | ||||||
|  |       # Check mode | ||||||
|  |       - 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_module | ||||||
|  |         check_mode: yes | ||||||
|  | 
 | ||||||
|  |       - name: Attributes | Run query to check appended attributes 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 appended and existing attribute stays | ||||||
|  |         assert: | ||||||
|  |           that: | ||||||
|  |             - result_module is changed | ||||||
|  |             - result_module.attributes.key1 == "value1" | ||||||
|  |             - result_module.attributes.key2 == "value2" | ||||||
|  |             - "'key2' not in result_query.query_result[0][0]['ATTRIBUTE'] | from_yaml" | ||||||
|  | 
 | ||||||
|  |       # Real mode | ||||||
|  |       - name: Attributes | Test appending attributes to an existing user | ||||||
|  |         mysql_user: | ||||||
|  |           <<: *mysql_params | ||||||
|  |           name: '{{ user_name_2 }}' | ||||||
|  |           host: '%' | ||||||
|  |           attributes: | ||||||
|  |             key2: "value2" | ||||||
|  |         register: result_module | ||||||
|  | 
 | ||||||
|  |       - name: Attributes | Run query to check appended 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 new attribute was appended and existing attribute stays | ||||||
|  |         assert: | ||||||
|  |           that: | ||||||
|  |             - result_module is changed | ||||||
|  |             - result_module.attributes.key1 == "value1" | ||||||
|  |             - result_module.attributes.key2 == "value2" | ||||||
|  |             - (result_query.query_result[0][0]['ATTRIBUTE'] | from_yaml)['key1'] == "value1" | ||||||
|  |             - (result_query.query_result[0][0]['ATTRIBUTE'] | from_yaml)['key2'] == "value2" | ||||||
|  | 
 | ||||||
|  |       # ============================================================ | ||||||
|  |       # Test updating existing attributes | ||||||
|  |       # | ||||||
|  | 
 | ||||||
|  |       # Check mode | ||||||
|  |       - 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_module | ||||||
|  | 
 | ||||||
|  |       - name: Attributes | Run query to verify updated 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 updated | ||||||
|  |         assert: | ||||||
|  |           that: | ||||||
|  |             - result_module is changed | ||||||
|  |             - result_module.attributes.key2 == "new_value2" | ||||||
|  |             - (result_query.query_result[0][0]['ATTRIBUTE'] | from_yaml)['key2'] == "value2" | ||||||
|  | 
 | ||||||
|  |       # Real mode | ||||||
|  |       - name: Attributes | Test updating attributes on an existing user | ||||||
|  |         mysql_user: | ||||||
|  |           <<: *mysql_params | ||||||
|  |           name: '{{ user_name_2 }}' | ||||||
|  |           host: '%' | ||||||
|  |           attributes: | ||||||
|  |             key2: "new_value2" | ||||||
|  |         register: result_module | ||||||
|  | 
 | ||||||
|  |       - name: Attributes | Run query to verify updated 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 updated | ||||||
|  |         assert: | ||||||
|  |           that: | ||||||
|  |             - result_module is changed | ||||||
|  |             - result_module.attributes.key2 == "new_value2" | ||||||
|  |             - (result_query.query_result[0][0]['ATTRIBUTE'] | from_yaml)['key2'] == "new_value2" | ||||||
|  | 
 | ||||||
|  |       # ============================================================ | ||||||
|  |       # Test attribute idempotency when specifying attributes | ||||||
|  |       # | ||||||
|  | 
 | ||||||
|  |       # 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: | ||||||
|  |             key1: "value1" | ||||||
|  |         register: result_module | ||||||
|  |         check_mode: yes | ||||||
|  | 
 | ||||||
|  |       - 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 not have been updated | ||||||
|  |         assert: | ||||||
|  |           that: | ||||||
|  |             - 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 attribute idempotency by trying to change an already correct attribute | ||||||
|  |         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 | ||||||
|  |         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 | ||||||
|  |         assert: | ||||||
|  |           that: | ||||||
|  |             - 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 not specifying attribute parameter | ||||||
|  |       # | ||||||
|  | 
 | ||||||
|  |       # Check mode | ||||||
|  |       - name: Attributes | Test attribute idempotency by not specifying attribute parameter in check mode | ||||||
|  |         mysql_user: | ||||||
|  |           <<: *mysql_params | ||||||
|  |           name: '{{ user_name_2 }}' | ||||||
|  |           host: '%' | ||||||
|  |         register: result_module | ||||||
|  |         check_mode: yes | ||||||
|  | 
 | ||||||
|  |       - 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 is returned in check mode | ||||||
|  |         assert: | ||||||
|  |           that: | ||||||
|  |             - 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 attribute idempotency by not specifying attribute parameter | ||||||
|  |         mysql_user: | ||||||
|  |           <<: *mysql_params | ||||||
|  |           name: '{{ user_name_2 }}' | ||||||
|  |           host: '%' | ||||||
|  |         register: result_module | ||||||
|  | 
 | ||||||
|  |       - 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 is returned | ||||||
|  |         assert: | ||||||
|  |           that: | ||||||
|  |             - result_module is not changed | ||||||
|  |             - result_module.attributes.key1 == "value1" | ||||||
|  |             - (result_query.query_result[0][0]['ATTRIBUTE'] | from_yaml)['key1'] == "value1" | ||||||
|  | 
 | ||||||
|  |       # ============================================================ | ||||||
|  |       # Test deleting attributes | ||||||
|  |       # | ||||||
|  | 
 | ||||||
|  |       # 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 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 would have been deleted | ||||||
|  |         assert: | ||||||
|  |           that: | ||||||
|  |             - 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 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 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 was deleted | ||||||
|  |         assert: | ||||||
|  |           that: | ||||||
|  |             - 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 | ||||||
|  | 
 | ||||||
|  |       # 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 | ||||||
|  | 
 | ||||||
|  |       # ============================================================ | ||||||
|  |       # Cleanup | ||||||
|  |       # | ||||||
|  |       - include_tasks: utils/remove_user.yml | ||||||
|  |         vars: | ||||||
|  |           user_name: "{{ user_name_2 }}" | ||||||
|  | @ -1,8 +1,2 @@ | ||||||
| plugins/modules/mysql_db.py validate-modules:doc-elements-mismatch |  | ||||||
| plugins/modules/mysql_db.py validate-modules:parameter-list-no-elements |  | ||||||
| plugins/modules/mysql_db.py validate-modules:use-run-command-not-popen | plugins/modules/mysql_db.py validate-modules:use-run-command-not-popen | ||||||
| plugins/modules/mysql_info.py validate-modules:doc-elements-mismatch |  | ||||||
| plugins/modules/mysql_info.py validate-modules:parameter-list-no-elements |  | ||||||
| plugins/modules/mysql_query.py validate-modules:parameter-list-no-elements |  | ||||||
| plugins/modules/mysql_user.py validate-modules:undocumented-parameter | plugins/modules/mysql_user.py validate-modules:undocumented-parameter | ||||||
| plugins/modules/mysql_variables.py validate-modules:doc-required-mismatch |  | ||||||
|  |  | ||||||
|  | @ -1,10 +1,4 @@ | ||||||
| plugins/modules/mysql_db.py validate-modules:doc-elements-mismatch |  | ||||||
| plugins/modules/mysql_db.py validate-modules:parameter-list-no-elements |  | ||||||
| plugins/modules/mysql_db.py validate-modules:use-run-command-not-popen | plugins/modules/mysql_db.py validate-modules:use-run-command-not-popen | ||||||
| plugins/modules/mysql_info.py validate-modules:doc-elements-mismatch |  | ||||||
| plugins/modules/mysql_info.py validate-modules:parameter-list-no-elements |  | ||||||
| plugins/modules/mysql_query.py validate-modules:parameter-list-no-elements |  | ||||||
| plugins/modules/mysql_user.py validate-modules:undocumented-parameter | plugins/modules/mysql_user.py validate-modules:undocumented-parameter | ||||||
| plugins/modules/mysql_variables.py validate-modules:doc-required-mismatch |  | ||||||
| plugins/module_utils/mysql.py pylint:unused-import | plugins/module_utils/mysql.py pylint:unused-import | ||||||
| plugins/module_utils/version.py pylint:unused-import | plugins/module_utils/version.py pylint:unused-import | ||||||
|  |  | ||||||
|  | @ -1,10 +1,4 @@ | ||||||
| plugins/modules/mysql_db.py validate-modules:doc-elements-mismatch |  | ||||||
| plugins/modules/mysql_db.py validate-modules:parameter-list-no-elements |  | ||||||
| plugins/modules/mysql_db.py validate-modules:use-run-command-not-popen | plugins/modules/mysql_db.py validate-modules:use-run-command-not-popen | ||||||
| plugins/modules/mysql_info.py validate-modules:doc-elements-mismatch |  | ||||||
| plugins/modules/mysql_info.py validate-modules:parameter-list-no-elements |  | ||||||
| plugins/modules/mysql_query.py validate-modules:parameter-list-no-elements |  | ||||||
| plugins/modules/mysql_user.py validate-modules:undocumented-parameter | plugins/modules/mysql_user.py validate-modules:undocumented-parameter | ||||||
| plugins/modules/mysql_variables.py validate-modules:doc-required-mismatch |  | ||||||
| plugins/module_utils/mysql.py pylint:unused-import | plugins/module_utils/mysql.py pylint:unused-import | ||||||
| plugins/module_utils/version.py pylint:unused-import | plugins/module_utils/version.py pylint:unused-import | ||||||
|  |  | ||||||
|  | @ -1,10 +1,4 @@ | ||||||
| plugins/modules/mysql_db.py validate-modules:doc-elements-mismatch |  | ||||||
| plugins/modules/mysql_db.py validate-modules:parameter-list-no-elements |  | ||||||
| plugins/modules/mysql_db.py validate-modules:use-run-command-not-popen | plugins/modules/mysql_db.py validate-modules:use-run-command-not-popen | ||||||
| plugins/modules/mysql_info.py validate-modules:doc-elements-mismatch |  | ||||||
| plugins/modules/mysql_info.py validate-modules:parameter-list-no-elements |  | ||||||
| plugins/modules/mysql_query.py validate-modules:parameter-list-no-elements |  | ||||||
| plugins/modules/mysql_user.py validate-modules:undocumented-parameter | plugins/modules/mysql_user.py validate-modules:undocumented-parameter | ||||||
| plugins/modules/mysql_variables.py validate-modules:doc-required-mismatch |  | ||||||
| plugins/module_utils/mysql.py pylint:unused-import | plugins/module_utils/mysql.py pylint:unused-import | ||||||
| plugins/module_utils/version.py pylint:unused-import | plugins/module_utils/version.py pylint:unused-import | ||||||
|  |  | ||||||
|  | @ -1,9 +1,10 @@ | ||||||
| from __future__ import (absolute_import, division, print_function) | from __future__ import (absolute_import, division, print_function) | ||||||
|  | 
 | ||||||
| __metaclass__ = type | __metaclass__ = type | ||||||
| 
 | 
 | ||||||
| import pytest | import pytest | ||||||
| 
 | 
 | ||||||
| from ansible_collections.community.mysql.plugins.module_utils.mysql import get_server_version | from ansible_collections.community.mysql.plugins.module_utils.mysql import get_server_version, get_server_implementation | ||||||
| from ..utils import dummy_cursor_class | from ..utils import dummy_cursor_class | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -22,3 +23,21 @@ def test_get_server_version(cursor_return_version, cursor_return_type): | ||||||
|     """ |     """ | ||||||
|     cursor = dummy_cursor_class(cursor_return_version, cursor_return_type) |     cursor = dummy_cursor_class(cursor_return_version, cursor_return_type) | ||||||
|     assert get_server_version(cursor) == cursor_return_version |     assert get_server_version(cursor) == cursor_return_version | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @pytest.mark.parametrize( | ||||||
|  |     'cursor_return_version,cursor_return_type,server_implementation', | ||||||
|  |     [ | ||||||
|  |         ('5.7.0-mysql', 'dict', 'mysql'), | ||||||
|  |         ('8.0.0-mysql', 'list', 'mysql'), | ||||||
|  |         ('10.5.0-mariadb', 'dict', 'mariadb'), | ||||||
|  |         ('10.5.1-mariadb', 'list', 'mariadb'), | ||||||
|  |     ] | ||||||
|  | ) | ||||||
|  | def test_get_server_implamentation(cursor_return_version, cursor_return_type, server_implementation): | ||||||
|  |     """ | ||||||
|  |     Test that server implementation are handled properly by get_server_implementation() whether the server version returned as a list or dict. | ||||||
|  |     """ | ||||||
|  |     cursor = dummy_cursor_class(cursor_return_version, cursor_return_type) | ||||||
|  | 
 | ||||||
|  |     assert get_server_implementation(cursor) == server_implementation | ||||||
|  |  | ||||||
|  | @ -14,15 +14,15 @@ from ansible_collections.community.mysql.plugins.modules.mysql_info import MySQL | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @pytest.mark.parametrize( | @pytest.mark.parametrize( | ||||||
|     'suffix,cursor_output', |     'suffix,cursor_output,server_implementation', | ||||||
|     [ |     [ | ||||||
|         ('mysql', '5.5.1-mysql'), |         ('mysql', '5.5.1-mysql', 'mysql'), | ||||||
|         ('log', '5.7.31-log'), |         ('log', '5.7.31-log', 'mysql'), | ||||||
|         ('mariadb', '10.5.0-mariadb'), |         ('mariadb', '10.5.0-mariadb', 'mariadb'), | ||||||
|         ('', '8.0.22'), |         ('', '8.0.22', 'mysql'), | ||||||
|     ] |     ] | ||||||
| ) | ) | ||||||
| def test_get_info_suffix(suffix, cursor_output): | def test_get_info_suffix(suffix, cursor_output, server_implementation): | ||||||
|     def __cursor_return_value(input_parameter): |     def __cursor_return_value(input_parameter): | ||||||
|         if input_parameter == "SHOW GLOBAL VARIABLES": |         if input_parameter == "SHOW GLOBAL VARIABLES": | ||||||
|             cursor.fetchall.return_value = [{"Variable_name": "version", "Value": cursor_output}] |             cursor.fetchall.return_value = [{"Variable_name": "version", "Value": cursor_output}] | ||||||
|  | @ -32,6 +32,6 @@ def test_get_info_suffix(suffix, cursor_output): | ||||||
|     cursor = MagicMock() |     cursor = MagicMock() | ||||||
|     cursor.execute.side_effect = __cursor_return_value |     cursor.execute.side_effect = __cursor_return_value | ||||||
| 
 | 
 | ||||||
|     info = MySQL_Info(MagicMock(), cursor) |     info = MySQL_Info(MagicMock(), cursor, server_implementation) | ||||||
| 
 | 
 | ||||||
|     assert info.get_info([], [], False)['version']['suffix'] == suffix |     assert info.get_info([], [], False)['version']['suffix'] == suffix | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue