From 8c79011dbd999eb787b5c2ea0f01c5b413ec075f Mon Sep 17 00:00:00 2001 From: "Jorge Rodriguez (A.K.A. Tiriel)" Date: Mon, 12 Oct 2020 21:19:43 +0300 Subject: [PATCH] Disable hostname check (#38) * Add changelog fragment * Add check_hostname option * Propagate check_hostname option across the collection * Update documentation fragment * Propagate test to all other plugins * Remove stray line * Give test user privileges to run test operations * Extend integration tests job matrix * Add caution note to documentation fragment. * Update matrix job name * Rearrange job matrix * Fix sanity issues * Fix issue with mysqldb silently failing to update out of range variables * Fix variable overwrite * Ignore `check_hostname` when using MySQLdb * Update plugins/doc_fragments/mysql.py Co-authored-by: Andrew Klychkov * Update plugins/doc_fragments/mysql.py Co-authored-by: Andrew Klychkov * Update changelogs/fragments/35-disable-hostname-check.yml Co-authored-by: Andrew Klychkov Co-authored-by: Andrew Klychkov --- .github/workflows/ansible-test-plugins.yml | 13 ++- .../fragments/35-disable-hostname-check.yml | 2 + plugins/doc_fragments/mysql.py | 8 ++ plugins/module_utils/mysql.py | 17 +++- plugins/modules/mysql_db.py | 61 ++++++------- plugins/modules/mysql_info.py | 2 + plugins/modules/mysql_query.py | 9 +- plugins/modules/mysql_replication.py | 69 +++++++-------- plugins/modules/mysql_user.py | 55 ++++++------ plugins/modules/mysql_variables.py | 28 +++--- .../targets/setup_mysql/tasks/install.yml | 7 ++ .../targets/setup_mysql/vars/main.yml | 8 +- .../targets/test_mysql_db/defaults/main.yml | 3 + .../tasks/config_overrides_defaults.yml | 2 +- .../targets/test_mysql_db/tasks/issue-28.yml | 81 +++++++++++++++++ .../targets/test_mysql_db/tasks/main.yml | 2 + .../targets/test_mysql_info/defaults/main.yml | 3 + .../test_mysql_info/tasks/issue-28.yml | 78 +++++++++++++++++ .../targets/test_mysql_info/tasks/main.yml | 2 + .../test_mysql_query/defaults/main.yml | 3 + .../test_mysql_query/tasks/issue-28.yml | 78 +++++++++++++++++ .../targets/test_mysql_query/tasks/main.yml | 2 + .../test_mysql_replication/defaults/main.yml | 3 + .../test_mysql_replication/tasks/issue-28.yml | 79 +++++++++++++++++ .../test_mysql_replication/tasks/main.yml | 2 + .../tasks/mysql_replication_initial.yml | 5 ++ .../test_mysql_user/tasks/issue-28.yml | 86 +++++++++++++++++++ .../targets/test_mysql_user/tasks/main.yml | 2 + .../test_mysql_variables/defaults/main.yml | 3 + .../test_mysql_variables/tasks/issue-28.yml | 79 +++++++++++++++++ .../test_mysql_variables/tasks/main.yml | 2 + .../tasks/mysql_variables.yml | 21 +++-- 32 files changed, 680 insertions(+), 135 deletions(-) create mode 100644 changelogs/fragments/35-disable-hostname-check.yml create mode 100644 tests/integration/targets/test_mysql_db/tasks/issue-28.yml create mode 100644 tests/integration/targets/test_mysql_info/tasks/issue-28.yml create mode 100644 tests/integration/targets/test_mysql_query/tasks/issue-28.yml create mode 100644 tests/integration/targets/test_mysql_replication/tasks/issue-28.yml create mode 100644 tests/integration/targets/test_mysql_user/tasks/issue-28.yml create mode 100644 tests/integration/targets/test_mysql_variables/tasks/issue-28.yml diff --git a/.github/workflows/ansible-test-plugins.yml b/.github/workflows/ansible-test-plugins.yml index 6faf9b6..3c3ea5e 100644 --- a/.github/workflows/ansible-test-plugins.yml +++ b/.github/workflows/ansible-test-plugins.yml @@ -16,6 +16,7 @@ on: env: mysql_version_file: "./ansible_collections/community/mysql/tests/integration/targets/setup_mysql/defaults/main.yml" + connector_version_file: "./ansible_collections/community/mysql/tests/integration/targets/setup_mysql/vars/main.yml" jobs: sanity: @@ -47,7 +48,7 @@ jobs: working-directory: ./ansible_collections/community/mysql integration: - name: "Integration (Python: ${{ matrix.python }}, Ansible: ${{ matrix.ansible }}, MySQL: ${{ matrix.mysql }})" + name: "Integration (Python: ${{ matrix.python }}, Ansible: ${{ matrix.ansible }}, MySQL: ${{ matrix.mysql }}, Connector: ${{ matrix.connector }})" runs-on: ubuntu-latest strategy: fail-fast: false @@ -61,6 +62,13 @@ jobs: - devel python: - 3.6 + connector: + - pymysql==0.7.10 + - pymysql==0.9.3 + - mysqlclient==2.0.1 + exclude: + - mysql: 8.0.21 + connector: pymysql==0.7.10 steps: - name: Check out code @@ -79,6 +87,9 @@ jobs: - name: Set MySQL version (${{ matrix.mysql }}) run: "sed -i 's/^mysql_version:.*/mysql_version: \"${{ matrix.mysql }}\"/g' ${{ env.mysql_version_file }}" + - name: Set Connector version (${{ matrix.connector }}) + run: "sed -i 's/^python_packages:.*/python_packages: [${{ matrix.connector }}]/' ${{ env.connector_version_file }}" + - name: Run integration tests run: ansible-test integration --docker -v --color --retry-on-error --continue-on-error --python ${{ matrix.python }} --diff --coverage working-directory: ./ansible_collections/community/mysql diff --git a/changelogs/fragments/35-disable-hostname-check.yml b/changelogs/fragments/35-disable-hostname-check.yml new file mode 100644 index 0000000..8992621 --- /dev/null +++ b/changelogs/fragments/35-disable-hostname-check.yml @@ -0,0 +1,2 @@ +minor_changes: + - mysql modules - add the ``check_hostname`` option (https://github.com/ansible-collections/community.mysql/issues/28). diff --git a/plugins/doc_fragments/mysql.py b/plugins/doc_fragments/mysql.py index 074eee7..9f6b465 100644 --- a/plugins/doc_fragments/mysql.py +++ b/plugins/doc_fragments/mysql.py @@ -62,6 +62,14 @@ options: - The path to the client private key. type: path aliases: [ ssl_key ] + check_hostname: + description: + - Whether to validate the server host name when an SSL connection is required. + - Setting this to C(false) disables hostname verification. Use with caution. + - Requires pymysql >= 0.7.11. + - This optoin has no effect on MySQLdb. + type: bool + version_added: '1.1.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 d4f94ab..02cdd68 100644 --- a/plugins/module_utils/mysql.py +++ b/plugins/module_utils/mysql.py @@ -10,6 +10,7 @@ # Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause) from __future__ import (absolute_import, division, print_function) +from functools import reduce __metaclass__ = type import os @@ -37,8 +38,8 @@ 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): + ssl_key=None, ssl_ca=None, db=None, cursor_class=None, connect_timeout=30, + autocommit=False, config_overrides_defaults=False, check_hostname=None): config = {} if config_file and os.path.exists(config_file): @@ -51,10 +52,10 @@ def mysql_connect(module, login_user=None, login_password=None, config_file='', module.params['login_port'] = cp.getint('client', 'port', fallback=module.params['login_port']) except Exception as e: if "got an unexpected keyword argument 'fallback'" in e.message: - module.fail_json('To use config_overrides_defaults, ' + module.fail_json(msg='To use config_overrides_defaults, ' 'it needs Python 3.5+ as the default interpreter on a target host') - if ssl_ca is not None or ssl_key is not None or ssl_cert is not None: + if ssl_ca is not None or ssl_key is not None or ssl_cert is not None or check_hostname is not None: config['ssl'] = {} if module.params['login_unix_socket']: @@ -79,6 +80,13 @@ def mysql_connect(module, login_user=None, login_password=None, config_file='', config['db'] = db if connect_timeout is not None: config['connect_timeout'] = connect_timeout + if check_hostname is not None: + if mysql_driver.__name__ == "pymysql": + version_tuple = (n for n in mysql_driver.__version__.split('.') if n != 'None') + if reduce(lambda x, y: int(x) * 100 + int(y), version_tuple) >= 711: + 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 _mysql_cursor_param == 'cursor': # In case of PyMySQL driver: @@ -113,4 +121,5 @@ def mysql_common_argument_spec(): client_cert=dict(type='path', aliases=['ssl_cert']), client_key=dict(type='path', aliases=['ssl_key']), ca_cert=dict(type='path', aliases=['ssl_ca']), + check_hostname=dict(type='bool', default=None), ) diff --git a/plugins/modules/mysql_db.py b/plugins/modules/mysql_db.py index 330a81f..814af4e 100644 --- a/plugins/modules/mysql_db.py +++ b/plugins/modules/mysql_db.py @@ -318,7 +318,7 @@ import traceback from ansible.module_utils.basic import AnsibleModule from ansible_collections.community.mysql.plugins.module_utils.database import mysql_quote_identifier -from ansible_collections.community.mysql.plugins.module_utils.mysql import mysql_connect, mysql_driver, mysql_driver_fail_msg +from ansible_collections.community.mysql.plugins.module_utils.mysql import mysql_connect, mysql_driver, mysql_driver_fail_msg, mysql_common_argument_spec from ansible.module_utils.six.moves import shlex_quote from ansible.module_utils._text import to_native @@ -543,37 +543,30 @@ def db_create(cursor, db, encoding, collation): def main(): + argument_spec = mysql_common_argument_spec() + argument_spec.update( + name=dict(type='list', required=True, aliases=['db']), + encoding=dict(type='str', default=''), + collation=dict(type='str', default=''), + target=dict(type='path'), + state=dict(type='str', default='present', choices=['absent', 'dump', 'import', 'present']), + single_transaction=dict(type='bool', default=False), + quick=dict(type='bool', default=True), + ignore_tables=dict(type='list', default=[]), + hex_blob=dict(default=False, type='bool'), + force=dict(type='bool', default=False), + master_data=dict(type='int', default=0, choices=[0, 1, 2]), + skip_lock_tables=dict(type='bool', default=False), + dump_extra_args=dict(type='str'), + use_shell=dict(type='bool', default=False), + unsafe_login_password=dict(type='bool', default=False), + restrict_config_file=dict(type='bool', default=False), + check_implicit_admin=dict(type='bool', default=False), + config_overrides_defaults=dict(type='bool', default=False), + ) + module = AnsibleModule( - argument_spec=dict( - login_user=dict(type='str'), - login_password=dict(type='str', no_log=True), - login_host=dict(type='str', default='localhost'), - login_port=dict(type='int', default=3306), - login_unix_socket=dict(type='str'), - name=dict(type='list', required=True, aliases=['db']), - encoding=dict(type='str', default=''), - collation=dict(type='str', default=''), - target=dict(type='path'), - state=dict(type='str', default='present', choices=['absent', 'dump', 'import', 'present']), - client_cert=dict(type='path', aliases=['ssl_cert']), - client_key=dict(type='path', aliases=['ssl_key']), - ca_cert=dict(type='path', aliases=['ssl_ca']), - connect_timeout=dict(type='int', default=30), - config_file=dict(type='path', default='~/.my.cnf'), - single_transaction=dict(type='bool', default=False), - quick=dict(type='bool', default=True), - ignore_tables=dict(type='list', default=[]), - hex_blob=dict(default=False, type='bool'), - force=dict(type='bool', default=False), - master_data=dict(type='int', default=0, choices=[0, 1, 2]), - skip_lock_tables=dict(type='bool', default=False), - dump_extra_args=dict(type='str'), - use_shell=dict(type='bool', default=False), - unsafe_login_password=dict(type='bool', default=False, no_log=True), - restrict_config_file=dict(type='bool', default=False), - check_implicit_admin=dict(type='bool', default=False), - config_overrides_defaults=dict(type='bool', default=False), - ), + argument_spec=argument_spec, supports_check_mode=True, ) @@ -596,6 +589,7 @@ def main(): ssl_cert = module.params["client_cert"] ssl_key = module.params["client_key"] ssl_ca = module.params["ca_cert"] + check_hostname = module.params["check_hostname"] connect_timeout = module.params['connect_timeout'] config_file = module.params['config_file'] login_password = module.params["login_password"] @@ -636,7 +630,7 @@ def main(): if check_implicit_admin: try: cursor, db_conn = mysql_connect(module, 'root', '', config_file, ssl_cert, ssl_key, ssl_ca, - connect_timeout=connect_timeout, + connect_timeout=connect_timeout, check_hostname=check_hostname, config_overrides_defaults=config_overrides_defaults) except Exception as e: check_implicit_admin = False @@ -644,7 +638,8 @@ def main(): if not cursor: cursor, db_conn = mysql_connect(module, login_user, login_password, config_file, ssl_cert, ssl_key, ssl_ca, - connect_timeout=connect_timeout, config_overrides_defaults=config_overrides_defaults) + connect_timeout=connect_timeout, config_overrides_defaults=config_overrides_defaults, + check_hostname=check_hostname) except Exception as e: if os.path.exists(config_file): 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_info.py b/plugins/modules/mysql_info.py index 2259078..6dc0853 100644 --- a/plugins/modules/mysql_info.py +++ b/plugins/modules/mysql_info.py @@ -509,6 +509,7 @@ def main(): ssl_cert = module.params['client_cert'] ssl_key = module.params['client_key'] ssl_ca = module.params['ca_cert'] + check_hostname = module.params['check_hostname'] config_file = module.params['config_file'] filter_ = module.params['filter'] exclude_fields = module.params['exclude_fields'] @@ -526,6 +527,7 @@ def main(): try: cursor, db_conn = mysql_connect(module, login_user, login_password, config_file, ssl_cert, ssl_key, ssl_ca, db, + check_hostname=check_hostname, connect_timeout=connect_timeout, cursor_class='DictCursor') 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_query.py b/plugins/modules/mysql_query.py index 5dad74e..0101c20 100644 --- a/plugins/modules/mysql_query.py +++ b/plugins/modules/mysql_query.py @@ -144,6 +144,7 @@ def main(): ssl_cert = module.params['client_cert'] ssl_key = module.params['client_key'] ssl_ca = module.params['ca_cert'] + check_hostname = module.params['check_hostname'] config_file = module.params['config_file'] query = module.params["query"] if module.params["single_transaction"]: @@ -165,12 +166,14 @@ def main(): try: cursor, db_connection = mysql_connect(module, login_user, login_password, config_file, ssl_cert, ssl_key, ssl_ca, db, + check_hostname=check_hostname, connect_timeout=connect_timeout, cursor_class='DictCursor', autocommit=autocommit) 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. " "Exception message: %s" % (config_file, to_native(e))) + # Set defaults: changed = False @@ -210,7 +213,11 @@ def main(): if keyword in q: changed = True - executed_queries.append(cursor._last_executed) + try: + executed_queries.append(cursor._last_executed) + except AttributeError: + # MySQLdb removed cursor._last_executed as a duplicate of cursor._executed + executed_queries.append(cursor._executed) rowcount.append(cursor.rowcount) # When the module run with the single_transaction == True: diff --git a/plugins/modules/mysql_replication.py b/plugins/modules/mysql_replication.py index beaa2fc..9fb2a35 100644 --- a/plugins/modules/mysql_replication.py +++ b/plugins/modules/mysql_replication.py @@ -238,7 +238,7 @@ import os import warnings from ansible.module_utils.basic import AnsibleModule -from ansible_collections.community.mysql.plugins.module_utils.mysql import mysql_connect, mysql_driver, mysql_driver_fail_msg +from ansible_collections.community.mysql.plugins.module_utils.mysql import mysql_connect, mysql_driver, mysql_driver_fail_msg, mysql_common_argument_spec from ansible.module_utils._text import to_native executed_queries = [] @@ -381,43 +381,35 @@ def changemaster(cursor, chm, connection_name='', channel=''): def main(): + argument_spec = mysql_common_argument_spec() + argument_spec.update( + mode=dict(type='str', default='getslave', choices=[ + 'getmaster', 'getslave', 'changemaster', 'stopslave', + 'startslave', 'resetmaster', 'resetslave', 'resetslaveall']), + master_auto_position=dict(type='bool', default=False), + master_host=dict(type='str'), + master_user=dict(type='str'), + master_password=dict(type='str', no_log=True), + master_port=dict(type='int'), + master_connect_retry=dict(type='int'), + master_log_file=dict(type='str'), + master_log_pos=dict(type='int'), + relay_log_file=dict(type='str'), + relay_log_pos=dict(type='int'), + master_ssl=dict(type='bool', default=False), + master_ssl_ca=dict(type='str'), + master_ssl_capath=dict(type='str'), + master_ssl_cert=dict(type='str'), + master_ssl_key=dict(type='str'), + master_ssl_cipher=dict(type='str'), + master_use_gtid=dict(type='str', choices=['current_pos', 'slave_pos', 'disabled']), + master_delay=dict(type='int'), + connection_name=dict(type='str'), + channel=dict(type='str'), + fail_on_error=dict(type='bool', default=False), + ) module = AnsibleModule( - argument_spec=dict( - login_user=dict(type='str'), - login_password=dict(type='str', no_log=True), - login_host=dict(type='str', default='localhost'), - login_port=dict(type='int', default=3306), - login_unix_socket=dict(type='str'), - mode=dict(type='str', default='getslave', choices=[ - 'getmaster', 'getslave', 'changemaster', 'stopslave', - 'startslave', 'resetmaster', 'resetslave', 'resetslaveall']), - master_auto_position=dict(type='bool', default=False), - master_host=dict(type='str'), - master_user=dict(type='str'), - master_password=dict(type='str', no_log=True), - master_port=dict(type='int'), - master_connect_retry=dict(type='int'), - master_log_file=dict(type='str'), - master_log_pos=dict(type='int'), - relay_log_file=dict(type='str'), - relay_log_pos=dict(type='int'), - master_ssl=dict(type='bool', default=False), - master_ssl_ca=dict(type='str'), - master_ssl_capath=dict(type='str'), - master_ssl_cert=dict(type='str'), - master_ssl_key=dict(type='str'), - master_ssl_cipher=dict(type='str'), - connect_timeout=dict(type='int', default=30), - config_file=dict(type='path', default='~/.my.cnf'), - client_cert=dict(type='path', aliases=['ssl_cert']), - client_key=dict(type='path', aliases=['ssl_key']), - ca_cert=dict(type='path', aliases=['ssl_ca']), - master_use_gtid=dict(type='str', choices=['current_pos', 'slave_pos', 'disabled']), - master_delay=dict(type='int'), - connection_name=dict(type='str'), - channel=dict(type='str'), - fail_on_error=dict(type='bool', default=False), - ), + argument_spec=argument_spec, mutually_exclusive=[ ['connection_name', 'channel'] ], @@ -442,6 +434,7 @@ def main(): ssl_cert = module.params["client_cert"] ssl_key = module.params["client_key"] ssl_ca = module.params["ca_cert"] + check_hostname = module.params["check_hostname"] connect_timeout = module.params['connect_timeout'] config_file = module.params['config_file'] master_delay = module.params['master_delay'] @@ -464,7 +457,7 @@ def main(): try: cursor, db_conn = mysql_connect(module, login_user, login_password, config_file, ssl_cert, ssl_key, ssl_ca, None, cursor_class='DictCursor', - connect_timeout=connect_timeout) + connect_timeout=connect_timeout, check_hostname=check_hostname) except Exception as e: if os.path.exists(config_file): 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 2cdc6a4..c5b5b09 100644 --- a/plugins/modules/mysql_user.py +++ b/plugins/modules/mysql_user.py @@ -297,7 +297,7 @@ import string from ansible.module_utils.basic import AnsibleModule from ansible_collections.community.mysql.plugins.module_utils.database import SQLParseError -from ansible_collections.community.mysql.plugins.module_utils.mysql import mysql_connect, mysql_driver, mysql_driver_fail_msg +from ansible_collections.community.mysql.plugins.module_utils.mysql import mysql_connect, mysql_driver, mysql_driver_fail_msg, mysql_common_argument_spec from ansible.module_utils.six import iteritems from ansible.module_utils._text import to_native @@ -996,35 +996,27 @@ def limit_resources(module, cursor, user, host, resource_limits, check_mode): def main(): + argument_spec = mysql_common_argument_spec() + argument_spec.update( + user=dict(type='str', required=True, aliases=['name']), + password=dict(type='str', no_log=True), + encrypted=dict(type='bool', default=False), + host=dict(type='str', default='localhost'), + host_all=dict(type="bool", default=False), + state=dict(type='str', default='present', choices=['absent', 'present']), + priv=dict(type='raw'), + tls_requires=dict(type='dict'), + append_privs=dict(type='bool', default=False), + check_implicit_admin=dict(type='bool', default=False), + update_password=dict(type='str', default='always', choices=['always', 'on_create'], no_log=False), + sql_log_bin=dict(type='bool', default=True), + plugin=dict(default=None, type='str'), + plugin_hash_string=dict(default=None, type='str'), + plugin_auth_string=dict(default=None, type='str'), + resource_limits=dict(type='dict'), + ) module = AnsibleModule( - argument_spec=dict( - login_user=dict(type='str'), - login_password=dict(type='str', no_log=True), - login_host=dict(type='str', default='localhost'), - login_port=dict(type='int', default=3306), - login_unix_socket=dict(type='str'), - user=dict(type='str', required=True, aliases=['name']), - password=dict(type='str', no_log=True), - encrypted=dict(type='bool', default=False), - host=dict(type='str', default='localhost'), - host_all=dict(type="bool", default=False), - state=dict(type='str', default='present', choices=['absent', 'present']), - priv=dict(type='raw'), - tls_requires=dict(type='dict'), - append_privs=dict(type='bool', default=False), - check_implicit_admin=dict(type='bool', default=False), - update_password=dict(type='str', default='always', choices=['always', 'on_create'], no_log=False), - connect_timeout=dict(type='int', default=30), - config_file=dict(type='path', default='~/.my.cnf'), - sql_log_bin=dict(type='bool', default=True), - client_cert=dict(type='path', aliases=['ssl_cert']), - client_key=dict(type='path', aliases=['ssl_key']), - ca_cert=dict(type='path', aliases=['ssl_ca']), - plugin=dict(default=None, type='str'), - plugin_hash_string=dict(default=None, type='str'), - plugin_auth_string=dict(default=None, type='str'), - resource_limits=dict(type='dict'), - ), + argument_spec=argument_spec, supports_check_mode=True, ) login_user = module.params["login_user"] @@ -1045,6 +1037,7 @@ def main(): ssl_cert = module.params["client_cert"] ssl_key = module.params["client_key"] ssl_ca = module.params["ca_cert"] + check_hostname = module.params["check_hostname"] db = '' sql_log_bin = module.params["sql_log_bin"] plugin = module.params["plugin"] @@ -1065,13 +1058,13 @@ 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) + connect_timeout=connect_timeout, check_hostname=check_hostname) except Exception: pass if not cursor: cursor, db_conn = mysql_connect(module, login_user, login_password, config_file, ssl_cert, ssl_key, ssl_ca, db, - connect_timeout=connect_timeout) + connect_timeout=connect_timeout, check_hostname=check_hostname) 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. " "Exception message: %s" % (config_file, to_native(e))) diff --git a/plugins/modules/mysql_variables.py b/plugins/modules/mysql_variables.py index cf30e80..1b3418f 100644 --- a/plugins/modules/mysql_variables.py +++ b/plugins/modules/mysql_variables.py @@ -82,7 +82,7 @@ from re import match from ansible.module_utils.basic import AnsibleModule from ansible_collections.community.mysql.plugins.module_utils.database import SQLParseError, mysql_quote_identifier -from ansible_collections.community.mysql.plugins.module_utils.mysql import mysql_connect, mysql_driver, mysql_driver_fail_msg +from ansible_collections.community.mysql.plugins.module_utils.mysql import mysql_connect, mysql_driver, mysql_driver_fail_msg, mysql_common_argument_spec from ansible.module_utils._text import to_native executed_queries = [] @@ -168,22 +168,15 @@ def setvariable(cursor, mysqlvar, value, mode='global'): def main(): + argument_spec = mysql_common_argument_spec() + argument_spec.update( + variable=dict(type='str'), + value=dict(type='str'), + mode=dict(type='str', choices=['global', 'persist', 'persist_only'], default='global'), + ) + module = AnsibleModule( - argument_spec=dict( - login_user=dict(type='str'), - login_password=dict(type='str', no_log=True), - login_host=dict(type='str', default='localhost'), - login_port=dict(type='int', default=3306), - login_unix_socket=dict(type='str'), - variable=dict(type='str'), - value=dict(type='str'), - client_cert=dict(type='path', aliases=['ssl_cert']), - client_key=dict(type='path', aliases=['ssl_key']), - ca_cert=dict(type='path', aliases=['ssl_ca']), - connect_timeout=dict(type='int', default=30), - config_file=dict(type='path', default='~/.my.cnf'), - mode=dict(type='str', choices=['global', 'persist', 'persist_only'], default='global'), - ), + argument_spec=argument_spec ) user = module.params["login_user"] password = module.params["login_password"] @@ -191,6 +184,7 @@ def main(): ssl_cert = module.params["client_cert"] ssl_key = module.params["client_key"] ssl_ca = module.params["ca_cert"] + check_hostname = module.params["check_hostname"] config_file = module.params['config_file'] db = 'mysql' @@ -209,7 +203,7 @@ def main(): try: cursor, db_conn = mysql_connect(module, user, password, config_file, ssl_cert, ssl_key, ssl_ca, db, - connect_timeout=connect_timeout) + connect_timeout=connect_timeout, check_hostname=check_hostname) except Exception as e: if os.path.exists(config_file): module.fail_json(msg=("unable to connect to database, check login_user and " diff --git a/tests/integration/targets/setup_mysql/tasks/install.yml b/tests/integration/targets/setup_mysql/tasks/install.yml index f84086d..aacdddc 100644 --- a/tests/integration/targets/setup_mysql/tasks/install.yml +++ b/tests/integration/targets/setup_mysql/tasks/install.yml @@ -18,6 +18,13 @@ environment: DEBIAN_FRONTEND: noninteractive +- name: "{{ role_name }} | install | install packages required by mysql connector" + apt: + name: "{{ install_python_prereqs }}" + state: present + environment: + DEBIAN_FRONTEND: noninteractive + - name: "{{ role_name }} | install | install python packages" pip: name: "{{ python_packages }}" diff --git a/tests/integration/targets/setup_mysql/vars/main.yml b/tests/integration/targets/setup_mysql/vars/main.yml index edc6ff4..e267307 100644 --- a/tests/integration/targets/setup_mysql/vars/main.yml +++ b/tests/integration/targets/setup_mysql/vars/main.yml @@ -14,13 +14,17 @@ percona_mysql_repos: percona_mysql_packages: - percona-server-client-{{ percona_client_version }} -python_packages: - - pymysql==0.9.3 # temporary fix pinning to 0.9.3 to ensure warnings are handled (https://github.com/ansible-collections/community.mysql/pull/9#issuecomment-663040948) +python_packages: [pymysql == 0.9.3] install_prereqs: - libaio1 - libnuma1 +install_python_prereqs: + - python3-dev + - default-libmysqlclient-dev + - build-essential + mysql_tarball: "mysql-{{ mysql_version }}-linux-glibc2.12-x86_64.tar.{{ mysql_compression_extension }}" mysql_src: "https://dev.mysql.com/get/Downloads/MySQL-{{ mysql_major_version }}/{{ mysql_tarball }}" mariadb_tarball: "mariadb-{{ mariadb_version }}-linux-x86_64.tar.gz" diff --git a/tests/integration/targets/test_mysql_db/defaults/main.yml b/tests/integration/targets/test_mysql_db/defaults/main.yml index 2223ad7..b6ae780 100644 --- a/tests/integration/targets/test_mysql_db/defaults/main.yml +++ b/tests/integration/targets/test_mysql_db/defaults/main.yml @@ -12,3 +12,6 @@ db_user2: 'datauser2' tmp_dir: '/tmp' db_latin1_name: 'db_latin1' file4: 'latin1_file' + +user_name_1: 'db_user1' +user_password_1: 'gadfFDSdtTU^Sdfuj' diff --git a/tests/integration/targets/test_mysql_db/tasks/config_overrides_defaults.yml b/tests/integration/targets/test_mysql_db/tasks/config_overrides_defaults.yml index 422fba3..ef4c062 100644 --- a/tests/integration/targets/test_mysql_db/tasks/config_overrides_defaults.yml +++ b/tests/integration/targets/test_mysql_db/tasks/config_overrides_defaults.yml @@ -71,7 +71,7 @@ assert: that: - result is failed - - result.msg is search("Can't connect to MySQL server on '{{ fake_host }}'") + - result.msg is search("Can't connect to MySQL server on '{{ fake_host }}'") or result.msg is search("Unknown MySQL server host '{{ fake_host }}'") # Clean up - name: Remove test db diff --git a/tests/integration/targets/test_mysql_db/tasks/issue-28.yml b/tests/integration/targets/test_mysql_db/tasks/issue-28.yml new file mode 100644 index 0000000..871e92d --- /dev/null +++ b/tests/integration/targets/test_mysql_db/tasks/issue-28.yml @@ -0,0 +1,81 @@ +--- +- 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: + + # ============================================================ + - shell: pip show pymysql | awk '/Version/ {print $2}' + register: pymysql_version + + - name: get server certificate + copy: + content: "{{ lookup('pipe', \"openssl s_client -starttls mysql -connect localhost:3307 -showcerts 2>/dev/null = 0.7.11 is required' in result.msg + + - name: Drop mysql user + mysql_user: + <<: *mysql_params + name: '{{ user_name_1 }}' + host: 127.0.0.1 + state: absent diff --git a/tests/integration/targets/test_mysql_db/tasks/main.yml b/tests/integration/targets/test_mysql_db/tasks/main.yml index e22ffc1..139d5bb 100644 --- a/tests/integration/targets/test_mysql_db/tasks/main.yml +++ b/tests/integration/targets/test_mysql_db/tasks/main.yml @@ -322,3 +322,5 @@ - include: config_overrides_defaults.yml when: ansible_python.version_info[0] >= 3 + +- include: issue-28.yml diff --git a/tests/integration/targets/test_mysql_info/defaults/main.yml b/tests/integration/targets/test_mysql_info/defaults/main.yml index fadeb9a..e1b932c 100644 --- a/tests/integration/targets/test_mysql_info/defaults/main.yml +++ b/tests/integration/targets/test_mysql_info/defaults/main.yml @@ -6,3 +6,6 @@ mysql_host: 127.0.0.1 mysql_primary_port: 3307 db_name: data + +user_name_1: 'db_user1' +user_password_1: 'gadfFDSdtTU^Sdfuj' diff --git a/tests/integration/targets/test_mysql_info/tasks/issue-28.yml b/tests/integration/targets/test_mysql_info/tasks/issue-28.yml new file mode 100644 index 0000000..955683d --- /dev/null +++ b/tests/integration/targets/test_mysql_info/tasks/issue-28.yml @@ -0,0 +1,78 @@ +--- +- 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: + + # ============================================================ + - shell: pip show pymysql | awk '/Version/ {print $2}' + register: pymysql_version + + - name: get server certificate + copy: + content: "{{ lookup('pipe', \"openssl s_client -starttls mysql -connect localhost:3307 -showcerts 2>/dev/null = 0.7.11 is required' in result.msg + + - name: Drop mysql user + mysql_user: + <<: *mysql_params + name: '{{ user_name_1 }}' + host: 127.0.0.1 + state: absent diff --git a/tests/integration/targets/test_mysql_info/tasks/main.yml b/tests/integration/targets/test_mysql_info/tasks/main.yml index 935b5f3..13ddbcb 100644 --- a/tests/integration/targets/test_mysql_info/tasks/main.yml +++ b/tests/integration/targets/test_mysql_info/tasks/main.yml @@ -189,3 +189,5 @@ <<: *mysql_params name: acme state: absent + + - include: issue-28.yml diff --git a/tests/integration/targets/test_mysql_query/defaults/main.yml b/tests/integration/targets/test_mysql_query/defaults/main.yml index f8b6768..5b92aae 100644 --- a/tests/integration/targets/test_mysql_query/defaults/main.yml +++ b/tests/integration/targets/test_mysql_query/defaults/main.yml @@ -7,3 +7,6 @@ test_db: testdb test_table1: test1 test_table2: test2 test_script_path: /tmp/test.sql + +user_name_1: 'db_user1' +user_password_1: 'gadfFDSdtTU^Sdfuj' diff --git a/tests/integration/targets/test_mysql_query/tasks/issue-28.yml b/tests/integration/targets/test_mysql_query/tasks/issue-28.yml new file mode 100644 index 0000000..e2b51a6 --- /dev/null +++ b/tests/integration/targets/test_mysql_query/tasks/issue-28.yml @@ -0,0 +1,78 @@ +--- +- 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: + + # ============================================================ + - shell: pip show pymysql | awk '/Version/ {print $2}' + register: pymysql_version + + - name: get server certificate + copy: + content: "{{ lookup('pipe', \"openssl s_client -starttls mysql -connect localhost:3307 -showcerts 2>/dev/null = 0.7.11 is required' in result.msg + + - name: Drop mysql user + mysql_user: + <<: *mysql_params + name: '{{ user_name_1 }}' + host: 127.0.0.1 + state: absent diff --git a/tests/integration/targets/test_mysql_query/tasks/main.yml b/tests/integration/targets/test_mysql_query/tasks/main.yml index df0fe42..6d17308 100644 --- a/tests/integration/targets/test_mysql_query/tasks/main.yml +++ b/tests/integration/targets/test_mysql_query/tasks/main.yml @@ -5,3 +5,5 @@ # mysql_query module initial CI tests - import_tasks: mysql_query_initial.yml + +- include: issue-28.yml diff --git a/tests/integration/targets/test_mysql_replication/defaults/main.yml b/tests/integration/targets/test_mysql_replication/defaults/main.yml index b58bebb..fefcf29 100644 --- a/tests/integration/targets/test_mysql_replication/defaults/main.yml +++ b/tests/integration/targets/test_mysql_replication/defaults/main.yml @@ -12,3 +12,6 @@ replication_user: replication_user replication_pass: replication_pass dump_path: /tmp/dump.sql test_channel: test_channel-1 + +user_name_1: 'db_user1' +user_password_1: 'gadfFDSdtTU^Sdfuj' diff --git a/tests/integration/targets/test_mysql_replication/tasks/issue-28.yml b/tests/integration/targets/test_mysql_replication/tasks/issue-28.yml new file mode 100644 index 0000000..c666820 --- /dev/null +++ b/tests/integration/targets/test_mysql_replication/tasks/issue-28.yml @@ -0,0 +1,79 @@ +--- +- 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: + + # ============================================================ + - shell: pip show pymysql | awk '/Version/ {print $2}' + register: pymysql_version + + - name: get server certificate + copy: + content: "{{ lookup('pipe', \"openssl s_client -starttls mysql -connect localhost:3307 -showcerts 2>/dev/null = 0.7.11 is required' in result.msg + + - name: Drop mysql user + mysql_user: + <<: *mysql_params + name: '{{ user_name_1 }}' + host: 127.0.0.1 + state: absent diff --git a/tests/integration/targets/test_mysql_replication/tasks/main.yml b/tests/integration/targets/test_mysql_replication/tasks/main.yml index 0efc899..239598a 100644 --- a/tests/integration/targets/test_mysql_replication/tasks/main.yml +++ b/tests/integration/targets/test_mysql_replication/tasks/main.yml @@ -17,3 +17,5 @@ # Tests of resetmaster mode: - import_tasks: mysql_replication_resetmaster_mode.yml + +- include: issue-28.yml diff --git a/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_initial.yml b/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_initial.yml index 632bd97..eb1b51a 100644 --- a/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_initial.yml +++ b/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_initial.yml @@ -165,6 +165,9 @@ that: - slave_status.Exec_Master_Log_Pos != mysql_primary_status.Position + - shell: pip show pymysql | awk '/Version/ {print $2}' + register: pymysql_version + - name: Start slave that is already running mysql_replication: <<: *mysql_params @@ -176,6 +179,7 @@ - assert: that: - result is not changed + when: (pymysql_version.stdout | default('1000', true)) is version('0.9.3', '<=') # Test stopslave mode: - name: Stop slave @@ -202,3 +206,4 @@ - assert: that: - result is not changed + when: (pymysql_version.stdout | default('1000', true)) is version('0.9.3', '<=') diff --git a/tests/integration/targets/test_mysql_user/tasks/issue-28.yml b/tests/integration/targets/test_mysql_user/tasks/issue-28.yml new file mode 100644 index 0000000..a5b3d2a --- /dev/null +++ b/tests/integration/targets/test_mysql_user/tasks/issue-28.yml @@ -0,0 +1,86 @@ +--- +- 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: + + # ============================================================ + - shell: pip show pymysql | awk '/Version/ {print $2}' + register: pymysql_version + + - name: get server certificate + copy: + content: "{{ lookup('pipe', \"openssl s_client -starttls mysql -connect localhost:3307 -showcerts 2>/dev/null = 0.7.11 is required' in result.msg + + - name: Drop mysql user + mysql_user: + <<: *mysql_params + name: '{{ item }}' + host: 127.0.0.1 + state: absent + with_items: + - "{{ user_name_1 }}" + - "{{ user_name_2 }}" diff --git a/tests/integration/targets/test_mysql_user/tasks/main.yml b/tests/integration/targets/test_mysql_user/tasks/main.yml index 0e9f7b4..3e50608 100644 --- a/tests/integration/targets/test_mysql_user/tasks/main.yml +++ b/tests/integration/targets/test_mysql_user/tasks/main.yml @@ -37,6 +37,8 @@ block: + - include: issue-28.yml + - include: create_user.yml user_name={{user_name_1}} user_password={{ user_password_1 }} - include: resource_limits.yml diff --git a/tests/integration/targets/test_mysql_variables/defaults/main.yml b/tests/integration/targets/test_mysql_variables/defaults/main.yml index 496a6ca..6d0e2ec 100644 --- a/tests/integration/targets/test_mysql_variables/defaults/main.yml +++ b/tests/integration/targets/test_mysql_variables/defaults/main.yml @@ -3,3 +3,6 @@ mysql_user: root mysql_password: msandbox mysql_primary_port: 3307 + +user_name_1: 'db_user1' +user_password_1: 'gadfFDSdtTU^Sdfuj' diff --git a/tests/integration/targets/test_mysql_variables/tasks/issue-28.yml b/tests/integration/targets/test_mysql_variables/tasks/issue-28.yml new file mode 100644 index 0000000..056771e --- /dev/null +++ b/tests/integration/targets/test_mysql_variables/tasks/issue-28.yml @@ -0,0 +1,79 @@ +--- +- 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: + + # ============================================================ + - shell: pip show pymysql | awk '/Version/ {print $2}' + register: pymysql_version + + - name: get server certificate + copy: + content: "{{ lookup('pipe', \"openssl s_client -starttls mysql -connect localhost:3307 -showcerts 2>/dev/null = 0.7.11 is required' in result.msg + + - name: Drop mysql user + mysql_user: + <<: *mysql_params + name: '{{ user_name_1 }}' + host: 127.0.0.1 + state: absent diff --git a/tests/integration/targets/test_mysql_variables/tasks/main.yml b/tests/integration/targets/test_mysql_variables/tasks/main.yml index 45b5d63..9c4cd7d 100644 --- a/tests/integration/targets/test_mysql_variables/tasks/main.yml +++ b/tests/integration/targets/test_mysql_variables/tasks/main.yml @@ -4,3 +4,5 @@ #################################################################### - import_tasks: mysql_variables.yml + +- include: issue-28.yml diff --git a/tests/integration/targets/test_mysql_variables/tasks/mysql_variables.yml b/tests/integration/targets/test_mysql_variables/tasks/mysql_variables.yml index 2473327..8a25849 100644 --- a/tests/integration/targets/test_mysql_variables/tasks/mysql_variables.yml +++ b/tests/integration/targets/test_mysql_variables/tasks/mysql_variables.yml @@ -141,7 +141,7 @@ - name: set mysql variable value to an expression mysql_variables: <<: *mysql_params - variable: max_tmp_tables + variable: max_connect_errors value: "1024*4" register: result ignore_errors: true @@ -151,15 +151,22 @@ # ============================================================ # Verify mysql_variable fails when setting an incorrect value (out of range) # + - shell: pip show pymysql | awk '/Version/ {print $2}' + register: pymysql_version + - name: set mysql variable value to a number out of range mysql_variables: <<: *mysql_params - variable: max_tmp_tables + variable: max_connect_errors value: -1 - register: result + register: oor_result ignore_errors: true - - include: assert_fail_msg.yml output={{ result }} msg='Truncated incorrect' + - include: assert_var.yml changed=true output={{ oor_result }} var_name=max_connect_errors var_value=1 + when: pymysql_version.stdout == "" + + - include: assert_fail_msg.yml output={{ oor_result }} msg='Truncated incorrect' + when: pymysql_version.stdout != "" # ============================================================ # Verify mysql_variable fails when setting an incorrect value (incorrect type) @@ -167,12 +174,12 @@ - name: set mysql variable value to a non-valid value number mysql_variables: <<: *mysql_params - variable: max_tmp_tables + variable: max_connect_errors value: TEST - register: result + register: nvv_result ignore_errors: true - - include: assert_fail_msg.yml output={{ result }} msg='Incorrect argument type to variable' + - include: assert_fail_msg.yml output={{ nvv_result }} msg='Incorrect argument type to variable' # ============================================================ # Verify mysql_variable fails when setting an unknown variable