diff --git a/plugins/doc_fragments/mysql.py b/plugins/doc_fragments/mysql.py index 6d7a546..d1c1a2e 100644 --- a/plugins/doc_fragments/mysql.py +++ b/plugins/doc_fragments/mysql.py @@ -70,6 +70,11 @@ options: - This optoin has no effect on MySQLdb. type: bool version_added: '1.1.0' + charset: + description: + - Charset value of the same database driver option. + type: str + version_added: '2.0.0' requirements: - PyMySQL (Python 2.7 and Python 3.X), or - MySQLdb (Python 2.x) diff --git a/plugins/module_utils/mysql.py b/plugins/module_utils/mysql.py index 67e0033..6f0c9ce 100644 --- a/plugins/module_utils/mysql.py +++ b/plugins/module_utils/mysql.py @@ -43,7 +43,7 @@ def parse_from_mysql_config_file(cnf): def mysql_connect(module, login_user=None, login_password=None, config_file='', ssl_cert=None, ssl_key=None, ssl_ca=None, db=None, cursor_class=None, connect_timeout=30, - autocommit=False, config_overrides_defaults=False, check_hostname=None): + autocommit=False, config_overrides_defaults=False, check_hostname=None, charset=None): config = {} if config_file and os.path.exists(config_file): @@ -97,6 +97,8 @@ def mysql_connect(module, login_user=None, login_password=None, config_file='', config['ssl']['check_hostname'] = check_hostname else: module.fail_json(msg='To use check_hostname, pymysql >= 0.7.11 is required on the target host') + if charset is not None: + config['charset'] = charset if _mysql_cursor_param == 'cursor': # In case of PyMySQL driver: @@ -132,6 +134,7 @@ def mysql_common_argument_spec(): client_key=dict(type='path', aliases=['ssl_key']), ca_cert=dict(type='path', aliases=['ssl_ca']), check_hostname=dict(type='bool', default=None), + charset=dict(type='str'), ) diff --git a/plugins/modules/mysql_query.py b/plugins/modules/mysql_query.py index ed3ace4..8e279a3 100644 --- a/plugins/modules/mysql_query.py +++ b/plugins/modules/mysql_query.py @@ -148,6 +148,7 @@ def main(): check_hostname = module.params['check_hostname'] config_file = module.params['config_file'] query = module.params["query"] + charset = module.params["charset"] if not isinstance(query, (str, list)): module.fail_json(msg="the query option value must be a string or list, passed %s" % type(query)) @@ -180,7 +181,8 @@ def main(): config_file, ssl_cert, ssl_key, ssl_ca, db, check_hostname=check_hostname, connect_timeout=connect_timeout, - cursor_class='DictCursor', autocommit=autocommit) + cursor_class='DictCursor', autocommit=autocommit, + charset=charset) except Exception as e: module.fail_json(msg="unable to connect to database, check login_user and " "login_password are correct or %s has the credentials. " diff --git a/plugins/modules/mysql_user.py b/plugins/modules/mysql_user.py index bb0f5a0..86d2b9c 100644 --- a/plugins/modules/mysql_user.py +++ b/plugins/modules/mysql_user.py @@ -1161,6 +1161,8 @@ def main(): plugin_hash_string = module.params["plugin_hash_string"] plugin_auth_string = module.params["plugin_auth_string"] resource_limits = module.params["resource_limits"] + charset = module.params["charset"] + if priv and not isinstance(priv, (str, dict)): module.fail_json(msg="priv parameter must be str or dict but %s was passed" % type(priv)) @@ -1175,7 +1177,7 @@ def main(): if check_implicit_admin: try: cursor, db_conn = mysql_connect(module, "root", "", config_file, ssl_cert, ssl_key, ssl_ca, db, - connect_timeout=connect_timeout, check_hostname=check_hostname) + connect_timeout=connect_timeout, check_hostname=check_hostname, charset=charset) except Exception: pass diff --git a/tests/integration/targets/test_mysql_user/defaults/main.yml b/tests/integration/targets/test_mysql_user/defaults/main.yml index 5cf9074..27620bf 100644 --- a/tests/integration/targets/test_mysql_user/defaults/main.yml +++ b/tests/integration/targets/test_mysql_user/defaults/main.yml @@ -10,11 +10,13 @@ user_name_1: 'db_user1' user_name_2: 'db_user2' user_name_3: 'db_user3' user_name_4: 'db_user4' +user_name_5: 'db_user5' user_password_1: 'gadfFDSdtTU^Sdfuj' user_password_2: 'jkFKUdfhdso78yi&td' user_password_3: 'jkFKUdfhdso78yi&tk' user_password_4: 's2R#7pLV31!ZJrXPa3' +user_password_5: '£gadfFDSdtTU^Sdfuj' root_password: 'zevuR6oPh7' diff --git a/tests/integration/targets/test_mysql_user/tasks/main.yml b/tests/integration/targets/test_mysql_user/tasks/main.yml index a744050..46cb98b 100644 --- a/tests/integration/targets/test_mysql_user/tasks/main.yml +++ b/tests/integration/targets/test_mysql_user/tasks/main.yml @@ -265,6 +265,9 @@ # Tests for the priv parameter with dict value (https://github.com/ansible/ansible/issues/57533) - include: test_priv_dict.yml + # Test charset option + - include: test_charset.yml + # Test that append_privs will not attempt to make a change where current privileges are a superset of new privileges # (https://github.com/ansible-collections/community.mysql/issues/69) - include: test_priv_append.yml enable_check_mode=no diff --git a/tests/integration/targets/test_mysql_user/tasks/test_charset.yml b/tests/integration/targets/test_mysql_user/tasks/test_charset.yml new file mode 100644 index 0000000..f435f07 --- /dev/null +++ b/tests/integration/targets/test_mysql_user/tasks/test_charset.yml @@ -0,0 +1,97 @@ +- vars: + mysql_parameters: &mysql_params + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + + block: + + #- name: Change database charset + # mysql_query: + # <<: *mysql_params + # query: ALTER DATABASE mysql CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci + + #- name: Change user table charset + # mysql_query: + # <<: *mysql_params + # query: ALTER TABLE mysql.user CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci + + - name: Show charset settings + mysql_query: + <<: *mysql_params + query: "SHOW SESSION VARIABLES LIKE 'character_set_%'" + register: result + - assert: + that: + - result is succeeded + + - name: Show collation settings + mysql_query: + <<: *mysql_params + query: "SHOW SESSION VARIABLES LIKE 'collation_connection%'" + register: result + - assert: + that: + - result is succeeded + + - name: Show charset settings + mysql_query: + <<: *mysql_params + query: "SELECT @@character_set_client" + register: result + - assert: + that: + - result is succeeded + + - name: Create user with default charset + mysql_user: + <<: *mysql_params + name: '{{ user_name_5 }}' + password: '{{ user_password_5 }}' + state: present + + - name: Try to get connect and get info, must fail + mysql_query: + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + login_user: '{{ user_name_5 }}' + login_password: '{{ user_password_5 }}' + query: 'SHOW DATABASES' + register: result + ignore_errors: yes + + - name: Assert that the previous task failed + assert: + that: + - result is failed + + - name: Drop user + mysql_user: + <<: *mysql_params + name: '{{ user_name_5 }}' + password: '{{ user_password_5 }}' + state: absent + + - name: Create user with utf8mb4 charset + mysql_user: + <<: *mysql_params + name: '{{ user_name_5 }}' + password: '{{ user_password_5 }}' + state: present + charset: utf8mb4 + + - name: Try to get connect and get info, must succeed + mysql_query: + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + login_user: '{{ user_name_5 }}' + login_password: '{{ user_password_5 }}' + query: 'SHOW DATABASES' + charset: utf8mb4 + register: result + + - name: Assert that the previous task failed + assert: + that: + - result is succeeded