From 1d1f7ec582b84b90c76b96d234e7925d271a263d Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Thu, 18 Jun 2020 11:24:23 +0300 Subject: [PATCH] mysql_db: add config_overrides_defaults parameter (#513) * mysql_db: add config_overrides_defaults parameter * Add CI tests * Add changelog fragment * add more tests * improve tests * fix CI * fix feature --- ...513-mysql_db_config_overrides_defaults.yml | 2 + plugins/module_utils/mysql.py | 29 ++++++-- plugins/modules/database/mysql/mysql_db.py | 16 ++++- .../tasks/config_overrides_defaults.yml | 71 +++++++++++++++++++ .../targets/mysql_db/tasks/main.yml | 3 + 5 files changed, 114 insertions(+), 7 deletions(-) create mode 100644 changelogs/fragments/513-mysql_db_config_overrides_defaults.yml create mode 100644 tests/integration/targets/mysql_db/tasks/config_overrides_defaults.yml diff --git a/changelogs/fragments/513-mysql_db_config_overrides_defaults.yml b/changelogs/fragments/513-mysql_db_config_overrides_defaults.yml new file mode 100644 index 0000000000..8a4ec7d5a1 --- /dev/null +++ b/changelogs/fragments/513-mysql_db_config_overrides_defaults.yml @@ -0,0 +1,2 @@ +minor_changes: +- mysql_db - add the ``config_overrides_defaults`` parameter (https://github.com/ansible/ansible/issues/26919). diff --git a/plugins/module_utils/mysql.py b/plugins/module_utils/mysql.py index 46198f367b..03b957bee5 100644 --- a/plugins/module_utils/mysql.py +++ b/plugins/module_utils/mysql.py @@ -29,6 +29,8 @@ import os +from ansible.module_utils.six.moves import configparser + try: import pymysql as mysql_driver _mysql_cursor_param = 'cursor' @@ -43,10 +45,30 @@ except ImportError: mysql_driver_fail_msg = 'The PyMySQL (Python 2.7 and Python 3.X) or MySQL-python (Python 2.X) module is required.' -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): +def parse_from_mysql_config_file(cnf): + cp = configparser.ConfigParser() + cp.read(cnf) + return cp + + +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): config = {} + if config_file and os.path.exists(config_file): + config['read_default_file'] = config_file + cp = parse_from_mysql_config_file(config_file) + # Override some commond defaults with values from config file if needed + if cp and cp.has_section('client') and config_overrides_defaults: + try: + module.params['login_host'] = cp.get('client', 'host', fallback=module.params['login_host']) + 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, ' + '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: config['ssl'] = {} @@ -56,9 +78,6 @@ def mysql_connect(module, login_user=None, login_password=None, config_file='', config['host'] = module.params['login_host'] config['port'] = module.params['login_port'] - if os.path.exists(config_file): - config['read_default_file'] = config_file - # If login_user or login_password are given, they should override the # config file if login_user is not None: diff --git a/plugins/modules/database/mysql/mysql_db.py b/plugins/modules/database/mysql/mysql_db.py index 23685def09..30fa46a706 100644 --- a/plugins/modules/database/mysql/mysql_db.py +++ b/plugins/modules/database/mysql/mysql_db.py @@ -144,6 +144,15 @@ options: type: bool default: no version_added: '0.2.0' + config_overrides_defaults: + description: + - If C(yes), connection parameters from I(config_file) will override the default + values of I(login_host) and I(login_port) parameters. + - Used when I(stat) is C(present) or C(absent), ignored otherwise. + - It needs Python 3.5+ as the default interpreter on a target host. + type: bool + default: no + version_added: '0.2.0' seealso: - module: mysql_info @@ -563,6 +572,7 @@ def main(): 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), ), supports_check_mode=True, ) @@ -606,6 +616,7 @@ def main(): use_shell = module.params["use_shell"] restrict_config_file = module.params["restrict_config_file"] check_implicit_admin = module.params['check_implicit_admin'] + config_overrides_defaults = module.params['config_overrides_defaults'] if len(db) > 1 and state == 'import': module.fail_json(msg="Multiple databases are not supported with state=import") @@ -625,14 +636,15 @@ 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, + config_overrides_defaults=config_overrides_defaults) except Exception as e: check_implicit_admin = False pass 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) + connect_timeout=connect_timeout, config_overrides_defaults=config_overrides_defaults) 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/tests/integration/targets/mysql_db/tasks/config_overrides_defaults.yml b/tests/integration/targets/mysql_db/tasks/config_overrides_defaults.yml new file mode 100644 index 0000000000..4dec21e4bb --- /dev/null +++ b/tests/integration/targets/mysql_db/tasks/config_overrides_defaults.yml @@ -0,0 +1,71 @@ +- set_fact: + db_to_create=testdb1 + config_file="/root/.my1.cnf" + fake_port=9999 + fake_host="blahblah.local" + +- name: Create custom config file + shell: 'echo "[client]" > {{ config_file }}' + +- name: Add fake port to config file + shell: 'echo "port = {{ fake_port }}" >> {{ config_file }}' + +- name: Create database using fake port to connect to, must fail + mysql_db: + name: '{{ db_to_create }}' + state: present + check_implicit_admin: yes + config_file: '{{ config_file }}' + config_overrides_defaults: yes + ignore_errors: yes + register: result + +- name: Must fail because login_port default has beed overriden by wrong value from config file + assert: + that: + - result is failed + - result.msg is search("unable to connect to database") + +- name: Create database using default port + mysql_db: + name: '{{ db_to_create }}' + state: present + check_implicit_admin: yes + config_file: '{{ config_file }}' + config_overrides_defaults: no + login_unix_socket: '{{ mysql_socket }}' + register: result + +- name: Must not fail because of the default of login_port is correct + assert: + that: + - result is changed + +- name: Reinit custom config file + shell: 'echo "[client]" > {{ config_file }}' + +- name: Add fake host to config file + shell: 'echo "host = {{ fake_host }}" >> {{ config_file }}' + +- name: Remove database using fake login_host + mysql_db: + name: '{{ db_to_create }}' + state: absent + config_file: '{{ config_file }}' + config_overrides_defaults: yes + register: result + ignore_errors: yes + +- name: Must fail because login_host default has beed overriden by wrong value from config file + assert: + that: + - result is failed + - result.msg is search("Can't connect to MySQL server on '{{ fake_host }}'") + +# Clean up +- name: Remove test db + mysql_db: + name: '{{ db_to_create }}' + state: absent + check_implicit_admin: yes + login_unix_socket: '{{ mysql_socket }}' diff --git a/tests/integration/targets/mysql_db/tasks/main.yml b/tests/integration/targets/mysql_db/tasks/main.yml index 7c79d327c9..e719fe82ff 100644 --- a/tests/integration/targets/mysql_db/tasks/main.yml +++ b/tests/integration/targets/mysql_db/tasks/main.yml @@ -258,3 +258,6 @@ - include: multi_db_create_delete.yml - include: encoding_dump_import.yml file=latin1.sql format_msg_type=ASCII + +- include: config_overrides_defaults.yml + when: ansible_python.version_info[0] >= 3