From f7c84a71e5049fc7b7033aa232651b4dc3c6e42b Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Thu, 15 Apr 2021 10:14:34 +0200 Subject: [PATCH 001/217] Update main branch after branching new stable-2 branch (#146) --- changelogs/changelog.yaml | 239 +----------------- .../fragments/101-drop-requiressl-support.yml | 4 - .../103-mysql_and_mariadb_divergence.yml | 2 - .../fragments/108-mysql_priv_add_grant.yml | 2 - ...d_mysql_full_version_suffix_return_var.yml | 2 - ...ange_deprecated_connection_ parameters.yml | 2 - ...replication_remove_slave_from_messages.yml | 4 - galaxy.yml | 2 +- 8 files changed, 3 insertions(+), 254 deletions(-) delete mode 100644 changelogs/fragments/101-drop-requiressl-support.yml delete mode 100644 changelogs/fragments/103-mysql_and_mariadb_divergence.yml delete mode 100644 changelogs/fragments/108-mysql_priv_add_grant.yml delete mode 100644 changelogs/fragments/115-add_mysql_full_version_suffix_return_var.yml delete mode 100644 changelogs/fragments/116-change_deprecated_connection_ parameters.yml delete mode 100644 changelogs/fragments/144-mysql_replication_remove_slave_from_messages.yml diff --git a/changelogs/changelog.yaml b/changelogs/changelog.yaml index c163341..e78468a 100644 --- a/changelogs/changelog.yaml +++ b/changelogs/changelog.yaml @@ -1,237 +1,2 @@ -ancestor: null -releases: - 1.0.0: - changes: - bugfixes: - - mysql - dont mask ``mysql_connect`` function errors from modules (https://github.com/ansible/ansible/issues/64560). - - mysql_db - fix Broken pipe error appearance when state is import and the target - file is compressed (https://github.com/ansible/ansible/issues/20196). - - mysql_db - fix bug in the ``db_import`` function introduced by https://github.com/ansible/ansible/pull/56721 - (https://github.com/ansible/ansible/issues/65351). - - mysql_info - add parameter for __collect to get only what are wanted (https://github.com/ansible-collections/community.general/pull/136). - - mysql_replication - allow to pass empty values to parameters (https://github.com/ansible/ansible/issues/23976). - - mysql_user - Fix idempotence when long grant lists are used (https://github.com/ansible/ansible/issues/68044) - - mysql_user - Remove false positive ``no_log`` warning for ``update_password`` - option - - mysql_user - add ``INVOKE LAMBDA`` privilege support (https://github.com/ansible-collections/community.general/issues/283). - - mysql_user - add missed privileges to support (https://github.com/ansible-collections/community.general/issues/617). - - mysql_user - fix ``host_all`` arguments conversion string formatting error - (https://github.com/ansible/ansible/issues/29644). - - mysql_user - fix overriding password to the same (https://github.com/ansible-collections/community.general/issues/543). - - mysql_user - fix support privileges with underscore (https://github.com/ansible/ansible/issues/66974). - - mysql_user - fix the error No database selected (https://github.com/ansible/ansible/issues/68070). - - mysql_user - make sure current_pass_hash is a string before using it in comparison - (https://github.com/ansible/ansible/issues/60567). - - mysql_variable - fix the module doesn't support variables name with dot (https://github.com/ansible/ansible/issues/54239). - minor_changes: - - mysql_db - add ``master_data`` parameter (https://github.com/ansible/ansible/pull/66048). - - mysql_db - add ``skip_lock_tables`` option (https://github.com/ansible/ansible/pull/66688). - - mysql_db - add the ``check_implicit_admin`` parameter (https://github.com/ansible/ansible/issues/24418). - - mysql_db - add the ``dump_extra_args`` parameter (https://github.com/ansible/ansible/pull/67747). - - mysql_db - add the ``executed_commands`` returned value (https://github.com/ansible/ansible/pull/65498). - - mysql_db - add the ``force`` parameter (https://github.com/ansible/ansible/pull/65547). - - mysql_db - add the ``restrict_config_file`` parameter (https://github.com/ansible/ansible/issues/34488). - - mysql_db - add the ``unsafe_login_password`` parameter (https://github.com/ansible/ansible/issues/63955). - - mysql_db - add the ``use_shell`` parameter (https://github.com/ansible/ansible/issues/20196). - - mysql_info - add ``exclude_fields`` parameter (https://github.com/ansible/ansible/issues/63319). - - mysql_info - add ``global_status`` filter parameter option and return (https://github.com/ansible/ansible/pull/63189). - - mysql_info - add ``return_empty_dbs`` parameter to list empty databases (https://github.com/ansible/ansible/issues/65727). - - mysql_replication - add ``channel`` parameter (https://github.com/ansible/ansible/issues/29311). - - mysql_replication - add ``connection_name`` parameter (https://github.com/ansible/ansible/issues/46243). - - mysql_replication - add ``fail_on_error`` parameter (https://github.com/ansible/ansible/pull/66252). - - mysql_replication - add ``master_delay`` parameter (https://github.com/ansible/ansible/issues/51326). - - mysql_replication - add ``master_use_gtid`` parameter (https://github.com/ansible/ansible/pull/62648). - - mysql_replication - add ``queries`` return value (https://github.com/ansible/ansible/pull/63036). - - mysql_replication - add support of ``resetmaster`` choice to ``mode`` parameter - (https://github.com/ansible/ansible/issues/42870). - - mysql_user - ``priv`` parameter can be string or dictionary (https://github.com/ansible/ansible/issues/57533). - - mysql_user - add TLS REQUIRES parameters (https://github.com/ansible-collections/community.mysql/pull/9). - - mysql_user - add ``plugin_auth_string`` parameter (https://github.com/ansible/ansible/pull/44267). - - mysql_user - add ``plugin_hash_string`` parameter (https://github.com/ansible/ansible/pull/44267). - - mysql_user - add ``plugin`` parameter (https://github.com/ansible/ansible/pull/44267). - - mysql_user - add the resource_limits parameter (https://github.com/ansible-collections/community.general/issues/133). - - mysql_variables - add ``mode`` parameter (https://github.com/ansible/ansible/issues/60119). - release_summary: 'This is the first proper release of the ``community.mysql`` - collection. - - This changelog contains all changes to the modules in this collection that - were added after the release of Ansible 2.9.0. - - ' - fragments: - - 1.0.0.yml - - 142-mysql_user_add_resource_limit_parameter.yml - - 151-mysql_db_add_use_shell_parameter.yml - - 18-mysql_user-update_password-no_log.yml - - 225-mysql_user_fix_no_database_selected.yml - - 285-mysql_user_invoke_lambda_support.yml - - 369-mysql_user_add_tls_requires.yml - - 428-mysql_db_add_unsafe_login_password_param.yml - - 468-mysql_db_add_restrict_config_file_param.yml - - 486-mysql_db_add_check_implicit_admin_parameter.yml - - 490-mysql_user_fix_cursor_errors.yml - - 609-mysql_user_fix_overriding_password_to_the_same.yml - - 618-mysql_user_add_missed_privileges.yml - - 62648-mysql_replication_add_master_use_gtid_param.yml - - 63036-mysql_replication_add_return_value.yml - - 63130-mysql_replication_add_master_delay_parameter.yml - - 63189-mysql_info-global-status.yml - - 63229-mysql_replication_add_connection_name_parameter.yml - - 63271-mysql_replication_add_channel_parameter.yml - - 63321-mysql_replication_add_resetmaster_to_mode.yml - - 63371-mysql_info_add_exclude_fields_parameter.yml - - 63546-mysql_replication_allow_to_pass_empty_values.yml - - 63547-mysql_variables_add_mode_param.yml - - 64059-mysql_user_fix_password_comparison.yaml - - 64585-mysql_dont_mask_mysql_connect_errors_from_modules.yml - - 65498-mysql_db_add_executed_commands_return_val.yml - - 65547-mysql_db_add_force_param.yml - - 65755-mysql_info_doesnt_list_empty_dbs.yml - - 65789-mysql_user_add_plugin_authentication_parameters.yml - - 66048-mysql_add_master_data_parameter.yml - - 66252-mysql_replication_fail_on_error.yml - - 66688-mysql_db_add_skip_lock_tables_option.yml - - 66801-mysql_user_priv_can_be_dict.yml - - 66806-mysql_variables_not_support_variables_with_dot.yml - - 66974-mysql_user_doesnt_support_privs_with_underscore.yml - - 67337-fix-proxysql-mysql-cursor.yaml - - 67747-mysql_db_add_dump_extra_args_param.yml - - 67767-mysql_db_fix_bug_introduced_by_56721.yml - - mysql_info_add_parameter.yml - - mysql_user_idempotency.yml - release_date: '2020-08-17' - 1.0.1: - changes: - bugfixes: - - mysql_db - fix false warning related to ``unsafe_login_password`` option (https://github.com/ansible-collections/community.mysql/issues/33). - - mysql_user - added tests to verify that TLS requirements are removed with - an empty ``tls_requires`` option (https://github.com/ansible-collections/community.mysql/issues/20). - - mysql_user - correct procedure to check existing TLS requirements (https://github.com/ansible-collections/community.mysql/pull/26). - - mysql_user - minor syntax fixes (https://github.com/ansible-collections/community.mysql/pull/26). - release_summary: 'This is the patch release of the ``community.mysql`` collection. - - This changelog contains all changes to the modules in this collection that - have been added after the release of ``community.mysql`` 1.0.0. - - ' - fragments: - - 1.0.1.yml - - 26-remove_tls_requirements.yml - - 34-mysql_db_fix_false_warning.yml - release_date: '2020-09-29' - 1.0.2: - changes: - bugfixes: - - mysql_user - fix module's crash when modifying a user with ``host_all`` (https://github.com/ansible-collections/community.mysql/issues/39). - release_summary: 'This is the patch release of the ``community.mysql`` collection. - - This changelog contains all changes to the modules in this collection that - have been added after the release of ``community.mysql`` 1.0.1. - - ' - fragments: - - 1.0.2.yml - - 40-mysql_user_fix_error_when_host_all_used.yml - release_date: '2020-10-01' - 1.1.0: - changes: - bugfixes: - - mysql modules - fix crash when ``!includedir`` option is in config file (https://github.com/ansible-collections/community.mysql/issues/46). - minor_changes: - - mysql modules - add the ``check_hostname`` option (https://github.com/ansible-collections/community.mysql/issues/28). - - mysql modules - patch the ``Connection`` class to add a destructor that ensures - connections to the server are explicitly closed (https://github.com/ansible-collections/community.mysql/pull/44). - release_summary: 'This is the minor release of the ``community.mysql`` collection. - - This changelog contains all changes to the modules in this collection that - have been added after the release of ``community.mysql`` 1.0.2. - - ' - fragments: - - 1.1.0.yml - - 35-disable-hostname-check.yml - - 44-close-connection.yml - - 47-mysql_modules_fix_failings_when_include_dir_in_config_file.yml - release_date: '2020-10-13' - 1.1.1: - changes: - bugfixes: - - mysql_query - fix failing when single-row query contains commas (https://github.com/ansible-collections/community.mysql/issues/51). - release_summary: 'This is the patch release of the ``community.mysql`` collection. - - This changelog contains all changes to the modules in this collection that - - have been added after the release of ``community.mysql`` 1.1.0. - - ' - fragments: - - 1.1.1.yml - - 53-mysql_query_fix_single_query_with_commas.yml - release_date: '2020-11-03' - 1.1.2: - changes: - bugfixes: - - mysql_db - fix false warning related to ``unsafe_login_password`` option (https://github.com/ansible-collections/community.mysql/issues/33). - - mysql_replication - fix crashes of mariadb >= 10.5.1 and mysql >= 8.0.22 caused - by using deprecated terminology (https://github.com/ansible-collections/community.mysql/issues/70). - - mysql_user - fixed change detection when using append_privs (https://github.com/ansible-collections/community.mysql/pull/72). - minor_changes: - - mysql_query - simple refactoring of query type check (https://github.com/ansible-collections/community.mysql/pull/58). - - mysql_user - simple refactoring of priv type check (https://github.com/ansible-collections/community.mysql/pull/58). - release_summary: 'This is the patch release of the ``community.mysql`` collection. - - This changelog contains all changes to the modules in this collection that - - have been added after the release of ``community.mysql`` 1.1.1.' - fragments: - - 1.1.2.yml - - 58-mysql_query_refactoring.yml - - 71-mysql_replication_add_replica_keyword_support.yml - - 72-mysql_db_fix_false_warning.yml - - 72-mysql_user_change_detection.yml - release_date: '2020-12-18' - 1.2.0: - changes: - bugfixes: - - mysql_user - add ``SHOW_ROUTINE`` privilege support (https://github.com/ansible-collections/community.mysql/issues/86). - - mysql_user - fixed creating user with encrypted password in MySQL 8.0 (https://github.com/ansible-collections/community.mysql/pull/79). - minor_changes: - - mysql_user - refactor to reduce cursor.execute() calls in preparation for - adding query logging (https://github.com/ansible-collections/community.mysql/pull/76). - release_summary: 'This is the minor release of the ``community.mysql`` collection. - - This changelog contains all changes to the modules in this collection - - that have been added after the release of ``community.mysql`` 1.1.2.' - fragments: - - 1.2.0.yml - - 76-mysql-user-query-refact.yaml - - 79-mysql-user-tests-and-fixes.yml - - 87-mysql_user_show_routine_support.yml - release_date: '2021-01-18' - 1.3.0: - changes: - bugfixes: - - mysql_user - fix handling of INSERT, UPDATE, REFERENCES on columns (https://github.com/ansible-collections/community.mysql/issues/106). - - mysql_user - the module is not idempotent when SELECT on columns granted (https://github.com/ansible-collections/community.mysql/issues/99). - major_changes: - - mysql_replication - the mode options values ``getslave``, ``startslave``, - ``stopslave``, ``resetslave``, ``resetslaveall` and the master_use_gtid option - ``slave_pos`` are deprecated (see the alternative values) and will be removed - in ``community.mysql`` 3.0.0 (https://github.com/ansible-collections/community.mysql/pull/97). - - mysql_replication - the word ``SLAVE`` in messages returned by the module - will be changed to ``REPLICA`` in ``community.mysql`` 2.0.0 (https://github.com/ansible-collections/community.mysql/issues/98). - minor_changes: - - mysql_replication - deprecate offending terminology, add alternative choices - to the ``mode`` option (https://github.com/ansible-collections/community.mysql/issues/78). - release_summary: 'This is the minor release of the ``community.mysql`` collection. - - This changelog contains all changes to the modules in this collection - - that have been added after the release of ``community.mysql`` 1.2.0.' - fragments: - - 1.3.0.yml - - 100-mysql_user_not_idemponent_when_select_on_columns_granted.yml - - 107-mysql_user_fix_grant_on_col_handling.yml - - 97-mysql_replication_deprecate_offending_terminology.yml - release_date: '2021-03-08' +ancestor: 2.0.0 +releases: {} diff --git a/changelogs/fragments/101-drop-requiressl-support.yml b/changelogs/fragments/101-drop-requiressl-support.yml deleted file mode 100644 index fded3e3..0000000 --- a/changelogs/fragments/101-drop-requiressl-support.yml +++ /dev/null @@ -1,4 +0,0 @@ -minor_changes: - - mysql_user - deprecate the ``REQUIRESSL`` privilege (https://github.com/ansible-collections/community.mysql/issues/101). -major_changes: - - mysql_user - the ``REQUIRESSL`` is an alias for the ``ssl`` key in the ``tls_requires`` option in ``community.mysql`` 2.0.0 and support will be dropped altogether in ``community.mysql`` 3.0.0 (https://github.com/ansible-collections/community.mysql/issues/121). diff --git a/changelogs/fragments/103-mysql_and_mariadb_divergence.yml b/changelogs/fragments/103-mysql_and_mariadb_divergence.yml deleted file mode 100644 index 039c654..0000000 --- a/changelogs/fragments/103-mysql_and_mariadb_divergence.yml +++ /dev/null @@ -1,2 +0,0 @@ -minor_changes: -- mysql_collection - introduce codebabse split to handle divergences between MySQL and MariaDB (https://github.com/ansible-collections/community.mysql/pull/103). diff --git a/changelogs/fragments/108-mysql_priv_add_grant.yml b/changelogs/fragments/108-mysql_priv_add_grant.yml deleted file mode 100644 index 4cfd00e..0000000 --- a/changelogs/fragments/108-mysql_priv_add_grant.yml +++ /dev/null @@ -1,2 +0,0 @@ -bugfixes: -- mysql_user - add support for ``REPLICA MONITOR`` privilege (https://github.com/ansible-collections/community.mysql/issues/105). diff --git a/changelogs/fragments/115-add_mysql_full_version_suffix_return_var.yml b/changelogs/fragments/115-add_mysql_full_version_suffix_return_var.yml deleted file mode 100644 index 3eadb46..0000000 --- a/changelogs/fragments/115-add_mysql_full_version_suffix_return_var.yml +++ /dev/null @@ -1,2 +0,0 @@ -minor_changes: -- mysql_info - add `version.full` and `version.suffix` return values (https://github.com/ansible-collections/community.mysql/issues/114). diff --git a/changelogs/fragments/116-change_deprecated_connection_ parameters.yml b/changelogs/fragments/116-change_deprecated_connection_ parameters.yml deleted file mode 100644 index d6b0d7e..0000000 --- a/changelogs/fragments/116-change_deprecated_connection_ parameters.yml +++ /dev/null @@ -1,2 +0,0 @@ -minor_changes: -- mysql module utils - change deprecated connection parameters ``passwd`` and ``db`` to ``password`` and ``database`` (https://github.com/ansible-collections/community.mysql/pull/116). \ No newline at end of file diff --git a/changelogs/fragments/144-mysql_replication_remove_slave_from_messages.yml b/changelogs/fragments/144-mysql_replication_remove_slave_from_messages.yml deleted file mode 100644 index e88691e..0000000 --- a/changelogs/fragments/144-mysql_replication_remove_slave_from_messages.yml +++ /dev/null @@ -1,4 +0,0 @@ -major_changes: -- mysql_replication - the word ``slave`` in messages returned by the module replaced with ``replica`` (https://github.com/ansible-collections/community.mysql/issues/98). -- mysql_replication - the return value ``Is_Slave`` and ``Is_Master`` will be replaced with ``Is_Replica`` and ``Is_Primary`` in ``community.mysql`` 3.0.0 (https://github.com/ansible-collections/community.mysql/issues/145). -- mysql_replication - the word ``master`` in messages returned by the module will be replaced with ``primary`` in ``community.mysql`` 3.0.0 (https://github.com/ansible-collections/community.mysql/issues/145). diff --git a/galaxy.yml b/galaxy.yml index dad76b9..5d44fe8 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -1,6 +1,6 @@ namespace: community name: mysql -version: 1.3.0 +version: 2.1.0 readme: README.md authors: - Ansible community From 853db5a2a415cb2b9dbcea0c0c65aec64e9a2f82 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Fri, 16 Apr 2021 06:34:35 +0200 Subject: [PATCH 002/217] mysql_replication: add deprecation warning about future replacement of Is_Slave and Is_Master return values, add alternatives (#147) * mysql_replication: add deprecation warning about future replacement of Is_Slave and Is_Master return values, add alternatives * Add changelog fragment --- ...7-mysql_replication_deprecate_ret_vals.yml | 5 ++++ plugins/modules/mysql_replication.py | 23 ++++++++++++++----- .../tasks/mysql_replication_channel.yml | 4 ++-- .../tasks/mysql_replication_initial.yml | 4 ++-- 4 files changed, 26 insertions(+), 10 deletions(-) create mode 100644 changelogs/fragments/147-mysql_replication_deprecate_ret_vals.yml diff --git a/changelogs/fragments/147-mysql_replication_deprecate_ret_vals.yml b/changelogs/fragments/147-mysql_replication_deprecate_ret_vals.yml new file mode 100644 index 0000000..9261453 --- /dev/null +++ b/changelogs/fragments/147-mysql_replication_deprecate_ret_vals.yml @@ -0,0 +1,5 @@ +major_changes: +- mysql_replication - add deprecation warning that the ``Is_Slave`` and ``Is_Master`` return values will be replaced with ``Is_Primary`` and ``Is_Replica`` in ``community.mysql`` 3.0.0 (https://github.com/ansible-collections/community.mysql/pull/147). + +minor_changes: +- mysql_replication - add the ``Is_Primary`` and ``Is_Replica`` alternatives to the ``Is_Slave`` and ``Is_Master`` return values as a preparation for replacement in ``community.mysql`` 3.0.0 (https://github.com/ansible-collections/community.mysql/pull/147). diff --git a/plugins/modules/mysql_replication.py b/plugins/modules/mysql_replication.py index e81ef17..8f1d269 100644 --- a/plugins/modules/mysql_replication.py +++ b/plugins/modules/mysql_replication.py @@ -524,9 +524,17 @@ def main(): if mode in "getmaster": status = get_master_status(cursor) if not isinstance(status, dict): - status = dict(Is_Master=False, msg="Server is not configured as mysql master") + status = dict(Is_Master=False, Is_Primary=False, + msg="Server is not configured as mysql master") else: status['Is_Master'] = True + status['Is_Primary'] = True + + module.deprecate('"Is_Master" and "Is_Slave" return values are deprecated ' + 'and will be replaced with "Is_Primary" and "Is_Replica" ' + 'in the next major release. Use "Is_Primary" and "Is_Replica" instead.', + version='3.0.0', collection_name='community.mysql') + module.exit_json(queries=executed_queries, **status) elif mode in ("getreplica", "getslave"): @@ -536,13 +544,16 @@ def main(): status = get_replica_status(cursor, connection_name, channel, replica_term) if not isinstance(status, dict): - # TODO: announce it and replace with Replica - # in the next major release. Maybe a warning? - status = dict(Is_Slave=False, msg="Server is not configured as mysql replica") + status = dict(Is_Slave=False, Is_Replica=False, msg="Server is not configured as mysql replica") else: - # TODO: announce it and replace with Replica - # in the next major release. Maybe a warning? status['Is_Slave'] = True + status['Is_Replica'] = True + + module.deprecate('"Is_Master" and "Is_Slave" return values are deprecated ' + 'and will be replaced with "Is_Primary" and "Is_Replica" ' + 'in the next major release. Use "Is_Primary" and "Is_Replica" instead.', + version='3.0.0', collection_name='community.mysql') + module.exit_json(queries=executed_queries, **status) elif mode in "changemaster": diff --git a/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_channel.yml b/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_channel.yml index 1bbc1bc..c95f792 100644 --- a/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_channel.yml +++ b/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_channel.yml @@ -61,7 +61,7 @@ - assert: that: - - replica_status.Is_Slave == true + - replica_status.Is_Replica == true - replica_status.Master_Host == '{{ mysql_host }}' - replica_status.Exec_Master_Log_Pos == mysql_primary_status.Position - replica_status.Master_Port == {{ mysql_primary_port }} @@ -73,7 +73,7 @@ - assert: that: - - replica_status.Is_Slave == true + - replica_status.Is_Replica == true - replica_status.Source_Host == '{{ mysql_host }}' - replica_status.Exec_Source_Log_Pos == mysql_primary_status.Position - replica_status.Source_Port == {{ mysql_primary_port }} 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 4e90707..27743fb 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 @@ -59,7 +59,7 @@ - assert: that: - - mysql_primary_status.Is_Master == true + - mysql_primary_status.Is_Primary == true - mysql_primary_status.Position != 0 - mysql_primary_status is not changed @@ -148,7 +148,7 @@ - assert: that: - - replica_status.Is_Slave == true + - replica_status.Is_Replica == true - replica_status.Master_Host == '{{ mysql_host }}' - replica_status.Exec_Master_Log_Pos == mysql_primary_status.Position - replica_status.Master_Port == {{ mysql_primary_port }} From c8f9b1cd3f668305d7fd5ff169db64e9225bcb4b Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Tue, 20 Apr 2021 15:58:21 +0200 Subject: [PATCH 003/217] mysql_replication: Add aliases to "master" options, add alternatives to "master" state options, add announcement (#150) * mysql_replication: Add aliases, add alternatives for the state option, announce major changes * Change tests * Add changelog fragment * Fix changelog * Update plugins/modules/mysql_replication.py Co-authored-by: Jorge Rodriguez (A.K.A. Tiriel) * Update plugins/modules/mysql_replication.py Co-authored-by: Jorge Rodriguez (A.K.A. Tiriel) Co-authored-by: Jorge Rodriguez (A.K.A. Tiriel) --- .../150-mysql_replication_master_related.yml | 6 + plugins/modules/mysql_replication.py | 317 ++++++++++-------- .../test_mysql_replication/defaults/main.yml | 2 +- .../test_mysql_replication/tasks/issue-28.yml | 4 +- .../test_mysql_replication/tasks/main.yml | 8 +- .../tasks/mysql_replication_channel.yml | 22 +- .../tasks/mysql_replication_initial.yml | 32 +- ...ml => mysql_replication_primary_delay.yml} | 10 +- ...> mysql_replication_resetprimary_mode.yml} | 18 +- 9 files changed, 234 insertions(+), 185 deletions(-) create mode 100644 changelogs/fragments/150-mysql_replication_master_related.yml rename tests/integration/targets/test_mysql_replication/tasks/{mysql_replication_master_delay.yml => mysql_replication_primary_delay.yml} (84%) rename tests/integration/targets/test_mysql_replication/tasks/{mysql_replication_resetmaster_mode.yml => mysql_replication_resetprimary_mode.yml} (83%) diff --git a/changelogs/fragments/150-mysql_replication_master_related.yml b/changelogs/fragments/150-mysql_replication_master_related.yml new file mode 100644 index 0000000..83b3538 --- /dev/null +++ b/changelogs/fragments/150-mysql_replication_master_related.yml @@ -0,0 +1,6 @@ +minor_changes: +- mysql_replication - change ``master_`` options to ``primary_`` options, add aliases to keep compatibility (https://github.com/ansible-collections/community.mysql/pull/150). +- mysql_replication - add alternative (``primary``) choices to the ``state`` option choices containing ``master`` (https://github.com/ansible-collections/community.mysql/pull/150). + +major_changes: +- mysql_replication - the choices of the ``state`` option containing ``master`` will be finally replaced with the alternative ``primary`` choices in ``community.mysql`` 3.0.0, add deprecation warnings (https://github.com/ansible-collections/community.mysql/pull/150). diff --git a/plugins/modules/mysql_replication.py b/plugins/modules/mysql_replication.py index 8f1d269..12dffc4 100644 --- a/plugins/modules/mysql_replication.py +++ b/plugins/modules/mysql_replication.py @@ -15,7 +15,7 @@ DOCUMENTATION = r''' module: mysql_replication short_description: Manage MySQL replication description: -- Manages MySQL server replication, replica, master status, get and change master host. +- Manages MySQL server replication, replica, primary status, get and change primary host. author: - Balazs Pocze (@banyek) - Andrew Klychkov (@Andersson007) @@ -23,17 +23,19 @@ options: mode: description: - Module operating mode. Could be - C(changemaster) (CHANGE MASTER TO), - C(getmaster) (SHOW MASTER STATUS), + C(changeprimary | changemaster) (CHANGE PRIMARY | MASTER TO), + C(getprimary | getmaster) (SHOW PRIMARY | MASTER STATUS), C(getreplica | getslave) (SHOW REPLICA | SLAVE STATUS), C(startreplica | startslave) (START REPLICA | SLAVE), C(stopreplica | stopslave) (STOP REPLICA | SLAVE), - C(resetmaster) (RESET MASTER) - supported since community.mysql 0.1.0, + C(resetprimary | resetmaster) (RESET PRIMARY | MASTER) - supported since community.mysql 0.1.0, C(resetreplica, resetslave) (RESET REPLICA | SLAVE), C(resetreplicaall, resetslave) (RESET REPLICA | SLAVE ALL). type: str choices: + - changeprimary - changemaster + - getprimary - getmaster - getreplica - getslave @@ -41,40 +43,48 @@ options: - startslave - stopreplica - stopslave + - resetprimary - resetmaster - resetreplica - resetslave - resetreplicaall - resetslaveall default: getreplica - master_host: + primary_host: description: - - Same as mysql variable. + - Same as the C(MASTER_HOST) mysql variable. type: str - master_user: + aliases: [master_host] + primary_user: description: - - Same as mysql variable. + - Same as the C(MASTER_USER) mysql variable. type: str - master_password: + aliases: [master_user] + primary_password: description: - - Same as mysql variable. + - Same as the C(MASTER_PASSWORD) mysql variable. type: str - master_port: + aliases: [master_password] + primary_port: description: - - Same as mysql variable. + - Same as the C(MASTER_PORT) mysql variable. type: int - master_connect_retry: + aliases: [master_port] + primary_connect_retry: description: - - Same as mysql variable. + - Same as the C(MASTER_CONNECT_RETRY) mysql variable. type: int - master_log_file: + aliases: [master_connect_retry] + primary_log_file: description: - - Same as mysql variable. + - Same as the C(MASTER_LOG_FILE) mysql variable. type: str - master_log_pos: + aliases: [master_log_file] + primary_log_pos: description: - - Same as mysql variable. + - Same as the C(MASTER_LOG_POS) mysql variable. type: int + aliases: [master_log_pos] relay_log_file: description: - Same as mysql variable. @@ -83,7 +93,7 @@ options: description: - Same as mysql variable. type: int - master_ssl: + primary_ssl: description: - Same as the C(MASTER_SSL) mysql variable. - When setting it to C(yes), the connection attempt only succeeds @@ -92,43 +102,51 @@ options: L(MySQL encrypted replication documentation,https://dev.mysql.com/doc/refman/8.0/en/replication-solutions-encrypted-connections.html). type: bool default: false - master_ssl_ca: + aliases: [master_ssl] + primary_ssl_ca: description: - Same as the C(MASTER_SSL_CA) mysql variable. - For details, refer to L(MySQL encrypted replication documentation,https://dev.mysql.com/doc/refman/8.0/en/replication-solutions-encrypted-connections.html). type: str - master_ssl_capath: + aliases: [master_ssl_ca] + primary_ssl_capath: description: - Same as the C(MASTER_SSL_CAPATH) mysql variable. - For details, refer to L(MySQL encrypted replication documentation,https://dev.mysql.com/doc/refman/8.0/en/replication-solutions-encrypted-connections.html). type: str - master_ssl_cert: + aliases: [master_ssl_capath] + primary_ssl_cert: description: - Same as the C(MASTER_SSL_CERT) mysql variable. - For details, refer to L(MySQL encrypted replication documentation,https://dev.mysql.com/doc/refman/8.0/en/replication-solutions-encrypted-connections.html). type: str - master_ssl_key: + aliases: [master_ssl_cert] + primary_ssl_key: description: - Same as the C(MASTER_SSL_KEY) mysql variable. - For details, refer to L(MySQL encrypted replication documentation,https://dev.mysql.com/doc/refman/8.0/en/replication-solutions-encrypted-connections.html). type: str - master_ssl_cipher: + aliases: [master_ssl_key] + primary_ssl_cipher: description: - Same as the C(MASTER_SSL_CIPHER) mysql variable. - Specifies a colon-separated list of one or more ciphers permitted by the replica for the replication connection. - For details, refer to L(MySQL encrypted replication documentation,https://dev.mysql.com/doc/refman/8.0/en/replication-solutions-encrypted-connections.html). type: str - master_auto_position: + aliases: [master_ssl_cipher] + primary_auto_position: description: - Whether the host uses GTID based replication or not. + - Same as the C(MASTER_AUTO_POSITION) mysql variable. type: bool default: false - master_use_gtid: + aliases: [master_auto_position] + primary_use_gtid: description: - Configures the replica to use the MariaDB Global Transaction ID. - C(disabled) equals MASTER_USE_GTID=no command. @@ -140,16 +158,19 @@ options: choices: [current_pos, replica_pos, slave_pos, disabled] type: str version_added: '0.1.0' - master_delay: + aliases: [master_use_gtid] + primary_delay: description: - - Time lag behind the master's state (in seconds). + - Time lag behind the primary's state (in seconds). + - Same as the C(MASTER_DELAY) mysql variable. - Available from MySQL 5.6. - For more information see U(https://dev.mysql.com/doc/refman/8.0/en/replication-delayed.html). type: int version_added: '0.1.0' + aliases: [master_delay] connection_name: description: - - Name of the master connection. + - Name of the primary connection. - Supported from MariaDB 10.0.1. - Mutually exclusive with I(channel). - For more information see U(https://mariadb.com/kb/en/library/multi-source-replication/). @@ -195,16 +216,16 @@ EXAMPLES = r''' community.mysql.mysql_replication: mode: stopreplica -- name: Get master binlog file name and binlog position +- name: Get primary binlog file name and binlog position community.mysql.mysql_replication: - mode: getmaster + mode: getprimary -- name: Change master to master server 192.0.2.1 and use binary log 'mysql-bin.000009' with position 4578 +- name: Change primary to primary server 192.0.2.1 and use binary log 'mysql-bin.000009' with position 4578 community.mysql.mysql_replication: - mode: changemaster - master_host: 192.0.2.1 - master_log_file: mysql-bin.000009 - master_log_pos: 4578 + mode: changeprimary + primary_host: 192.0.2.1 + primary_log_file: mysql-bin.000009 + primary_log_pos: 4578 - name: Check replica status using port 3308 community.mysql.mysql_replication: @@ -212,42 +233,42 @@ EXAMPLES = r''' login_host: ansible.example.com login_port: 3308 -- name: On MariaDB change master to use GTID current_pos +- name: On MariaDB change primary to use GTID current_pos community.mysql.mysql_replication: - mode: changemaster - master_use_gtid: current_pos + mode: changeprimary + primary_use_gtid: current_pos -- name: Change master to use replication delay 3600 seconds +- name: Change primary to use replication delay 3600 seconds community.mysql.mysql_replication: - mode: changemaster - master_host: 192.0.2.1 - master_delay: 3600 + mode: changeprimary + primary_host: 192.0.2.1 + primary_delay: 3600 -- name: Start MariaDB replica with connection name master-1 +- name: Start MariaDB replica with connection name primary-1 community.mysql.mysql_replication: mode: startreplica - connection_name: master-1 + connection_name: primary-1 -- name: Stop replication in channel master-1 +- name: Stop replication in channel primary-1 community.mysql.mysql_replication: mode: stopreplica - channel: master-1 + channel: primary-1 - name: > Run RESET MASTER command which will delete all existing binary log files - and reset the binary log index file on the master + and reset the binary log index file on the primary community.mysql.mysql_replication: - mode: resetmaster + mode: resetprimary - name: Run start replica and fail the task on errors community.mysql.mysql_replication: mode: startreplica - connection_name: master-1 + connection_name: primary-1 fail_on_error: yes -- name: Change master and fail on error (like when replica thread is running) +- name: Change primary and fail on error (like when replica thread is running) community.mysql.mysql_replication: - mode: changemaster + mode: changeprimary fail_on_error: yes ''' @@ -257,7 +278,7 @@ queries: description: List of executed queries which modified DB's state. returned: always type: list - sample: ["CHANGE MASTER TO MASTER_HOST='master2.example.com',MASTER_PORT=3306"] + sample: ["CHANGE MASTER TO MASTER_HOST='primary2.example.com',MASTER_PORT=3306"] version_added: '0.1.0' ''' @@ -277,10 +298,13 @@ from distutils.version import LooseVersion executed_queries = [] -def get_master_status(cursor): +def get_primary_status(cursor): + # TODO: when it's available to change on MySQL's side, + # change MASTER to PRIMARY using the approach from + # get_replica_status() function. Same for other functions. cursor.execute("SHOW MASTER STATUS") - masterstatus = cursor.fetchone() - return masterstatus + primarystatus = cursor.fetchone() + return primarystatus def get_replica_status(cursor, connection_name='', channel='', term='REPLICA'): @@ -363,7 +387,7 @@ def reset_replica_all(module, cursor, connection_name='', channel='', fail_on_er return reset -def reset_master(module, cursor, fail_on_error=False): +def reset_primary(module, cursor, fail_on_error=False): query = 'RESET MASTER' try: executed_queries.append(query) @@ -400,7 +424,7 @@ def start_replica(module, cursor, connection_name='', channel='', fail_on_error= return started -def changemaster(cursor, chm, connection_name='', channel=''): +def changeprimary(cursor, chm, connection_name='', channel=''): if connection_name: query = "CHANGE MASTER '%s' TO %s" % (connection_name, ','.join(chm)) else: @@ -417,29 +441,33 @@ def main(): argument_spec = mysql_common_argument_spec() argument_spec.update( mode=dict(type='str', default='getreplica', choices=[ - 'getmaster', 'getreplica', 'getslave', 'changemaster', - 'stopreplica', 'stopslave', 'startreplica', 'startslave', - 'resetmaster', 'resetreplica', 'resetslave', + 'getprimary', 'getmaster', + 'getreplica', 'getslave', + 'changeprimary', 'changemaster', + 'stopreplica', 'stopslave', + 'startreplica', 'startslave', + 'resetprimary', 'resetmaster', + 'resetreplica', 'resetslave', 'resetreplicaall', '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'), + primary_auto_position=dict(type='bool', default=False, aliases=['master_auto_position']), + primary_host=dict(type='str', aliases=['master_host']), + primary_user=dict(type='str', aliases=['master_user']), + primary_password=dict(type='str', no_log=True, aliases=['master_password']), + primary_port=dict(type='int', aliases=['master_port']), + primary_connect_retry=dict(type='int', aliases=['master_connect_retry']), + primary_log_file=dict(type='str', aliases=['master_log_file']), + primary_log_pos=dict(type='int', aliases=['master_log_pos']), 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', no_log=False), - master_ssl_cipher=dict(type='str'), - master_use_gtid=dict(type='str', choices=[ - 'current_pos', 'replica_pos', 'slave_pos', 'disabled']), - master_delay=dict(type='int'), + primary_ssl=dict(type='bool', default=False, aliases=['master_ssl']), + primary_ssl_ca=dict(type='str', aliases=['master_ssl_ca']), + primary_ssl_capath=dict(type='str', aliases=['master_ssl_capath']), + primary_ssl_cert=dict(type='str', aliases=['master_ssl_cert']), + primary_ssl_key=dict(type='str', no_log=False, aliases=['master_ssl_key']), + primary_ssl_cipher=dict(type='str', aliases=['master_ssl_cipher']), + primary_use_gtid=dict(type='str', choices=[ + 'current_pos', 'replica_pos', 'slave_pos', 'disabled'], aliases=['master_use_gtid']), + primary_delay=dict(type='int', aliases=['master_delay']), connection_name=dict(type='str'), channel=dict(type='str'), fail_on_error=dict(type='bool', default=False), @@ -451,33 +479,33 @@ def main(): ], ) mode = module.params["mode"] - master_host = module.params["master_host"] - master_user = module.params["master_user"] - master_password = module.params["master_password"] - master_port = module.params["master_port"] - master_connect_retry = module.params["master_connect_retry"] - master_log_file = module.params["master_log_file"] - master_log_pos = module.params["master_log_pos"] + primary_host = module.params["primary_host"] + primary_user = module.params["primary_user"] + primary_password = module.params["primary_password"] + primary_port = module.params["primary_port"] + primary_connect_retry = module.params["primary_connect_retry"] + primary_log_file = module.params["primary_log_file"] + primary_log_pos = module.params["primary_log_pos"] relay_log_file = module.params["relay_log_file"] relay_log_pos = module.params["relay_log_pos"] - master_ssl = module.params["master_ssl"] - master_ssl_ca = module.params["master_ssl_ca"] - master_ssl_capath = module.params["master_ssl_capath"] - master_ssl_cert = module.params["master_ssl_cert"] - master_ssl_key = module.params["master_ssl_key"] - master_ssl_cipher = module.params["master_ssl_cipher"] - master_auto_position = module.params["master_auto_position"] + primary_ssl = module.params["primary_ssl"] + primary_ssl_ca = module.params["primary_ssl_ca"] + primary_ssl_capath = module.params["primary_ssl_capath"] + primary_ssl_cert = module.params["primary_ssl_cert"] + primary_ssl_key = module.params["primary_ssl_key"] + primary_ssl_cipher = module.params["primary_ssl_cipher"] + primary_auto_position = module.params["primary_auto_position"] 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'] - if module.params.get("master_use_gtid") == 'disabled': - master_use_gtid = 'no' + primary_delay = module.params['primary_delay'] + if module.params.get("primary_use_gtid") == 'disabled': + primary_use_gtid = 'no' else: - master_use_gtid = module.params["master_use_gtid"] + primary_use_gtid = module.params["primary_use_gtid"] connection_name = module.params["connection_name"] channel = module.params['channel'] fail_on_error = module.params['fail_on_error'] @@ -512,18 +540,24 @@ def main(): # "REPLICA" must be used instead of "SLAVE" if impl.uses_replica_terminology(cursor): replica_term = 'REPLICA' - if master_use_gtid == 'slave_pos': - module.deprecate('master_use_gtid "slave_pos" value is deprecated, use "replica_pos" instead.', + if primary_use_gtid == 'slave_pos': + module.deprecate('primary_use_gtid | master_use_gtid "slave_pos" value is ' + 'deprecated, use "replica_pos" instead.', version='3.0.0', collection_name='community.mysql') - master_use_gtid = 'replica_pos' + primary_use_gtid = 'replica_pos' else: replica_term = 'SLAVE' - if master_use_gtid == 'replica_pos': - master_use_gtid = 'slave_pos' + if primary_use_gtid == 'replica_pos': + primary_use_gtid = 'slave_pos' - if mode in "getmaster": - status = get_master_status(cursor) + if mode in ('getprimary', 'getmaster'): + if mode == 'getmaster': + module.deprecate('"getmaster" option is deprecated, use "getprimary" instead.', + version='3.0.0', collection_name='community.mysql') + + status = get_primary_status(cursor) if not isinstance(status, dict): + # TODO: change the word master to primary in 3.0.0 status = dict(Is_Master=False, Is_Primary=False, msg="Server is not configured as mysql master") else: @@ -556,47 +590,50 @@ def main(): module.exit_json(queries=executed_queries, **status) - elif mode in "changemaster": + elif mode in ('changeprimary', 'changemaster'): + if mode == 'changemaster': + module.deprecate('"changemaster" option is deprecated, use "changeprimary" instead.', + version='3.0.0', collection_name='community.mysql') chm = [] result = {} - if master_host is not None: - chm.append("MASTER_HOST='%s'" % master_host) - if master_user is not None: - chm.append("MASTER_USER='%s'" % master_user) - if master_password is not None: - chm.append("MASTER_PASSWORD='%s'" % master_password) - if master_port is not None: - chm.append("MASTER_PORT=%s" % master_port) - if master_connect_retry is not None: - chm.append("MASTER_CONNECT_RETRY=%s" % master_connect_retry) - if master_log_file is not None: - chm.append("MASTER_LOG_FILE='%s'" % master_log_file) - if master_log_pos is not None: - chm.append("MASTER_LOG_POS=%s" % master_log_pos) - if master_delay is not None: - chm.append("MASTER_DELAY=%s" % master_delay) + if primary_host is not None: + chm.append("MASTER_HOST='%s'" % primary_host) + if primary_user is not None: + chm.append("MASTER_USER='%s'" % primary_user) + if primary_password is not None: + chm.append("MASTER_PASSWORD='%s'" % primary_password) + if primary_port is not None: + chm.append("MASTER_PORT=%s" % primary_port) + if primary_connect_retry is not None: + chm.append("MASTER_CONNECT_RETRY=%s" % primary_connect_retry) + if primary_log_file is not None: + chm.append("MASTER_LOG_FILE='%s'" % primary_log_file) + if primary_log_pos is not None: + chm.append("MASTER_LOG_POS=%s" % primary_log_pos) + if primary_delay is not None: + chm.append("MASTER_DELAY=%s" % primary_delay) if relay_log_file is not None: chm.append("RELAY_LOG_FILE='%s'" % relay_log_file) if relay_log_pos is not None: chm.append("RELAY_LOG_POS=%s" % relay_log_pos) - if master_ssl: + if primary_ssl: chm.append("MASTER_SSL=1") - if master_ssl_ca is not None: - chm.append("MASTER_SSL_CA='%s'" % master_ssl_ca) - if master_ssl_capath is not None: - chm.append("MASTER_SSL_CAPATH='%s'" % master_ssl_capath) - if master_ssl_cert is not None: - chm.append("MASTER_SSL_CERT='%s'" % master_ssl_cert) - if master_ssl_key is not None: - chm.append("MASTER_SSL_KEY='%s'" % master_ssl_key) - if master_ssl_cipher is not None: - chm.append("MASTER_SSL_CIPHER='%s'" % master_ssl_cipher) - if master_auto_position: + if primary_ssl_ca is not None: + chm.append("MASTER_SSL_CA='%s'" % primary_ssl_ca) + if primary_ssl_capath is not None: + chm.append("MASTER_SSL_CAPATH='%s'" % primary_ssl_capath) + if primary_ssl_cert is not None: + chm.append("MASTER_SSL_CERT='%s'" % primary_ssl_cert) + if primary_ssl_key is not None: + chm.append("MASTER_SSL_KEY='%s'" % primary_ssl_key) + if primary_ssl_cipher is not None: + chm.append("MASTER_SSL_CIPHER='%s'" % primary_ssl_cipher) + if primary_auto_position: chm.append("MASTER_AUTO_POSITION=1") - if master_use_gtid is not None: - chm.append("MASTER_USE_GTID=%s" % master_use_gtid) + if primary_use_gtid is not None: + chm.append("MASTER_USE_GTID=%s" % primary_use_gtid) try: - changemaster(cursor, chm, connection_name, channel) + changeprimary(cursor, chm, connection_name, channel) except mysql_driver.Warning as e: result['warning'] = to_native(e) except Exception as e: @@ -623,11 +660,17 @@ def main(): module.exit_json(msg="Replica stopped", changed=True, queries=executed_queries) else: module.exit_json(msg="Replica already stopped", changed=False, queries=executed_queries) - elif mode in "resetmaster": - reset = reset_master(module, cursor, fail_on_error) + elif mode in ('resetprimary', 'resetmaster'): + if mode == 'resetmaster': + module.deprecate('"resetmaster" option is deprecated, use "resetprimary" instead.', + version='3.0.0', collection_name='community.mysql') + + reset = reset_primary(module, cursor, fail_on_error) if reset is True: + # TODO: Change "Master" to "Primary" in release 3.0.0 module.exit_json(msg="Master reset", changed=True, queries=executed_queries) else: + # TODO: Change "Master" to "Primary" in release 3.0.0 module.exit_json(msg="Master already reset", changed=False, queries=executed_queries) elif mode in ("resetreplica", "resetslave"): if mode == "resetslave": diff --git a/tests/integration/targets/test_mysql_replication/defaults/main.yml b/tests/integration/targets/test_mysql_replication/defaults/main.yml index fefcf29..d2d2080 100644 --- a/tests/integration/targets/test_mysql_replication/defaults/main.yml +++ b/tests/integration/targets/test_mysql_replication/defaults/main.yml @@ -7,7 +7,7 @@ mysql_replica2_port: 3309 test_db: test_db test_table: test_table -test_master_delay: 60 +test_primary_delay: 60 replication_user: replication_user replication_pass: replication_pass dump_path: /tmp/dump.sql diff --git a/tests/integration/targets/test_mysql_replication/tasks/issue-28.yml b/tests/integration/targets/test_mysql_replication/tasks/issue-28.yml index c666820..7943e35 100644 --- a/tests/integration/targets/test_mysql_replication/tasks/issue-28.yml +++ b/tests/integration/targets/test_mysql_replication/tasks/issue-28.yml @@ -36,7 +36,7 @@ - name: attempt connection with newly created user (expect failure) mysql_replication: - mode: getmaster + mode: getprimary login_user: '{{ user_name_1 }}' login_password: '{{ user_password_1 }}' login_host: 127.0.0.1 @@ -57,7 +57,7 @@ - name: attempt connection with newly created user ignoring hostname mysql_replication: - mode: getmaster + mode: getprimary login_user: '{{ user_name_1 }}' login_password: '{{ user_password_1 }}' login_host: 127.0.0.1 diff --git a/tests/integration/targets/test_mysql_replication/tasks/main.yml b/tests/integration/targets/test_mysql_replication/tasks/main.yml index 239598a..d4c7150 100644 --- a/tests/integration/targets/test_mysql_replication/tasks/main.yml +++ b/tests/integration/targets/test_mysql_replication/tasks/main.yml @@ -9,13 +9,13 @@ # Initial CI tests of mysql_replication module: - import_tasks: mysql_replication_initial.yml -# Tests of master_delay parameter: -- import_tasks: mysql_replication_master_delay.yml +# Tests of primary_delay parameter: +- import_tasks: mysql_replication_primary_delay.yml # Tests of channel parameter: - import_tasks: mysql_replication_channel.yml -# Tests of resetmaster mode: -- import_tasks: mysql_replication_resetmaster_mode.yml +# Tests of resetprimary mode: +- import_tasks: mysql_replication_resetprimary_mode.yml - include: issue-28.yml diff --git a/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_channel.yml b/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_channel.yml index c95f792..e314aae 100644 --- a/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_channel.yml +++ b/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_channel.yml @@ -8,26 +8,26 @@ login_host: 127.0.0.1 block: - # Get master log file and log pos: - - name: Get master status + # Get primary log file and log pos: + - name: Get primary status mysql_replication: <<: *mysql_params login_port: '{{ mysql_primary_port }}' - mode: getmaster + mode: getprimary register: mysql_primary_status - # Test changemaster mode: + # Test changeprimary mode: - name: Run replication with channel mysql_replication: <<: *mysql_params login_port: '{{ mysql_replica2_port }}' - mode: changemaster - master_host: '{{ mysql_host }}' - master_port: '{{ mysql_primary_port }}' - master_user: '{{ replication_user }}' - master_password: '{{ replication_pass }}' - master_log_file: '{{ mysql_primary_status.File }}' - master_log_pos: '{{ mysql_primary_status.Position }}' + mode: changeprimary + primary_host: '{{ mysql_host }}' + primary_port: '{{ mysql_primary_port }}' + primary_user: '{{ replication_user }}' + primary_password: '{{ replication_pass }}' + primary_log_file: '{{ mysql_primary_status.File }}' + primary_log_pos: '{{ mysql_primary_status.Position }}' channel: '{{ test_channel }}' register: result 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 27743fb..c80bae4 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 @@ -49,12 +49,12 @@ - name: Restore the dump to replica2 shell: '{{ mysql_command }} -P{{ mysql_replica2_port }} < {{ dump_path }}' - # Test getmaster mode: - - name: Get master status + # Test getprimary mode: + - name: Get primary status mysql_replication: <<: *mysql_params login_port: '{{ mysql_primary_port }}' - mode: getmaster + mode: getprimary register: mysql_primary_status - assert: @@ -63,8 +63,8 @@ - mysql_primary_status.Position != 0 - mysql_primary_status is not changed - # Test startreplica fails without changemaster first. This needs fail_on_error - - name: Start replica (using deprecated startslave choice) and fail because master is not specified; failing on error as requested + # Test startreplica fails without changeprimary first. This needs fail_on_error + - name: Start replica (using deprecated startslave choice) and fail because primary is not specified; failing on error as requested mysql_replication: <<: *mysql_params login_port: '{{ mysql_replica1_port }}' @@ -103,21 +103,21 @@ that: - result is not failed - # Test changemaster mode: - # master_ssl_ca will be set as '' to check the module's behaviour for #23976, + # Test changeprimary mode: + # primary_ssl_ca will be set as '' to check the module's behaviour for #23976, # must be converted to an empty string - name: Run replication mysql_replication: <<: *mysql_params login_port: '{{ mysql_replica1_port }}' - mode: changemaster - master_host: '{{ mysql_host }}' - master_port: '{{ mysql_primary_port }}' - master_user: '{{ replication_user }}' - master_password: '{{ replication_pass }}' - master_log_file: '{{ mysql_primary_status.File }}' - master_log_pos: '{{ mysql_primary_status.Position }}' - master_ssl_ca: '' + mode: changeprimary + primary_host: '{{ mysql_host }}' + primary_port: '{{ mysql_primary_port }}' + primary_user: '{{ replication_user }}' + primary_password: '{{ replication_pass }}' + primary_log_file: '{{ mysql_primary_status.File }}' + primary_log_pos: '{{ mysql_primary_status.Position }}' + primary_ssl_ca: '' register: result - assert: @@ -179,7 +179,7 @@ pause: seconds: 2 - # Test master log pos has been changed: + # Test primary log pos has been changed: - name: Get replica status mysql_replication: <<: *mysql_params diff --git a/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_master_delay.yml b/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_primary_delay.yml similarity index 84% rename from tests/integration/targets/test_mysql_replication/tasks/mysql_replication_master_delay.yml rename to tests/integration/targets/test_mysql_replication/tasks/mysql_replication_primary_delay.yml index 94a10b2..ecdcc81 100644 --- a/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_master_delay.yml +++ b/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_primary_delay.yml @@ -9,13 +9,13 @@ block: - # Test master_delay mode: + # Test primary_delay mode: - name: Run replication mysql_replication: <<: *mysql_params login_port: '{{ mysql_replica1_port }}' - mode: changemaster - master_delay: '{{ test_master_delay }}' + mode: changeprimary + primary_delay: '{{ test_primary_delay }}' register: result - assert: @@ -31,7 +31,7 @@ mode: startreplica register: result - # Check master_delay: + # Check primary_delay: - name: Get standby status mysql_replication: <<: *mysql_params @@ -41,5 +41,5 @@ - assert: that: - - replica_status.SQL_Delay == {{ test_master_delay }} + - replica_status.SQL_Delay == {{ test_primary_delay }} - replica_status is not changed diff --git a/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_resetmaster_mode.yml b/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_resetprimary_mode.yml similarity index 83% rename from tests/integration/targets/test_mysql_replication/tasks/mysql_replication_resetmaster_mode.yml rename to tests/integration/targets/test_mysql_replication/tasks/mysql_replication_resetprimary_mode.yml index 223e325..a4ed75e 100644 --- a/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_resetmaster_mode.yml +++ b/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_resetprimary_mode.yml @@ -22,20 +22,20 @@ login_port: '{{ mysql_replica1_port }}' mode: resetreplicaall - # Get master initial status: - - name: Get master status + # Get primary initial status: + - name: Get primary status mysql_replication: <<: *mysql_params login_port: '{{ mysql_primary_port }}' - mode: getmaster + mode: getprimary register: mysql_primary_initial_status - # Test resetmaster mode: - - name: Reset master + # Test resetprimary mode: + - name: Reset primary mysql_replication: <<: *mysql_params login_port: '{{ mysql_primary_port }}' - mode: resetmaster + mode: resetprimary register: result - assert: @@ -43,12 +43,12 @@ - result is changed - result.queries == ["RESET MASTER"] - # Get master final status: - - name: Get master status + # Get primary final status: + - name: Get primary status mysql_replication: <<: *mysql_params login_port: '{{ mysql_primary_port }}' - mode: getmaster + mode: getprimary register: mysql_primary_final_status - assert: From 738343d64c058207f2e14aa4a03ac0cb1713304a Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Fri, 23 Apr 2021 13:57:23 +0300 Subject: [PATCH 004/217] mysql: revert changes made in PR 116 (#153) * mysql: revert changes made in PR 116 * Add changelog fragment * Fix CI * Fix CI * Fix CI * Update CI * Fix CI --- .github/workflows/ansible-test-plugins.yml | 2 +- changelogs/fragments/153-mysql_revert_connector_changes.yml | 2 ++ plugins/module_utils/mysql.py | 4 ++-- 3 files changed, 5 insertions(+), 3 deletions(-) create mode 100644 changelogs/fragments/153-mysql_revert_connector_changes.yml diff --git a/.github/workflows/ansible-test-plugins.yml b/.github/workflows/ansible-test-plugins.yml index 7e5c33d..da79c04 100644 --- a/.github/workflows/ansible-test-plugins.yml +++ b/.github/workflows/ansible-test-plugins.yml @@ -61,7 +61,7 @@ jobs: - stable-2.9 - stable-2.10 - stable-2.11 - - devel + #- devel python: - 3.6 connector: diff --git a/changelogs/fragments/153-mysql_revert_connector_changes.yml b/changelogs/fragments/153-mysql_revert_connector_changes.yml new file mode 100644 index 0000000..48c8b36 --- /dev/null +++ b/changelogs/fragments/153-mysql_revert_connector_changes.yml @@ -0,0 +1,2 @@ +bugfixes: +- mysql - revert changes of connector arguments made in pull request 116 causing the invalid keyword argument error (https://github.com/ansible-collections/community.mysql/pull/116). diff --git a/plugins/module_utils/mysql.py b/plugins/module_utils/mysql.py index 67e0033..5af9c20 100644 --- a/plugins/module_utils/mysql.py +++ b/plugins/module_utils/mysql.py @@ -79,7 +79,7 @@ def mysql_connect(module, login_user=None, login_password=None, config_file='', if login_user is not None: config['user'] = login_user if login_password is not None: - config['password'] = login_password + config['passwd'] = login_password if ssl_cert is not None: config['ssl']['cert'] = ssl_cert if ssl_key is not None: @@ -87,7 +87,7 @@ def mysql_connect(module, login_user=None, login_password=None, config_file='', if ssl_ca is not None: config['ssl']['ca'] = ssl_ca if db is not None: - config['database'] = db + config['db'] = db if connect_timeout is not None: config['connect_timeout'] = connect_timeout if check_hostname is not None: From 158471e7ee320d19e8250cf5536c3854b552c03f Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Wed, 28 Apr 2021 08:45:00 +0300 Subject: [PATCH 005/217] Remove changelog fragments after release 2.1.0 (#161) --- .../fragments/147-mysql_replication_deprecate_ret_vals.yml | 5 ----- .../fragments/150-mysql_replication_master_related.yml | 6 ------ changelogs/fragments/153-mysql_revert_connector_changes.yml | 2 -- 3 files changed, 13 deletions(-) delete mode 100644 changelogs/fragments/147-mysql_replication_deprecate_ret_vals.yml delete mode 100644 changelogs/fragments/150-mysql_replication_master_related.yml delete mode 100644 changelogs/fragments/153-mysql_revert_connector_changes.yml diff --git a/changelogs/fragments/147-mysql_replication_deprecate_ret_vals.yml b/changelogs/fragments/147-mysql_replication_deprecate_ret_vals.yml deleted file mode 100644 index 9261453..0000000 --- a/changelogs/fragments/147-mysql_replication_deprecate_ret_vals.yml +++ /dev/null @@ -1,5 +0,0 @@ -major_changes: -- mysql_replication - add deprecation warning that the ``Is_Slave`` and ``Is_Master`` return values will be replaced with ``Is_Primary`` and ``Is_Replica`` in ``community.mysql`` 3.0.0 (https://github.com/ansible-collections/community.mysql/pull/147). - -minor_changes: -- mysql_replication - add the ``Is_Primary`` and ``Is_Replica`` alternatives to the ``Is_Slave`` and ``Is_Master`` return values as a preparation for replacement in ``community.mysql`` 3.0.0 (https://github.com/ansible-collections/community.mysql/pull/147). diff --git a/changelogs/fragments/150-mysql_replication_master_related.yml b/changelogs/fragments/150-mysql_replication_master_related.yml deleted file mode 100644 index 83b3538..0000000 --- a/changelogs/fragments/150-mysql_replication_master_related.yml +++ /dev/null @@ -1,6 +0,0 @@ -minor_changes: -- mysql_replication - change ``master_`` options to ``primary_`` options, add aliases to keep compatibility (https://github.com/ansible-collections/community.mysql/pull/150). -- mysql_replication - add alternative (``primary``) choices to the ``state`` option choices containing ``master`` (https://github.com/ansible-collections/community.mysql/pull/150). - -major_changes: -- mysql_replication - the choices of the ``state`` option containing ``master`` will be finally replaced with the alternative ``primary`` choices in ``community.mysql`` 3.0.0, add deprecation warnings (https://github.com/ansible-collections/community.mysql/pull/150). diff --git a/changelogs/fragments/153-mysql_revert_connector_changes.yml b/changelogs/fragments/153-mysql_revert_connector_changes.yml deleted file mode 100644 index 48c8b36..0000000 --- a/changelogs/fragments/153-mysql_revert_connector_changes.yml +++ /dev/null @@ -1,2 +0,0 @@ -bugfixes: -- mysql - revert changes of connector arguments made in pull request 116 causing the invalid keyword argument error (https://github.com/ansible-collections/community.mysql/pull/116). From ac927fdb085c1068d12bf4567ae6163684344fdd Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Thu, 13 May 2021 17:59:15 +0300 Subject: [PATCH 006/217] Add CONTRIBUTORS file (#166) --- CONTRIBUTORS | 273 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 273 insertions(+) create mode 100644 CONTRIBUTORS diff --git a/CONTRIBUTORS b/CONTRIBUTORS new file mode 100644 index 0000000..7678773 --- /dev/null +++ b/CONTRIBUTORS @@ -0,0 +1,273 @@ +116davinder +20 +28 +29 +4 +4n70w4 +abadger +abondis +acozine +adamchainz +adq +Akasurde +Alexander198961 +alustenberg +aminvakil +amitk79 +amree +Andersson007 +andrewhowdencom +ansibot +anthonyxpalermo +antonioribeiro +apollo13 +aquach +arcmop +asad-at-srt +AshDevilRed +aurimasl +axelll +axisK +azielke +baldpale +banyek +BarbzYHOOL +Berbe +bjne +bmalynovytch +bmildren +boreal321 +brutus +burner1024 +calfonso +candeira +caphrim007 +cdalbergue +checkphi +chrismeyersfsu +ChristopherGAndrews +cmodijk +codeaken +codebymikey +coreylane +CormacBracken +cosmix +cptMikky +crashes +dagwieers +damianmoore +Davidffry +denisemauldin +diclophis +d-lee +dmp1ce +dnelson +dramaley +drybjed +drzraf +DSpeichert +dungdm93 +dwagelaar +dylanjbarth +einarc +E-M +eowin +Ernest0x +esamattis +Everspace +F21 +faitno +felixfontein +flatrocks +fourjay +fraff +g00fy- +geerlingguy +georgeOsdDev +ghjm +ghost +giacmir +giorgio-v +gkoller +gottwald +gstorme +gundalow +hansbaer +hchargois +hluaces +hwali +hyperfocus1338 +igormukhingmailcom +imjoseangel +infigoKriti +ipergenitsa +iredmail +ivandigiusto +jadbaz +jaikdean +jamescassell +janosmiko +jarnold-timeout +JaSafieddine +jborean93 +jctanner +jean-christophe-manciot +Jean-Daniel +jgornick +jhagg +jhoekx +jirib +jkleckner +jkordish +jlaska +Jmainguy +jochu +JoelFeiner +johnavp1989 +jonatasbaldin +Jorge-Rodriguez +jpjaatin +jpmens +JSafieddine +jsmartin +juergenhoetzel +jw34 +kalaisubbiah +kenichi-ogawa-1988 +kkeane +klingac +kotso +kuntalFreshBooks +kurtdavis +larsks +ldesgrange +leeadh +LeonB +leucos +loomsen +lorin +lowwalker +lperezs +makmanalp +manuelmorena +MarcinOrlowski +markdorison +markotitel +marktheunissen +markuman +mattclay +matt-horwood-mayden +mavimo +maxamillion +maxbube +mcgoldrickm +meanstrong +meersjo +megamisan +michaeldg +michalmedvecky +MikeiLL +milky-milk +milosz +mistaka0s +mklassen +mkrizek +mmoya +mohag +mohsenSy +mpdehaan +MRwangyd +mverwijs +mvgrimes +mysqlbox +netmonk +nhojpatrick +nicolas-g +NielsH +nitinkansal1984 +nitzmahone +Ompragash +on +order +organman91 +p53 +pakal +paulbadcock +pennycoders +petoju +petracvv +pgrenaud +philfry +pileofrogs +pkaramol +platypus-geek +plumbeo +pratikgadiya12 +pshanbhag +r0bj +rajsshah86 +reduzent +relrod +resmo +ricco24 +richlv +riupie +rndmh3ro +robertdebock +robpblake +rokka-n +Roxyrob +roysmith +rthouvenin +ruudk +samccann +samdoran +sayap +scottbrown +seanorama +sedrubal +sergey-trukhin +Shaps +shrikeh +sivel +skalfyfan +skoriy88 +sperantus +spoyd +steverweber +steveteahan +stijnopheide +stintel +stoned +strixBE +SWADESNA +tapologo +tejatsk14 +tersmitten +the +the02 +thomasliddledba +time-palominodb +timorunge +Tomasthanes +tomdymond +Tronde +tvlooy +tyll +UncertaintyP +vamshi8 +vanne +vdboor +vmahadev +v-zhuravlev +webmat +wedi +whysthatso +willthames +windowsansiblernew +wrosario +xiata +Xyon +yangchao0512 +ziegenberg +Zverik From 6bce48e77120614a87c96eb78a91e401d99f4a04 Mon Sep 17 00:00:00 2001 From: Alexander Skiba Date: Tue, 18 May 2021 07:57:07 +0200 Subject: [PATCH 007/217] Add documentation for privs with functions and procedures (#169) --- plugins/modules/mysql_user.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/plugins/modules/mysql_user.py b/plugins/modules/mysql_user.py index b39b11a..38c1a55 100644 --- a/plugins/modules/mysql_user.py +++ b/plugins/modules/mysql_user.py @@ -53,6 +53,7 @@ options: the module will always report changes. It includes grouping columns by permission (C(SELECT(col1,col2)) instead of C(SELECT(col1),SELECT(col2))). - Can be passed as a dictionary (see the examples). + - Supports GRANTs for procedures and functions (see the examples). type: raw append_privs: description: @@ -188,6 +189,15 @@ EXAMPLES = r''' 'db1.*': 'ALL,GRANT' 'db2.*': 'ALL,GRANT' +# Use 'PROCEDURE' instead of 'FUNCTION' to apply GRANTs for a MySQL procedure instead. +- name: Grant a user the right to execute a function + community.mysql.mysql_user: + name: readonly + password: 12345 + priv: + FUNCTION my_db.my_function: EXECUTE + state: present + # Note that REQUIRESSL is a special privilege that should only apply to *.* by itself. # Setting this privilege in this manner is deprecated. # Use 'tls_requires' instead. From 479edd81d1daf40b1ffbf4131fa77e05f35ae86c Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Tue, 18 May 2021 16:47:02 +0300 Subject: [PATCH 008/217] Update README.md (#168) * Update README.md * Fix * Add MAINTAINERS file --- MAINTAINERS | 3 +++ README.md | 26 +++++++++++++++++++++----- 2 files changed, 24 insertions(+), 5 deletions(-) create mode 100644 MAINTAINERS diff --git a/MAINTAINERS b/MAINTAINERS new file mode 100644 index 0000000..b8ff271 --- /dev/null +++ b/MAINTAINERS @@ -0,0 +1,3 @@ +Andersson007 +bmalynovytch +Jorge-Rodriguez diff --git a/README.md b/README.md index b1bd9d2..40203d5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,26 @@ # MySQL collection for Ansible [![Plugins CI](https://github.com/ansible-collections/community.mysql/workflows/Plugins%20CI/badge.svg?event=push)](https://github.com/ansible-collections/community.mysql/actions?query=workflow%3A"Plugins+CI") [![Roles CI](https://github.com/ansible-collections/community.mysql/workflows/Roles%20CI/badge.svg?event=push)](https://github.com/ansible-collections/community.mysql/actions?query=workflow%3A"Roles+CI") [![Codecov](https://img.shields.io/codecov/c/github/ansible-collections/community.mysql)](https://codecov.io/gh/ansible-collections/community.mysql) +## Code of Conduct + +We follow [Ansible Code of Conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html) in all our interactions within this project. + +## Contributing + +The content of this collection are made by [people](CONTRIBUTORS) like you. + +Every contribution is very welcomed! + +You don't know how to start? Refer to our [contribution guide](CONTRIBUTING.md)! + +The current maintainers are listed in the [MAINTAINERS](MAINTAINERS) file. Don't hesitate to reach them out mentioning in the proposals. + +## Governance + +The process of decision making in this collection is based on discussing and finding consensus among participants. + +Every voice is important and every idea is valuable. If you have something on your mind, create an issue or dedicated discussion and let's discuss it! + ## Included content - **Modules**: @@ -20,7 +40,7 @@ ## External requirements -The MySQL modules rely on a MySQL connector. The list of supported drivers is below: +The MySQL modules rely on a MySQL connector. The list of supported drivers is below: - [PyMySQL](https://github.com/PyMySQL/PyMySQL) - [MySQLdb](https://github.com/PyMySQL/mysqlclient-python) @@ -46,10 +66,6 @@ collections: See [Ansible Using collections](https://docs.ansible.com/ansible/latest/user_guide/collections_using.html) for more details. -## Contributing - -See the [contribution guide](CONTRIBUTING.md). - ## Licensing From c909aa2182421e3f2ff25cf97029470d4b3119f6 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Thu, 20 May 2021 14:40:16 +0300 Subject: [PATCH 009/217] Improve wording in README (#170) * Improve wording in README * Update README.md Co-authored-by: Jorge Rodriguez (A.K.A. Tiriel) Co-authored-by: Jorge Rodriguez (A.K.A. Tiriel) --- MAINTAINERS | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index b8ff271..1b71ea3 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1,3 +1,3 @@ -Andersson007 bmalynovytch Jorge-Rodriguez +Andersson007 diff --git a/README.md b/README.md index 40203d5..305e652 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ We follow [Ansible Code of Conduct](https://docs.ansible.com/ansible/latest/comm The content of this collection are made by [people](CONTRIBUTORS) like you. -Every contribution is very welcomed! +Any kind of contribution is very welcome. You don't know how to start? Refer to our [contribution guide](CONTRIBUTING.md)! From 2236110bae9bf022b13477ec486b06d29fe4bd6c Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Thu, 20 May 2021 14:41:11 +0300 Subject: [PATCH 010/217] Update REVIEW_CHECKLIST.md (#171) --- REVIEW_CHECKLIST.md | 37 +------------------------------------ 1 file changed, 1 insertion(+), 36 deletions(-) diff --git a/REVIEW_CHECKLIST.md b/REVIEW_CHECKLIST.md index 3ea3440..9dccf7e 100644 --- a/REVIEW_CHECKLIST.md +++ b/REVIEW_CHECKLIST.md @@ -1,38 +1,3 @@ # Review Checklist -When reviewing, keep in mind that we follow [Ansible Code of Conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html) in all our contributions and interactions within this repository. - -If you are a committer, also refer to the [Ansible committer guidelines](https://docs.ansible.com/ansible/devel/community/committer_guidelines.html). - -**General tips** -- Try to create a culture of collaboration when reviewing -- Welcome the author and thank them for the pull request -- When suggesting changes, try to use questions, not statements -- When suggesting mandatory changes, do it as politely as possible providing documentation references -- If your suggestion is optional or a matter of personal preferences, please say it explicitly -- When asking for adding tests or for complex code refactoring, say that the author is welcome to ask for clarifications and help if they need -- If somebody suggests a good idea, mention it or put a thumbs up -- After merging, thank the author and reviewers for their time and effort - -**Standards and documentation** -- [ ] if the pull request is not a documentation fix, it must include a [changelog fragment](https://docs.ansible.com/ansible/devel/community/development_process.html#creating-a-changelog-fragment) - please check the format carefully -- [ ] if new files are added with the pull request, they follow the [licensing rules](https://github.com/ansible-collections/overview/blob/main/collection_requirements.rst#licensing) -- [ ] the changes follow the [Ansible documentation standards](https://docs.ansible.com/ansible/devel/dev_guide/developing_modules_documenting.html) and the [style guide](https://docs.ansible.com/ansible/devel/dev_guide/style_guide/index.html#style-guide) -- [ ] the changes follow the [development conventions](https://docs.ansible.com/ansible/devel/dev_guide/developing_modules_best_practices.html) -- [ ] if a new plugin is added, it is one of the [allowed plugin types](https://github.com/ansible-collections/overview/blob/main/collection_requirements.rst#modules-plugins) -- [ ] documentation, examples, and return sections use FQCNs for the `M(..)` [format macros](https://docs.ansible.com/ansible/latest/dev_guide/developing_modules_documenting.html#linking-and-other-format-macros-within-module-documentation) when referring to modules -- [ ] modules and plugins from ansible-core use `ansible.builtin.` as a FQCN prefix when mentioned -- [ ] when a new option, module, plugin, or return value is added, the corresponding documentation or return sections use `version_added:` containing the *collection* version which they will be first released in - * this usually is the next minor release, sometimes the next major release (example: if 2.7.5 is the current release, the next minor release will be 2.8.0, and the next major release will be 3.0.0) -- [ ] FQCNs are used for `extends_documentation_fragment:`, unless the author is referring to doc_fragments from ansible-core - -**Tests (if applicable and technically possible to implement)** -- [ ] the pull request has [integration tests](https://docs.ansible.com/ansible/devel/dev_guide/testing_integration.html) -- [ ] the pull request has [unit tests](https://docs.ansible.com/ansible/devel/dev_guide/testing_units.html) -- [ ] all changes are covered -- [ ] integration tests also cover `check_mode` (if it is supported) -- [ ] integration tests check an actual state of the system, not only what the module reports (for example, if the module changes a file, check that the file was actually changed by using the `ansible.builtin.stat` module) - -**Other** -- [ ] the pull request does not contain merge commits (see GitHub warnings at the bottom of the pull request) - in this case, ask the author to rebase the pull request branch -- [ ] if the pull request contains breaking changes, ask the author and the collection maintainers if it is really needed and there is no way not to introduce them +Refer to the [Collection review checklist](https://github.com/ansible/community-docs/blob/main/review_checklist.rst). From be4e84a92a39f999c65977a918f8dfd636e0a956 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Mon, 24 May 2021 14:31:30 +0300 Subject: [PATCH 011/217] README: add a note how people can complain (#172) * README: add a note how people can complain * Change * Improve * Update README.md Co-authored-by: Jorge Rodriguez (A.K.A. Tiriel) Co-authored-by: Jorge Rodriguez (A.K.A. Tiriel) --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 305e652..cade639 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,8 @@ We follow [Ansible Code of Conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html) in all our interactions within this project. +If you encounter abusive behavior violating the [Ansible Code of Conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html), please refer to the [policy violations](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html#policy-violations) section of the Code of Conduct for information on how to raise a complaint. + ## Contributing The content of this collection are made by [people](CONTRIBUTORS) like you. From 2a80c301a64bd9e13d8e43b84f1945dac6039ae2 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Tue, 25 May 2021 07:54:16 +0300 Subject: [PATCH 012/217] README: fix typos (#174) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cade639..91103a6 100644 --- a/README.md +++ b/README.md @@ -3,13 +3,13 @@ ## Code of Conduct -We follow [Ansible Code of Conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html) in all our interactions within this project. +We follow the [Ansible Code of Conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html) in all our interactions within this project. If you encounter abusive behavior violating the [Ansible Code of Conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html), please refer to the [policy violations](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html#policy-violations) section of the Code of Conduct for information on how to raise a complaint. ## Contributing -The content of this collection are made by [people](CONTRIBUTORS) like you. +The content of this collection is made by [people](CONTRIBUTORS) like you. Any kind of contribution is very welcome. From 71b274213ecb60fd234aa2663d8e80f509d77161 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Tue, 25 May 2021 07:54:50 +0300 Subject: [PATCH 013/217] Fix a typo in README (#173) From 3335a95ba5f6aab6876bc83f7168ea2383933938 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Wed, 2 Jun 2021 08:05:57 +0300 Subject: [PATCH 014/217] README.md: Add link to IRC (#175) --- MAINTAINERS | 2 +- README.md | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index 1b71ea3..c520538 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1,3 +1,3 @@ bmalynovytch Jorge-Rodriguez -Andersson007 +Andersson007 (andersson007_ in #ansible-community IRC) diff --git a/README.md b/README.md index 91103a6..c64ad43 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,9 @@ You don't know how to start? Refer to our [contribution guide](CONTRIBUTING.md)! The current maintainers are listed in the [MAINTAINERS](MAINTAINERS) file. Don't hesitate to reach them out mentioning in the proposals. +Join us on IRC in the ``ansible-community`` [irc.libera.chat](https://libera.chat/) channel. +See the [Registration guide](https://libera.chat/guides/registration) to learn how to register. + ## Governance The process of decision making in this collection is based on discussing and finding consensus among participants. From 8fad3f85b8e9a2d62ede48535d5aeea6bfdf4d52 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Thu, 3 Jun 2021 11:06:25 +0300 Subject: [PATCH 015/217] README.md, CONTRIBUTING.md: add links to the Maintainer guidelines (#179) --- CONTRIBUTING.md | 2 ++ README.md | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 88207e9..70a18b3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,6 +4,8 @@ We follow [Ansible Code of Conduct](https://docs.ansible.com/ansible/latest/comm If you are a committer, also refer to the [Ansible committer guidelines](https://docs.ansible.com/ansible/devel/community/committer_guidelines.html). +If you are a maintainer / interested in becoming a maintainer of this collection, refer to the [Maintainer guidelines](https://github.com/ansible/community-docs/blob/main/maintaining.rst). + ## Issue tracker Whether you are looking for an opportunity to contribute or you found a bug and already know how to solve it, please go to the [issue tracker](https://github.com/ansible-collections/community.mysql/issues). diff --git a/README.md b/README.md index c64ad43..024568b 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Any kind of contribution is very welcome. You don't know how to start? Refer to our [contribution guide](CONTRIBUTING.md)! -The current maintainers are listed in the [MAINTAINERS](MAINTAINERS) file. Don't hesitate to reach them out mentioning in the proposals. +The current maintainers are listed in the [MAINTAINERS](MAINTAINERS) file. Don't hesitate to reach them out mentioning in the proposals. To learn how to maintain / become a maintainer of this collection, refer to the [Maintainer guidelines](https://github.com/ansible/community-docs/blob/main/maintaining.rst). Join us on IRC in the ``ansible-community`` [irc.libera.chat](https://libera.chat/) channel. See the [Registration guide](https://libera.chat/guides/registration) to learn how to register. From 6d9288d19bc21ca11e9c19fd3e8cc91d947c5cd4 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Fri, 4 Jun 2021 11:44:27 +0300 Subject: [PATCH 016/217] Update README (#181) --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 024568b..cf44f31 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # MySQL collection for Ansible [![Plugins CI](https://github.com/ansible-collections/community.mysql/workflows/Plugins%20CI/badge.svg?event=push)](https://github.com/ansible-collections/community.mysql/actions?query=workflow%3A"Plugins+CI") [![Roles CI](https://github.com/ansible-collections/community.mysql/workflows/Roles%20CI/badge.svg?event=push)](https://github.com/ansible-collections/community.mysql/actions?query=workflow%3A"Roles+CI") [![Codecov](https://img.shields.io/codecov/c/github/ansible-collections/community.mysql)](https://codecov.io/gh/ansible-collections/community.mysql) +This collection is a part of Ansible package. + ## Code of Conduct We follow the [Ansible Code of Conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html) in all our interactions within this project. From 69012a2eb9be7f64f41d790356e4839de33824d9 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Mon, 14 Jun 2021 12:30:35 +0300 Subject: [PATCH 017/217] Update README.md (#183) * Update README.md * Change IRC ref --- README.md | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index cf44f31..7ac617f 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ If you encounter abusive behavior violating the [Ansible Code of Conduct](https: ## Contributing -The content of this collection is made by [people](CONTRIBUTORS) like you. +The content of this collection is made by [people](CONTRIBUTORS) like you, a community of individuals collaborating on making the world better through developing automation software. Any kind of contribution is very welcome. @@ -19,8 +19,15 @@ You don't know how to start? Refer to our [contribution guide](CONTRIBUTING.md)! The current maintainers are listed in the [MAINTAINERS](MAINTAINERS) file. Don't hesitate to reach them out mentioning in the proposals. To learn how to maintain / become a maintainer of this collection, refer to the [Maintainer guidelines](https://github.com/ansible/community-docs/blob/main/maintaining.rst). -Join us on IRC in the ``ansible-community`` [irc.libera.chat](https://libera.chat/) channel. -See the [Registration guide](https://libera.chat/guides/registration) to learn how to register. +## Communication + +We announce releases and important changes through the [Ansible Bullhorn newsletter](https://github.com/ansible/community/issues/546). Be sure you are subscribed. + +Join us in the ``ansible-community`` [IRC channel](https://docs.ansible.com/ansible/devel/community/communication.html#irc-meetings). + +We take part in the global quarterly [Ansible Contributor Summit](https://github.com/ansible/community/wiki/Contributor-Summit) virtually or in-person. Track the [Bullhorn newsletter](https://github.com/ansible/community/issues/546) and join us. + +For more information about communication, refer to the [Ansible Communication guide](https://docs.ansible.com/ansible/devel/community/communication.html). ## Governance @@ -71,6 +78,18 @@ collections: - name: community.mysql ``` +Note that if you install the collection from Ansible Galaxy, it will not be upgraded automatically with upgrade of the Ansible package. To upgrade the collection to the latest available version, run the following command: + +```bash +ansible-galaxy collection install community.mysql --upgrade +``` + +You can also install a specific version of the collection, for example, if you need to downgrade when something is broken in the latest version (please report an issue in this repository). Use the following syntax: + +```bash +ansible-galaxy collection install community.mysql:==2.0.0 +``` + See [Ansible Using collections](https://docs.ansible.com/ansible/latest/user_guide/collections_using.html) for more details. ## Licensing From 8ab6ea771423936e4d992c90ae9c9200ad883da1 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Mon, 14 Jun 2021 16:41:47 +0300 Subject: [PATCH 018/217] README: fix link (#184) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7ac617f..95df196 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ The current maintainers are listed in the [MAINTAINERS](MAINTAINERS) file. Don't We announce releases and important changes through the [Ansible Bullhorn newsletter](https://github.com/ansible/community/issues/546). Be sure you are subscribed. -Join us in the ``ansible-community`` [IRC channel](https://docs.ansible.com/ansible/devel/community/communication.html#irc-meetings). +Join us in the ``ansible-community`` [IRC channel](https://docs.ansible.com/ansible/devel/community/communication.html#irc-channels). We take part in the global quarterly [Ansible Contributor Summit](https://github.com/ansible/community/wiki/Contributor-Summit) virtually or in-person. Track the [Bullhorn newsletter](https://github.com/ansible/community/issues/546) and join us. From cd759924fd21f9eb48cb6d390a6a8f695eb54706 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Tue, 15 Jun 2021 07:57:36 +0300 Subject: [PATCH 019/217] README: fix the channel name (#185) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 95df196..28213f3 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ The current maintainers are listed in the [MAINTAINERS](MAINTAINERS) file. Don't We announce releases and important changes through the [Ansible Bullhorn newsletter](https://github.com/ansible/community/issues/546). Be sure you are subscribed. -Join us in the ``ansible-community`` [IRC channel](https://docs.ansible.com/ansible/devel/community/communication.html#irc-channels). +Join us in the ``#ansible-community`` [IRC channel](https://docs.ansible.com/ansible/devel/community/communication.html#irc-channels). We take part in the global quarterly [Ansible Contributor Summit](https://github.com/ansible/community/wiki/Contributor-Summit) virtually or in-person. Track the [Bullhorn newsletter](https://github.com/ansible/community/issues/546) and join us. From 56a214885a9e6445a0ba864ff900245248371ea8 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Wed, 16 Jun 2021 16:53:32 +0300 Subject: [PATCH 020/217] CONTRIBUTING.md: replace the content with a link to Ansible contributing guidelines (#187) --- CONTRIBUTING.md | 201 +----------------------------------------------- 1 file changed, 1 insertion(+), 200 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 70a18b3..edcfe55 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,202 +1,3 @@ # Contributing -We follow [Ansible Code of Conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html) in all our contributions and interactions within this repository. - -If you are a committer, also refer to the [Ansible committer guidelines](https://docs.ansible.com/ansible/devel/community/committer_guidelines.html). - -If you are a maintainer / interested in becoming a maintainer of this collection, refer to the [Maintainer guidelines](https://github.com/ansible/community-docs/blob/main/maintaining.rst). - -## Issue tracker - -Whether you are looking for an opportunity to contribute or you found a bug and already know how to solve it, please go to the [issue tracker](https://github.com/ansible-collections/community.mysql/issues). -There you can find feature ideas to implement, reports about bugs to solve, or submit an issue to discuss your idea before implementing it which can help choose a right direction at the beginning of your work and potentially save a lot of time and effort. -Also somebody may already have started discussing or working on implementing the same or a similar idea, -so you can cooperate to create a better solution together. - -## Open pull requests - -Look through currently [open pull requests](https://github.com/ansible-collections/community.mysql/pulls). -You can help by reviewing them. Reviews help move pull requests to merge state. Some good pull requests cannot be merged only due to a lack of reviews. And it is always worth saying that good reviews are often more valuable than pull requests themselves. For more information how to provide a good review, refer to the [review checklist](REVIEW_CHECKLIST.md). - -Also, consider taking up a valuable, reviewed, but abandoned pull request which you could politely ask the original authors to complete yourself. - -## Discussions - -For open questions, broad suggestions, and other comments that will not typically fit in the scope of an issue or pull request, [discussions](https://github.com/ansible-collections/community.mysql/discussions) are available. That section provides a place to have a more open and informal conversation about any and all things related to this collection, included but not limited to future development plans, functionality explanations and feature proposals that are not yet fleshed out enough for an issue of their own. - -## Looking for an idea to implement - -First, see the paragraphs above. - -If you came up with a new feature, it is always worth creating an issue -before starting to write code to discuss the idea with the community first. -If you are going to implement the feature yourself, say it in the issue explicitly to avoid working in parallel with somebody else. - -## Step-by-step guide how to get into development quickly - -We assume that you use Linux as a work environment (you can use a virtual machine as well) and have `git` installed. - -1. If possible, make sure that you have installed and started `docker`. While you can also run tests without docker, this makes it a lot easier since you do not have to install the precise requirements, and tests are running properly isolated and in the exact same environments as in CI. You often can also use `podman` with the `docker` executable shim, so if you have that you probably do not need to install `docker`. - -2. Clone the [ansible-core](https://github.com/ansible/ansible) repository: -```bash -git clone https://github.com/ansible/ansible.git -``` - -Instead of installing ansible-core from source, you can also work with an already existing installation of Ansible, ansible-base or ansible-core. Simply skip steps 2 and 3 in that case. - -3. Go to the cloned repository and prepare the environment: -```bash -cd ansible && source hacking/env-setup -pip install -r requirements.txt -cd ~ -``` -4. Create the following directories in your home directory: -```bash -mkdir -p ~/ansible_collections/community/mysql -``` - -5. Fork the `community.mysql` repository through the GitHub web interface. - -6. Clone the forked repository from your profile to the created path: -```bash -git clone https://github.com/YOURACC/community.mysql.git ~/ansible_collections/community/mysql -``` - -If you prefer to use the SSH protocol: -```bash -git clone git@github.com:YOURACC/community.mysql.git ~/ansible_collections/community/mysql -``` - - -7. Go to your new cloned repository. -```bash -cd ~/ansible_collections/community/mysql -``` - -8. Be sure you are in the main branch: -```bash -git status -``` - -9. Show remotes. There should be the `origin` repository only: -```bash -git remote -v -``` - -10. Add the `upstream` repository: -```bash -git remote add upstream https://github.com/ansible-collections/community.mysql.git -``` - -11. Update your local `main` branch: -```bash -git fetch upstream -git rebase upstream/main -``` - -12. Create a branch for your changes: -```bash -git checkout -b name_of_my_branch -``` - -13. We recommend you start with writing integration tests if applicable. - -Note: If there are any difficulties with writing the tests or you are not sure if the case can be covered, feel free to skip this step. -If needed, other contributors can help you with it later. - -All integration tests are stored in `tests/integration/targets` subdirectories. -Go to the subdirectory containing the name of module you are going to change. -For example, if you are fixing the `mysql_user` module, its tests are in `tests/integration/targets/test_mysql_user/tasks`. - -The `main.yml` file holds test tasks and includes other test files. -Look for a suitable test file to integrate your tests or create and include a dedicated test file. -You can use one of the existing test files as a draft. - -When fixing a bug, write a task which reproduces the bug from the issue. - -Put the reported case in the tests, then run integration tests with the following command: -```bash -ansible-test integration test_mysql_user --docker -vvv -``` -If the tests do not want to run, first, check you complete step 3 of this guide. - -If the tests ran successfully, there are usually two possible outcomes: -a) If the bug has not appeared and the tests have passed successfully, ask the reporter to provide more details. The bug can be not a bug actually or can relate to a particular software version used or specifics of local environment configuration. - -b) The bug has appeared and the tests has failed as expected showing the reported symptoms. - -14. Fix the bug. - -15. Run `flake8` against a changed file. If it is `plugins/modules/mysql_user.py`: -```bash -flake8 plugins/modules/mysql_user.py -``` -It is worth installing and running `flake8` against the changed file(s) first. -It shows unused imports, which is not shown by sanity tests (see the next step), as well as other common issues. -Optionally, you can use the `--max-line-length=160` command-line argument. - -16. Run sanity tests: -```bash -ansible-test sanity plugins/modules/mysql_user.py --docker -``` -If they failed, look at the output carefully - it is usually very informative and helps to identify a problem line quickly. -Sanity failings usually relate to wrong code and documentation formatting. - -17. Run integration tests: -```bash -ansible-test integration test_mysql_user --docker -vvv -``` - -There are two possible outcomes: -a) They have failed. Look at the output of the command. -Fix the problem place in the code and run again. -Repeat the cycle until the tests pass. - -b) They have passed. Remember they failed originally? Our congratulations! You have fixed the bug. - -18. Commit your changes with an informative but short commit message: -```bash -git add /path/to/changed/file -git commit -m "mysql_user: fix crash when ..." -``` - -19. Push the branch to the `origin` (your fork): -```bash -git push origin name_of_my_branch -``` - -20. Go to the `upstream` (http://github.com/ansible-collections/community.mysql). - -21. Go to `Pull requests` tab and create a pull request. - -GitHub is tracking your fork, so it should see the new branch in it and automatically offer -to create a pull request. Sometimes GitHub does not do it and you should click the `New pull request` button yourself. -Then choose `compare across forks` under the `Compare changes` title. -Choose your repository and the new branch you pushed in the right drop-down list. -Confirm. Fill out the pull request template with all information you want to mention. -Put "Fixes + link to the issue" in the pull request's description. -Put "[WIP] + short description" in the pull request's title. It's often a good idea to mention the name of the module/plugin you are modifying at the beginning of the description. -Click `Create pull request`. - -22. Add a [changelog fragment](https://docs.ansible.com/ansible/devel/community/development_process.html#changelogs) to the `changelog/fragments` directory. It will be published in release notes, so users will know about the fix. - -Commit and push it: -```bash -git add changelog/fragments/myfragment.yml -git commit -m "Add changelog fragment" -git push origin name_of_my_branch -``` - -23. The CI tests will run automatically on Red Hat infrastructure after every commit. - -You will see the CI status in the bottom of your pull request. -If they are green, remove "[WIP]" from the title. Mention the issue reporter in a comment and let contributors know that the pull request is "Ready for review". - -24. Wait for reviews. You can also ask for review on IRC in the #ansible-community channel. - -25. If the pull request looks good to the community, committers will merge it. - -For details, refer to the [Ansible developer guide](https://docs.ansible.com/ansible/latest/dev_guide/index.html). - -If you find any inconsistencies or places in this document which can be improved, feel free to raise an issue or pull request to fix it. +Refer to the [Ansible Contributing guidelines](https://github.com/ansible/community-docs/blob/main/contributing.rst) to learn how to contribute to this collection. From adb201a7950a43a38c8708d2d00853001ee11ae8 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Thu, 17 Jun 2021 16:55:24 +0300 Subject: [PATCH 021/217] Update README (#186) * Update README * Fix * Fix * Fix * Fix * Add Libera.Chat link --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 28213f3..55df07e 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # MySQL collection for Ansible [![Plugins CI](https://github.com/ansible-collections/community.mysql/workflows/Plugins%20CI/badge.svg?event=push)](https://github.com/ansible-collections/community.mysql/actions?query=workflow%3A"Plugins+CI") [![Roles CI](https://github.com/ansible-collections/community.mysql/workflows/Roles%20CI/badge.svg?event=push)](https://github.com/ansible-collections/community.mysql/actions?query=workflow%3A"Roles+CI") [![Codecov](https://img.shields.io/codecov/c/github/ansible-collections/community.mysql)](https://codecov.io/gh/ansible-collections/community.mysql) -This collection is a part of Ansible package. +This collection is a part of the Ansible package. ## Code of Conduct @@ -11,21 +11,21 @@ If you encounter abusive behavior violating the [Ansible Code of Conduct](https: ## Contributing -The content of this collection is made by [people](CONTRIBUTORS) like you, a community of individuals collaborating on making the world better through developing automation software. +The content of this collection is made by [people](https://github.com/ansible-collections/community.mysql/blob/main/CONTRIBUTORS) like you, a community of individuals collaborating on making the world better through developing automation software. Any kind of contribution is very welcome. -You don't know how to start? Refer to our [contribution guide](CONTRIBUTING.md)! +You don't know how to start? Refer to our [contribution guide](https://github.com/ansible-collections/community.mysql/blob/main/CONTRIBUTING.md)! -The current maintainers are listed in the [MAINTAINERS](MAINTAINERS) file. Don't hesitate to reach them out mentioning in the proposals. To learn how to maintain / become a maintainer of this collection, refer to the [Maintainer guidelines](https://github.com/ansible/community-docs/blob/main/maintaining.rst). +The current maintainers (contributors with `write` or higher access) are listed in the [MAINTAINERS](https://github.com/ansible-collections/community.mysql/blob/main/MAINTAINERS) file. If you have questions or need help, feel free to mention them in the proposals. To learn how to maintain / become a maintainer of this collection, refer to the [Maintainer guidelines](https://github.com/ansible/community-docs/blob/main/maintaining.rst). ## Communication -We announce releases and important changes through the [Ansible Bullhorn newsletter](https://github.com/ansible/community/issues/546). Be sure you are subscribed. +We announce releases and important changes through Ansible's [The Bullhorn newsletter](https://eepurl.com/gZmiEP). Be sure you are subscribed. -Join us in the ``#ansible-community`` [IRC channel](https://docs.ansible.com/ansible/devel/community/communication.html#irc-channels). +Join us in the `#ansible` (general use questions and support), `#ansible-community` (community and collection development questions), and other [IRC channels](https://docs.ansible.com/ansible/devel/community/communication.html#irc-channels) on [Libera.Chat](https://libera.chat). -We take part in the global quarterly [Ansible Contributor Summit](https://github.com/ansible/community/wiki/Contributor-Summit) virtually or in-person. Track the [Bullhorn newsletter](https://github.com/ansible/community/issues/546) and join us. +We take part in the global quarterly [Ansible Contributor Summit](https://github.com/ansible/community/wiki/Contributor-Summit) virtually or in-person. Track [The Bullhorn newsletter](https://eepurl.com/gZmiEP) and join us. For more information about communication, refer to the [Ansible Communication guide](https://docs.ansible.com/ansible/devel/community/communication.html). @@ -78,7 +78,7 @@ collections: - name: community.mysql ``` -Note that if you install the collection from Ansible Galaxy, it will not be upgraded automatically with upgrade of the Ansible package. To upgrade the collection to the latest available version, run the following command: +Note that if you install the collection from Ansible Galaxy, it will not be upgraded automatically if you upgrade the Ansible package. To upgrade the collection to the latest available version, run the following command: ```bash ansible-galaxy collection install community.mysql --upgrade From 596ba0cedbd14211c2fc1646633b7aa360546c89 Mon Sep 17 00:00:00 2001 From: Baptiste Mille-Mathias Date: Fri, 9 Jul 2021 09:17:44 +0200 Subject: [PATCH 022/217] fix typo (#190) "optoin" -> "option" --- plugins/doc_fragments/mysql.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/doc_fragments/mysql.py b/plugins/doc_fragments/mysql.py index 6d7a546..b7a9152 100644 --- a/plugins/doc_fragments/mysql.py +++ b/plugins/doc_fragments/mysql.py @@ -67,7 +67,7 @@ options: - Whether to validate the server host name when an SSL connection is required. Corresponds to MySQL CLIs C(--ssl) switch. - Setting this to C(false) disables hostname verification. Use with caution. - Requires pymysql >= 0.7.11. - - This optoin has no effect on MySQLdb. + - This option has no effect on MySQLdb. type: bool version_added: '1.1.0' requirements: From 6f02cb266a6768e82739ae71a80841142940624f Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Wed, 21 Jul 2021 11:31:50 +0300 Subject: [PATCH 023/217] Update README.md (#191) * Update README.md * Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 55df07e..e46b9d9 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,9 @@ If you encounter abusive behavior violating the [Ansible Code of Conduct](https: ## Contributing -The content of this collection is made by [people](https://github.com/ansible-collections/community.mysql/blob/main/CONTRIBUTORS) like you, a community of individuals collaborating on making the world better through developing automation software. +The content of this collection is made by [people](https://github.com/ansible-collections/community.mysql/blob/main/CONTRIBUTORS) just like you, a community of individuals collaborating on making the world better through developing automation software. + +We are actively accepting new contributors. Any kind of contribution is very welcome. From 0fabb2b77a9255bc3d15d5b37da8849098ba9f63 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Wed, 21 Jul 2021 11:51:38 +0300 Subject: [PATCH 024/217] Add MAINTAINING.md, update README.md (#192) --- MAINTAINING.md | 3 +++ README.md | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 MAINTAINING.md diff --git a/MAINTAINING.md b/MAINTAINING.md new file mode 100644 index 0000000..9fad0d3 --- /dev/null +++ b/MAINTAINING.md @@ -0,0 +1,3 @@ +# Maintaining this collection + +Refer to the [Maintainer guidelines](https://github.com/ansible/community-docs/blob/main/maintaining.rst). diff --git a/README.md b/README.md index e46b9d9..8d1349e 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,11 @@ Any kind of contribution is very welcome. You don't know how to start? Refer to our [contribution guide](https://github.com/ansible-collections/community.mysql/blob/main/CONTRIBUTING.md)! -The current maintainers (contributors with `write` or higher access) are listed in the [MAINTAINERS](https://github.com/ansible-collections/community.mysql/blob/main/MAINTAINERS) file. If you have questions or need help, feel free to mention them in the proposals. To learn how to maintain / become a maintainer of this collection, refer to the [Maintainer guidelines](https://github.com/ansible/community-docs/blob/main/maintaining.rst). +## Collection maintenance + +The current maintainers (contributors with `write` or higher access) are listed in the [MAINTAINERS](https://github.com/ansible-collections/community.mysql/blob/main/MAINTAINERS) file. If you have questions or need help, feel free to mention them in the proposals. + +To learn how to maintain / become a maintainer of this collection, refer to the [Maintainer guidelines](https://github.com/ansible-collections/community.mysql/blob/main/MAINTAINING.md). ## Communication From 9055bb4c8c95aede61e7ebe4aa21d7c6217b4a80 Mon Sep 17 00:00:00 2001 From: Tong He <68936428+unnecessary-username@users.noreply.github.com> Date: Wed, 28 Jul 2021 03:18:00 -0400 Subject: [PATCH 025/217] mysql_query: correctly reflect changed status in replace statements (#193) * mysql_query: correctly reflect changed status in replace statements. * Fix the wrong indent. --- CONTRIBUTORS | 1 + ...t_changed_status_in_replace_statements.yml | 2 ++ plugins/modules/mysql_query.py | 2 +- .../test_mysql_query/defaults/main.yml | 1 + .../tasks/mysql_query_initial.yml | 32 +++++++++++++++++++ 5 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 changelogs/fragments/193-reflect_changed_status_in_replace_statements.yml diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 7678773..718caa2 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -255,6 +255,7 @@ Tronde tvlooy tyll UncertaintyP +unnecessary-username vamshi8 vanne vdboor diff --git a/changelogs/fragments/193-reflect_changed_status_in_replace_statements.yml b/changelogs/fragments/193-reflect_changed_status_in_replace_statements.yml new file mode 100644 index 0000000..8ce0461 --- /dev/null +++ b/changelogs/fragments/193-reflect_changed_status_in_replace_statements.yml @@ -0,0 +1,2 @@ +minor_changes: +- mysql_query - correctly reflect changed status in replace statements (https://github.com/ansible-collections/community.mysql/pull/193). \ No newline at end of file diff --git a/plugins/modules/mysql_query.py b/plugins/modules/mysql_query.py index ed3ace4..fc789c5 100644 --- a/plugins/modules/mysql_query.py +++ b/plugins/modules/mysql_query.py @@ -112,7 +112,7 @@ from ansible_collections.community.mysql.plugins.module_utils.mysql import ( ) from ansible.module_utils._text import to_native -DML_QUERY_KEYWORDS = ('INSERT', 'UPDATE', 'DELETE') +DML_QUERY_KEYWORDS = ('INSERT', 'UPDATE', 'DELETE', 'REPLACE') # TRUNCATE is not DDL query but it also returns 0 rows affected: DDL_QUERY_KEYWORDS = ('CREATE', 'DROP', 'ALTER', 'RENAME', 'TRUNCATE') diff --git a/tests/integration/targets/test_mysql_query/defaults/main.yml b/tests/integration/targets/test_mysql_query/defaults/main.yml index 51a3bd7..4ee25ff 100644 --- a/tests/integration/targets/test_mysql_query/defaults/main.yml +++ b/tests/integration/targets/test_mysql_query/defaults/main.yml @@ -7,6 +7,7 @@ test_db: testdb test_table1: test1 test_table2: test2 test_table3: test3 +test_table4: test4 test_script_path: /tmp/test.sql user_name_1: 'db_user1' diff --git a/tests/integration/targets/test_mysql_query/tasks/mysql_query_initial.yml b/tests/integration/targets/test_mysql_query/tasks/mysql_query_initial.yml index b01de55..30182fe 100644 --- a/tests/integration/targets/test_mysql_query/tasks/mysql_query_initial.yml +++ b/tests/integration/targets/test_mysql_query/tasks/mysql_query_initial.yml @@ -289,6 +289,38 @@ - result is failed - result.msg is search('the elements in query list must be strings') + - name: Create {{ test_table4 }} + mysql_query: + <<: *mysql_params + login_db: '{{ test_db }}' + query: 'CREATE TABLE {{ test_table4 }} (id int primary key, story text)' + + - name: Insert test data using replace statement + mysql_query: + <<: *mysql_params + login_db: '{{ test_db }}' + query: "REPLACE INTO {{ test_table4 }} VALUES (1, 'first')" + single_transaction: yes + register: result + + - assert: + that: + - result is changed + - result.rowcount == [1] + + - name: Replace test data + mysql_query: + <<: *mysql_params + login_db: '{{ test_db }}' + query: "REPLACE INTO {{ test_table4 }} VALUES (1, 'one')" + single_transaction: yes + register: result + + - assert: + that: + - result is changed + - result.rowcount == [2] + - name: Drop db {{ test_db }} mysql_query: <<: *mysql_params From ce2b269f8427dfd62eecad71469e45479acb94ef Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Tue, 10 Aug 2021 14:30:34 +0300 Subject: [PATCH 026/217] mysql_role: new module (#189) * mysql_role: new module * fixes * fixes * Add the role class * Check if role exists * role.add() * role.__get_members * tmp * tmp * Change tests * Fix * Fix * add_members() * get_privs() * tmp * __extract_grants() filler version * Before big work * tmp * drop() * tmp * tmp * Big changes * Fix * append_members, detach_members, append_privs * tmp * admin option * Add tests * Add tests * Fix tests * Remove debug warning * Fix tests * Add documentation * Fix MariaDB case * Fix MariaDB * Fix MariaDB * Fix MariaDB * Fix MariaDB * Fix MariaDB * Fix * Fix * Remove debug warning * Add try-except block * tmp * tmp * tmp * Fix * Add err handling * Add user check * Check admin in db * Fix CI * Fix CI * Fix CI * Fix CI * Fix * Add mutually exclusive options * Small refactoring, documenting * Documenting, refactoring * Change docs * Refactoring * Refactoring * Refactoring * Add unit tests * Update README.md --- README.md | 1 + .../implementations/mariadb/role.py | 15 + .../implementations/mysql/role.py | 15 + plugins/module_utils/user.py | 866 +++++++++++++ plugins/modules/mysql_role.py | 1065 +++++++++++++++ plugins/modules/mysql_user.py | 839 +----------- .../targets/test_mysql_role/defaults/main.yml | 16 + .../targets/test_mysql_role/meta/main.yml | 2 + .../targets/test_mysql_role/tasks/main.yml | 7 + .../tasks/mysql_role_initial.yml | 1151 +++++++++++++++++ .../test_mysql_user.py | 2 +- tests/unit/plugins/modules/test_mysql_role.py | 119 ++ 12 files changed, 3273 insertions(+), 825 deletions(-) create mode 100644 plugins/module_utils/implementations/mariadb/role.py create mode 100644 plugins/module_utils/implementations/mysql/role.py create mode 100644 plugins/module_utils/user.py create mode 100644 plugins/modules/mysql_role.py create mode 100644 tests/integration/targets/test_mysql_role/defaults/main.yml create mode 100644 tests/integration/targets/test_mysql_role/meta/main.yml create mode 100644 tests/integration/targets/test_mysql_role/tasks/main.yml create mode 100644 tests/integration/targets/test_mysql_role/tasks/mysql_role_initial.yml rename tests/unit/plugins/{modules => module_utils}/test_mysql_user.py (98%) create mode 100644 tests/unit/plugins/modules/test_mysql_role.py diff --git a/README.md b/README.md index 8d1349e..f3768f0 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,7 @@ Every voice is important and every idea is valuable. If you have something on yo - [mysql_info](https://docs.ansible.com/ansible/latest/collections/community/mysql/mysql_info_module.html) - [mysql_query](https://docs.ansible.com/ansible/latest/collections/community/mysql/mysql_query_module.html) - [mysql_replication](https://docs.ansible.com/ansible/latest/collections/community/mysql/mysql_replication_module.html) + - [mysql_role](https://docs.ansible.com/ansible/latest/collections/community/mysql/mysql_role_module.html) - [mysql_user](https://docs.ansible.com/ansible/latest/collections/community/mysql/mysql_user_module.html) - [mysql_variables](https://docs.ansible.com/ansible/latest/collections/community/mysql/mysql_variables_module.html) diff --git a/plugins/module_utils/implementations/mariadb/role.py b/plugins/module_utils/implementations/mariadb/role.py new file mode 100644 index 0000000..3b4c777 --- /dev/null +++ b/plugins/module_utils/implementations/mariadb/role.py @@ -0,0 +1,15 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from distutils.version import LooseVersion +from ansible_collections.community.mysql.plugins.module_utils.mysql import get_server_version + + +def supports_roles(cursor): + version = get_server_version(cursor) + + return LooseVersion(version) >= LooseVersion('10.0.5') + + +def is_mariadb(): + return True diff --git a/plugins/module_utils/implementations/mysql/role.py b/plugins/module_utils/implementations/mysql/role.py new file mode 100644 index 0000000..42276ff --- /dev/null +++ b/plugins/module_utils/implementations/mysql/role.py @@ -0,0 +1,15 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from distutils.version import LooseVersion +from ansible_collections.community.mysql.plugins.module_utils.mysql import get_server_version + + +def supports_roles(cursor): + version = get_server_version(cursor) + + return LooseVersion(version) >= LooseVersion('8') + + +def is_mariadb(): + return False diff --git a/plugins/module_utils/user.py b/plugins/module_utils/user.py new file mode 100644 index 0000000..c59f72a --- /dev/null +++ b/plugins/module_utils/user.py @@ -0,0 +1,866 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +# This code is part of Ansible, but is an independent component. +# This particular file snippet, and this file snippet only, is BSD licensed. +# Modules you write using this snippet, which is embedded dynamically by Ansible +# still belong to the author of the module, and may assign their own license +# to the complete work. +# +# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause) + +import string +import re + +from ansible.module_utils.six import iteritems + +from ansible_collections.community.mysql.plugins.module_utils.mysql import ( + mysql_driver, +) + + +VALID_PRIVS = frozenset(('CREATE', 'DROP', 'GRANT', 'GRANT OPTION', + 'LOCK TABLES', 'REFERENCES', 'EVENT', 'ALTER', + 'DELETE', 'INDEX', 'INSERT', 'SELECT', 'UPDATE', + 'CREATE TEMPORARY TABLES', 'TRIGGER', 'CREATE VIEW', + 'SHOW VIEW', 'ALTER ROUTINE', 'CREATE ROUTINE', + 'EXECUTE', 'FILE', 'CREATE TABLESPACE', 'CREATE USER', + 'PROCESS', 'PROXY', 'RELOAD', 'REPLICATION CLIENT', + 'REPLICATION SLAVE', 'SHOW DATABASES', 'SHUTDOWN', + 'SUPER', 'ALL', 'ALL PRIVILEGES', 'USAGE', + 'REQUIRESSL', # Deprecated, to be removed in version 3.0.0 + 'CREATE ROLE', 'DROP ROLE', 'APPLICATION_PASSWORD_ADMIN', + 'AUDIT_ADMIN', 'BACKUP_ADMIN', 'BINLOG_ADMIN', + 'BINLOG_ENCRYPTION_ADMIN', 'CLONE_ADMIN', 'CONNECTION_ADMIN', + 'ENCRYPTION_KEY_ADMIN', 'FIREWALL_ADMIN', 'FIREWALL_USER', + 'GROUP_REPLICATION_ADMIN', 'INNODB_REDO_LOG_ARCHIVE', + 'NDB_STORED_USER', 'PERSIST_RO_VARIABLES_ADMIN', + 'REPLICATION_APPLIER', 'REPLICATION_SLAVE_ADMIN', + 'RESOURCE_GROUP_ADMIN', 'RESOURCE_GROUP_USER', + 'ROLE_ADMIN', 'SESSION_VARIABLES_ADMIN', 'SET_USER_ID', + 'SYSTEM_USER', 'SYSTEM_VARIABLES_ADMIN', 'SYSTEM_USER', + 'TABLE_ENCRYPTION_ADMIN', 'VERSION_TOKEN_ADMIN', + 'XA_RECOVER_ADMIN', 'LOAD FROM S3', 'SELECT INTO S3', + 'INVOKE LAMBDA', + 'ALTER ROUTINE', + 'BINLOG ADMIN', + 'BINLOG MONITOR', + 'BINLOG REPLAY', + 'CONNECTION ADMIN', + 'READ_ONLY ADMIN', + 'REPLICATION MASTER ADMIN', + 'REPLICATION SLAVE ADMIN', + 'SET USER', + 'SHOW_ROUTINE', + 'SLAVE MONITOR', + 'REPLICA MONITOR',)) + + +class InvalidPrivsError(Exception): + pass + + +def get_mode(cursor): + cursor.execute('SELECT @@GLOBAL.sql_mode') + result = cursor.fetchone() + mode_str = result[0] + if 'ANSI' in mode_str: + mode = 'ANSI' + else: + mode = 'NOTANSI' + return mode + + +def user_exists(cursor, user, host, host_all): + if host_all: + cursor.execute("SELECT count(*) FROM mysql.user WHERE user = %s", (user,)) + else: + cursor.execute("SELECT count(*) FROM mysql.user WHERE user = %s AND host = %s", (user, host)) + + count = cursor.fetchone() + return count[0] > 0 + + +def sanitize_requires(tls_requires): + sanitized_requires = {} + if tls_requires: + for key in tls_requires.keys(): + sanitized_requires[key.upper()] = tls_requires[key] + if any([key in ["CIPHER", "ISSUER", "SUBJECT"] for key in sanitized_requires.keys()]): + sanitized_requires.pop("SSL", None) + sanitized_requires.pop("X509", None) + return sanitized_requires + + if "X509" in sanitized_requires.keys(): + sanitized_requires = "X509" + else: + sanitized_requires = "SSL" + + return sanitized_requires + return None + + +def mogrify_requires(query, params, tls_requires): + if tls_requires: + if isinstance(tls_requires, dict): + k, v = zip(*tls_requires.items()) + requires_query = " AND ".join(("%s %%s" % key for key in k)) + params += v + else: + requires_query = tls_requires + query = " REQUIRE ".join((query, requires_query)) + return query, params + + +def do_not_mogrify_requires(query, params, tls_requires): + return query, params + + +def get_tls_requires(cursor, user, host): + if user: + if not impl.use_old_user_mgmt(cursor): + query = "SHOW CREATE USER '%s'@'%s'" % (user, host) + else: + query = "SHOW GRANTS for '%s'@'%s'" % (user, host) + + cursor.execute(query) + require_list = [tuple[0] for tuple in filter(lambda x: "REQUIRE" in x[0], cursor.fetchall())] + require_line = require_list[0] if require_list else "" + pattern = r"(?<=\bREQUIRE\b)(.*?)(?=(?:\bPASSWORD\b|$))" + requires_match = re.search(pattern, require_line) + requires = requires_match.group().strip() if requires_match else "" + if any((requires.startswith(req) for req in ('SSL', 'X509', 'NONE'))): + requires = requires.split()[0] + if requires == 'NONE': + requires = None + else: + import shlex + + items = iter(shlex.split(requires)) + requires = dict(zip(items, items)) + return requires or None + + +def get_grants(cursor, user, host): + cursor.execute("SHOW GRANTS FOR %s@%s", (user, host)) + grants_line = list(filter(lambda x: "ON *.*" in x[0], cursor.fetchall()))[0] + pattern = r"(?<=\bGRANT\b)(.*?)(?=(?:\bON\b))" + grants = re.search(pattern, grants_line[0]).group().strip() + return grants.split(", ") + + +def user_add(cursor, user, host, host_all, password, encrypted, + plugin, plugin_hash_string, plugin_auth_string, new_priv, + tls_requires, check_mode): + # we cannot create users without a proper hostname + if host_all: + return False + + if check_mode: + return True + + # Determine what user management method server uses + old_user_mgmt = impl.use_old_user_mgmt(cursor) + + mogrify = do_not_mogrify_requires if old_user_mgmt else mogrify_requires + + if password and encrypted: + if impl.supports_identified_by_password(cursor): + query_with_args = "CREATE USER %s@%s IDENTIFIED BY PASSWORD %s", (user, host, password) + else: + query_with_args = "CREATE USER %s@%s IDENTIFIED WITH mysql_native_password AS %s", (user, host, password) + elif password and not encrypted: + if old_user_mgmt: + query_with_args = "CREATE USER %s@%s IDENTIFIED BY %s", (user, host, password) + else: + cursor.execute("SELECT CONCAT('*', UCASE(SHA1(UNHEX(SHA1(%s)))))", (password,)) + encrypted_password = cursor.fetchone()[0] + query_with_args = "CREATE USER %s@%s IDENTIFIED WITH mysql_native_password AS %s", (user, host, encrypted_password) + elif plugin and plugin_hash_string: + query_with_args = "CREATE USER %s@%s IDENTIFIED WITH %s AS %s", (user, host, plugin, plugin_hash_string) + elif plugin and plugin_auth_string: + query_with_args = "CREATE USER %s@%s IDENTIFIED WITH %s BY %s", (user, host, plugin, plugin_auth_string) + elif plugin: + query_with_args = "CREATE USER %s@%s IDENTIFIED WITH %s", (user, host, plugin) + else: + query_with_args = "CREATE USER %s@%s", (user, host) + + query_with_args_and_tls_requires = query_with_args + (tls_requires,) + cursor.execute(*mogrify(*query_with_args_and_tls_requires)) + + if new_priv is not None: + for db_table, priv in iteritems(new_priv): + privileges_grant(cursor, user, host, db_table, priv, tls_requires) + if tls_requires is not None: + privileges_grant(cursor, user, host, "*.*", get_grants(cursor, user, host), tls_requires) + return True + + +def is_hash(password): + ishash = False + if len(password) == 41 and password[0] == '*': + if frozenset(password[1:]).issubset(string.hexdigits): + ishash = True + return ishash + + +def user_mod(cursor, user, host, host_all, password, encrypted, + plugin, plugin_hash_string, plugin_auth_string, new_priv, + append_privs, tls_requires, module, role=False, maria_role=False): + changed = False + msg = "User unchanged" + grant_option = False + + # Determine what user management method server uses + old_user_mgmt = impl.use_old_user_mgmt(cursor) + + if host_all and not role: + hostnames = user_get_hostnames(cursor, user) + else: + hostnames = [host] + + for host in hostnames: + # Handle clear text and hashed passwords. + if not role: + if bool(password): + + # Get a list of valid columns in mysql.user table to check if Password and/or authentication_string exist + cursor.execute(""" + SELECT COLUMN_NAME FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = 'mysql' AND TABLE_NAME = 'user' AND COLUMN_NAME IN ('Password', 'authentication_string') + ORDER BY COLUMN_NAME DESC LIMIT 1 + """) + colA = cursor.fetchone() + + cursor.execute(""" + SELECT COLUMN_NAME FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = 'mysql' AND TABLE_NAME = 'user' AND COLUMN_NAME IN ('Password', 'authentication_string') + ORDER BY COLUMN_NAME ASC LIMIT 1 + """) + colB = cursor.fetchone() + + # Select hash from either Password or authentication_string, depending which one exists and/or is filled + cursor.execute(""" + SELECT COALESCE( + CASE WHEN %s = '' THEN NULL ELSE %s END, + CASE WHEN %s = '' THEN NULL ELSE %s END + ) + FROM mysql.user WHERE user = %%s AND host = %%s + """ % (colA[0], colA[0], colB[0], colB[0]), (user, host)) + current_pass_hash = cursor.fetchone()[0] + if isinstance(current_pass_hash, bytes): + current_pass_hash = current_pass_hash.decode('ascii') + + if encrypted: + encrypted_password = password + if not is_hash(encrypted_password): + module.fail_json(msg="encrypted was specified however it does not appear to be a valid hash expecting: *SHA1(SHA1(your_password))") + else: + if old_user_mgmt: + cursor.execute("SELECT PASSWORD(%s)", (password,)) + else: + cursor.execute("SELECT CONCAT('*', UCASE(SHA1(UNHEX(SHA1(%s)))))", (password,)) + encrypted_password = cursor.fetchone()[0] + + if current_pass_hash != encrypted_password: + msg = "Password updated" + if module.check_mode: + return (True, msg) + if old_user_mgmt: + cursor.execute("SET PASSWORD FOR %s@%s = %s", (user, host, encrypted_password)) + msg = "Password updated (old style)" + else: + try: + cursor.execute("ALTER USER %s@%s IDENTIFIED WITH mysql_native_password AS %s", (user, host, encrypted_password)) + msg = "Password updated (new style)" + except (mysql_driver.Error) as e: + # https://stackoverflow.com/questions/51600000/authentication-string-of-root-user-on-mysql + # Replacing empty root password with new authentication mechanisms fails with error 1396 + if e.args[0] == 1396: + cursor.execute( + "UPDATE mysql.user SET plugin = %s, authentication_string = %s, Password = '' WHERE User = %s AND Host = %s", + ('mysql_native_password', encrypted_password, user, host) + ) + cursor.execute("FLUSH PRIVILEGES") + msg = "Password forced update" + else: + raise e + changed = True + + # Handle plugin authentication + if plugin and not role: + cursor.execute("SELECT plugin, authentication_string FROM mysql.user " + "WHERE user = %s AND host = %s", (user, host)) + current_plugin = cursor.fetchone() + + update = False + + if current_plugin[0] != plugin: + update = True + + if plugin_hash_string and current_plugin[1] != plugin_hash_string: + update = True + + if plugin_auth_string and current_plugin[1] != plugin_auth_string: + # this case can cause more updates than expected, + # as plugin can hash auth_string in any way it wants + # and there's no way to figure it out for + # a check, so I prefer to update more often than never + update = True + + if update: + if plugin_hash_string: + query_with_args = "ALTER USER %s@%s IDENTIFIED WITH %s AS %s", (user, host, plugin, plugin_hash_string) + elif plugin_auth_string: + query_with_args = "ALTER USER %s@%s IDENTIFIED WITH %s BY %s", (user, host, plugin, plugin_auth_string) + else: + query_with_args = "ALTER USER %s@%s IDENTIFIED WITH %s", (user, host, plugin) + + cursor.execute(*query_with_args) + changed = True + + # Handle privileges + if new_priv is not None: + curr_priv = privileges_get(cursor, user, host, maria_role) + + # If the user has privileges on a db.table that doesn't appear at all in + # the new specification, then revoke all privileges on it. + for db_table, priv in iteritems(curr_priv): + # If the user has the GRANT OPTION on a db.table, revoke it first. + if "GRANT" in priv: + grant_option = True + if db_table not in new_priv: + if user != "root" and "PROXY" not in priv and not append_privs: + msg = "Privileges updated" + if module.check_mode: + return (True, msg) + privileges_revoke(cursor, user, host, db_table, priv, grant_option, maria_role) + changed = True + + # If the user doesn't currently have any privileges on a db.table, then + # we can perform a straight grant operation. + for db_table, priv in iteritems(new_priv): + if db_table not in curr_priv: + msg = "New privileges granted" + if module.check_mode: + return (True, msg) + privileges_grant(cursor, user, host, db_table, priv, tls_requires, maria_role) + changed = True + + # If the db.table specification exists in both the user's current privileges + # and in the new privileges, then we need to see if there's a difference. + db_table_intersect = set(new_priv.keys()) & set(curr_priv.keys()) + for db_table in db_table_intersect: + + # If appending privileges, only the set difference between new privileges and current privileges matter. + # The symmetric difference isn't relevant for append because existing privileges will not be revoked. + if append_privs: + priv_diff = set(new_priv[db_table]) - set(curr_priv[db_table]) + else: + priv_diff = set(new_priv[db_table]) ^ set(curr_priv[db_table]) + + if len(priv_diff) > 0: + msg = "Privileges updated" + if module.check_mode: + return (True, msg) + if not append_privs: + privileges_revoke(cursor, user, host, db_table, curr_priv[db_table], grant_option, maria_role) + privileges_grant(cursor, user, host, db_table, new_priv[db_table], tls_requires, maria_role) + changed = True + + if role: + continue + + # Handle TLS requirements + current_requires = get_tls_requires(cursor, user, host) + if current_requires != tls_requires: + msg = "TLS requires updated" + if module.check_mode: + return (True, msg) + if not old_user_mgmt: + pre_query = "ALTER USER" + else: + pre_query = "GRANT %s ON *.* TO" % ",".join(get_grants(cursor, user, host)) + + if tls_requires is not None: + query = " ".join((pre_query, "%s@%s")) + query_with_args = mogrify_requires(query, (user, host), tls_requires) + else: + query = " ".join((pre_query, "%s@%s REQUIRE NONE")) + query_with_args = query, (user, host) + + cursor.execute(*query_with_args) + changed = True + + return (changed, msg) + + +def user_delete(cursor, user, host, host_all, check_mode): + if check_mode: + return True + + if host_all: + hostnames = user_get_hostnames(cursor, user) + else: + hostnames = [host] + + for hostname in hostnames: + cursor.execute("DROP USER %s@%s", (user, hostname)) + + return True + + +def user_get_hostnames(cursor, user): + cursor.execute("SELECT Host FROM mysql.user WHERE user = %s", (user,)) + hostnames_raw = cursor.fetchall() + hostnames = [] + + for hostname_raw in hostnames_raw: + hostnames.append(hostname_raw[0]) + + return hostnames + + +def privileges_get(cursor, user, host, maria_role=False): + """ MySQL doesn't have a better method of getting privileges aside from the + SHOW GRANTS query syntax, which requires us to then parse the returned string. + Here's an example of the string that is returned from MySQL: + + GRANT USAGE ON *.* TO 'user'@'localhost' IDENTIFIED BY 'pass'; + + This function makes the query and returns a dictionary containing the results. + The dictionary format is the same as that returned by privileges_unpack() below. + """ + output = {} + if not maria_role: + cursor.execute("SHOW GRANTS FOR %s@%s", (user, host)) + else: + cursor.execute("SHOW GRANTS FOR %s", (user)) + grants = cursor.fetchall() + + def pick(x): + if x == 'ALL PRIVILEGES': + return 'ALL' + else: + return x + + for grant in grants: + if not maria_role: + res = re.match("""GRANT (.+) ON (.+) TO (['`"]).*\\3@(['`"]).*\\4( IDENTIFIED BY PASSWORD (['`"]).+\\6)? ?(.*)""", grant[0]) + else: + res = re.match("""GRANT (.+) ON (.+) TO (['`"]).*\\3""", grant[0]) + if res is None: + raise InvalidPrivsError('unable to parse the MySQL grant string: %s' % grant[0]) + privileges = res.group(1).split(",") + privileges = [pick(x.strip()) for x in privileges] + + # Handle cases when there's privs like GRANT SELECT (colA, ...) in privs. + # To this point, the privileges list can look like + # ['SELECT (`A`', '`B`)', 'INSERT'] that is incorrect (SELECT statement is splitted). + # Columns should also be sorted to compare it with desired privileges later. + # Determine if there's a case similar to the above: + privileges = normalize_col_grants(privileges) + + if not maria_role: + if "WITH GRANT OPTION" in res.group(7): + privileges.append('GRANT') + db = res.group(2) + output.setdefault(db, []).extend(privileges) + return output + + +def normalize_col_grants(privileges): + """Fix and sort grants on columns in privileges list + + Make ['SELECT (A, B)', 'INSERT (A, B)', 'DETELE'] + from ['SELECT (A', 'B)', 'INSERT (B', 'A)', 'DELETE']. + See unit tests in tests/unit/plugins/modules/test_mysql_user.py + """ + for grant in ('SELECT', 'UPDATE', 'INSERT', 'REFERENCES'): + start, end = has_grant_on_col(privileges, grant) + # If not, either start and end will be None + if start is not None: + privileges = handle_grant_on_col(privileges, start, end) + + return privileges + + +def has_grant_on_col(privileges, grant): + """Check if there is a statement like SELECT (colA, colB) + in the privilege list. + + Return (start index, end index). + """ + # Determine elements of privileges where + # columns are listed + start = None + end = None + for n, priv in enumerate(privileges): + if '%s (' % grant in priv: + # We found the start element + start = n + + if start is not None and ')' in priv: + # We found the end element + end = n + break + + if start is not None and end is not None: + # if the privileges list consist of, for example, + # ['SELECT (A', 'B), 'INSERT'], return indexes of related elements + return start, end + else: + # If start and end position is the same element, + # it means there's expression like 'SELECT (A)', + # so no need to handle it + return None, None + + +def handle_grant_on_col(privileges, start, end): + """Handle cases when the privs like SELECT (colA, ...) is in the privileges list.""" + # When the privileges list look like ['SELECT (colA,', 'colB)'] + # (Notice that the statement is splitted) + if start != end: + output = list(privileges[:start]) + + select_on_col = ', '.join(privileges[start:end + 1]) + + select_on_col = sort_column_order(select_on_col) + + output.append(select_on_col) + + output.extend(privileges[end + 1:]) + + # When it look like it should be, e.g. ['SELECT (colA, colB)'], + # we need to be sure, the columns is sorted + else: + output = list(privileges) + output[start] = sort_column_order(output[start]) + + return output + + +def sort_column_order(statement): + """Sort column order in grants like SELECT (colA, colB, ...). + + MySQL changes columns order like below: + --------------------------------------- + mysql> GRANT SELECT (testColA, testColB), INSERT ON `testDb`.`testTable` TO 'testUser'@'localhost'; + Query OK, 0 rows affected (0.04 sec) + + mysql> flush privileges; + Query OK, 0 rows affected (0.00 sec) + + mysql> SHOW GRANTS FOR testUser@localhost; + +---------------------------------------------------------------------------------------------+ + | Grants for testUser@localhost | + +---------------------------------------------------------------------------------------------+ + | GRANT USAGE ON *.* TO 'testUser'@'localhost' | + | GRANT SELECT (testColB, testColA), INSERT ON `testDb`.`testTable` TO 'testUser'@'localhost' | + +---------------------------------------------------------------------------------------------+ + + We should sort columns in our statement, otherwise the module always will return + that the state has changed. + """ + # 1. Extract stuff inside () + # 2. Split + # 3. Sort + # 4. Put between () and return + + # "SELECT/UPDATE/.. (colA, colB) => "colA, colB" + tmp = statement.split('(') + priv_name = tmp[0] + columns = tmp[1].rstrip(')') + + # "colA, colB" => ["colA", "colB"] + columns = columns.split(',') + + for i, col in enumerate(columns): + col = col.strip() + columns[i] = col.strip('`') + + columns.sort() + return '%s(%s)' % (priv_name, ', '.join(columns)) + + +def privileges_unpack(priv, mode): + """ Take a privileges string, typically passed as a parameter, and unserialize + it into a dictionary, the same format as privileges_get() above. We have this + custom format to avoid using YAML/JSON strings inside YAML playbooks. Example + of a privileges string: + + mydb.*:INSERT,UPDATE/anotherdb.*:SELECT/yetanother.*:ALL + + The privilege USAGE stands for no privileges, so we add that in on *.* if it's + not specified in the string, as MySQL will always provide this by default. + """ + if mode == 'ANSI': + quote = '"' + else: + quote = '`' + output = {} + privs = [] + for item in priv.strip().split('/'): + pieces = item.strip().rsplit(':', 1) + dbpriv = pieces[0].rsplit(".", 1) + + # Check for FUNCTION or PROCEDURE object types + parts = dbpriv[0].split(" ", 1) + object_type = '' + if len(parts) > 1 and (parts[0] == 'FUNCTION' or parts[0] == 'PROCEDURE'): + object_type = parts[0] + ' ' + dbpriv[0] = parts[1] + + # Do not escape if privilege is for database or table, i.e. + # neither quote *. nor .* + for i, side in enumerate(dbpriv): + if side.strip('`') != '*': + dbpriv[i] = '%s%s%s' % (quote, side.strip('`'), quote) + pieces[0] = object_type + '.'.join(dbpriv) + + if '(' in pieces[1]: + output[pieces[0]] = re.split(r',\s*(?=[^)]*(?:\(|$))', pieces[1].upper()) + for i in output[pieces[0]]: + privs.append(re.sub(r'\s*\(.*\)', '', i)) + else: + output[pieces[0]] = pieces[1].upper().split(',') + privs = output[pieces[0]] + + # Handle cases when there's privs like GRANT SELECT (colA, ...) in privs. + output[pieces[0]] = normalize_col_grants(output[pieces[0]]) + + new_privs = frozenset(privs) + if not new_privs.issubset(VALID_PRIVS): + raise InvalidPrivsError('Invalid privileges specified: %s' % new_privs.difference(VALID_PRIVS)) + + if '*.*' not in output: + output['*.*'] = ['USAGE'] + + return output + + +def privileges_revoke(cursor, user, host, db_table, priv, grant_option, maria_role=False): + # Escape '%' since mysql db.execute() uses a format string + db_table = db_table.replace('%', '%%') + if grant_option: + query = ["REVOKE GRANT OPTION ON %s" % db_table] + if not maria_role: + query.append("FROM %s@%s") + else: + query.append("FROM %s") + + query = ' '.join(query) + cursor.execute(query, (user, host)) + priv_string = ",".join([p for p in priv if p not in ('GRANT', )]) + query = ["REVOKE %s ON %s" % (priv_string, db_table)] + + if not maria_role: + query.append("FROM %s@%s") + params = (user, host) + else: + query.append("FROM %s") + params = (user) + + query = ' '.join(query) + cursor.execute(query, params) + + +def privileges_grant(cursor, user, host, db_table, priv, tls_requires, maria_role=False): + # Escape '%' since mysql db.execute uses a format string and the + # specification of db and table often use a % (SQL wildcard) + db_table = db_table.replace('%', '%%') + priv_string = ",".join([p for p in priv if p not in ('GRANT', )]) + query = ["GRANT %s ON %s" % (priv_string, db_table)] + + if not maria_role: + query.append("TO %s@%s") + params = (user, host) + else: + query.append("TO %s") + params = (user) + + if tls_requires and impl.use_old_user_mgmt(cursor): + query, params = mogrify_requires(" ".join(query), params, tls_requires) + query = [query] + if 'GRANT' in priv: + query.append("WITH GRANT OPTION") + query = ' '.join(query) + cursor.execute(query, params) + + +def convert_priv_dict_to_str(priv): + """Converts privs dictionary to string of certain format. + + Args: + priv (dict): Dict of privileges that needs to be converted to string. + + Returns: + priv (str): String representation of input argument. + """ + priv_list = ['%s:%s' % (key, val) for key, val in iteritems(priv)] + + return '/'.join(priv_list) + + +def handle_requiressl_in_priv_string(module, priv, tls_requires): + module.deprecate('The "REQUIRESSL" privilege is deprecated, use the "tls_requires" option instead.', + version='3.0.0', collection_name='community.mysql') + priv_groups = re.search(r"(.*?)(\*\.\*:)([^/]*)(.*)", priv) + if priv_groups.group(3) == "REQUIRESSL": + priv = priv_groups.group(1) + priv_groups.group(4) or None + else: + inner_priv_groups = re.search(r"(.*?),?REQUIRESSL,?(.*)", priv_groups.group(3)) + priv = '{0}{1}{2}{3}'.format( + priv_groups.group(1), + priv_groups.group(2), + ','.join(filter(None, (inner_priv_groups.group(1), inner_priv_groups.group(2)))), + priv_groups.group(4) + ) + if not tls_requires: + tls_requires = "SSL" + else: + module.warn('Ignoring "REQUIRESSL" privilege as "tls_requires" is defined and it takes precedence.') + return priv, tls_requires + + +# Alter user is supported since MySQL 5.6 and MariaDB 10.2.0 +def server_supports_alter_user(cursor): + """Check if the server supports ALTER USER statement or doesn't. + + Args: + cursor (cursor): DB driver cursor object. + + Returns: True if supports, False otherwise. + """ + cursor.execute("SELECT VERSION()") + version_str = cursor.fetchone()[0] + version = version_str.split('.') + + if 'mariadb' in version_str.lower(): + # MariaDB 10.2 and later + if int(version[0]) * 1000 + int(version[1]) >= 10002: + return True + else: + return False + else: + # MySQL 5.6 and later + if int(version[0]) * 1000 + int(version[1]) >= 5006: + return True + else: + return False + + +def get_resource_limits(cursor, user, host): + """Get user resource limits. + + Args: + cursor (cursor): DB driver cursor object. + user (str): User name. + host (str): User host name. + + Returns: Dictionary containing current resource limits. + """ + + query = ('SELECT max_questions AS MAX_QUERIES_PER_HOUR, ' + 'max_updates AS MAX_UPDATES_PER_HOUR, ' + 'max_connections AS MAX_CONNECTIONS_PER_HOUR, ' + 'max_user_connections AS MAX_USER_CONNECTIONS ' + 'FROM mysql.user WHERE User = %s AND Host = %s') + cursor.execute(query, (user, host)) + res = cursor.fetchone() + + if not res: + return None + + current_limits = { + 'MAX_QUERIES_PER_HOUR': res[0], + 'MAX_UPDATES_PER_HOUR': res[1], + 'MAX_CONNECTIONS_PER_HOUR': res[2], + 'MAX_USER_CONNECTIONS': res[3], + } + return current_limits + + +def match_resource_limits(module, current, desired): + """Check and match limits. + + Args: + module (AnsibleModule): Ansible module object. + current (dict): Dictionary with current limits. + desired (dict): Dictionary with desired limits. + + Returns: Dictionary containing parameters that need to change. + """ + + if not current: + # It means the user does not exists, so we need + # to set all limits after its creation + return desired + + needs_to_change = {} + + for key, val in iteritems(desired): + if key not in current: + # Supported keys are listed in the documentation + # and must be determined in the get_resource_limits function + # (follow 'AS' keyword) + module.fail_json(msg="resource_limits: key '%s' is unsupported." % key) + + try: + val = int(val) + except Exception: + module.fail_json(msg="Can't convert value '%s' to integer." % val) + + if val != current.get(key): + needs_to_change[key] = val + + return needs_to_change + + +def limit_resources(module, cursor, user, host, resource_limits, check_mode): + """Limit user resources. + + Args: + module (AnsibleModule): Ansible module object. + cursor (cursor): DB driver cursor object. + user (str): User name. + host (str): User host name. + resource_limit (dict): Dictionary with desired limits. + check_mode (bool): Run the function in check mode or not. + + Returns: True, if changed, False otherwise. + """ + if not server_supports_alter_user(cursor): + module.fail_json(msg="The server version does not match the requirements " + "for resource_limits parameter. See module's documentation.") + + current_limits = get_resource_limits(cursor, user, host) + + needs_to_change = match_resource_limits(module, current_limits, resource_limits) + + if not needs_to_change: + return False + + if needs_to_change and check_mode: + return True + + # If not check_mode + tmp = [] + for key, val in iteritems(needs_to_change): + tmp.append('%s %s' % (key, val)) + + query = "ALTER USER %s@%s" + query += ' WITH %s' % ' '.join(tmp) + cursor.execute(query, (user, host)) + return True + + +def get_impl(cursor): + global impl + cursor.execute("SELECT VERSION()") + if 'mariadb' in cursor.fetchone()[0].lower(): + from ansible_collections.community.mysql.plugins.module_utils.implementations.mariadb import user as mysqluser + impl = mysqluser + else: + from ansible_collections.community.mysql.plugins.module_utils.implementations.mysql import user as mariauser + impl = mariauser diff --git a/plugins/modules/mysql_role.py b/plugins/modules/mysql_role.py new file mode 100644 index 0000000..80d0144 --- /dev/null +++ b/plugins/modules/mysql_role.py @@ -0,0 +1,1065 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2021, Andrew Klychkov +# 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 +__metaclass__ = type + +DOCUMENTATION = r''' +--- +module: mysql_role + +short_description: Adds, removes, or updates a MySQL role + +description: + - Adds, removes, or updates a MySQL role. + - Roles are supported since MySQL 8.0.0 and MariaDB 10.0.5. + +version_added: '2.2.0' + +options: + name: + description: + - Name of the role to add or remove. + type: str + required: true + + admin: + description: + - Supported by B(MariaDB). + - Name of the admin user of the role (the I(login_user), by default). + type: str + + priv: + description: + - "MySQL privileges string in the format: C(db.table:priv1,priv2)." + - "You can specify multiple privileges by separating each one using + a forward slash: C(db.table:priv/db.table:priv)." + - The format is based on MySQL C(GRANT) statement. + - Database and table names can be quoted, MySQL-style. + - If column privileges are used, the C(priv1,priv2) part must be + exactly as returned by a C(SHOW GRANT) statement. If not followed, + the module will always report changes. It includes grouping columns + by permission (C(SELECT(col1,col2)) instead of C(SELECT(col1),SELECT(col2))). + - Can be passed as a dictionary (see the examples). + - Supports GRANTs for procedures and functions + (see the examples for the M(community.mysql.mysql_user) module). + type: raw + + append_privs: + description: + - Append the privileges defined by the I(priv) option to the existing ones + for this role instead of overwriting them. + type: bool + default: no + + members: + description: + - List of members of the role. + - For users, use the format C(username@hostname). + Always specify the hostname part explicitly. + - For roles, use the format C(rolename). + - Mutually exclusive with I(admin). + type: list + elements: str + + append_members: + description: + - Add members defined by the I(members) option to the existing ones + for this role instead of overwriting them. + - Mutually exclusive with the I(detach_members) and I(admin) option. + type: bool + default: no + + detach_members: + description: + - Detaches members defined by the I(members) option from the role + instead of overwriting all the current members. + - Mutually exclusive with the I(append_members) and I(admin) option. + type: bool + default: no + + set_default_role_all: + description: + - Is not supported by MariaDB and is silently ignored when working with MariaDB. + - If C(yes), runs B(SET DEFAULT ROLE ALL TO) each of the I(members) when changed. + - If you want to avoid this behavior, set this option to C(no) explicitly. + type: bool + default: yes + + state: + description: + - If C(present) and the role does not exist, creates the role. + - If C(present) and the role exists, does nothing or updates its attributes. + - If C(absent), removes the role. + type: str + choices: [ absent, present ] + default: present + + check_implicit_admin: + description: + - Check if mysql allows login as root/nopassword before trying supplied credentials. + - If success, passed I(login_user)/I(login_password) will be ignored. + type: bool + default: no + +notes: + - Pay attention that the module runs C(SET DEFAULT ROLE ALL TO) + all the I(members) passed by default when the state has changed. + If you want to avoid this behavior, set I(set_default_role_all) to C(no). + - Supports C(check_mode). + +seealso: + - module: community.mysql.mysql_user + - name: MySQL role reference + description: Complete reference of the MySQL role documentation. + link: https://dev.mysql.com/doc/refman/8.0/en/create-role.html + +author: + - Andrew Klychkov (@Andersson007) + +extends_documentation_fragment: + - community.mysql.mysql +''' + +EXAMPLES = r''' +# Example of a .my.cnf file content for setting a root password +# [client] +# user=root +# password=n<_665{vS43y +# +# Example of a privileges dictionary passed through the priv option +# priv: +# 'mydb.*': 'INSERT,UPDATE' +# 'anotherdb.*': 'SELECT' +# 'yetanotherdb.*': 'ALL' +# +# You can also use the string format like in the community.mysql.mysql_user module, for example +# mydb.*:INSERT,UPDATE/anotherdb.*:SELECT/yetanotherdb.*:ALL +# +# For more examples on how to specify privileges, refer to the community.mysql.mysql_user module + +# Create a role developers with all database privileges +# and add alice and bob as members. +# The statement 'SET DEFAULT ROLE ALL' to them will be run. +- name: Create role developers, add members + community.mysql.mysql_role: + name: developers + state: present + priv: '*.*:ALL' + members: + - 'alice@%' + - 'bob@%' + +- name: Same as above but do not run SET DEFAULT ROLE ALL TO each member + community.mysql.mysql_role: + name: developers + state: present + priv: '*.*:ALL' + members: + - 'alice@%' + - 'bob@%' + set_default_role_all: no + +# Assuming that the role developers exists, +# add john to the current members +- name: Add members to an existing role + community.mysql.mysql_role: + name: developers + state: present + append_members: yes + members: + - 'joe@localhost' + +# Create role readers with the SELECT privilege +# on all tables in the fiction database +- name: Create role developers, add members + community.mysql.mysql_role: + name: readers + state: present + priv: 'fiction.*:SELECT' + +# Assuming that the role readers exists, +# add the UPDATE privilege to the role on all tables in the fiction database +- name: Create role developers, add members + community.mysql.mysql_role: + name: readers + state: present + priv: 'fiction.*:UPDATE' + append_privs: yes + +- name: Create role with the 'SELECT' and 'UPDATE' privileges in db1 and db2 + community.mysql.mysql_role: + state: present + name: foo + priv: + 'db1.*': 'SELECT,UPDATE' + 'db2.*': 'SELECT,UPDATE' + +- name: Remove joe from readers + community.mysql.mysql_role: + state: present + name: readers + members: + - 'joe@localhost' + detach_members: yes + +- name: Remove the role readers if exists + community.mysql.mysql_role: + state: absent + name: readers + +- name: Example of using login_unix_socket to connect to the server + community.mysql.mysql_role: + name: readers + state: present + login_unix_socket: /var/run/mysqld/mysqld.sock + +# Pay attention that the admin cannot be changed later +# and will be ignored if a role currently exists. +# To change members, you need to run a separate task using the admin +# of the role as the login_user. +- name: On MariaDB, create the role readers with alice as its admin + community.mysql.mysql_role: + state: present + name: readers + admin: 'alice@%' + +- name: Create the role business, add the role marketing to members + community.mysql.mysql_role: + state: present + name: business + members: + - marketing +''' + +RETURN = '''#''' + +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, + mysql_common_argument_spec +) +from ansible_collections.community.mysql.plugins.module_utils.user import ( + convert_priv_dict_to_str, + get_impl, + get_mode, + user_mod, + privileges_grant, + privileges_unpack, +) +from ansible.module_utils._text import to_native +from ansible.module_utils.six import iteritems + + +def normalize_users(module, users, is_mariadb=False): + """Normalize passed user names. + + Example of transformation: + ['user0'] => [('user0', '')] / ['user0'] => [('user0', '%')] + ['user0@host0'] => [('user0', 'host0')] + + Args: + module (AnsibleModule): Object of the AnsibleModule class. + users (list): List of user names. + is_mariadb (bool): Flag indicating we are working with MariaDB + + Returns: + list: List of tuples like [('user0', ''), ('user0', 'host0')]. + """ + normalized_users = [] + + for user in users: + try: + tmp = user.split('@') + + if tmp[0] == '': + module.fail_json(msg="Member's name cannot be empty.") + + if len(tmp) == 1: + if not is_mariadb: + normalized_users.append((tmp[0], '%')) + else: + normalized_users.append((tmp[0], '')) + + elif len(tmp) == 2: + normalized_users.append((tmp[0], tmp[1])) + + except Exception as e: + msg = ('Error occured while parsing the name "%s": %s. ' + 'It must be in the format "username" or ' + '"username@hostname" ' % (user, to_native(e))) + module.fail_json(msg=msg) + + return normalized_users + + +class DbServer(): + """Class to fetch information from a database. + + Args: + module (AnsibleModule): Object of the AnsibleModule class. + cursor (cursor): Cursor object of a database Python connector. + + Attributes: + module (AnsibleModule): Object of the AnsibleModule class. + cursor (cursor): Cursor object of a database Python connector. + role_impl (library): Corresponding library depending + on a server type (MariaDB or MySQL) + mariadb (bool): True if MariaDB, False otherwise. + roles_supported (bool): True if roles are supported, False otherwise. + users (set): Set of users existing in a DB in the form (username, hostname). + """ + def __init__(self, module, cursor): + self.module = module + self.cursor = cursor + self.role_impl = self.get_implementation() + self.mariadb = self.role_impl.is_mariadb() + self.roles_supported = self.role_impl.supports_roles(self.cursor) + self.users = set(self.__get_users()) + + def is_mariadb(self): + """Get info whether a DB server is a MariaDB instance. + + Returns: + self.mariadb: Attribute value. + """ + return self.mariadb + + def supports_roles(self): + """Get info whether a DB server supports roles. + + Returns: + self.roles_supported: Attribute value. + """ + return self.roles_supported + + def get_implementation(self): + """Get a current server implementation depending on its type. + + Returns: + library: Depending on a server type (MySQL or MariaDB). + """ + self.cursor.execute("SELECT VERSION()") + + if 'mariadb' in self.cursor.fetchone()[0].lower(): + import ansible_collections.community.mysql.plugins.module_utils.implementations.mariadb.role as role_impl + else: + import ansible_collections.community.mysql.plugins.module_utils.implementations.mysql.role as role_impl + + return role_impl + + def check_users_in_db(self, users): + """Check if users exist in a database. + + Args: + users (list): List of tuples (username, hostname) to check. + """ + for user in users: + if user not in self.users: + msg = 'User / role `%s` with host `%s` does not exist' % (user[0], user[1]) + self.module.fail_json(msg=msg) + + def __get_users(self): + """Get users. + + Returns: + list: List of tuples (username, hostname). + """ + self.cursor.execute('SELECT User, Host FROM mysql.user') + return self.cursor.fetchall() + + def get_users(self): + """Get set of tuples (username, hostname) existing in a DB. + + Returns: + self.users: Attribute value. + """ + return self.users + + def get_grants(self, user, host): + """Get grants. + + Args: + user (str): User name + host (str): Host name + + Returns: + list: List of tuples like [(grant1,), (grant2,), ... ]. + """ + if host: + self.cursor.execute('SHOW GRANTS FOR %s@%s', (user, host)) + else: + self.cursor.execute('SHOW GRANTS FOR %s', (user,)) + + return self.cursor.fetchall() + + +class MySQLQueryBuilder(): + """Class to build and return queries specific to MySQL. + + Args: + name (str): Role name. + host (str): Role host. + + Attributes: + name (str): Role name. + host (str): Role host. + """ + def __init__(self, name, host): + self.name = name + self.host = host + + def role_exists(self): + """Return a query to check if a role with self.name and self.host exists in a database. + + Returns: + tuple: (query_string, tuple_containing_parameters). + """ + return 'SELECT count(*) FROM mysql.user WHERE user = %s AND host = %s', (self.name, self.host) + + def role_grant(self, user): + """Return a query to grant a role to a user or a role. + + Args: + user (tuple): User / role to grant the role to in the form (username, hostname). + + Returns: + tuple: (query_string, tuple_containing_parameters). + """ + if user[1]: + return 'GRANT %s@%s TO %s@%s', (self.name, self.host, user[0], user[1]) + else: + return 'GRANT %s@%s TO %s', (self.name, self.host, user[0]) + + def role_revoke(self, user): + """Return a query to revoke a role from a user or role. + + Args: + user (tuple): User / role to revoke the role from in the form (username, hostname). + + Returns: + tuple: (query_string, tuple_containing_parameters). + """ + if user[1]: + return 'REVOKE %s@%s FROM %s@%s', (self.name, self.host, user[0], user[1]) + else: + return 'REVOKE %s@%s FROM %s', (self.name, self.host, user[0]) + + def role_create(self, admin=None): + """Return a query to create a role. + + Args: + admin (tuple): Admin user in the form (username, hostname). + Because it is not supported by MySQL, we ignore it. + + Returns: + tuple: (query_string, tuple_containing_parameters). + """ + return 'CREATE ROLE %s', (self.name,) + + +class MariaDBQueryBuilder(): + """Class to build and return queries specific to MariaDB. + + Args: + name (str): Role name. + + Attributes: + name (str): Role name. + """ + def __init__(self, name): + self.name = name + + def role_exists(self): + """Return a query to check if a role with self.name exists in a database. + + Returns: + tuple: (query_string, tuple_containing_parameters). + """ + return "SELECT count(*) FROM mysql.user WHERE user = %s AND is_role = 'Y'", (self.name) + + def role_grant(self, user): + """Return a query to grant a role to a user or role. + + Args: + user (tuple): User / role to grant the role to in the form (username, hostname). + + Returns: + tuple: (query_string, tuple_containing_parameters). + """ + if user[1]: + return 'GRANT %s TO %s@%s', (self.name, user[0], user[1]) + else: + return 'GRANT %s TO %s', (self.name, user[0]) + + def role_revoke(self, user): + """Return a query to revoke a role from a user or role. + + Args: + user (tuple): User / role to revoke the role from in the form (username, hostname). + + Returns: + tuple: (query_string, tuple_containing_parameters). + """ + if user[1]: + return 'REVOKE %s FROM %s@%s', (self.name, user[0], user[1]) + else: + return 'REVOKE %s FROM %s', (self.name, user[0]) + + def role_create(self, admin=None): + """Return a query to create a role. + + Args: + admin (tuple): Admin user in the form (username, hostname). + + Returns: + tuple: (query_string, tuple_containing_parameters). + """ + if not admin: + return 'CREATE ROLE %s', (self.name,) + + if admin[1]: + return 'CREATE ROLE %s WITH ADMIN %s@%s', (self.name, admin[0], admin[1]) + else: + return 'CREATE ROLE %s WITH ADMIN %s', (self.name, admin[0]) + + +class MySQLRoleImpl(): + """Class to work with MySQL role implementation. + + Args: + module (AnsibleModule): Object of the AnsibleModule class. + cursor (cursor): Cursor object of a database Python connector. + name (str): Role name. + host (str): Role host. + + Attributes: + module (AnsibleModule): Object of the AnsibleModule class. + cursor (cursor): Cursor object of a database Python connector. + name (str): Role name. + host (str): Role host. + """ + def __init__(self, module, cursor, name, host): + self.module = module + self.cursor = cursor + self.name = name + self.host = host + + def set_default_role_all(self, user): + """Run 'SET DEFAULT ROLE ALL TO' a user. + + Args: + user (tuple): User / role to run the command against in the form (username, hostname). + """ + if user[1]: + self.cursor.execute('SET DEFAULT ROLE ALL TO %s@%s', (user[0], user[1])) + else: + self.cursor.execute('SET DEFAULT ROLE ALL TO %s', (user[0],)) + + def get_admin(self): + """Get a current admin of a role. + + Not supported by MySQL, so ignored here. + """ + pass + + def set_admin(self, admin): + """Set an admin of a role. + + Not supported by MySQL, so ignored here. + + TODO: Implement the feature if this gets supported. + + Args: + admin (tuple): Admin user of the role in the form (username, hostname). + """ + pass + + +class MariaDBRoleImpl(): + """Class to work with MariaDB role implementation. + + Args: + module (AnsibleModule): Object of the AnsibleModule class. + cursor (cursor): Cursor object of a database Python connector. + name (str): Role name. + + Attributes: + module (AnsibleModule): Object of the AnsibleModule class. + cursor (cursor): Cursor object of a database Python connector. + name (str): Role name. + """ + def __init__(self, module, cursor, name): + self.module = module + self.cursor = cursor + self.name = name + + def set_default_role_all(self, user): + """Run 'SET DEFAULT ROLE ALL TO' a user. + + The command is not supported by MariaDB, ignored. + + Args: + user (tuple): User / role to run the command against in the form (username, hostname). + """ + pass + + def get_admin(self): + """Get a current admin of a role. + + Returns: + tuple: Of the form (username, hostname). + """ + query = ("SELECT User, Host FROM mysql.roles_mapping " + "WHERE Role = %s and Admin_option = 'Y'") + + self.cursor.execute(query, (self.name,)) + return self.cursor.fetchone() + + def set_admin(self, admin): + """Set an admin of a role. + + TODO: Implement changing when ALTER ROLE statement to + change role's admin gets supported. + + Args: + admin (tuple): Admin user of the role in the form (username, hostname). + """ + admin_user = admin[0] + admin_host = admin[1] + current_admin = self.get_admin() + + if (admin_user, admin_host) != current_admin: + msg = ('The "admin" option value and the current ' + 'roles admin (%s@%s) don not match. Ignored. ' + 'To change the admin, you need to drop and create the ' + 'role again.' % (current_admin[0], current_admin[1])) + self.module.warn(msg) + + +class Role(): + """Class to work with MySQL role objects. + + Args: + module (AnsibleModule): Object of the AnsibleModule class. + cursor (cursor): Cursor object of a database Python connector. + name (str): Role name. + server (DbServer): Object of the DbServer class. + + Attributes: + module (AnsibleModule): Object of the AnsibleModule class. + cursor (cursor): Cursor object of a database Python connector. + name (str): Role name. + server (DbServer): Object of the DbServer class. + host (str): Role's host. + full_name (str): Role's full name. + exists (bool): Indicates if a role exists or not. + members (set): Set of current role's members. + """ + def __init__(self, module, cursor, name, server): + self.module = module + self.cursor = cursor + self.name = name + self.server = server + self.is_mariadb = self.server.is_mariadb() + + if self.is_mariadb: + self.q_builder = MariaDBQueryBuilder(self.name) + self.role_impl = MariaDBRoleImpl(self.module, self.cursor, self.name) + self.full_name = '`%s`' % self.name + self.host = '' + else: + self.host = '%' + self.q_builder = MySQLQueryBuilder(self.name, self.host) + self.role_impl = MySQLRoleImpl(self.module, self.cursor, self.name, self.host) + self.full_name = '`%s`@`%s`' % (self.name, self.host) + + self.exists = self.__role_exists() + self.members = set() + + if self.exists: + self.members = self.__get_members() + + def __role_exists(self): + """Check if a role exists. + + Returns: + bool: True if the role exists, False if it does not. + """ + self.cursor.execute(*self.q_builder.role_exists()) + return self.cursor.fetchone()[0] > 0 + + def add(self, users, privs, check_mode=False, admin=False, + set_default_role_all=True): + """Add a role. + + Args: + users (list): Role members. + privs (str): String containing privileges. + check_mode (bool): If True, just checks and does nothing. + admin (tuple): Role's admin. Contains (username, hostname). + set_default_role_all (bool): If True, runs SET DEFAULT ROLE ALL TO each member. + + Returns: + bool: True if the state has changed, False if has not. + """ + if check_mode: + if not self.exists: + return True + return False + + self.cursor.execute(*self.q_builder.role_create(admin)) + + if users: + self.update_members(users, set_default_role_all=set_default_role_all) + + if privs: + for db_table, priv in iteritems(privs): + privileges_grant(self.cursor, self.name, self.host, + db_table, priv, tls_requires=None, + maria_role=self.is_mariadb) + + return True + + def drop(self, check_mode=False): + """Drop a role. + + Args: + check_mode (bool): If True, just checks and does nothing. + + Returns: + bool: True if the state has changed, False if has not. + """ + if not self.exists: + return False + + if check_mode and self.exists: + return True + + self.cursor.execute('DROP ROLE %s', (self.name,)) + return True + + def update_members(self, users, check_mode=False, append_members=False, + set_default_role_all=True): + """Add users to a role. + + Args: + users (list): Role members. + check_mode (bool): If True, just checks and does nothing. + append_members (bool): If True, adds new members passed through users + not touching current members. + set_default_role_all (bool): If True, runs SET DEFAULT ROLE ALL TO each member. + + Returns: + bool: True if the state has changed, False if has not. + """ + if not users: + return False + + changed = False + for user in users: + if user not in self.members: + if check_mode: + return True + + self.cursor.execute(*self.q_builder.role_grant(user)) + + self.role_impl.set_default_role_all(user) + + changed = True + + if append_members: + return changed + + for user in self.members: + if user not in users and user != ('root', 'localhost'): + changed = self.__remove_member(user, check_mode) + + return changed + + def remove_members(self, users, check_mode=False): + """Remove members from a role. + + Args: + users (list): Role members. + check_mode (bool): If True, just checks and does nothing. + + Returns: + bool: True if the state has changed, False if has not. + """ + if not users: + return False + + changed = False + for user in users: + if user in self.members: + changed = self.__remove_member(user, check_mode) + + return changed + + def __remove_member(self, user, check_mode=False): + """Remove a member from a role. + + Args: + user (str): Role member to remove. + check_mode (bool): If True, just returns True and does nothing. + + Returns: + bool: True if the state has changed, False if has not. + """ + if check_mode: + return True + + self.cursor.execute(*self.q_builder.role_revoke(user)) + + return True + + def update(self, users, privs, check_mode=False, + append_privs=False, append_members=False, + detach_members=False, admin=False, + set_default_role_all=True): + """Update a role. + + Update a role if needed. + + Todo: Implement changing of role's admin when ALTER ROLE statement + to do that gets supported. + + Args: + users (list): Role members. + privs (str): String containing privileges. + check_mode (bool): If True, just checks and does nothing. + append_privs (bool): If True, adds new privileges passed through privs + not touching current privileges. + append_members (bool): If True, adds new members passed through users + not touching current members. + detach_members (bool): If True, removes members passed through users from a role. + admin (tuple): Role's admin. Contains (username, hostname). + set_default_role_all (bool): If True, runs SET DEFAULT ROLE ALL TO each member. + + Returns: + bool: True if the state has changed, False if has not. + """ + changed = False + members_changed = False + + if users: + if detach_members: + members_changed = self.remove_members(users, check_mode=check_mode) + + else: + members_changed = self.update_members(users, check_mode=check_mode, + append_members=append_members, + set_default_role_all=set_default_role_all) + + if privs: + changed, msg = user_mod(self.cursor, self.name, self.host, + None, None, None, None, None, None, + privs, append_privs, None, + self.module, role=True, maria_role=self.is_mariadb) + + if admin: + self.role_impl.set_admin(admin) + + changed = changed or members_changed + + return changed + + def __get_members(self): + """Get current role's members. + + Returns: + set: Members. + """ + members = set() + + for user, host in self.server.get_users(): + # Don't handle itself + if user == self.name and host == self.host: + continue + + grants = self.server.get_grants(user, host) + + if self.__is_member(grants): + members.add((user, host)) + + return members + + def __is_member(self, grants): + """Check if a user / role is a member of a role. + + To check if a user is a member of a role, + we parse their grants looking for the role name in them. + In the following grants, we can see that test@% is a member of readers. + +---------------------------------------------------+ + | Grants for test@% | + +---------------------------------------------------+ + | GRANT SELECT, INSERT, UPDATE ON *.* TO `test`@`%` | + | GRANT ALL PRIVILEGES ON `mysql`.* TO `test`@`%` | + | GRANT INSERT ON `mysql`.`user` TO `test`@`%` | + | GRANT `readers`@`%` TO `test`@`%` | + +---------------------------------------------------+ + + Args: + grants (list): Grants of a user to parse. + + Returns: + bool: True if the self.full_name has been found in grants, + otherwise returns False. + """ + if not grants: + return False + + for grant in grants: + if self.full_name in grant[0]: + return True + + return False + + +def main(): + argument_spec = mysql_common_argument_spec() + argument_spec.update( + name=dict(type='str', required=True), + state=dict(type='str', default='present', choices=['absent', 'present']), + admin=dict(type='str'), + priv=dict(type='raw'), + append_privs=dict(type='bool', default=False), + members=dict(type='list', elements='str'), + append_members=dict(type='bool', default=False), + detach_members=dict(type='bool', default=False), + check_implicit_admin=dict(type='bool', default=False), + set_default_role_all=dict(type='bool', default=True), + ) + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + mutually_exclusive=( + ('append_members', 'detach_members'), + ('admin', 'members'), + ('admin', 'append_members'), + ('admin', 'detach_members'), + ), + ) + + login_user = module.params['login_user'] + login_password = module.params['login_password'] + name = module.params['name'] + state = module.params['state'] + admin = module.params['admin'] + priv = module.params['priv'] + check_implicit_admin = module.params['check_implicit_admin'] + connect_timeout = module.params['connect_timeout'] + config_file = module.params['config_file'] + append_privs = module.params['append_privs'] + members = module.params['members'] + append_members = module.params['append_members'] + detach_members = module.params['detach_members'] + 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 = '' + set_default_role_all = module.params['set_default_role_all'] + + if priv and not isinstance(priv, (str, dict)): + msg = ('The "priv" parameter must be str or dict ' + 'but %s was passed' % type(priv)) + module.fail_json(msg=msg) + + if priv and isinstance(priv, dict): + priv = convert_priv_dict_to_str(priv) + + if mysql_driver is None: + module.fail_json(msg=mysql_driver_fail_msg) + + cursor = None + try: + 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) + 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, + 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))) + + # Set defaults + changed = False + + get_impl(cursor) + + if priv is not None: + try: + mode = get_mode(cursor) + except Exception as e: + module.fail_json(msg=to_native(e)) + + try: + priv = privileges_unpack(priv, mode) + except Exception as e: + module.fail_json(msg='Invalid privileges string: %s' % to_native(e)) + + server = DbServer(module, cursor) + + # Check if the server supports roles + if not server.supports_roles(): + msg = ('Roles are not supported by the server. ' + 'Minimal versions are MySQL 8.0.0 or MariaDB 10.0.5.') + module.fail_json(msg=msg) + + if admin: + if not server.is_mariadb(): + module.fail_json(msg='The "admin" option can be used only with MariaDB.') + + admin = normalize_users(module, [admin])[0] + server.check_users_in_db([admin]) + + if members: + members = normalize_users(module, members, server.is_mariadb()) + server.check_users_in_db(members) + + # Main job starts here + role = Role(module, cursor, name, server) + + try: + if state == 'present': + if not role.exists: + changed = role.add(members, priv, module.check_mode, admin, + set_default_role_all) + + else: + changed = role.update(members, priv, module.check_mode, append_privs, + append_members, detach_members, admin, + set_default_role_all) + + elif state == 'absent': + changed = role.drop(module.check_mode) + + except Exception as e: + module.fail_json(msg=to_native(e)) + + # Exit + db_conn.close() + module.exit_json(changed=changed) + + +if __name__ == '__main__': + main() diff --git a/plugins/modules/mysql_user.py b/plugins/modules/mysql_user.py index 38c1a55..3172553 100644 --- a/plugins/modules/mysql_user.py +++ b/plugins/modules/mysql_user.py @@ -306,830 +306,28 @@ EXAMPLES = r''' RETURN = '''#''' -import re -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, mysql_common_argument_spec ) -from ansible.module_utils.six import iteritems +from ansible_collections.community.mysql.plugins.module_utils.user import ( + convert_priv_dict_to_str, + get_impl, + get_mode, + handle_requiressl_in_priv_string, + InvalidPrivsError, + limit_resources, + privileges_unpack, + sanitize_requires, + user_add, + user_delete, + user_exists, + user_mod, +) from ansible.module_utils._text import to_native -VALID_PRIVS = frozenset(('CREATE', 'DROP', 'GRANT', 'GRANT OPTION', - 'LOCK TABLES', 'REFERENCES', 'EVENT', 'ALTER', - 'DELETE', 'INDEX', 'INSERT', 'SELECT', 'UPDATE', - 'CREATE TEMPORARY TABLES', 'TRIGGER', 'CREATE VIEW', - 'SHOW VIEW', 'ALTER ROUTINE', 'CREATE ROUTINE', - 'EXECUTE', 'FILE', 'CREATE TABLESPACE', 'CREATE USER', - 'PROCESS', 'PROXY', 'RELOAD', 'REPLICATION CLIENT', - 'REPLICATION SLAVE', 'SHOW DATABASES', 'SHUTDOWN', - 'SUPER', 'ALL', 'ALL PRIVILEGES', 'USAGE', - 'REQUIRESSL', # Deprecated, to be removed in version 3.0.0 - 'CREATE ROLE', 'DROP ROLE', 'APPLICATION_PASSWORD_ADMIN', - 'AUDIT_ADMIN', 'BACKUP_ADMIN', 'BINLOG_ADMIN', - 'BINLOG_ENCRYPTION_ADMIN', 'CLONE_ADMIN', 'CONNECTION_ADMIN', - 'ENCRYPTION_KEY_ADMIN', 'FIREWALL_ADMIN', 'FIREWALL_USER', - 'GROUP_REPLICATION_ADMIN', 'INNODB_REDO_LOG_ARCHIVE', - 'NDB_STORED_USER', 'PERSIST_RO_VARIABLES_ADMIN', - 'REPLICATION_APPLIER', 'REPLICATION_SLAVE_ADMIN', - 'RESOURCE_GROUP_ADMIN', 'RESOURCE_GROUP_USER', - 'ROLE_ADMIN', 'SESSION_VARIABLES_ADMIN', 'SET_USER_ID', - 'SYSTEM_USER', 'SYSTEM_VARIABLES_ADMIN', 'SYSTEM_USER', - 'TABLE_ENCRYPTION_ADMIN', 'VERSION_TOKEN_ADMIN', - 'XA_RECOVER_ADMIN', 'LOAD FROM S3', 'SELECT INTO S3', - 'INVOKE LAMBDA', - 'ALTER ROUTINE', - 'BINLOG ADMIN', - 'BINLOG MONITOR', - 'BINLOG REPLAY', - 'CONNECTION ADMIN', - 'READ_ONLY ADMIN', - 'REPLICATION MASTER ADMIN', - 'REPLICATION SLAVE ADMIN', - 'SET USER', - 'SHOW_ROUTINE', - 'SLAVE MONITOR', - 'REPLICA MONITOR',)) - - -class InvalidPrivsError(Exception): - pass - -# =========================================== -# MySQL module specific support methods. -# - - -def get_mode(cursor): - cursor.execute('SELECT @@GLOBAL.sql_mode') - result = cursor.fetchone() - mode_str = result[0] - if 'ANSI' in mode_str: - mode = 'ANSI' - else: - mode = 'NOTANSI' - return mode - - -def user_exists(cursor, user, host, host_all): - if host_all: - cursor.execute("SELECT count(*) FROM mysql.user WHERE user = %s", (user,)) - else: - cursor.execute("SELECT count(*) FROM mysql.user WHERE user = %s AND host = %s", (user, host)) - - count = cursor.fetchone() - return count[0] > 0 - - -def sanitize_requires(tls_requires): - sanitized_requires = {} - if tls_requires: - for key in tls_requires.keys(): - sanitized_requires[key.upper()] = tls_requires[key] - if any([key in ["CIPHER", "ISSUER", "SUBJECT"] for key in sanitized_requires.keys()]): - sanitized_requires.pop("SSL", None) - sanitized_requires.pop("X509", None) - return sanitized_requires - - if "X509" in sanitized_requires.keys(): - sanitized_requires = "X509" - else: - sanitized_requires = "SSL" - - return sanitized_requires - return None - - -def mogrify_requires(query, params, tls_requires): - if tls_requires: - if isinstance(tls_requires, dict): - k, v = zip(*tls_requires.items()) - requires_query = " AND ".join(("%s %%s" % key for key in k)) - params += v - else: - requires_query = tls_requires - query = " REQUIRE ".join((query, requires_query)) - return query, params - - -def do_not_mogrify_requires(query, params, tls_requires): - return query, params - - -def get_tls_requires(cursor, user, host): - if user: - if not impl.use_old_user_mgmt(cursor): - query = "SHOW CREATE USER '%s'@'%s'" % (user, host) - else: - query = "SHOW GRANTS for '%s'@'%s'" % (user, host) - - cursor.execute(query) - require_list = [tuple[0] for tuple in filter(lambda x: "REQUIRE" in x[0], cursor.fetchall())] - require_line = require_list[0] if require_list else "" - pattern = r"(?<=\bREQUIRE\b)(.*?)(?=(?:\bPASSWORD\b|$))" - requires_match = re.search(pattern, require_line) - requires = requires_match.group().strip() if requires_match else "" - if any((requires.startswith(req) for req in ('SSL', 'X509', 'NONE'))): - requires = requires.split()[0] - if requires == 'NONE': - requires = None - else: - import shlex - - items = iter(shlex.split(requires)) - requires = dict(zip(items, items)) - return requires or None - - -def get_grants(cursor, user, host): - cursor.execute("SHOW GRANTS FOR %s@%s", (user, host)) - grants_line = list(filter(lambda x: "ON *.*" in x[0], cursor.fetchall()))[0] - pattern = r"(?<=\bGRANT\b)(.*?)(?=(?:\bON\b))" - grants = re.search(pattern, grants_line[0]).group().strip() - return grants.split(", ") - - -def user_add(cursor, user, host, host_all, password, encrypted, - plugin, plugin_hash_string, plugin_auth_string, new_priv, - tls_requires, check_mode): - # we cannot create users without a proper hostname - if host_all: - return False - - if check_mode: - return True - - # Determine what user management method server uses - old_user_mgmt = impl.use_old_user_mgmt(cursor) - - mogrify = do_not_mogrify_requires if old_user_mgmt else mogrify_requires - - if password and encrypted: - if impl.supports_identified_by_password(cursor): - query_with_args = "CREATE USER %s@%s IDENTIFIED BY PASSWORD %s", (user, host, password) - else: - query_with_args = "CREATE USER %s@%s IDENTIFIED WITH mysql_native_password AS %s", (user, host, password) - elif password and not encrypted: - if old_user_mgmt: - query_with_args = "CREATE USER %s@%s IDENTIFIED BY %s", (user, host, password) - else: - cursor.execute("SELECT CONCAT('*', UCASE(SHA1(UNHEX(SHA1(%s)))))", (password,)) - encrypted_password = cursor.fetchone()[0] - query_with_args = "CREATE USER %s@%s IDENTIFIED WITH mysql_native_password AS %s", (user, host, encrypted_password) - elif plugin and plugin_hash_string: - query_with_args = "CREATE USER %s@%s IDENTIFIED WITH %s AS %s", (user, host, plugin, plugin_hash_string) - elif plugin and plugin_auth_string: - query_with_args = "CREATE USER %s@%s IDENTIFIED WITH %s BY %s", (user, host, plugin, plugin_auth_string) - elif plugin: - query_with_args = "CREATE USER %s@%s IDENTIFIED WITH %s", (user, host, plugin) - else: - query_with_args = "CREATE USER %s@%s", (user, host) - - query_with_args_and_tls_requires = query_with_args + (tls_requires,) - cursor.execute(*mogrify(*query_with_args_and_tls_requires)) - - if new_priv is not None: - for db_table, priv in iteritems(new_priv): - privileges_grant(cursor, user, host, db_table, priv, tls_requires) - if tls_requires is not None: - privileges_grant(cursor, user, host, "*.*", get_grants(cursor, user, host), tls_requires) - return True - - -def is_hash(password): - ishash = False - if len(password) == 41 and password[0] == '*': - if frozenset(password[1:]).issubset(string.hexdigits): - ishash = True - return ishash - - -def user_mod(cursor, user, host, host_all, password, encrypted, - plugin, plugin_hash_string, plugin_auth_string, new_priv, - append_privs, tls_requires, module): - changed = False - msg = "User unchanged" - grant_option = False - - # Determine what user management method server uses - old_user_mgmt = impl.use_old_user_mgmt(cursor) - - if host_all: - hostnames = user_get_hostnames(cursor, user) - else: - hostnames = [host] - - for host in hostnames: - # Handle clear text and hashed passwords. - if bool(password): - - # Get a list of valid columns in mysql.user table to check if Password and/or authentication_string exist - cursor.execute(""" - SELECT COLUMN_NAME FROM information_schema.COLUMNS - WHERE TABLE_SCHEMA = 'mysql' AND TABLE_NAME = 'user' AND COLUMN_NAME IN ('Password', 'authentication_string') - ORDER BY COLUMN_NAME DESC LIMIT 1 - """) - colA = cursor.fetchone() - - cursor.execute(""" - SELECT COLUMN_NAME FROM information_schema.COLUMNS - WHERE TABLE_SCHEMA = 'mysql' AND TABLE_NAME = 'user' AND COLUMN_NAME IN ('Password', 'authentication_string') - ORDER BY COLUMN_NAME ASC LIMIT 1 - """) - colB = cursor.fetchone() - - # Select hash from either Password or authentication_string, depending which one exists and/or is filled - cursor.execute(""" - SELECT COALESCE( - CASE WHEN %s = '' THEN NULL ELSE %s END, - CASE WHEN %s = '' THEN NULL ELSE %s END - ) - FROM mysql.user WHERE user = %%s AND host = %%s - """ % (colA[0], colA[0], colB[0], colB[0]), (user, host)) - current_pass_hash = cursor.fetchone()[0] - if isinstance(current_pass_hash, bytes): - current_pass_hash = current_pass_hash.decode('ascii') - - if encrypted: - encrypted_password = password - if not is_hash(encrypted_password): - module.fail_json(msg="encrypted was specified however it does not appear to be a valid hash expecting: *SHA1(SHA1(your_password))") - else: - if old_user_mgmt: - cursor.execute("SELECT PASSWORD(%s)", (password,)) - else: - cursor.execute("SELECT CONCAT('*', UCASE(SHA1(UNHEX(SHA1(%s)))))", (password,)) - encrypted_password = cursor.fetchone()[0] - - if current_pass_hash != encrypted_password: - msg = "Password updated" - if module.check_mode: - return (True, msg) - if old_user_mgmt: - cursor.execute("SET PASSWORD FOR %s@%s = %s", (user, host, encrypted_password)) - msg = "Password updated (old style)" - else: - try: - cursor.execute("ALTER USER %s@%s IDENTIFIED WITH mysql_native_password AS %s", (user, host, encrypted_password)) - msg = "Password updated (new style)" - except (mysql_driver.Error) as e: - # https://stackoverflow.com/questions/51600000/authentication-string-of-root-user-on-mysql - # Replacing empty root password with new authentication mechanisms fails with error 1396 - if e.args[0] == 1396: - cursor.execute( - "UPDATE mysql.user SET plugin = %s, authentication_string = %s, Password = '' WHERE User = %s AND Host = %s", - ('mysql_native_password', encrypted_password, user, host) - ) - cursor.execute("FLUSH PRIVILEGES") - msg = "Password forced update" - else: - raise e - changed = True - - # Handle plugin authentication - if plugin: - cursor.execute("SELECT plugin, authentication_string FROM mysql.user " - "WHERE user = %s AND host = %s", (user, host)) - current_plugin = cursor.fetchone() - - update = False - - if current_plugin[0] != plugin: - update = True - - if plugin_hash_string and current_plugin[1] != plugin_hash_string: - update = True - - if plugin_auth_string and current_plugin[1] != plugin_auth_string: - # this case can cause more updates than expected, - # as plugin can hash auth_string in any way it wants - # and there's no way to figure it out for - # a check, so I prefer to update more often than never - update = True - - if update: - if plugin_hash_string: - query_with_args = "ALTER USER %s@%s IDENTIFIED WITH %s AS %s", (user, host, plugin, plugin_hash_string) - elif plugin_auth_string: - query_with_args = "ALTER USER %s@%s IDENTIFIED WITH %s BY %s", (user, host, plugin, plugin_auth_string) - else: - query_with_args = "ALTER USER %s@%s IDENTIFIED WITH %s", (user, host, plugin) - - cursor.execute(*query_with_args) - changed = True - - # Handle privileges - if new_priv is not None: - curr_priv = privileges_get(cursor, user, host) - - # If the user has privileges on a db.table that doesn't appear at all in - # the new specification, then revoke all privileges on it. - for db_table, priv in iteritems(curr_priv): - # If the user has the GRANT OPTION on a db.table, revoke it first. - if "GRANT" in priv: - grant_option = True - if db_table not in new_priv: - if user != "root" and "PROXY" not in priv and not append_privs: - msg = "Privileges updated" - if module.check_mode: - return (True, msg) - privileges_revoke(cursor, user, host, db_table, priv, grant_option) - changed = True - - # If the user doesn't currently have any privileges on a db.table, then - # we can perform a straight grant operation. - for db_table, priv in iteritems(new_priv): - if db_table not in curr_priv: - msg = "New privileges granted" - if module.check_mode: - return (True, msg) - privileges_grant(cursor, user, host, db_table, priv, tls_requires) - changed = True - - # If the db.table specification exists in both the user's current privileges - # and in the new privileges, then we need to see if there's a difference. - db_table_intersect = set(new_priv.keys()) & set(curr_priv.keys()) - for db_table in db_table_intersect: - - # If appending privileges, only the set difference between new privileges and current privileges matter. - # The symmetric difference isn't relevant for append because existing privileges will not be revoked. - if append_privs: - priv_diff = set(new_priv[db_table]) - set(curr_priv[db_table]) - else: - priv_diff = set(new_priv[db_table]) ^ set(curr_priv[db_table]) - - if len(priv_diff) > 0: - msg = "Privileges updated" - if module.check_mode: - return (True, msg) - if not append_privs: - privileges_revoke(cursor, user, host, db_table, curr_priv[db_table], grant_option) - privileges_grant(cursor, user, host, db_table, new_priv[db_table], tls_requires) - changed = True - - # Handle TLS requirements - current_requires = get_tls_requires(cursor, user, host) - if current_requires != tls_requires: - msg = "TLS requires updated" - if module.check_mode: - return (True, msg) - if not old_user_mgmt: - pre_query = "ALTER USER" - else: - pre_query = "GRANT %s ON *.* TO" % ",".join(get_grants(cursor, user, host)) - - if tls_requires is not None: - query = " ".join((pre_query, "%s@%s")) - query_with_args = mogrify_requires(query, (user, host), tls_requires) - else: - query = " ".join((pre_query, "%s@%s REQUIRE NONE")) - query_with_args = query, (user, host) - - cursor.execute(*query_with_args) - changed = True - - return (changed, msg) - - -def user_delete(cursor, user, host, host_all, check_mode): - if check_mode: - return True - - if host_all: - hostnames = user_get_hostnames(cursor, user) - else: - hostnames = [host] - - for hostname in hostnames: - cursor.execute("DROP USER %s@%s", (user, hostname)) - - return True - - -def user_get_hostnames(cursor, user): - cursor.execute("SELECT Host FROM mysql.user WHERE user = %s", (user,)) - hostnames_raw = cursor.fetchall() - hostnames = [] - - for hostname_raw in hostnames_raw: - hostnames.append(hostname_raw[0]) - - return hostnames - - -def privileges_get(cursor, user, host): - """ MySQL doesn't have a better method of getting privileges aside from the - SHOW GRANTS query syntax, which requires us to then parse the returned string. - Here's an example of the string that is returned from MySQL: - - GRANT USAGE ON *.* TO 'user'@'localhost' IDENTIFIED BY 'pass'; - - This function makes the query and returns a dictionary containing the results. - The dictionary format is the same as that returned by privileges_unpack() below. - """ - output = {} - cursor.execute("SHOW GRANTS FOR %s@%s", (user, host)) - grants = cursor.fetchall() - - def pick(x): - if x == 'ALL PRIVILEGES': - return 'ALL' - else: - return x - - for grant in grants: - res = re.match("""GRANT (.+) ON (.+) TO (['`"]).*\\3@(['`"]).*\\4( IDENTIFIED BY PASSWORD (['`"]).+\\6)? ?(.*)""", grant[0]) - if res is None: - raise InvalidPrivsError('unable to parse the MySQL grant string: %s' % grant[0]) - privileges = res.group(1).split(",") - privileges = [pick(x.strip()) for x in privileges] - - # Handle cases when there's privs like GRANT SELECT (colA, ...) in privs. - # To this point, the privileges list can look like - # ['SELECT (`A`', '`B`)', 'INSERT'] that is incorrect (SELECT statement is splitted). - # Columns should also be sorted to compare it with desired privileges later. - # Determine if there's a case similar to the above: - privileges = normalize_col_grants(privileges) - - if "WITH GRANT OPTION" in res.group(7): - privileges.append('GRANT') - db = res.group(2) - output.setdefault(db, []).extend(privileges) - return output - - -def normalize_col_grants(privileges): - """Fix and sort grants on columns in privileges list - - Make ['SELECT (A, B)', 'INSERT (A, B)', 'DETELE'] - from ['SELECT (A', 'B)', 'INSERT (B', 'A)', 'DELETE']. - See unit tests in tests/unit/plugins/modules/test_mysql_user.py - """ - for grant in ('SELECT', 'UPDATE', 'INSERT', 'REFERENCES'): - start, end = has_grant_on_col(privileges, grant) - # If not, either start and end will be None - if start is not None: - privileges = handle_grant_on_col(privileges, start, end) - - return privileges - - -def has_grant_on_col(privileges, grant): - """Check if there is a statement like SELECT (colA, colB) - in the privilege list. - - Return (start index, end index). - """ - # Determine elements of privileges where - # columns are listed - start = None - end = None - for n, priv in enumerate(privileges): - if '%s (' % grant in priv: - # We found the start element - start = n - - if start is not None and ')' in priv: - # We found the end element - end = n - break - - if start is not None and end is not None: - # if the privileges list consist of, for example, - # ['SELECT (A', 'B), 'INSERT'], return indexes of related elements - return start, end - else: - # If start and end position is the same element, - # it means there's expression like 'SELECT (A)', - # so no need to handle it - return None, None - - -def handle_grant_on_col(privileges, start, end): - """Handle cases when the privs like SELECT (colA, ...) is in the privileges list.""" - # When the privileges list look like ['SELECT (colA,', 'colB)'] - # (Notice that the statement is splitted) - if start != end: - output = list(privileges[:start]) - - select_on_col = ', '.join(privileges[start:end + 1]) - - select_on_col = sort_column_order(select_on_col) - - output.append(select_on_col) - - output.extend(privileges[end + 1:]) - - # When it look like it should be, e.g. ['SELECT (colA, colB)'], - # we need to be sure, the columns is sorted - else: - output = list(privileges) - output[start] = sort_column_order(output[start]) - - return output - - -def sort_column_order(statement): - """Sort column order in grants like SELECT (colA, colB, ...). - - MySQL changes columns order like below: - --------------------------------------- - mysql> GRANT SELECT (testColA, testColB), INSERT ON `testDb`.`testTable` TO 'testUser'@'localhost'; - Query OK, 0 rows affected (0.04 sec) - - mysql> flush privileges; - Query OK, 0 rows affected (0.00 sec) - - mysql> SHOW GRANTS FOR testUser@localhost; - +---------------------------------------------------------------------------------------------+ - | Grants for testUser@localhost | - +---------------------------------------------------------------------------------------------+ - | GRANT USAGE ON *.* TO 'testUser'@'localhost' | - | GRANT SELECT (testColB, testColA), INSERT ON `testDb`.`testTable` TO 'testUser'@'localhost' | - +---------------------------------------------------------------------------------------------+ - - We should sort columns in our statement, otherwise the module always will return - that the state has changed. - """ - # 1. Extract stuff inside () - # 2. Split - # 3. Sort - # 4. Put between () and return - - # "SELECT/UPDATE/.. (colA, colB) => "colA, colB" - tmp = statement.split('(') - priv_name = tmp[0] - columns = tmp[1].rstrip(')') - - # "colA, colB" => ["colA", "colB"] - columns = columns.split(',') - - for i, col in enumerate(columns): - col = col.strip() - columns[i] = col.strip('`') - - columns.sort() - return '%s(%s)' % (priv_name, ', '.join(columns)) - - -def privileges_unpack(priv, mode): - """ Take a privileges string, typically passed as a parameter, and unserialize - it into a dictionary, the same format as privileges_get() above. We have this - custom format to avoid using YAML/JSON strings inside YAML playbooks. Example - of a privileges string: - - mydb.*:INSERT,UPDATE/anotherdb.*:SELECT/yetanother.*:ALL - - The privilege USAGE stands for no privileges, so we add that in on *.* if it's - not specified in the string, as MySQL will always provide this by default. - """ - if mode == 'ANSI': - quote = '"' - else: - quote = '`' - output = {} - privs = [] - for item in priv.strip().split('/'): - pieces = item.strip().rsplit(':', 1) - dbpriv = pieces[0].rsplit(".", 1) - - # Check for FUNCTION or PROCEDURE object types - parts = dbpriv[0].split(" ", 1) - object_type = '' - if len(parts) > 1 and (parts[0] == 'FUNCTION' or parts[0] == 'PROCEDURE'): - object_type = parts[0] + ' ' - dbpriv[0] = parts[1] - - # Do not escape if privilege is for database or table, i.e. - # neither quote *. nor .* - for i, side in enumerate(dbpriv): - if side.strip('`') != '*': - dbpriv[i] = '%s%s%s' % (quote, side.strip('`'), quote) - pieces[0] = object_type + '.'.join(dbpriv) - - if '(' in pieces[1]: - output[pieces[0]] = re.split(r',\s*(?=[^)]*(?:\(|$))', pieces[1].upper()) - for i in output[pieces[0]]: - privs.append(re.sub(r'\s*\(.*\)', '', i)) - else: - output[pieces[0]] = pieces[1].upper().split(',') - privs = output[pieces[0]] - - # Handle cases when there's privs like GRANT SELECT (colA, ...) in privs. - output[pieces[0]] = normalize_col_grants(output[pieces[0]]) - - new_privs = frozenset(privs) - if not new_privs.issubset(VALID_PRIVS): - raise InvalidPrivsError('Invalid privileges specified: %s' % new_privs.difference(VALID_PRIVS)) - - if '*.*' not in output: - output['*.*'] = ['USAGE'] - - return output - - -def privileges_revoke(cursor, user, host, db_table, priv, grant_option): - # Escape '%' since mysql db.execute() uses a format string - db_table = db_table.replace('%', '%%') - if grant_option: - query = ["REVOKE GRANT OPTION ON %s" % db_table] - query.append("FROM %s@%s") - query = ' '.join(query) - cursor.execute(query, (user, host)) - priv_string = ",".join([p for p in priv if p not in ('GRANT', )]) - query = ["REVOKE %s ON %s" % (priv_string, db_table)] - query.append("FROM %s@%s") - query = ' '.join(query) - cursor.execute(query, (user, host)) - - -def privileges_grant(cursor, user, host, db_table, priv, tls_requires): - # Escape '%' since mysql db.execute uses a format string and the - # specification of db and table often use a % (SQL wildcard) - db_table = db_table.replace('%', '%%') - priv_string = ",".join([p for p in priv if p not in ('GRANT', )]) - query = ["GRANT %s ON %s" % (priv_string, db_table)] - query.append("TO %s@%s") - params = (user, host) - if tls_requires and impl.use_old_user_mgmt(cursor): - query, params = mogrify_requires(" ".join(query), params, tls_requires) - query = [query] - if 'GRANT' in priv: - query.append("WITH GRANT OPTION") - query = ' '.join(query) - cursor.execute(query, params) - - -def convert_priv_dict_to_str(priv): - """Converts privs dictionary to string of certain format. - - Args: - priv (dict): Dict of privileges that needs to be converted to string. - - Returns: - priv (str): String representation of input argument. - """ - priv_list = ['%s:%s' % (key, val) for key, val in iteritems(priv)] - - return '/'.join(priv_list) - - -def handle_requiressl_in_priv_string(module, priv, tls_requires): - module.deprecate('The "REQUIRESSL" privilege is deprecated, use the "tls_requires" option instead.', - version='3.0.0', collection_name='community.mysql') - priv_groups = re.search(r"(.*?)(\*\.\*:)([^/]*)(.*)", priv) - if priv_groups.group(3) == "REQUIRESSL": - priv = priv_groups.group(1) + priv_groups.group(4) or None - else: - inner_priv_groups = re.search(r"(.*?),?REQUIRESSL,?(.*)", priv_groups.group(3)) - priv = '{0}{1}{2}{3}'.format( - priv_groups.group(1), - priv_groups.group(2), - ','.join(filter(None, (inner_priv_groups.group(1), inner_priv_groups.group(2)))), - priv_groups.group(4) - ) - if not tls_requires: - tls_requires = "SSL" - else: - module.warn('Ignoring "REQUIRESSL" privilege as "tls_requires" is defined and it takes precedence.') - return priv, tls_requires - - -# Alter user is supported since MySQL 5.6 and MariaDB 10.2.0 -def server_supports_alter_user(cursor): - """Check if the server supports ALTER USER statement or doesn't. - - Args: - cursor (cursor): DB driver cursor object. - - Returns: True if supports, False otherwise. - """ - cursor.execute("SELECT VERSION()") - version_str = cursor.fetchone()[0] - version = version_str.split('.') - - if 'mariadb' in version_str.lower(): - # MariaDB 10.2 and later - if int(version[0]) * 1000 + int(version[1]) >= 10002: - return True - else: - return False - else: - # MySQL 5.6 and later - if int(version[0]) * 1000 + int(version[1]) >= 5006: - return True - else: - return False - - -def get_resource_limits(cursor, user, host): - """Get user resource limits. - - Args: - cursor (cursor): DB driver cursor object. - user (str): User name. - host (str): User host name. - - Returns: Dictionary containing current resource limits. - """ - - query = ('SELECT max_questions AS MAX_QUERIES_PER_HOUR, ' - 'max_updates AS MAX_UPDATES_PER_HOUR, ' - 'max_connections AS MAX_CONNECTIONS_PER_HOUR, ' - 'max_user_connections AS MAX_USER_CONNECTIONS ' - 'FROM mysql.user WHERE User = %s AND Host = %s') - cursor.execute(query, (user, host)) - res = cursor.fetchone() - - if not res: - return None - - current_limits = { - 'MAX_QUERIES_PER_HOUR': res[0], - 'MAX_UPDATES_PER_HOUR': res[1], - 'MAX_CONNECTIONS_PER_HOUR': res[2], - 'MAX_USER_CONNECTIONS': res[3], - } - return current_limits - - -def match_resource_limits(module, current, desired): - """Check and match limits. - - Args: - module (AnsibleModule): Ansible module object. - current (dict): Dictionary with current limits. - desired (dict): Dictionary with desired limits. - - Returns: Dictionary containing parameters that need to change. - """ - - if not current: - # It means the user does not exists, so we need - # to set all limits after its creation - return desired - - needs_to_change = {} - - for key, val in iteritems(desired): - if key not in current: - # Supported keys are listed in the documentation - # and must be determined in the get_resource_limits function - # (follow 'AS' keyword) - module.fail_json(msg="resource_limits: key '%s' is unsupported." % key) - - try: - val = int(val) - except Exception: - module.fail_json(msg="Can't convert value '%s' to integer." % val) - - if val != current.get(key): - needs_to_change[key] = val - - return needs_to_change - - -def limit_resources(module, cursor, user, host, resource_limits, check_mode): - """Limit user resources. - - Args: - module (AnsibleModule): Ansible module object. - cursor (cursor): DB driver cursor object. - user (str): User name. - host (str): User host name. - resource_limit (dict): Dictionary with desired limits. - check_mode (bool): Run the function in check mode or not. - - Returns: True, if changed, False otherwise. - """ - if not server_supports_alter_user(cursor): - module.fail_json(msg="The server version does not match the requirements " - "for resource_limits parameter. See module's documentation.") - - current_limits = get_resource_limits(cursor, user, host) - - needs_to_change = match_resource_limits(module, current_limits, resource_limits) - - if not needs_to_change: - return False - - if needs_to_change and check_mode: - return True - - # If not check_mode - tmp = [] - for key, val in iteritems(needs_to_change): - tmp.append('%s %s' % (key, val)) - - query = "ALTER USER %s@%s" - query += ' WITH %s' % ' '.join(tmp) - cursor.execute(query, (user, host)) - return True - - # =========================================== # Module execution. # @@ -1215,14 +413,7 @@ def main(): if not sql_log_bin: cursor.execute("SET SQL_LOG_BIN=0;") - global impl - cursor.execute("SELECT VERSION()") - if 'mariadb' in cursor.fetchone()[0].lower(): - from ansible_collections.community.mysql.plugins.module_utils.implementations.mariadb import user as mysqluser - impl = mysqluser - else: - from ansible_collections.community.mysql.plugins.module_utils.implementations.mysql import user as mariauser - impl = mariauser + get_impl(cursor) if priv is not None: try: diff --git a/tests/integration/targets/test_mysql_role/defaults/main.yml b/tests/integration/targets/test_mysql_role/defaults/main.yml new file mode 100644 index 0000000..744ba34 --- /dev/null +++ b/tests/integration/targets/test_mysql_role/defaults/main.yml @@ -0,0 +1,16 @@ +mysql_user: root +mysql_password: msandbox +mysql_primary_port: 3307 + +test_db: test_db +test_table: test_table +test_db1: test_db1 +test_db2: test_db2 + +user0: user0 +user1: user1 +user2: user2 +nonexistent: user3 + +role0: role0 +role1: role1 diff --git a/tests/integration/targets/test_mysql_role/meta/main.yml b/tests/integration/targets/test_mysql_role/meta/main.yml new file mode 100644 index 0000000..ce08dc4 --- /dev/null +++ b/tests/integration/targets/test_mysql_role/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: +- setup_mysql diff --git a/tests/integration/targets/test_mysql_role/tasks/main.yml b/tests/integration/targets/test_mysql_role/tasks/main.yml new file mode 100644 index 0000000..5bcd5ec --- /dev/null +++ b/tests/integration/targets/test_mysql_role/tasks/main.yml @@ -0,0 +1,7 @@ +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +# mysql_role module initial CI tests +- import_tasks: mysql_role_initial.yml diff --git a/tests/integration/targets/test_mysql_role/tasks/mysql_role_initial.yml b/tests/integration/targets/test_mysql_role/tasks/mysql_role_initial.yml new file mode 100644 index 0000000..ba3125c --- /dev/null +++ b/tests/integration/targets/test_mysql_role/tasks/mysql_role_initial.yml @@ -0,0 +1,1151 @@ +# Test code for mysql_role module + +- vars: + mysql_parameters: &mysql_params + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + + task_parameters: &task_params + register: result + + block: + + - name: Get server version + mysql_info: + <<: *mysql_params + register: srv + + - name: When run with unsupported server versions, must fail + <<: *task_params + mysql_role: + <<: *mysql_params + name: test + ignore_errors: yes + + - name: Must fail when meet unsupported version + assert: + that: + - result is failed + - result is search('Roles are not supported by the server') + when: + - srv['version']['major'] < 8 + + # Skip unsupported versions + - meta: end_play + when: + - srv['version']['major'] < 8 + + ######### + # Prepare + - name: Create db {{ test_db }} + <<: *task_params + mysql_db: + <<: *mysql_params + name: '{{ test_db }}' + + - name: Create table {{ test_table }} + <<: *task_params + mysql_query: + <<: *mysql_params + login_db: '{{ test_db }}' + query: 'DROP TABLE IF EXISTS {{ test_table }}' + + - name: Create table {{ test_table }} + <<: *task_params + mysql_query: + <<: *mysql_params + login_db: '{{ test_db }}' + query: 'CREATE TABLE IF NOT EXISTS {{ test_table }} (id int)' + + - name: Create users + <<: *task_params + mysql_user: + <<: *mysql_params + name: '{{ item }}' + password: '{{ mysql_password }}' + loop: + - '{{ user0 }}' + - '{{ user1 }}' + - '{{ user2 }}' + + ########### + # Run tests + + - name: Create role {{ role0 }} in check_mode + <<: *task_params + mysql_role: + <<: *mysql_params + name: '{{ role0 }}' + state: present + members: + - '{{ user0 }}@localhost' + check_mode: yes + + - name: Check + assert: + that: + - result is changed + + - name: Check in DB + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SELECT 1 FROM mysql.user WHERE User = '{{ role0 }}' AND Host = '%'" + + - name: Check + assert: + that: + - result.rowcount.0 == 0 + + # It must fail because of check_mode + - name: Check in DB, if not granted, the query will fail + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SHOW GRANTS FOR {{ user0 }}@localhost USING '{{ role0 }}'" + ignore_errors: yes + + - name: Check + assert: + that: + - result is failed + + #===================== + + - name: Check that the user have no active roles + <<: *task_params + mysql_query: + login_user: '{{ user0 }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + query: 'SELECT current_role()' + + - name: Check + assert: + that: + - result.query_result.0.0["current_role()"] == "NONE" + + - name: Create role {{ role0 }} + <<: *task_params + mysql_role: + <<: *mysql_params + name: '{{ role0 }}' + state: present + members: + - '{{ user0 }}@localhost' + + - name: Check + assert: + that: + - result is changed + + - name: Check in DB + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SELECT 1 FROM mysql.user WHERE User = '{{ role0 }}' AND Host = '%'" + + - name: Check + assert: + that: + - result.rowcount.0 == 1 + + - name: Check in DB, if not granted, the query will fail + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SHOW GRANTS FOR {{ user0 }}@localhost USING '{{ role0 }}'" + + - name: Check + assert: + that: + - result is succeeded + + - name: Check that the role is active + <<: *task_params + mysql_query: + login_user: '{{ user0 }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + query: 'SELECT current_role()' + + - name: Check + assert: + that: + - result.query_result.0.0["current_role()"] == "`{{ role0 }}`@`%`" + + #======================== + + - name: Create role {{ role0 }} again in check_mode + <<: *task_params + mysql_role: + <<: *mysql_params + name: '{{ role0 }}' + state: present + check_mode: yes + + - name: Check + assert: + that: + - result is not changed + + - name: Check in DB + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SELECT 1 FROM mysql.user WHERE User = '{{ role0 }}' AND Host = '%'" + + - name: Check + assert: + that: + - result.rowcount.0 == 1 + + - name: Check in DB, if not granted, the query will fail + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SHOW GRANTS FOR {{ user0 }}@localhost USING '{{ role0 }}'" + + - name: Check + assert: + that: + - result is succeeded + + #======================== + + - name: Create role {{ role0 }} again + <<: *task_params + mysql_role: + <<: *mysql_params + name: '{{ role0 }}' + state: present + + - name: Check + assert: + that: + - result is not changed + + - name: Check in DB + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SELECT 1 FROM mysql.user WHERE User = '{{ role0 }}' AND Host = '%'" + + - name: Check + assert: + that: + - result.rowcount.0 == 1 + + #======================== + + - name: Drop role {{ role0 }} in check_mode + <<: *task_params + mysql_role: + <<: *mysql_params + name: '{{ role0 }}' + state: absent + check_mode: yes + + - name: Check + assert: + that: + - result is changed + + - name: Check in DB + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SELECT 1 FROM mysql.user WHERE User = '{{ role0 }}' AND Host = '%'" + + - name: Check + assert: + that: + - result.rowcount.0 == 1 + + # Must pass because of check_mode + - name: Check in DB, if not granted, the query will fail + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SHOW GRANTS FOR {{ user0 }}@localhost USING '{{ role0 }}'" + + - name: Check + assert: + that: + - result is succeeded + + #======================== + + - name: Drop role {{ role0 }} + <<: *task_params + mysql_role: + <<: *mysql_params + name: '{{ role0 }}' + state: absent + + - name: Check + assert: + that: + - result is changed + + - name: Check in DB + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SELECT 1 FROM mysql.user WHERE User = '{{ role0 }}' AND Host = '%'" + + - name: Check + assert: + that: + - result.rowcount.0 == 0 + + - name: Check in DB, if not granted, the query will fail + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SHOW GRANTS FOR {{ user0 }}@localhost USING '{{ role0 }}'" + ignore_errors: yes + + - name: Check + assert: + that: + - result is failed + + #======================== + + - name: Drop role {{ role0 }} again in check_mode + <<: *task_params + mysql_role: + <<: *mysql_params + name: '{{ role0 }}' + state: absent + check_mode: yes + + - name: Check + assert: + that: + - result is not changed + + - name: Drop role {{ role0 }} again + <<: *task_params + mysql_role: + <<: *mysql_params + name: '{{ role0 }}' + state: absent + + - name: Check + assert: + that: + - result is not changed + + # ================== + + - name: Create role {{ role0 }} in check_mode + <<: *task_params + mysql_role: + <<: *mysql_params + name: '{{ role0 }}' + state: present + members: + - '{{ user0 }}@localhost' + priv: + '*.*': 'SELECT,INSERT' + 'mysql.*': 'UPDATE' + check_mode: yes + + - name: Check + assert: + that: + - result is changed + + - name: Check in DB + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SELECT 1 FROM mysql.user WHERE User = '{{ role0 }}' AND Host = '%'" + + - name: Check + assert: + that: + - result.rowcount.0 == 0 + + #======================== + + - name: Create role {{ role0 }} + <<: *task_params + mysql_role: + <<: *mysql_params + name: '{{ role0 }}' + state: present + members: + - '{{ user0 }}@localhost' + priv: + '*.*': 'SELECT,INSERT' + 'mysql.*': 'UPDATE' + + - name: Check + assert: + that: + - result is changed + + - name: Check in DB + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SELECT 1 FROM mysql.user WHERE User = '{{ role0 }}' AND Host = '%'" + + - name: Check + assert: + that: + - result.rowcount.0 == 1 + + #======================== + + - name: Create role {{ role0 }} in check_mode again + <<: *task_params + mysql_role: + <<: *mysql_params + name: '{{ role0 }}' + state: present + members: + - '{{ user0 }}@localhost' + priv: + '*.*': 'SELECT,INSERT' + 'mysql.*': 'UPDATE' + check_mode: yes + + - name: Check + assert: + that: + - result is not changed + + #======================== + + - name: Create role {{ role0 }} again + <<: *task_params + mysql_role: + <<: *mysql_params + name: '{{ role0 }}' + state: present + members: + - '{{ user0 }}@localhost' + priv: + '*.*': 'SELECT,INSERT' + 'mysql.*': 'UPDATE' + + - name: Check + assert: + that: + - result is not changed + + # ############################################## + # Test rewriting / appending / detaching members + # ############################################## + + - name: Create role {{ role1 }} + <<: *task_params + mysql_role: + <<: *mysql_params + name: '{{ role1 }}' + state: present + + # Rewriting members + - name: Rewrite members in check_mode + <<: *task_params + mysql_role: + <<: *mysql_params + name: '{{ role0 }}' + state: present + members: + - '{{ user1 }}@localhost' + - '{{ user2 }}@localhost' + - '{{ role1 }}' + check_mode: yes + + - name: Check + assert: + that: + - result is changed + + # user0 is still a member because of check_mode + - name: Check in DB, if not granted, the query will fail + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SHOW GRANTS FOR {{ user0 }}@localhost USING '{{ role0 }}'" + + - name: Check + assert: + that: + - result is succeeded + + # user1, user2, and role1 are not members because of check_mode + - name: Check in DB, if not granted, the query will fail + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SHOW GRANTS FOR {{ user1 }}@localhost USING '{{ role0 }}'" + ignore_errors: yes + + - name: Check + assert: + that: + - result is failed + + - name: Check in DB, if not granted, the query will fail + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SHOW GRANTS FOR {{ user2 }}@localhost USING '{{ role0 }}'" + ignore_errors: yes + + - name: Check + assert: + that: + - result is failed + + - name: Check in DB, if not granted, the query will fail + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SHOW GRANTS FOR {{ role1 }} USING '{{ role0 }}'" + ignore_errors: yes + + - name: Check + assert: + that: + - result is failed + #======================== + + - name: Rewrite members + <<: *task_params + mysql_role: + <<: *mysql_params + name: '{{ role0 }}' + state: present + members: + - '{{ user1 }}@localhost' + - '{{ user2 }}@localhost' + - '{{ role1 }}' + + - name: Check + assert: + that: + - result is changed + + # user0 is not a member any more + - name: Check in DB, if not granted, the query will fail + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SHOW GRANTS FOR {{ user0 }}@localhost USING '{{ role0 }}'" + ignore_errors: yes + + - name: Check + assert: + that: + - result is failed + + - name: Check in DB, if not granted, the query will fail + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SHOW GRANTS FOR {{ user1 }}@localhost USING '{{ role0 }}'" + + - name: Check + assert: + that: + - result is succeeded + + - name: Check in DB, if not granted, the query will fail + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SHOW GRANTS FOR {{ user2 }}@localhost USING '{{ role0 }}'" + + - name: Check + assert: + that: + - result is succeeded + + - name: Check in DB, if not granted, the query will fail + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SHOW GRANTS FOR {{ role1 }} USING '{{ role0 }}'" + ignore_errors: yes + + - name: Check + assert: + that: + - result is succeeded + + #========================== + + - name: Rewrite members again in check_mode + <<: *task_params + mysql_role: + <<: *mysql_params + name: '{{ role0 }}' + state: present + members: + - '{{ user1 }}@localhost' + - '{{ user2 }}@localhost' + - '{{ role1 }}' + check_mode: yes + + - name: Check + assert: + that: + - result is not changed + + #========================== + + - name: Rewrite members again + <<: *task_params + mysql_role: + <<: *mysql_params + name: '{{ role0 }}' + state: present + members: + - '{{ user1 }}@localhost' + - '{{ user2 }}@localhost' + - '{{ role1 }}' + + - name: Check + assert: + that: + - result is not changed + + #========================== + + # Append members + - name: Append a member in check_mode + <<: *task_params + mysql_role: + <<: *mysql_params + name: '{{ role0 }}' + state: present + append_members: yes + members: + - '{{ user0 }}@localhost' + check_mode: yes + + - name: Check + assert: + that: + - result is changed + + - name: Check in DB, if not granted, the query will fail + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SHOW GRANTS FOR {{ user0 }}@localhost USING '{{ role0 }}'" + ignore_errors: yes + + - name: Check + assert: + that: + - result is failed + + #===================== + + - name: Append a member + <<: *task_params + mysql_role: + <<: *mysql_params + name: '{{ role0 }}' + state: present + append_members: yes + members: + - '{{ user0 }}@localhost' + + - name: Check + assert: + that: + - result is changed + + - name: Check in DB, if not granted, the query will fail + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SHOW GRANTS FOR {{ user0 }}@localhost USING '{{ role0 }}'" + + - name: Check + assert: + that: + - result is succeeded + + # user1 and user2 must still be in DB because we are appending + - name: Check in DB, if not granted, the query will fail + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SHOW GRANTS FOR {{ user1 }}@localhost USING '{{ role0 }}'" + + - name: Check + assert: + that: + - result is succeeded + + - name: Check in DB, if not granted, the query will fail + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SHOW GRANTS FOR {{ user2 }}@localhost USING '{{ role0 }}'" + + - name: Check + assert: + that: + - result is succeeded + + #======================== + + - name: Append a member again in check_mode + <<: *task_params + mysql_role: + <<: *mysql_params + name: '{{ role0 }}' + state: present + append_members: yes + members: + - '{{ user0 }}@localhost' + check_mode: yes + + - name: Check + assert: + that: + - result is not changed + + #======================== + + - name: Append a member again + <<: *task_params + mysql_role: + <<: *mysql_params + name: '{{ role0 }}' + state: present + append_members: yes + members: + - '{{ user0 }}@localhost' + + - name: Check + assert: + that: + - result is not changed + + ############## + # Detach users + - name: Detach users in check_mode + <<: *task_params + mysql_role: + <<: *mysql_params + name: '{{ role0 }}' + state: present + detach_members: yes + members: + - '{{ user1 }}@localhost' + - '{{ user2 }}@localhost' + check_mode: yes + + - name: Check + assert: + that: + - result is changed + + # They must be there because of check_mode + - name: Check in DB, if not granted, the query will fail + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SHOW GRANTS FOR {{ user0 }}@localhost USING '{{ role0 }}'" + + - name: Check + assert: + that: + - result is succeeded + + - name: Check in DB, if not granted, the query will fail + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SHOW GRANTS FOR {{ user1 }}@localhost USING '{{ role0 }}'" + + - name: Check + assert: + that: + - result is succeeded + + - name: Check in DB, if not granted, the query will fail + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SHOW GRANTS FOR {{ user2 }}@localhost USING '{{ role0 }}'" + + - name: Check + assert: + that: + - result is succeeded + + #======================== + + - name: Detach users + <<: *task_params + mysql_role: + <<: *mysql_params + name: '{{ role0 }}' + state: present + detach_members: yes + members: + - '{{ user1 }}@localhost' + - '{{ user2 }}@localhost' + + - name: Check + assert: + that: + - result is changed + + - name: Check in DB, if not granted, the query will fail + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SHOW GRANTS FOR {{ user0 }}@localhost USING '{{ role0 }}'" + + - name: Check + assert: + that: + - result is succeeded + + - name: Check in DB, if not granted, the query will fail + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SHOW GRANTS FOR {{ user1 }}@localhost USING '{{ role0 }}'" + ignore_errors: yes + + - name: Check + assert: + that: + - result is failed + + - name: Check in DB, if not granted, the query will fail + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SHOW GRANTS FOR {{ user2 }}@localhost USING '{{ role0 }}'" + ignore_errors: yes + + - name: Check + assert: + that: + - result is failed + + #===================== + + - name: Detach users in check_mode again + <<: *task_params + mysql_role: + <<: *mysql_params + name: '{{ role0 }}' + state: present + detach_members: yes + members: + - '{{ user1 }}@localhost' + - '{{ user2 }}@localhost' + check_mode: yes + + - name: Check + assert: + that: + - result is not changed + + - name: Detach users again + <<: *task_params + mysql_role: + <<: *mysql_params + name: '{{ role0 }}' + state: present + detach_members: yes + members: + - '{{ user1 }}@localhost' + - '{{ user2 }}@localhost' + + - name: Check + assert: + that: + - result is not changed + + # ########## + # Test privs + # ########## + + - name: Create test DBs + <<: *task_params + mysql_query: + <<: *mysql_params + query: 'CREATE DATABASE {{ item }}' + loop: + - '{{ test_db1 }}' + - '{{ test_db2 }}' + + - name: Create table {{ test_table }} + <<: *task_params + mysql_query: + <<: *mysql_params + login_db: '{{ item }}' + query: 'CREATE TABLE {{ test_table }} (id int)' + loop: + - '{{ test_db1 }}' + - '{{ test_db2 }}' + + - name: Check grants + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SHOW GRANTS FOR {{ role0 }}" + + - name: Check + assert: + that: + - result.query_result.0.0["Grants for role0@%"] == "GRANT SELECT, INSERT ON *.* TO `role0`@`%`" + - result.query_result.0.1["Grants for role0@%"] == "GRANT UPDATE ON `mysql`.* TO `role0`@`%`" + - result.rowcount.0 == 2 + + - name: Append privs in check_mode + <<: *task_params + mysql_role: + <<: *mysql_params + name: '{{ role0 }}' + state: present + priv: '{{ test_db1 }}.{{ test_table }}:SELECT,INSERT/{{ test_db2 }}.{{ test_table }}:DELETE' + append_privs: yes + check_mode: yes + + - name: Check + assert: + that: + - result is changed + + - name: Check grants + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SHOW GRANTS FOR {{ role0 }}" + + - name: Check + assert: + that: + - result.query_result.0.0["Grants for role0@%"] == "GRANT SELECT, INSERT ON *.* TO `role0`@`%`" + - result.query_result.0.1["Grants for role0@%"] == "GRANT UPDATE ON `mysql`.* TO `role0`@`%`" + - result.rowcount.0 == 2 + + - name: Append privs + <<: *task_params + mysql_role: + <<: *mysql_params + name: '{{ role0 }}' + state: present + priv: '{{ test_db1 }}.{{ test_table }}:SELECT,INSERT/{{ test_db2 }}.{{ test_table }}:DELETE' + append_privs: yes + + - name: Check + assert: + that: + - result is changed + + - name: Check grants + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SHOW GRANTS FOR {{ role0 }}" + + - name: Check + assert: + that: + - result.query_result.0.0["Grants for role0@%"] == "GRANT SELECT, INSERT ON *.* TO `role0`@`%`" + - result.query_result.0.1["Grants for role0@%"] == "GRANT UPDATE ON `mysql`.* TO `role0`@`%`" + - result.query_result.0.2["Grants for role0@%"] == "GRANT SELECT, INSERT ON `test_db1`.`test_table` TO `role0`@`%`" + - result.query_result.0.3["Grants for role0@%"] == "GRANT DELETE ON `test_db2`.`test_table` TO `role0`@`%`" + - result.rowcount.0 == 4 + + - name: Append privs again in check_mode + <<: *task_params + mysql_role: + <<: *mysql_params + name: '{{ role0 }}' + state: present + priv: '{{ test_db1 }}.{{ test_table }}:SELECT,INSERT/{{ test_db2 }}.{{ test_table }}:DELETE' + append_privs: yes + check_mode: yes + + # TODO it must be changed. The module uses user_mod function + # taken from mysql_user module. It's a bug / expected behavior + # because I added a similar tasks to mysql_user tests + # https://github.com/ansible-collections/community.mysql/issues/50#issuecomment-871216825 + # and it's also failed. Create an issue after the module is merged to avoid conflicts. + # TODO Fix this after user_mod is fixed. + - name: Check + assert: + that: + - result is changed + + - name: Append privs again + <<: *task_params + mysql_role: + <<: *mysql_params + name: '{{ role0 }}' + state: present + priv: '{{ test_db1 }}.{{ test_table }}:SELECT,INSERT/{{ test_db2 }}.{{ test_table }}:DELETE' + append_privs: yes + + # TODO it must be changed. The module uses user_mod function + # taken from mysql_user module. It's a bug / expected behavior + # because I added a similar tasks to mysql_user tests + # https://github.com/ansible-collections/community.mysql/issues/50#issuecomment-871216825 + # and it's also failed. Create an issue after the module is merged to avoid conflicts. + # TODO Fix this after user_mod is fixed. + - name: Check + assert: + that: + - result is changed + + - name: Rewrite privs + <<: *task_params + mysql_role: + <<: *mysql_params + name: '{{ role0 }}' + state: present + priv: + '*.*': 'SELECT' + + - name: Check + assert: + that: + - result is changed + + - name: Check grants + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SHOW GRANTS FOR {{ role0 }}" + + - name: Check + assert: + that: + - result.query_result.0.0["Grants for role0@%"] == "GRANT SELECT ON *.* TO `role0`@`%`" + - result.rowcount.0 == 1 + + # ################# + # Test admin option + # ################# + + - name: Drop role + <<: *task_params + mysql_role: + <<: *mysql_params + name: '{{ role0 }}' + state: absent + + - name: Create role with admin + <<: *task_params + mysql_role: + <<: *mysql_params + name: '{{ role0 }}' + state: present + admin: '{{ user0 }}' + ignore_errors: yes + + - name: Check with MySQL + assert: + that: + - result is failed + - result.msg is search('option can be used only with MariaDB') + when: + # Semantically, when there's MySQL + - srv['version']['major'] < 10 + + - name: Check with MariaDB + assert: + that: + - result is changed + when: + # Semantically, when there's MariaDB + - srv['version']['major'] >= 10 + + - name: Check in DB + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SELECT 1 FROM mysql.user WHERE User = '{{ role0 }}' AND Host = '%'" + when: + # Semantically, when there's MariaDB + - srv['version']['major'] >= 10 + + - name: Check + assert: + that: + - result.rowcount.0 == 1 + when: + # Semantically, when there's MariaDB + - srv['version']['major'] >= 10 + + - name: Create role with admin again + <<: *task_params + mysql_role: + <<: *mysql_params + name: '{{ role0 }}' + state: present + admin: '{{ user0 }}' + ignore_errors: yes + + - name: Check with MySQL + assert: + that: + - result is failed + - result.msg is search('option can be used only with MariaDB') + when: + # Semantically, when there's MySQL + - srv['version']['major'] < 10 + + - name: Check with MariaDB + assert: + that: + - result is not changed + # Semantically, when there's MariaDB + when: + - srv['version']['major'] >= 10 + + # Try to grant a role to a user who does not exist + - name: Create role with admin again + <<: *task_params + mysql_role: + <<: *mysql_params + name: '{{ role0 }}' + state: present + members: + - '{{ nonexistent }}@localhost' + ignore_errors: yes + + - name: Check with MySQL + assert: + that: + - result is failed + - result.msg is search('does not exist') + when: + # Semantically, when there's MySQL + - srv['version']['major'] < 10 + + always: + # Clean up + - name: Drop DBs + mysql_query: + <<: *mysql_params + query: 'DROP DATABASE {{ item }}' + loop: + - '{{ test_db }}' + - '{{ test_db1 }}' + - '{{ test_db2 }}' diff --git a/tests/unit/plugins/modules/test_mysql_user.py b/tests/unit/plugins/module_utils/test_mysql_user.py similarity index 98% rename from tests/unit/plugins/modules/test_mysql_user.py rename to tests/unit/plugins/module_utils/test_mysql_user.py index 3b88e24..0edf922 100644 --- a/tests/unit/plugins/modules/test_mysql_user.py +++ b/tests/unit/plugins/module_utils/test_mysql_user.py @@ -9,7 +9,7 @@ try: except ImportError: from mock import MagicMock -from ansible_collections.community.mysql.plugins.modules.mysql_user import ( +from ansible_collections.community.mysql.plugins.module_utils.user import ( handle_grant_on_col, has_grant_on_col, normalize_col_grants, diff --git a/tests/unit/plugins/modules/test_mysql_role.py b/tests/unit/plugins/modules/test_mysql_role.py new file mode 100644 index 0000000..2ed5a2c --- /dev/null +++ b/tests/unit/plugins/modules/test_mysql_role.py @@ -0,0 +1,119 @@ +# -*- coding: utf-8 -*- + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import pytest + +from ansible_collections.community.mysql.plugins.modules.mysql_role import ( + MariaDBQueryBuilder, + MySQLQueryBuilder, + normalize_users, +) + +# TODO: Also cover DbServer, Role, MySQLRoleImpl, MariaDBRoleImpl classes + + +class Module(): + def __init__(self): + self.msg = None + + def fail_json(self, msg=None): + self.msg = msg + + +module = Module() + + +@pytest.mark.parametrize( + 'builder,output', + [ + (MariaDBQueryBuilder('role0'), ("SELECT count(*) FROM mysql.user WHERE user = %s AND is_role = 'Y'", ('role0'))), + (MySQLQueryBuilder('role0', '%'), ('SELECT count(*) FROM mysql.user WHERE user = %s AND host = %s', ('role0', '%'))), + (MariaDBQueryBuilder('role1'), ("SELECT count(*) FROM mysql.user WHERE user = %s AND is_role = 'Y'", ('role1'))), + (MySQLQueryBuilder('role1', 'fake'), ('SELECT count(*) FROM mysql.user WHERE user = %s AND host = %s', ('role1', 'fake'))), + ] +) +def test_query_builder_role_exists(builder, output): + """Test role_exists method of the builder classes.""" + assert builder.role_exists() == output + + +@pytest.mark.parametrize( + 'builder,admin,output', + [ + (MariaDBQueryBuilder('role0'), None, ('CREATE ROLE %s', ('role0',))), + (MySQLQueryBuilder('role0', '%'), None, ('CREATE ROLE %s', ('role0',))), + (MariaDBQueryBuilder('role1'), None, ('CREATE ROLE %s', ('role1',))), + (MySQLQueryBuilder('role1', 'fake'), None, ('CREATE ROLE %s', ('role1',))), + (MariaDBQueryBuilder('role0'), ('user0', ''), ('CREATE ROLE %s WITH ADMIN %s', ('role0', 'user0'))), + (MySQLQueryBuilder('role0', '%'), ('user0', ''), ('CREATE ROLE %s', ('role0',))), + (MariaDBQueryBuilder('role1'), ('user0', 'localhost'), ('CREATE ROLE %s WITH ADMIN %s@%s', ('role1', 'user0', 'localhost'))), + (MySQLQueryBuilder('role1', 'fake'), ('user0', 'localhost'), ('CREATE ROLE %s', ('role1',))), + ] +) +def test_query_builder_role_create(builder, admin, output): + """Test role_create method of the builder classes.""" + assert builder.role_create(admin) == output + + +@pytest.mark.parametrize( + 'builder,user,output', + [ + (MariaDBQueryBuilder('role0'), ('user0', ''), ('GRANT %s TO %s', ('role0', 'user0'))), + (MySQLQueryBuilder('role0', '%'), ('user0', ''), ('GRANT %s@%s TO %s', ('role0', '%', 'user0'))), + (MariaDBQueryBuilder('role1'), ('user0', 'localhost'), ('GRANT %s TO %s@%s', ('role1', 'user0', 'localhost'))), + (MySQLQueryBuilder('role1', 'fake'), ('user0', 'localhost'), ('GRANT %s@%s TO %s@%s', ('role1', 'fake', 'user0', 'localhost'))), + ] +) +def test_query_builder_role_grant(builder, user, output): + """Test role_grant method of the builder classes.""" + assert builder.role_grant(user) == output + + +@pytest.mark.parametrize( + 'builder,user,output', + [ + (MariaDBQueryBuilder('role0'), ('user0', ''), ('REVOKE %s FROM %s', ('role0', 'user0'))), + (MySQLQueryBuilder('role0', '%'), ('user0', ''), ('REVOKE %s@%s FROM %s', ('role0', '%', 'user0'))), + (MariaDBQueryBuilder('role1'), ('user0', 'localhost'), ('REVOKE %s FROM %s@%s', ('role1', 'user0', 'localhost'))), + (MySQLQueryBuilder('role1', 'fake'), ('user0', 'localhost'), ('REVOKE %s@%s FROM %s@%s', ('role1', 'fake', 'user0', 'localhost'))), + ] +) +def test_query_builder_role_revoke(builder, user, output): + """Test role_revoke method of the builder classes.""" + assert builder.role_revoke(user) == output + + +@pytest.mark.parametrize( + 'input_,output,is_mariadb', + [ + (['user'], [('user', '')], True), + (['user'], [('user', '%')], False), + (['user@%'], [('user', '%')], True), + (['user@%'], [('user', '%')], False), + (['user@localhost'], [('user', 'localhost')], True), + (['user@localhost'], [('user', 'localhost')], False), + (['user', 'user@%'], [('user', ''), ('user', '%')], True), + (['user', 'user@%'], [('user', '%'), ('user', '%')], False), + ] +) +def test_normalize_users(input_, output, is_mariadb): + """Test normalize_users function with expected input.""" + assert normalize_users(None, input_, is_mariadb) == output + + +@pytest.mark.parametrize( + 'input_,is_mariadb,err_msg', + [ + ([''], True, "Member's name cannot be empty."), + ([''], False, "Member's name cannot be empty."), + ([None], True, "Error occured while parsing"), + ([None], False, "Error occured while parsing"), + ] +) +def test_normalize_users_failing(input_, is_mariadb, err_msg): + """Test normalize_users function with wrong input.""" + + normalize_users(module, input_, is_mariadb) + assert err_msg in module.msg From 968dabef7f634adf0e8f1b89e0e7889d553fa2d9 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Wed, 11 Aug 2021 12:12:42 +0300 Subject: [PATCH 027/217] Remove a changelog fragment after release (#203) --- .../193-reflect_changed_status_in_replace_statements.yml | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 changelogs/fragments/193-reflect_changed_status_in_replace_statements.yml diff --git a/changelogs/fragments/193-reflect_changed_status_in_replace_statements.yml b/changelogs/fragments/193-reflect_changed_status_in_replace_statements.yml deleted file mode 100644 index 8ce0461..0000000 --- a/changelogs/fragments/193-reflect_changed_status_in_replace_statements.yml +++ /dev/null @@ -1,2 +0,0 @@ -minor_changes: -- mysql_query - correctly reflect changed status in replace statements (https://github.com/ansible-collections/community.mysql/pull/193). \ No newline at end of file From 8a17e43eae5c93266a9d8cbdbc563c5550d00e1f Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Thu, 19 Aug 2021 17:20:28 +0300 Subject: [PATCH 028/217] Fix sanity errors (#206) --- plugins/module_utils/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/module_utils/user.py b/plugins/module_utils/user.py index c59f72a..a8da7f1 100644 --- a/plugins/module_utils/user.py +++ b/plugins/module_utils/user.py @@ -86,7 +86,7 @@ def sanitize_requires(tls_requires): if tls_requires: for key in tls_requires.keys(): sanitized_requires[key.upper()] = tls_requires[key] - if any([key in ["CIPHER", "ISSUER", "SUBJECT"] for key in sanitized_requires.keys()]): + if any(key in ["CIPHER", "ISSUER", "SUBJECT"] for key in sanitized_requires.keys()): sanitized_requires.pop("SSL", None) sanitized_requires.pop("X509", None) return sanitized_requires From a1f419d5413f198ace0e837d01e68ee1abb142fc Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Fri, 20 Aug 2021 09:17:34 +0300 Subject: [PATCH 029/217] mysql_info: fix TypeError failure when there are databases that do not contain tables (#205) * mysql_info: fix TypeError failure when there are databases that do not contain tables * Add changelog fragment --- ..._info_fix_failure_when_no_tables_in_db.yml | 2 ++ plugins/modules/mysql_info.py | 3 +++ .../targets/test_mysql_info/tasks/main.yml | 22 +++++++++++++++++++ 3 files changed, 27 insertions(+) create mode 100644 changelogs/fragments/205-mysql_info_fix_failure_when_no_tables_in_db.yml diff --git a/changelogs/fragments/205-mysql_info_fix_failure_when_no_tables_in_db.yml b/changelogs/fragments/205-mysql_info_fix_failure_when_no_tables_in_db.yml new file mode 100644 index 0000000..fd9260e --- /dev/null +++ b/changelogs/fragments/205-mysql_info_fix_failure_when_no_tables_in_db.yml @@ -0,0 +1,2 @@ +bugfixes: +- mysql_info - fix TypeError failure when there are databases that do not contain tables (https://github.com/ansible-collections/community.mysql/issues/204). diff --git a/plugins/modules/mysql_info.py b/plugins/modules/mysql_info.py index 7380b6b..6f57403 100644 --- a/plugins/modules/mysql_info.py +++ b/plugins/modules/mysql_info.py @@ -474,6 +474,9 @@ class MySQL_Info(object): self.info['databases'][db['name']] = {} if not exclude_fields or 'db_size' not in exclude_fields: + if db['size'] is None: + db['size'] = 0 + self.info['databases'][db['name']]['size'] = int(db['size']) # If empty dbs are not needed in the returned dict, exit from the method diff --git a/tests/integration/targets/test_mysql_info/tasks/main.yml b/tests/integration/targets/test_mysql_info/tasks/main.yml index 785e814..0ed2af9 100644 --- a/tests/integration/targets/test_mysql_info/tasks/main.yml +++ b/tests/integration/targets/test_mysql_info/tasks/main.yml @@ -191,3 +191,25 @@ state: absent - include: issue-28.yml + + # https://github.com/ansible-collections/community.mysql/issues/204 + - name: Create database containing only views + mysql_db: + <<: *mysql_params + name: allviews + + - name: Create view + mysql_query: + <<: *mysql_params + login_db: allviews + query: 'CREATE VIEW v_today (today) AS SELECT CURRENT_DATE' + + - name: Fetch info + mysql_info: + <<: *mysql_params + register: result + + - name: Check + assert: + that: + result.databases.allviews.size == 0 From 663590689f3fe9aa94d223ccec527399c383480a Mon Sep 17 00:00:00 2001 From: int32bit Date: Wed, 25 Aug 2021 15:21:24 +0800 Subject: [PATCH 030/217] Fix wrong impl for mysql (#210) If 'mariadb' in version info, the db instance should be mariadb(reverse in code) rather than mysql. --- plugins/module_utils/user.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/module_utils/user.py b/plugins/module_utils/user.py index a8da7f1..c475c19 100644 --- a/plugins/module_utils/user.py +++ b/plugins/module_utils/user.py @@ -859,8 +859,8 @@ def get_impl(cursor): global impl cursor.execute("SELECT VERSION()") if 'mariadb' in cursor.fetchone()[0].lower(): - from ansible_collections.community.mysql.plugins.module_utils.implementations.mariadb import user as mysqluser - impl = mysqluser - else: - from ansible_collections.community.mysql.plugins.module_utils.implementations.mysql import user as mariauser + from ansible_collections.community.mysql.plugins.module_utils.implementations.mariadb import user as mariauser impl = mariauser + else: + from ansible_collections.community.mysql.plugins.module_utils.implementations.mysql import user as mysqluser + impl = mysqluser From 4de0e25ea0eed0264763c43a73a10244dc7c1889 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Thu, 23 Sep 2021 12:53:14 +0300 Subject: [PATCH 031/217] Update README.md (#216) --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index f3768f0..11727ee 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,13 @@ The current maintainers (contributors with `write` or higher access) are listed To learn how to maintain / become a maintainer of this collection, refer to the [Maintainer guidelines](https://github.com/ansible-collections/community.mysql/blob/main/MAINTAINING.md). +It is necessary for maintainers of this collection to be subscribed to: + +* The collection itself (the `Watch` button -> `All Activity` in the upper right corner of the repository's homepage). +* The "Changes Impacting Collection Contributors and Maintainers" [issue](https://github.com/ansible-collections/overview/issues/45). + +They also should be subscribed to Ansible's [The Bullhorn newsletter](https://docs.ansible.com/ansible/devel/community/communication.html#the-bullhorn). + ## Communication We announce releases and important changes through Ansible's [The Bullhorn newsletter](https://eepurl.com/gZmiEP). Be sure you are subscribed. From 0ce1fa1634eab1152c7bb442ca7ee82935081e05 Mon Sep 17 00:00:00 2001 From: "R.Sicart" Date: Thu, 23 Sep 2021 11:53:37 +0200 Subject: [PATCH 032/217] mysql_user: replace VALID_PRIVS by get_valid_privs() function (#217) * mysql_user: replace VALID_PRIVS by get_valid_privs() function * Add EXTRA_PRIVS in case we need to add more privs in the future * Add changelog fragment --- ...-get-valid-privs-from-show-privilegees.yml | 2 + plugins/module_utils/user.py | 49 +++++-------------- plugins/modules/mysql_role.py | 4 +- plugins/modules/mysql_user.py | 4 +- 4 files changed, 19 insertions(+), 40 deletions(-) create mode 100644 changelogs/fragments/217-mysql-user-replace-get-valid-privs-from-show-privilegees.yml diff --git a/changelogs/fragments/217-mysql-user-replace-get-valid-privs-from-show-privilegees.yml b/changelogs/fragments/217-mysql-user-replace-get-valid-privs-from-show-privilegees.yml new file mode 100644 index 0000000..60cd451 --- /dev/null +++ b/changelogs/fragments/217-mysql-user-replace-get-valid-privs-from-show-privilegees.yml @@ -0,0 +1,2 @@ +minor_changes: + - mysql_user - replace VALID_PRIVS constant by get_valid_privs() function (https://github.com/ansible-collections/community.mysql/pull/217). diff --git a/plugins/module_utils/user.py b/plugins/module_utils/user.py index c475c19..2ba60f2 100644 --- a/plugins/module_utils/user.py +++ b/plugins/module_utils/user.py @@ -19,41 +19,7 @@ from ansible_collections.community.mysql.plugins.module_utils.mysql import ( ) -VALID_PRIVS = frozenset(('CREATE', 'DROP', 'GRANT', 'GRANT OPTION', - 'LOCK TABLES', 'REFERENCES', 'EVENT', 'ALTER', - 'DELETE', 'INDEX', 'INSERT', 'SELECT', 'UPDATE', - 'CREATE TEMPORARY TABLES', 'TRIGGER', 'CREATE VIEW', - 'SHOW VIEW', 'ALTER ROUTINE', 'CREATE ROUTINE', - 'EXECUTE', 'FILE', 'CREATE TABLESPACE', 'CREATE USER', - 'PROCESS', 'PROXY', 'RELOAD', 'REPLICATION CLIENT', - 'REPLICATION SLAVE', 'SHOW DATABASES', 'SHUTDOWN', - 'SUPER', 'ALL', 'ALL PRIVILEGES', 'USAGE', - 'REQUIRESSL', # Deprecated, to be removed in version 3.0.0 - 'CREATE ROLE', 'DROP ROLE', 'APPLICATION_PASSWORD_ADMIN', - 'AUDIT_ADMIN', 'BACKUP_ADMIN', 'BINLOG_ADMIN', - 'BINLOG_ENCRYPTION_ADMIN', 'CLONE_ADMIN', 'CONNECTION_ADMIN', - 'ENCRYPTION_KEY_ADMIN', 'FIREWALL_ADMIN', 'FIREWALL_USER', - 'GROUP_REPLICATION_ADMIN', 'INNODB_REDO_LOG_ARCHIVE', - 'NDB_STORED_USER', 'PERSIST_RO_VARIABLES_ADMIN', - 'REPLICATION_APPLIER', 'REPLICATION_SLAVE_ADMIN', - 'RESOURCE_GROUP_ADMIN', 'RESOURCE_GROUP_USER', - 'ROLE_ADMIN', 'SESSION_VARIABLES_ADMIN', 'SET_USER_ID', - 'SYSTEM_USER', 'SYSTEM_VARIABLES_ADMIN', 'SYSTEM_USER', - 'TABLE_ENCRYPTION_ADMIN', 'VERSION_TOKEN_ADMIN', - 'XA_RECOVER_ADMIN', 'LOAD FROM S3', 'SELECT INTO S3', - 'INVOKE LAMBDA', - 'ALTER ROUTINE', - 'BINLOG ADMIN', - 'BINLOG MONITOR', - 'BINLOG REPLAY', - 'CONNECTION ADMIN', - 'READ_ONLY ADMIN', - 'REPLICATION MASTER ADMIN', - 'REPLICATION SLAVE ADMIN', - 'SET USER', - 'SHOW_ROUTINE', - 'SLAVE MONITOR', - 'REPLICA MONITOR',)) +EXTRA_PRIVS = ['ALL', 'ALL PRIVILEGES', 'GRANT', 'REQUIRESSL'] class InvalidPrivsError(Exception): @@ -141,6 +107,13 @@ def get_tls_requires(cursor, user, host): return requires or None +def get_valid_privs(cursor): + cursor.execute("SHOW PRIVILEGES") + show_privs = [priv[0].upper() for priv in cursor.fetchall()] + all_privs = show_privs + EXTRA_PRIVS + return frozenset(all_privs) + + def get_grants(cursor, user, host): cursor.execute("SHOW GRANTS FOR %s@%s", (user, host)) grants_line = list(filter(lambda x: "ON *.*" in x[0], cursor.fetchall()))[0] @@ -583,7 +556,7 @@ def sort_column_order(statement): return '%s(%s)' % (priv_name, ', '.join(columns)) -def privileges_unpack(priv, mode): +def privileges_unpack(priv, mode, valid_privs): """ Take a privileges string, typically passed as a parameter, and unserialize it into a dictionary, the same format as privileges_get() above. We have this custom format to avoid using YAML/JSON strings inside YAML playbooks. Example @@ -630,8 +603,8 @@ def privileges_unpack(priv, mode): output[pieces[0]] = normalize_col_grants(output[pieces[0]]) new_privs = frozenset(privs) - if not new_privs.issubset(VALID_PRIVS): - raise InvalidPrivsError('Invalid privileges specified: %s' % new_privs.difference(VALID_PRIVS)) + if not new_privs.issubset(valid_privs): + raise InvalidPrivsError('Invalid privileges specified: %s' % new_privs.difference(valid_privs)) if '*.*' not in output: output['*.*'] = ['USAGE'] diff --git a/plugins/modules/mysql_role.py b/plugins/modules/mysql_role.py index 80d0144..47b0b58 100644 --- a/plugins/modules/mysql_role.py +++ b/plugins/modules/mysql_role.py @@ -250,6 +250,7 @@ from ansible_collections.community.mysql.plugins.module_utils.user import ( get_mode, user_mod, privileges_grant, + get_valid_privs, privileges_unpack, ) from ansible.module_utils._text import to_native @@ -1013,7 +1014,8 @@ def main(): module.fail_json(msg=to_native(e)) try: - priv = privileges_unpack(priv, mode) + valid_privs = get_valid_privs(cursor) + priv = privileges_unpack(priv, mode, valid_privs) except Exception as e: module.fail_json(msg='Invalid privileges string: %s' % to_native(e)) diff --git a/plugins/modules/mysql_user.py b/plugins/modules/mysql_user.py index 3172553..9c043cc 100644 --- a/plugins/modules/mysql_user.py +++ b/plugins/modules/mysql_user.py @@ -318,6 +318,7 @@ from ansible_collections.community.mysql.plugins.module_utils.user import ( handle_requiressl_in_priv_string, InvalidPrivsError, limit_resources, + get_valid_privs, privileges_unpack, sanitize_requires, user_add, @@ -421,7 +422,8 @@ def main(): except Exception as e: module.fail_json(msg=to_native(e)) try: - priv = privileges_unpack(priv, mode) + valid_privs = get_valid_privs(cursor) + priv = privileges_unpack(priv, mode, valid_privs) except Exception as e: module.fail_json(msg="invalid privileges string: %s" % to_native(e)) From 02060b31add54a11e1c3e06e4f3021296afa8df7 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Thu, 23 Sep 2021 16:23:38 +0300 Subject: [PATCH 033/217] Remove fragments after release (#223) --- .../205-mysql_info_fix_failure_when_no_tables_in_db.yml | 2 -- ...mysql-user-replace-get-valid-privs-from-show-privilegees.yml | 2 -- 2 files changed, 4 deletions(-) delete mode 100644 changelogs/fragments/205-mysql_info_fix_failure_when_no_tables_in_db.yml delete mode 100644 changelogs/fragments/217-mysql-user-replace-get-valid-privs-from-show-privilegees.yml diff --git a/changelogs/fragments/205-mysql_info_fix_failure_when_no_tables_in_db.yml b/changelogs/fragments/205-mysql_info_fix_failure_when_no_tables_in_db.yml deleted file mode 100644 index fd9260e..0000000 --- a/changelogs/fragments/205-mysql_info_fix_failure_when_no_tables_in_db.yml +++ /dev/null @@ -1,2 +0,0 @@ -bugfixes: -- mysql_info - fix TypeError failure when there are databases that do not contain tables (https://github.com/ansible-collections/community.mysql/issues/204). diff --git a/changelogs/fragments/217-mysql-user-replace-get-valid-privs-from-show-privilegees.yml b/changelogs/fragments/217-mysql-user-replace-get-valid-privs-from-show-privilegees.yml deleted file mode 100644 index 60cd451..0000000 --- a/changelogs/fragments/217-mysql-user-replace-get-valid-privs-from-show-privilegees.yml +++ /dev/null @@ -1,2 +0,0 @@ -minor_changes: - - mysql_user - replace VALID_PRIVS constant by get_valid_privs() function (https://github.com/ansible-collections/community.mysql/pull/217). From 4f205ef540baef1ea059f0591570eaf1524890be Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Tue, 28 Sep 2021 15:12:34 +0300 Subject: [PATCH 034/217] Copy ignore-2.12.txt to ignore-2.13.txt (#225) --- tests/sanity/ignore-2.13.txt | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 tests/sanity/ignore-2.13.txt diff --git a/tests/sanity/ignore-2.13.txt b/tests/sanity/ignore-2.13.txt new file mode 100644 index 0000000..c0323af --- /dev/null +++ b/tests/sanity/ignore-2.13.txt @@ -0,0 +1,8 @@ +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_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_variables.py validate-modules:doc-required-mismatch From fc984b28aa06f916e380381a51fea3b736d54d37 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Tue, 12 Oct 2021 12:24:28 +0300 Subject: [PATCH 035/217] CI matrix update (#226) * CI matrix update * Fix test_mysql_user * Fix CI * Fix CI * Fix CI * Fix CI * Fix CI --- .github/workflows/ansible-test-plugins.yml | 17 ++++++++++++++- .../targets/setup_mysql/tasks/install.yml | 21 +++++++++++++++++++ .../tasks/config_overrides_defaults.yml | 13 ++++++------ .../targets/test_mysql_db/tasks/issue-28.yml | 7 ++----- .../test_mysql_info/tasks/issue-28.yml | 7 ++----- .../test_mysql_query/tasks/issue-28.yml | 7 ++----- .../test_mysql_replication/tasks/issue-28.yml | 7 ++----- .../test_mysql_user/tasks/issue-28.yml | 7 ++----- .../test_mysql_variables/tasks/issue-28.yml | 7 ++----- .../tasks/mysql_variables.yml | 7 ++----- 10 files changed, 57 insertions(+), 43 deletions(-) diff --git a/.github/workflows/ansible-test-plugins.yml b/.github/workflows/ansible-test-plugins.yml index da79c04..c492261 100644 --- a/.github/workflows/ansible-test-plugins.yml +++ b/.github/workflows/ansible-test-plugins.yml @@ -28,6 +28,7 @@ jobs: - stable-2.9 - stable-2.10 - stable-2.11 + - stable-2.12 - devel steps: @@ -61,9 +62,11 @@ jobs: - stable-2.9 - stable-2.10 - stable-2.11 - #- devel + - stable-2.12 + - devel python: - 3.6 + - 3.8 connector: - pymysql==0.7.10 - pymysql==0.9.3 @@ -71,6 +74,17 @@ jobs: exclude: - mysql: 8.0.22 connector: pymysql==0.7.10 + - python: 3.8 + ansible: stable-2.9 + - python: 3.8 + ansible: stable-2.10 + - python: 3.8 + ansible: stable-2.11 + - python: 3.6 + ansible: stable-2.12 + - python: 3.6 + ansible: devel + steps: - name: Check out code @@ -116,6 +130,7 @@ jobs: - stable-2.9 - stable-2.10 - stable-2.11 + - stable-2.12 - devel steps: diff --git a/tests/integration/targets/setup_mysql/tasks/install.yml b/tests/integration/targets/setup_mysql/tasks/install.yml index aacdddc..e5514a8 100644 --- a/tests/integration/targets/setup_mysql/tasks/install.yml +++ b/tests/integration/targets/setup_mysql/tasks/install.yml @@ -28,6 +28,27 @@ - name: "{{ role_name }} | install | install python packages" pip: name: "{{ python_packages }}" + register: connector + +- name: Debug connector var content + debug: + msg: '{{ connector }}' + +- name: Extract connector.name.0 content + set_fact: + connector_name: "{{ connector.name.0 }}" + +- name: Debug connector_name content + debug: + msg: '{{ connector_name }}' + +- name: Extract connector version + set_fact: + connector_ver: "{{ connector_name.split('=')[2].strip() }}" + +- name: Debug connector_ver var content + debug: + msg: '{{ connector_ver }}' - name: "{{ role_name }} | install | install packages required by mysql" apt: 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 42d8fd7..90c72b5 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 @@ -11,27 +11,26 @@ - name: Add fake port to config file shell: 'echo "port = {{ fake_port }}" >> {{ config_file }}' -- name: Get pymysql version - shell: pip show pymysql | awk '/Version/ {print $2}' - register: pymysql_version - - name: Add blank line shell: 'echo "" >> {{ config_file }}' - when: (pymysql_version.stdout | default('1000', true)) is version('0.9.3', '>=') + when: + - (connector.name.0 is search('pymysql') and connector_ver is version('0.9.3', '>=')) or connector.name.0 is not search('pymysql') - name: Create include_dir file: path: '{{ include_dir }}' state: directory mode: '0777' - when: (pymysql_version.stdout | default('1000', true)) is version('0.9.3', '>=') + when: + - (connector.name.0 is search('pymysql') and connector_ver is version('0.9.3', '>=')) or connector.name.0 is not search('pymysql') - name: Add include_dir lineinfile: path: '{{ config_file }}' line: '!includedir {{ include_dir }}' insertafter: EOF - when: (pymysql_version.stdout | default('1000', true)) is version('0.9.3', '>=') + when: + - (connector.name.0 is search('pymysql') and connector_ver is version('0.9.3', '>=')) or connector.name.0 is not search('pymysql') - name: Create database using fake port to connect to, must fail mysql_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 index 871e92d..a0b037f 100644 --- a/tests/integration/targets/test_mysql_db/tasks/issue-28.yml +++ b/tests/integration/targets/test_mysql_db/tasks/issue-28.yml @@ -9,9 +9,6 @@ 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 /dev/null /dev/null /dev/null /dev/null /dev/null Date: Tue, 12 Oct 2021 14:05:07 +0300 Subject: [PATCH 036/217] integration tests: remove superfluous debug task (#228) * integration tests: remove superfluous debug task * Turn off integration tests against devel --- .github/workflows/ansible-test-plugins.yml | 2 +- tests/integration/targets/setup_mysql/tasks/install.yml | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/ansible-test-plugins.yml b/.github/workflows/ansible-test-plugins.yml index c492261..64435cd 100644 --- a/.github/workflows/ansible-test-plugins.yml +++ b/.github/workflows/ansible-test-plugins.yml @@ -63,7 +63,7 @@ jobs: - stable-2.10 - stable-2.11 - stable-2.12 - - devel + #- devel python: - 3.6 - 3.8 diff --git a/tests/integration/targets/setup_mysql/tasks/install.yml b/tests/integration/targets/setup_mysql/tasks/install.yml index e5514a8..57e4b31 100644 --- a/tests/integration/targets/setup_mysql/tasks/install.yml +++ b/tests/integration/targets/setup_mysql/tasks/install.yml @@ -30,10 +30,6 @@ name: "{{ python_packages }}" register: connector -- name: Debug connector var content - debug: - msg: '{{ connector }}' - - name: Extract connector.name.0 content set_fact: connector_name: "{{ connector.name.0 }}" From bb3e9fd3fa5d6fe62241b87bf38960330856e0ea Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Mon, 18 Oct 2021 16:25:00 +0300 Subject: [PATCH 037/217] mysql_user: fix broken compatibility for priviledge aliases (#233) * mysql_user: fix broken compatibility for priviledge aliases * add changelog fragment * fix changelog fragment * Improve formatting --- .../233-mysql_user_return_valid_privs.yml | 2 + plugins/module_utils/user.py | 43 ++++++++++++++++++- 2 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 changelogs/fragments/233-mysql_user_return_valid_privs.yml diff --git a/changelogs/fragments/233-mysql_user_return_valid_privs.yml b/changelogs/fragments/233-mysql_user_return_valid_privs.yml new file mode 100644 index 0000000..4f4b23e --- /dev/null +++ b/changelogs/fragments/233-mysql_user_return_valid_privs.yml @@ -0,0 +1,2 @@ +bugfixes: + - mysql_user - Fix crash reporting ``Invalid privileges specified`` when passing privileges that became aliases (https://github.com/ansible-collections/community.mysql/issues/232). diff --git a/plugins/module_utils/user.py b/plugins/module_utils/user.py index 2ba60f2..0532de9 100644 --- a/plugins/module_utils/user.py +++ b/plugins/module_utils/user.py @@ -21,6 +21,46 @@ from ansible_collections.community.mysql.plugins.module_utils.mysql import ( EXTRA_PRIVS = ['ALL', 'ALL PRIVILEGES', 'GRANT', 'REQUIRESSL'] +# This list is kept for backwards compatibility after release 2.3.0, +# see https://github.com/ansible-collections/community.mysql/issues/232 for details +VALID_PRIVS = [ + 'CREATE', 'DROP', 'GRANT', 'GRANT OPTION', + 'LOCK TABLES', 'REFERENCES', 'EVENT', 'ALTER', + 'DELETE', 'INDEX', 'INSERT', 'SELECT', 'UPDATE', + 'CREATE TEMPORARY TABLES', 'TRIGGER', 'CREATE VIEW', + 'SHOW VIEW', 'ALTER ROUTINE', 'CREATE ROUTINE', + 'EXECUTE', 'FILE', 'CREATE TABLESPACE', 'CREATE USER', + 'PROCESS', 'PROXY', 'RELOAD', 'REPLICATION CLIENT', + 'REPLICATION SLAVE', 'SHOW DATABASES', 'SHUTDOWN', + 'SUPER', 'ALL', 'ALL PRIVILEGES', 'USAGE', + 'REQUIRESSL', # Deprecated, to be removed in version 3.0.0 + 'CREATE ROLE', 'DROP ROLE', 'APPLICATION_PASSWORD_ADMIN', + 'AUDIT_ADMIN', 'BACKUP_ADMIN', 'BINLOG_ADMIN', + 'BINLOG_ENCRYPTION_ADMIN', 'CLONE_ADMIN', 'CONNECTION_ADMIN', + 'ENCRYPTION_KEY_ADMIN', 'FIREWALL_ADMIN', 'FIREWALL_USER', + 'GROUP_REPLICATION_ADMIN', 'INNODB_REDO_LOG_ARCHIVE', + 'NDB_STORED_USER', 'PERSIST_RO_VARIABLES_ADMIN', + 'REPLICATION_APPLIER', 'REPLICATION_SLAVE_ADMIN', + 'RESOURCE_GROUP_ADMIN', 'RESOURCE_GROUP_USER', + 'ROLE_ADMIN', 'SESSION_VARIABLES_ADMIN', 'SET_USER_ID', + 'SYSTEM_USER', 'SYSTEM_VARIABLES_ADMIN', 'SYSTEM_USER', + 'TABLE_ENCRYPTION_ADMIN', 'VERSION_TOKEN_ADMIN', + 'XA_RECOVER_ADMIN', 'LOAD FROM S3', 'SELECT INTO S3', + 'INVOKE LAMBDA', + 'ALTER ROUTINE', + 'BINLOG ADMIN', + 'BINLOG MONITOR', + 'BINLOG REPLAY', + 'CONNECTION ADMIN', + 'READ_ONLY ADMIN', + 'REPLICATION MASTER ADMIN', + 'REPLICATION SLAVE ADMIN', + 'SET USER', + 'SHOW_ROUTINE', + 'SLAVE MONITOR', + 'REPLICA MONITOR', +] + class InvalidPrivsError(Exception): pass @@ -110,7 +150,8 @@ def get_tls_requires(cursor, user, host): def get_valid_privs(cursor): cursor.execute("SHOW PRIVILEGES") show_privs = [priv[0].upper() for priv in cursor.fetchall()] - all_privs = show_privs + EXTRA_PRIVS + # See the comment above VALID_PRIVS declaration + all_privs = show_privs + EXTRA_PRIVS + VALID_PRIVS return frozenset(all_privs) From 3c3f6513c5b22214874526e5a72b48a439152639 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Tue, 19 Oct 2021 14:44:15 +0300 Subject: [PATCH 038/217] Remove fragments after release 2.3.1 (#237) --- changelogs/fragments/233-mysql_user_return_valid_privs.yml | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 changelogs/fragments/233-mysql_user_return_valid_privs.yml diff --git a/changelogs/fragments/233-mysql_user_return_valid_privs.yml b/changelogs/fragments/233-mysql_user_return_valid_privs.yml deleted file mode 100644 index 4f4b23e..0000000 --- a/changelogs/fragments/233-mysql_user_return_valid_privs.yml +++ /dev/null @@ -1,2 +0,0 @@ -bugfixes: - - mysql_user - Fix crash reporting ``Invalid privileges specified`` when passing privileges that became aliases (https://github.com/ansible-collections/community.mysql/issues/232). From 6b12435b2b4f21e724cb24f6ca0d988681824fb6 Mon Sep 17 00:00:00 2001 From: Nicolas PAYART Date: Thu, 21 Oct 2021 08:38:46 +0200 Subject: [PATCH 039/217] Allow the "%" character in database name (#227) The naming rules for MySQL/MariaDB identifiers, when quoted, allow the `%` character. However, currently, the use of the `%` character in database names results in mismatch or missing databases. - Rewrite query to identify the databases in the catalog using `information_schema` instead of `SHOW DATABASES LIKE` - Escape the `%` character in `CREATE DATABASE` query. Signed-off-by: Nicolas Payart --- .../fragments/227-db-create-special-name.yaml | 2 + plugins/modules/mysql_db.py | 5 +- .../targets/test_mysql_db/tasks/main.yml | 287 +---------------- .../tasks/state_present_absent.yml | 300 ++++++++++++++++++ 4 files changed, 309 insertions(+), 285 deletions(-) create mode 100644 changelogs/fragments/227-db-create-special-name.yaml create mode 100644 tests/integration/targets/test_mysql_db/tasks/state_present_absent.yml diff --git a/changelogs/fragments/227-db-create-special-name.yaml b/changelogs/fragments/227-db-create-special-name.yaml new file mode 100644 index 0000000..99f7e96 --- /dev/null +++ b/changelogs/fragments/227-db-create-special-name.yaml @@ -0,0 +1,2 @@ +bugfixes: + - mysql_db - Fix mismatch when database name contains a ``%`` character (https://github.com/ansible-collections/community.mysql/pull/227). diff --git a/plugins/modules/mysql_db.py b/plugins/modules/mysql_db.py index aa9ade0..c2a6fd8 100644 --- a/plugins/modules/mysql_db.py +++ b/plugins/modules/mysql_db.py @@ -330,7 +330,7 @@ executed_commands = [] def db_exists(cursor, db): res = 0 for each_db in db: - res += cursor.execute("SHOW DATABASES LIKE %s", (each_db.replace("_", r"\_"),)) + res += cursor.execute("SELECT SCHEMA_NAME FROM information_schema.SCHEMATA WHERE SCHEMA_NAME = %s", (each_db,)) return res == len(db) @@ -519,7 +519,8 @@ def db_create(cursor, db, encoding, collation): query_params = dict(enc=encoding, collate=collation) res = 0 for each_db in db: - query = ['CREATE DATABASE %s' % mysql_quote_identifier(each_db, 'database')] + # Escape '%' since mysql cursor.execute() uses a format string + query = ['CREATE DATABASE %s' % mysql_quote_identifier(each_db, 'database').replace('%', '%%')] if encoding: query.append("CHARACTER SET %(enc)s") if collation: diff --git a/tests/integration/targets/test_mysql_db/tasks/main.yml b/tests/integration/targets/test_mysql_db/tasks/main.yml index 139d5bb..def6a97 100644 --- a/tests/integration/targets/test_mysql_db/tasks/main.yml +++ b/tests/integration/targets/test_mysql_db/tasks/main.yml @@ -21,297 +21,18 @@ # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . -# ============================================================ - name: alias mysql command to include default options set_fact: mysql_command: "mysql -u{{ mysql_user }} -p{{ mysql_password }} -P{{ mysql_primary_port }} --protocol=tcp" -- name: remove database if it exists - command: > - "{{ mysql_command }} -sse 'drop database {{ db_name }}'" - ignore_errors: True +- include: state_present_absent.yml -- name: make sure the test database is not there - command: "{{ mysql_command }} {{ db_name }}" - register: mysql_db_check - failed_when: "'1049' not in mysql_db_check.stderr" +- include: state_present_absent.yml db_name="db%" -- name: test state=present for a database name (expect changed=true) - mysql_db: - login_user: '{{ mysql_user }}' - login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 - login_port: '{{ mysql_primary_port }}' - name: '{{ db_name }}' - state: present - register: result - -- name: assert output message that database exist - assert: - that: - - result is changed - - result.db == '{{ db_name }}' - - result.executed_commands == ["CREATE DATABASE `{{ db_name }}`"] - -- name: run command to test state=present for a database name (expect db_name in stdout) - command: "{{ mysql_command }} -e \"show databases like '{{ db_name }}'\"" - register: result - -- name: assert database exist - assert: - that: - - "'{{ db_name }}' in result.stdout" - -# ============================================================ -- name: test state=absent for a database name (expect changed=true) - mysql_db: - login_user: '{{ mysql_user }}' - login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 - login_port: '{{ mysql_primary_port }}' - name: '{{ db_name }}' - state: absent - register: result - -- name: assert output message that database does not exist - assert: - that: - - result is changed - - result.db == '{{ db_name }}' - - result.executed_commands == ["DROP DATABASE `{{ db_name }}`"] - -- name: run command to test state=absent for a database name (expect db_name not in stdout) - command: "{{ mysql_command }} -e \"show databases like '{{ db_name }}'\"" - register: result - -- name: assert database does not exist - assert: - that: - - "'{{ db_name }}' not in result.stdout" - -# ============================================================ -- name: test mysql_db encoding param not valid - issue 8075 - mysql_db: - login_user: '{{ mysql_user }}' - login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 - login_port: '{{ mysql_primary_port }}' - name: datanotvalid - state: present - encoding: notvalid - register: result - ignore_errors: true - -- name: assert test mysql_db encoding param not valid - issue 8075 (failed=true) - assert: - that: - - "result.failed == true" - - "'Traceback' not in result.msg" - - "'Unknown character set' in result.msg" - -# ============================================================ -- name: test mysql_db using a valid encoding utf8 (expect changed=true) - mysql_db: - login_user: '{{ mysql_user }}' - login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 - login_port: '{{ mysql_primary_port }}' - name: 'en{{ db_name }}' - state: present - encoding: utf8 - register: result - -- name: assert output message created a database - assert: - that: - - result is changed - - result.executed_commands == ["CREATE DATABASE `en{{ db_name }}` CHARACTER SET 'utf8'"] - -- name: test database was created - command: "{{ mysql_command }} -e \"SHOW CREATE DATABASE en{{ db_name }}\"" - register: result - -- name: assert created database is of encoding utf8 - assert: - that: - - "'utf8' in result.stdout" - -- name: remove database - mysql_db: - login_user: '{{ mysql_user }}' - login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 - login_port: '{{ mysql_primary_port }}' - name: 'en{{ db_name }}' - state: absent - -# ============================================================ -- name: test mysql_db using valid encoding binary (expect changed=true) - mysql_db: - login_user: '{{ mysql_user }}' - login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 - login_port: '{{ mysql_primary_port }}' - name: 'en{{ db_name }}' - state: present - encoding: binary - register: result - -- name: assert output message that database was created - assert: - that: - - result is changed - - result.executed_commands == ["CREATE DATABASE `en{{ db_name }}` CHARACTER SET 'binary'"] - -- name: run command to test database was created - command: "{{ mysql_command }} -e \"SHOW CREATE DATABASE en{{ db_name }}\"" - register: result - -- name: assert created database is of encoding binary - assert: - that: - - "'binary' in result.stdout" - -- name: remove database - mysql_db: - login_user: '{{ mysql_user }}' - login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 - login_port: '{{ mysql_primary_port }}' - name: 'en{{ db_name }}' - state: absent - -# ============================================================ -- name: create user1 to access database dbuser1 - mysql_user: - login_user: '{{ mysql_user }}' - login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 - login_port: '{{ mysql_primary_port }}' - name: user1 - password: 'Hfd6fds^dfA8Ga' - priv: '*.*:ALL' - state: present - -- name: create database dbuser1 using user1 - mysql_db: - login_user: user1 - login_password: 'Hfd6fds^dfA8Ga' - login_host: 127.0.0.1 - login_port: '{{ mysql_primary_port }}' - name: '{{ db_user1 }}' - state: present - register: result - -- name: assert output message that database was created - assert: - that: - - "result.changed == true" - -- name: run command to test database was created using user1 - command: "{{ mysql_command }} -e \"show databases like '{{ db_user1 }}'\"" - register: result - -- name: assert database exist - assert: - that: - - "'{{ db_user1 }}' in result.stdout" - -# ============================================================ -- name: create user2 to access database with privilege select only - mysql_user: - login_user: '{{ mysql_user }}' - login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 - login_port: '{{ mysql_primary_port }}' - name: user2 - password: 'kjsfd&F7safjad' - priv: '*.*:SELECT' - state: present - -- name: create database dbuser2 using user2 with no privilege to create (expect failed=true) - mysql_db: - login_user: user2 - login_password: 'kjsfd&F7safjad' - login_host: 127.0.0.1 - login_port: '{{ mysql_primary_port }}' - name: '{{ db_user2 }}' - state: present - register: result - ignore_errors: true - -- name: assert output message that database was not created using dbuser2 - assert: - that: - - "result.failed == true" - - "'Access denied' in result.msg" - -- name: run command to test that database was not created - command: "{{ mysql_command }} -e \"show databases like '{{ db_user2 }}'\"" - register: result - -- name: assert database does not exist - assert: - that: - - "'{{ db_user2 }}' not in result.stdout" - -# ============================================================ -- name: delete database using user2 with no privilege to delete (expect failed=true) - mysql_db: - login_user: user2 - login_password: 'kjsfd&F7safjad' - login_host: 127.0.0.1 - login_port: '{{ mysql_primary_port }}' - name: '{{ db_user1 }}' - state: absent - register: result - ignore_errors: true - -- name: assert output message that database was not deleted using dbuser2 - assert: - that: - - "result.failed == true" - - "'Access denied' in result.msg" - -- name: run command to test database was not deleted - command: "{{ mysql_command }} -e \"show databases like '{{ db_user1 }}'\"" - register: result - -- name: assert database still exist - assert: - that: - - "'{{ db_user1 }}' in result.stdout" - -# ============================================================ -- name: delete database using user1 with all privilege to delete a database (expect changed=true) - mysql_db: - login_user: user1 - login_password: 'Hfd6fds^dfA8Ga' - login_host: 127.0.0.1 - login_port: '{{ mysql_primary_port }}' - name: '{{ db_user1 }}' - state: absent - register: result - ignore_errors: true - -- name: assert output message that database was deleted using user1 - assert: - that: - - result is changed - - result.executed_commands == ["DROP DATABASE `{{ db_user1 }}`"] - -- name: run command to test database was deleted using user1 - command: "{{ mysql_command }} -e \"show databases like '{{ db_name }}'\"" - register: result - -- name: assert database does not exist - assert: - that: - - "'{{ db_user1 }}' not in result.stdout" - -# ============================================================ - include: state_dump_import.yml format_type=sql file=dbdata.sql format_msg_type=ASCII file2=dump2.sql file3=dump3.sql file4=dump4.sql +- include: state_dump_import.yml format_type=sql file=dbdata.sql format_msg_type=ASCII file2=dump2.sql file3=dump3.sql file4=dump4.sql db_name="db%" + - include: state_dump_import.yml format_type=gz file=dbdata.gz format_msg_type=gzip file2=dump2.gz file3=dump3.gz file4=dump4.gz - include: state_dump_import.yml format_type=bz2 file=dbdata.bz2 format_msg_type=bzip2 file2=dump2.bz2 file3=dump3.bz2 file4=dump4.bz2 diff --git a/tests/integration/targets/test_mysql_db/tasks/state_present_absent.yml b/tests/integration/targets/test_mysql_db/tasks/state_present_absent.yml new file mode 100644 index 0000000..02411f0 --- /dev/null +++ b/tests/integration/targets/test_mysql_db/tasks/state_present_absent.yml @@ -0,0 +1,300 @@ +# test code for mysql_db module with database name containing special chars + +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +# ============================================================ +- name: remove database if it exists + command: > + "{{ mysql_command }} -sse 'drop database {{ db_name }}'" + ignore_errors: True + +- name: make sure the test database is not there + command: "{{ mysql_command }} {{ db_name }}" + register: mysql_db_check + failed_when: "'1049' not in mysql_db_check.stderr" + +- name: test state=present for a database name (expect changed=true) + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: '{{ db_name }}' + state: present + register: result + +- name: assert output message that database exist + assert: + that: + - result is changed + - result.db == '{{ db_name }}' + - result.executed_commands == ["CREATE DATABASE `{{ db_name }}`"] + +- name: run command to test state=present for a database name (expect db_name in stdout) + command: "{{ mysql_command }} -e \"show databases like '{{ db_name | regex_replace(\"([%_\\\\])\", \"\\\\\\1\") }}'\"" + register: result + +- name: assert database exist + assert: + that: + - "'{{ db_name }}' in result.stdout" + +# ============================================================ +- name: test state=absent for a database name (expect changed=true) + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: '{{ db_name }}' + state: absent + register: result + +- name: assert output message that database does not exist + assert: + that: + - result is changed + - result.db == '{{ db_name }}' + - result.executed_commands == ["DROP DATABASE `{{ db_name }}`"] + +- name: run command to test state=absent for a database name (expect db_name not in stdout) + command: "{{ mysql_command }} -e \"show databases like '{{ db_name | regex_replace(\"([%_\\\\])\", \"\\\\\\1\") }}'\"" + register: result + +- name: assert database does not exist + assert: + that: + - "'{{ db_name }}' not in result.stdout" + +# ============================================================ +- name: test mysql_db encoding param not valid - issue 8075 + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: datanotvalid + state: present + encoding: notvalid + register: result + ignore_errors: true + +- name: assert test mysql_db encoding param not valid - issue 8075 (failed=true) + assert: + that: + - "result.failed == true" + - "'Traceback' not in result.msg" + - "'Unknown character set' in result.msg" + +# ============================================================ +- name: test mysql_db using a valid encoding utf8 (expect changed=true) + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: 'en{{ db_name }}' + state: present + encoding: utf8 + register: result + +- name: assert output message created a database + assert: + that: + - result is changed + - result.executed_commands == ["CREATE DATABASE `en{{ db_name }}` CHARACTER SET 'utf8'"] + +- name: test database was created + command: "{{ mysql_command }} -e \"SHOW CREATE DATABASE `en{{ db_name }}`\"" + register: result + +- name: assert created database is of encoding utf8 + assert: + that: + - "'utf8' in result.stdout" + +- name: remove database + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: 'en{{ db_name }}' + state: absent + +# ============================================================ +- name: test mysql_db using valid encoding binary (expect changed=true) + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: 'en{{ db_name }}' + state: present + encoding: binary + register: result + +- name: assert output message that database was created + assert: + that: + - result is changed + - result.executed_commands == ["CREATE DATABASE `en{{ db_name }}` CHARACTER SET 'binary'"] + +- name: run command to test database was created + command: "{{ mysql_command }} -e \"SHOW CREATE DATABASE `en{{ db_name }}`\"" + register: result + +- name: assert created database is of encoding binary + assert: + that: + - "'binary' in result.stdout" + +- name: remove database + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: 'en{{ db_name }}' + state: absent + +# ============================================================ +- name: create user1 to access database dbuser1 + mysql_user: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: user1 + password: 'Hfd6fds^dfA8Ga' + priv: '*.*:ALL' + state: present + +- name: create database dbuser1 using user1 + mysql_db: + login_user: user1 + login_password: 'Hfd6fds^dfA8Ga' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: '{{ db_user1 }}' + state: present + register: result + +- name: assert output message that database was created + assert: + that: + - "result.changed == true" + +- name: run command to test database was created using user1 + command: "{{ mysql_command }} -e \"show databases like '{{ db_user1 | regex_replace(\"([%_\\\\])\", \"\\\\\\1\") }}'\"" + register: result + +- name: assert database exist + assert: + that: + - "'{{ db_user1 }}' in result.stdout" + +# ============================================================ +- name: create user2 to access database with privilege select only + mysql_user: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: user2 + password: 'kjsfd&F7safjad' + priv: '*.*:SELECT' + state: present + +- name: create database dbuser2 using user2 with no privilege to create (expect failed=true) + mysql_db: + login_user: user2 + login_password: 'kjsfd&F7safjad' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: '{{ db_user2 }}' + state: present + register: result + ignore_errors: true + +- name: assert output message that database was not created using dbuser2 + assert: + that: + - "result.failed == true" + - "'Access denied' in result.msg" + +- name: run command to test that database was not created + command: "{{ mysql_command }} -e \"show databases like '{{ db_user2 | regex_replace(\"([%_\\\\])\", \"\\\\\\1\") }}'\"" + register: result + +- name: assert database does not exist + assert: + that: + - "'{{ db_user2 }}' not in result.stdout" + +# ============================================================ +- name: delete database using user2 with no privilege to delete (expect failed=true) + mysql_db: + login_user: user2 + login_password: 'kjsfd&F7safjad' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: '{{ db_user1 }}' + state: absent + register: result + ignore_errors: true + +- name: assert output message that database was not deleted using dbuser2 + assert: + that: + - "result.failed == true" + - "'Access denied' in result.msg" + +- name: run command to test database was not deleted + command: "{{ mysql_command }} -e \"show databases like '{{ db_user1 | regex_replace(\"([%_\\\\])\", \"\\\\\\1\") }}'\"" + register: result + +- name: assert database still exist + assert: + that: + - "'{{ db_user1 }}' in result.stdout" + +# ============================================================ +- name: delete database using user1 with all privilege to delete a database (expect changed=true) + mysql_db: + login_user: user1 + login_password: 'Hfd6fds^dfA8Ga' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: '{{ db_user1 }}' + state: absent + register: result + ignore_errors: true + +- name: assert output message that database was deleted using user1 + assert: + that: + - result is changed + - result.executed_commands == ["DROP DATABASE `{{ db_user1 }}`"] + +- name: run command to test database was deleted using user1 + command: "{{ mysql_command }} -e \"show databases like '{{ db_name | regex_replace(\"([%_\\\\])\", \"\\\\\\1\") }}'\"" + register: result + +- name: assert database does not exist + assert: + that: + - "'{{ db_user1 }}' not in result.stdout" From 5522e45284a72cf4b306ba74ed22d02e25f00390 Mon Sep 17 00:00:00 2001 From: Nicolas PAYART Date: Mon, 25 Oct 2021 09:35:44 +0200 Subject: [PATCH 040/217] mysql_db: Fix assert in tests suite (#239) Signed-off-by: Nicolas Payart --- .../targets/test_mysql_db/tasks/encoding_dump_import.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/integration/targets/test_mysql_db/tasks/encoding_dump_import.yml b/tests/integration/targets/test_mysql_db/tasks/encoding_dump_import.yml index 173386c..9ef3af5 100644 --- a/tests/integration/targets/test_mysql_db/tasks/encoding_dump_import.yml +++ b/tests/integration/targets/test_mysql_db/tasks/encoding_dump_import.yml @@ -45,7 +45,7 @@ encoding: latin1 target: "{{ latin1_file1 }}" state: dump - register: dump_result + register: result - assert: that: @@ -78,6 +78,7 @@ encoding: latin1 name: '{{ db_latin1_name }}' target: "{{ latin1_file1 }}" + register: result - assert: that: From e4de13aabee3990216e844181c46afbd49d6de08 Mon Sep 17 00:00:00 2001 From: Nicolas PAYART Date: Mon, 8 Nov 2021 09:52:54 +0100 Subject: [PATCH 041/217] mysql_db: Improve tests (#240) - Define variables "db_names" and "db_formats" in defaults - Use of the "vars" option in includes instead of default parameters that might be overridden by a previous task - Use of the "loop" option in includes instead of duplicating include tasks - Use a nested loop on db_names and db_formats in state_dump_import test Signed-off-by: Nicolas Payart --- .../targets/test_mysql_db/defaults/main.yml | 12 ++++- .../targets/test_mysql_db/tasks/main.yml | 46 +++++++++++++------ 2 files changed, 43 insertions(+), 15 deletions(-) diff --git a/tests/integration/targets/test_mysql_db/defaults/main.yml b/tests/integration/targets/test_mysql_db/defaults/main.yml index b6ae780..6448e15 100644 --- a/tests/integration/targets/test_mysql_db/defaults/main.yml +++ b/tests/integration/targets/test_mysql_db/defaults/main.yml @@ -4,7 +4,17 @@ mysql_user: root mysql_password: msandbox mysql_primary_port: 3307 -db_name: 'data' +# Database names +db_names: + - "data" + - "db%" + +# Database formats +db_formats: + - { format_type: "sql", file: "dbdata.sql", format_msg_type: "ASCII", file2: "dump2.sql", file3: "dump3.sql", file4: "dump4.sql" } + - { format_type: "gz", file: "dbdata.gz", format_msg_type: "gzip", file2: "dump2.gz", file3: "dump3.gz", file4: "dump4.gz" } + - { format_type: "bz2", file: "dbdata.bz2", format_msg_type: "bzip2", file2: "dump2.bz2", file3: "dump3.bz2", file4: "dump4.bz2" } + db_name2: 'data2' db_user1: 'datauser1' db_user2: 'datauser2' diff --git a/tests/integration/targets/test_mysql_db/tasks/main.yml b/tests/integration/targets/test_mysql_db/tasks/main.yml index def6a97..958e341 100644 --- a/tests/integration/targets/test_mysql_db/tasks/main.yml +++ b/tests/integration/targets/test_mysql_db/tasks/main.yml @@ -25,23 +25,41 @@ set_fact: mysql_command: "mysql -u{{ mysql_user }} -p{{ mysql_password }} -P{{ mysql_primary_port }} --protocol=tcp" -- include: state_present_absent.yml +- name: Check state present/absent + include_tasks: state_present_absent.yml + vars: + db_name: "{{ item }}" + loop: "{{ db_names }}" -- include: state_present_absent.yml db_name="db%" +- name: Check state dump/import + include_tasks: state_dump_import.yml + vars: + db_name: "{{ item.0 }}" + file: "{{ item.1.file }}" + file2: "{{ item.1.file2 }}" + file3: "{{ item.1.file3 }}" + file4: "{{ item.1.file4 }}" + format_msg_type: "{{ item.1.format_msg_type }}" + format_type: "{{ item.1.format_type }}" + with_nested: + - "{{ db_names }}" + - "{{ db_formats }}" -- include: state_dump_import.yml format_type=sql file=dbdata.sql format_msg_type=ASCII file2=dump2.sql file3=dump3.sql file4=dump4.sql +- name: Check state present/absent with multiple databases + include_tasks: multi_db_create_delete.yml -- include: state_dump_import.yml format_type=sql file=dbdata.sql format_msg_type=ASCII file2=dump2.sql file3=dump3.sql file4=dump4.sql db_name="db%" +- name: Check state dump/import with encoding + include_tasks: encoding_dump_import.yml + vars: + file: "latin1.sql" + format_msg_type: "ASCII" -- include: state_dump_import.yml format_type=gz file=dbdata.gz format_msg_type=gzip file2=dump2.gz file3=dump3.gz file4=dump4.gz - -- include: state_dump_import.yml format_type=bz2 file=dbdata.bz2 format_msg_type=bzip2 file2=dump2.bz2 file3=dump3.bz2 file4=dump4.bz2 - -- include: multi_db_create_delete.yml - -- include: encoding_dump_import.yml file=latin1.sql format_msg_type=ASCII - -- include: config_overrides_defaults.yml +- name: Check MySQL config file + include_tasks: config_overrides_defaults.yml when: ansible_python.version_info[0] >= 3 -- include: issue-28.yml +- name: Check issue 28 + include_tasks: issue-28.yml + vars: + db_name: "{{ item }}" + loop: "{{ db_names }}" From 727b638d135e3331da6ebe9f6b0cea5809c19ad2 Mon Sep 17 00:00:00 2001 From: "R.Sicart" Date: Sat, 20 Nov 2021 09:28:40 +0100 Subject: [PATCH 042/217] Get rid of privs comparison (#243) * Remove all code related to VALID_PRIVS and get_valid_privs() * Add tests to update user with invalid privs * Re-raise InvalidPrivsError when granting privileges * Fix: compatibility with python2 * More explicit assertions as commented by Andersson007 * Add changelog fragment --- .../243-get-rid-of-privs-comparison.yml | 2 + plugins/module_utils/user.py | 62 ++----------------- plugins/modules/mysql_role.py | 4 +- plugins/modules/mysql_user.py | 7 +-- .../tasks/test_priv_append.yml | 19 ++++++ .../test_mysql_user/tasks/test_privs.yml | 17 +++++ 6 files changed, 45 insertions(+), 66 deletions(-) create mode 100644 changelogs/fragments/243-get-rid-of-privs-comparison.yml diff --git a/changelogs/fragments/243-get-rid-of-privs-comparison.yml b/changelogs/fragments/243-get-rid-of-privs-comparison.yml new file mode 100644 index 0000000..6c29832 --- /dev/null +++ b/changelogs/fragments/243-get-rid-of-privs-comparison.yml @@ -0,0 +1,2 @@ +breaking_changes: + - mysql_user - validate privileges using database engine directly (https://github.com/ansible-collections/community.mysql/issues/234 https://github.com/ansible-collections/community.mysql/pull/243). Do not validate privileges in this module anymore. diff --git a/plugins/module_utils/user.py b/plugins/module_utils/user.py index 0532de9..fc9c984 100644 --- a/plugins/module_utils/user.py +++ b/plugins/module_utils/user.py @@ -19,49 +19,6 @@ from ansible_collections.community.mysql.plugins.module_utils.mysql import ( ) -EXTRA_PRIVS = ['ALL', 'ALL PRIVILEGES', 'GRANT', 'REQUIRESSL'] - -# This list is kept for backwards compatibility after release 2.3.0, -# see https://github.com/ansible-collections/community.mysql/issues/232 for details -VALID_PRIVS = [ - 'CREATE', 'DROP', 'GRANT', 'GRANT OPTION', - 'LOCK TABLES', 'REFERENCES', 'EVENT', 'ALTER', - 'DELETE', 'INDEX', 'INSERT', 'SELECT', 'UPDATE', - 'CREATE TEMPORARY TABLES', 'TRIGGER', 'CREATE VIEW', - 'SHOW VIEW', 'ALTER ROUTINE', 'CREATE ROUTINE', - 'EXECUTE', 'FILE', 'CREATE TABLESPACE', 'CREATE USER', - 'PROCESS', 'PROXY', 'RELOAD', 'REPLICATION CLIENT', - 'REPLICATION SLAVE', 'SHOW DATABASES', 'SHUTDOWN', - 'SUPER', 'ALL', 'ALL PRIVILEGES', 'USAGE', - 'REQUIRESSL', # Deprecated, to be removed in version 3.0.0 - 'CREATE ROLE', 'DROP ROLE', 'APPLICATION_PASSWORD_ADMIN', - 'AUDIT_ADMIN', 'BACKUP_ADMIN', 'BINLOG_ADMIN', - 'BINLOG_ENCRYPTION_ADMIN', 'CLONE_ADMIN', 'CONNECTION_ADMIN', - 'ENCRYPTION_KEY_ADMIN', 'FIREWALL_ADMIN', 'FIREWALL_USER', - 'GROUP_REPLICATION_ADMIN', 'INNODB_REDO_LOG_ARCHIVE', - 'NDB_STORED_USER', 'PERSIST_RO_VARIABLES_ADMIN', - 'REPLICATION_APPLIER', 'REPLICATION_SLAVE_ADMIN', - 'RESOURCE_GROUP_ADMIN', 'RESOURCE_GROUP_USER', - 'ROLE_ADMIN', 'SESSION_VARIABLES_ADMIN', 'SET_USER_ID', - 'SYSTEM_USER', 'SYSTEM_VARIABLES_ADMIN', 'SYSTEM_USER', - 'TABLE_ENCRYPTION_ADMIN', 'VERSION_TOKEN_ADMIN', - 'XA_RECOVER_ADMIN', 'LOAD FROM S3', 'SELECT INTO S3', - 'INVOKE LAMBDA', - 'ALTER ROUTINE', - 'BINLOG ADMIN', - 'BINLOG MONITOR', - 'BINLOG REPLAY', - 'CONNECTION ADMIN', - 'READ_ONLY ADMIN', - 'REPLICATION MASTER ADMIN', - 'REPLICATION SLAVE ADMIN', - 'SET USER', - 'SHOW_ROUTINE', - 'SLAVE MONITOR', - 'REPLICA MONITOR', -] - - class InvalidPrivsError(Exception): pass @@ -147,14 +104,6 @@ def get_tls_requires(cursor, user, host): return requires or None -def get_valid_privs(cursor): - cursor.execute("SHOW PRIVILEGES") - show_privs = [priv[0].upper() for priv in cursor.fetchall()] - # See the comment above VALID_PRIVS declaration - all_privs = show_privs + EXTRA_PRIVS + VALID_PRIVS - return frozenset(all_privs) - - def get_grants(cursor, user, host): cursor.execute("SHOW GRANTS FOR %s@%s", (user, host)) grants_line = list(filter(lambda x: "ON *.*" in x[0], cursor.fetchall()))[0] @@ -597,7 +546,7 @@ def sort_column_order(statement): return '%s(%s)' % (priv_name, ', '.join(columns)) -def privileges_unpack(priv, mode, valid_privs): +def privileges_unpack(priv, mode): """ Take a privileges string, typically passed as a parameter, and unserialize it into a dictionary, the same format as privileges_get() above. We have this custom format to avoid using YAML/JSON strings inside YAML playbooks. Example @@ -643,10 +592,6 @@ def privileges_unpack(priv, mode, valid_privs): # Handle cases when there's privs like GRANT SELECT (colA, ...) in privs. output[pieces[0]] = normalize_col_grants(output[pieces[0]]) - new_privs = frozenset(privs) - if not new_privs.issubset(valid_privs): - raise InvalidPrivsError('Invalid privileges specified: %s' % new_privs.difference(valid_privs)) - if '*.*' not in output: output['*.*'] = ['USAGE'] @@ -699,7 +644,10 @@ def privileges_grant(cursor, user, host, db_table, priv, tls_requires, maria_rol if 'GRANT' in priv: query.append("WITH GRANT OPTION") query = ' '.join(query) - cursor.execute(query, params) + try: + cursor.execute(query, params) + except (mysql_driver.ProgrammingError, mysql_driver.OperationalError, mysql_driver.InternalError) as e: + raise InvalidPrivsError("Error granting privileges, invalid priv string: %s" % priv_string) def convert_priv_dict_to_str(priv): diff --git a/plugins/modules/mysql_role.py b/plugins/modules/mysql_role.py index 47b0b58..80d0144 100644 --- a/plugins/modules/mysql_role.py +++ b/plugins/modules/mysql_role.py @@ -250,7 +250,6 @@ from ansible_collections.community.mysql.plugins.module_utils.user import ( get_mode, user_mod, privileges_grant, - get_valid_privs, privileges_unpack, ) from ansible.module_utils._text import to_native @@ -1014,8 +1013,7 @@ def main(): module.fail_json(msg=to_native(e)) try: - valid_privs = get_valid_privs(cursor) - priv = privileges_unpack(priv, mode, valid_privs) + priv = privileges_unpack(priv, mode) except Exception as e: module.fail_json(msg='Invalid privileges string: %s' % to_native(e)) diff --git a/plugins/modules/mysql_user.py b/plugins/modules/mysql_user.py index 9c043cc..1eb6768 100644 --- a/plugins/modules/mysql_user.py +++ b/plugins/modules/mysql_user.py @@ -318,7 +318,6 @@ from ansible_collections.community.mysql.plugins.module_utils.user import ( handle_requiressl_in_priv_string, InvalidPrivsError, limit_resources, - get_valid_privs, privileges_unpack, sanitize_requires, user_add, @@ -421,11 +420,7 @@ def main(): mode = get_mode(cursor) except Exception as e: module.fail_json(msg=to_native(e)) - try: - valid_privs = get_valid_privs(cursor) - priv = privileges_unpack(priv, mode, valid_privs) - except Exception as e: - module.fail_json(msg="invalid privileges string: %s" % to_native(e)) + priv = privileges_unpack(priv, mode) if state == "present": if user_exists(cursor, user, host, host_all): diff --git a/tests/integration/targets/test_mysql_user/tasks/test_priv_append.yml b/tests/integration/targets/test_mysql_user/tasks/test_priv_append.yml index 7dc15ca..cd10147 100644 --- a/tests/integration/targets/test_mysql_user/tasks/test_priv_append.yml +++ b/tests/integration/targets/test_mysql_user/tasks/test_priv_append.yml @@ -96,6 +96,25 @@ - "'GRANT SELECT, DELETE ON `data2`.*' in result.stdout" when: enable_check_mode == 'yes' + - name: Try to append invalid privileges + mysql_user: + <<: *mysql_params + name: '{{ user_name_4 }}' + password: '{{ user_password_4 }}' + priv: 'data1.*:INVALID/data2.*:SELECT' + append_privs: yes + state: present + check_mode: '{{ enable_check_mode }}' + register: result + ignore_errors: true + + - name: Assert that there wasn't a change in privileges if check_mode is set to 'no' + assert: + that: + - result is failed + - "'Error granting privileges' in result.msg" + when: enable_check_mode == 'no' + ########## # Clean up - name: Drop test databases diff --git a/tests/integration/targets/test_mysql_user/tasks/test_privs.yml b/tests/integration/targets/test_mysql_user/tasks/test_privs.yml index 4ed75d1..27beb77 100644 --- a/tests/integration/targets/test_mysql_user/tasks/test_privs.yml +++ b/tests/integration/targets/test_mysql_user/tasks/test_privs.yml @@ -178,6 +178,23 @@ that: - "result.changed == false" + # ============================================================ + - name: update user with invalid privileges + mysql_user: + <<: *mysql_params + name: '{{ user_name_2 }}' + password: '{{ user_password_2 }}' + priv: '*.*:INVALID' + state: present + register: result + ignore_errors: yes + + - name: Assert that priv did not change + assert: + that: + - result is failed + - "'Error granting privileges' in result.msg" + - name: remove username mysql_user: <<: *mysql_params From d411a8e2167cd808fa6c755420b1cddac56e501d Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Thu, 25 Nov 2021 13:11:34 +0300 Subject: [PATCH 043/217] MAINTAINERS file: Add new maintainer (#248) --- MAINTAINERS | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index c520538..97d0030 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1,3 +1,4 @@ bmalynovytch Jorge-Rodriguez -Andersson007 (andersson007_ in #ansible-community IRC) +rsicart +Andersson007 (andersson007_ in #ansible-community IRC/Matrix) From c74943a737fc238ace819b0a15e8e4611e41682a Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Mon, 29 Nov 2021 20:08:05 +0300 Subject: [PATCH 044/217] Remove changelog fragments after release 2.3.2 (#254) --- changelogs/fragments/227-db-create-special-name.yaml | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 changelogs/fragments/227-db-create-special-name.yaml diff --git a/changelogs/fragments/227-db-create-special-name.yaml b/changelogs/fragments/227-db-create-special-name.yaml deleted file mode 100644 index 99f7e96..0000000 --- a/changelogs/fragments/227-db-create-special-name.yaml +++ /dev/null @@ -1,2 +0,0 @@ -bugfixes: - - mysql_db - Fix mismatch when database name contains a ``%`` character (https://github.com/ansible-collections/community.mysql/pull/227). From dd4700989f84f7e4c076924a65ecc6e14f89d20d Mon Sep 17 00:00:00 2001 From: "Jorge Rodriguez (A.K.A. Tiriel)" Date: Wed, 1 Dec 2021 07:56:04 +0100 Subject: [PATCH 045/217] Remove support for REQUIRESSL privilege (#244) * Remove support for REQUIRESSL privilege * Fix error search string --- .../244-remove-requiressl-privilege.yaml | 2 + plugins/module_utils/user.py | 21 ------ plugins/modules/mysql_user.py | 14 ---- .../test_mysql_user/tasks/issue-121.yml | 53 +++------------ .../plugins/module_utils/test_mysql_user.py | 68 +------------------ 5 files changed, 11 insertions(+), 147 deletions(-) create mode 100644 changelogs/fragments/244-remove-requiressl-privilege.yaml diff --git a/changelogs/fragments/244-remove-requiressl-privilege.yaml b/changelogs/fragments/244-remove-requiressl-privilege.yaml new file mode 100644 index 0000000..474b372 --- /dev/null +++ b/changelogs/fragments/244-remove-requiressl-privilege.yaml @@ -0,0 +1,2 @@ +breaking_changes: + - mysql_user - remove support for the `REQUIRESSL` special privilege as it has ben superseded by the `tls_requires` option (https://github.com/ansible-collections/community.mysql/discussions/121). diff --git a/plugins/module_utils/user.py b/plugins/module_utils/user.py index fc9c984..a523534 100644 --- a/plugins/module_utils/user.py +++ b/plugins/module_utils/user.py @@ -664,27 +664,6 @@ def convert_priv_dict_to_str(priv): return '/'.join(priv_list) -def handle_requiressl_in_priv_string(module, priv, tls_requires): - module.deprecate('The "REQUIRESSL" privilege is deprecated, use the "tls_requires" option instead.', - version='3.0.0', collection_name='community.mysql') - priv_groups = re.search(r"(.*?)(\*\.\*:)([^/]*)(.*)", priv) - if priv_groups.group(3) == "REQUIRESSL": - priv = priv_groups.group(1) + priv_groups.group(4) or None - else: - inner_priv_groups = re.search(r"(.*?),?REQUIRESSL,?(.*)", priv_groups.group(3)) - priv = '{0}{1}{2}{3}'.format( - priv_groups.group(1), - priv_groups.group(2), - ','.join(filter(None, (inner_priv_groups.group(1), inner_priv_groups.group(2)))), - priv_groups.group(4) - ) - if not tls_requires: - tls_requires = "SSL" - else: - module.warn('Ignoring "REQUIRESSL" privilege as "tls_requires" is defined and it takes precedence.') - return priv, tls_requires - - # Alter user is supported since MySQL 5.6 and MariaDB 10.2.0 def server_supports_alter_user(cursor): """Check if the server supports ALTER USER statement or doesn't. diff --git a/plugins/modules/mysql_user.py b/plugins/modules/mysql_user.py index 1eb6768..10e37fc 100644 --- a/plugins/modules/mysql_user.py +++ b/plugins/modules/mysql_user.py @@ -198,16 +198,6 @@ EXAMPLES = r''' FUNCTION my_db.my_function: EXECUTE state: present -# Note that REQUIRESSL is a special privilege that should only apply to *.* by itself. -# Setting this privilege in this manner is deprecated. -# Use 'tls_requires' instead. -- name: Modify user to require SSL connections - community.mysql.mysql_user: - name: bob - append_privs: yes - priv: '*.*:REQUIRESSL' - state: present - - name: Modify user to require TLS connection with a valid client certificate community.mysql.mysql_user: name: bob @@ -315,7 +305,6 @@ from ansible_collections.community.mysql.plugins.module_utils.user import ( convert_priv_dict_to_str, get_impl, get_mode, - handle_requiressl_in_priv_string, InvalidPrivsError, limit_resources, privileges_unpack, @@ -388,9 +377,6 @@ def main(): if priv and isinstance(priv, dict): priv = convert_priv_dict_to_str(priv) - if priv and "REQUIRESSL" in priv: - priv, tls_requires = handle_requiressl_in_priv_string(module, priv, tls_requires) - if mysql_driver is None: module.fail_json(msg=mysql_driver_fail_msg) diff --git a/tests/integration/targets/test_mysql_user/tasks/issue-121.yml b/tests/integration/targets/test_mysql_user/tasks/issue-121.yml index d8a9d9e..fb5bef6 100644 --- a/tests/integration/targets/test_mysql_user/tasks/issue-121.yml +++ b/tests/integration/targets/test_mysql_user/tasks/issue-121.yml @@ -37,50 +37,19 @@ - "{{ user_name_1 }}" - "{{ user_name_2 }}" - - name: create user with REQUIRESSL privilege - mysql_user: - <<: *mysql_params - name: "{{ user_name_1 }}" - password: "{{ user_password_1 }}" - priv: '*.*:SELECT,CREATE USER,REQUIRESSL,GRANT' - - - name: verify REQUIRESSL is assigned to the user - mysql_query: - <<: *mysql_params - query: "SHOW {{ what }} '{{ user_name_1}}'@'localhost'" - register: result - vars: - what: "{{ 'GRANTS FOR' if old_user_mgmt else 'CREATE USER' }}" - - - assert: - that: - - result is succeeded and 'REQUIRE SSL' in (result.query_result | string) - - - name: create user with equivalent ssl requirement in tls_requires (expect unchanged) - mysql_user: - <<: *mysql_params - name: "{{ user_name_1 }}" - password: "{{ user_password_1 }}" - priv: '*.*:SELECT,CREATE USER,GRANT' - tls_requires: - SSL: - register: result - - - assert: - that: - - result is not changed - - - name: create the same user again, with REQUIRESSL privilege once more + - name: create user with REQUIRESSL privilege (expect failure) mysql_user: <<: *mysql_params name: "{{ user_name_1 }}" password: "{{ user_password_1 }}" priv: '*.*:SELECT,CREATE USER,REQUIRESSL,GRANT' register: result + ignore_errors: yes - assert: that: - - result is not changed + - result is failed + - result.msg is search('Error granting privileges') - name: create user with both REQUIRESSL privilege and an incompatible tls_requires option mysql_user: @@ -90,19 +59,13 @@ priv: '*.*:SELECT,CREATE USER,REQUIRESSL,GRANT' tls_requires: X509: - - - name: create same user again without REQUIRESSL privilege - mysql_user: - <<: *mysql_params - name: "{{ user_name_1 }}" - password: "{{ user_password_1 }}" - priv: '*.*:SELECT,CREATE USER,GRANT' - tls_requires: - X509: register: result + ignore_errors: yes - assert: - that: result is not changed + that: + - result is failed + - result.msg is search('Error granting privileges') - name: Drop mysql user mysql_user: diff --git a/tests/unit/plugins/module_utils/test_mysql_user.py b/tests/unit/plugins/module_utils/test_mysql_user.py index 0edf922..f0a7b32 100644 --- a/tests/unit/plugins/module_utils/test_mysql_user.py +++ b/tests/unit/plugins/module_utils/test_mysql_user.py @@ -13,8 +13,7 @@ from ansible_collections.community.mysql.plugins.module_utils.user import ( handle_grant_on_col, has_grant_on_col, normalize_col_grants, - sort_column_order, - handle_requiressl_in_priv_string + sort_column_order ) from ..utils import dummy_cursor_class @@ -79,71 +78,6 @@ def test_handle_grant_on_col(privileges, start, end, output): assert handle_grant_on_col(privileges, start, end) == output -@pytest.mark.parametrize( - 'input_tuple,output_tuple', - [ - (('*.*:REQUIRESSL', None), (None, 'SSL')), - (('*.*:ALL,REQUIRESSL', None), ('*.*:ALL', 'SSL')), - (('*.*:REQUIRESSL,ALL', None), ('*.*:ALL', 'SSL')), - (('*.*:ALL,REQUIRESSL,GRANT', None), ('*.*:ALL,GRANT', 'SSL')), - (('*.*:ALL,REQUIRESSL,GRANT/a.b:USAGE', None), ('*.*:ALL,GRANT/a.b:USAGE', 'SSL')), - (('*.*:REQUIRESSL', 'X509'), (None, 'X509')), - (('*.*:ALL,REQUIRESSL', 'X509'), ('*.*:ALL', 'X509')), - (('*.*:REQUIRESSL,ALL', 'X509'), ('*.*:ALL', 'X509')), - (('*.*:ALL,REQUIRESSL,GRANT', 'X509'), ('*.*:ALL,GRANT', 'X509')), - (('*.*:ALL,REQUIRESSL,GRANT/a.b:USAGE', 'X509'), ('*.*:ALL,GRANT/a.b:USAGE', 'X509')), - (('*.*:REQUIRESSL', { - 'subject': '/CN=alice/O=MyDom, Inc./C=US/ST=Oregon/L=Portland', - 'cipher': 'ECDHE-ECDSA-AES256-SHA384', - 'issuer': '/CN=org/O=MyDom, Inc./C=US/ST=Oregon/L=Portland' - }), (None, { - 'subject': '/CN=alice/O=MyDom, Inc./C=US/ST=Oregon/L=Portland', - 'cipher': 'ECDHE-ECDSA-AES256-SHA384', - 'issuer': '/CN=org/O=MyDom, Inc./C=US/ST=Oregon/L=Portland' - })), - (('*.*:ALL,REQUIRESSL', { - 'subject': '/CN=alice/O=MyDom, Inc./C=US/ST=Oregon/L=Portland', - 'cipher': 'ECDHE-ECDSA-AES256-SHA384', - 'issuer': '/CN=org/O=MyDom, Inc./C=US/ST=Oregon/L=Portland' - }), ('*.*:ALL', { - 'subject': '/CN=alice/O=MyDom, Inc./C=US/ST=Oregon/L=Portland', - 'cipher': 'ECDHE-ECDSA-AES256-SHA384', - 'issuer': '/CN=org/O=MyDom, Inc./C=US/ST=Oregon/L=Portland' - })), - (('*.*:REQUIRESSL,ALL', { - 'subject': '/CN=alice/O=MyDom, Inc./C=US/ST=Oregon/L=Portland', - 'cipher': 'ECDHE-ECDSA-AES256-SHA384', - 'issuer': '/CN=org/O=MyDom, Inc./C=US/ST=Oregon/L=Portland' - }), ('*.*:ALL', { - 'subject': '/CN=alice/O=MyDom, Inc./C=US/ST=Oregon/L=Portland', - 'cipher': 'ECDHE-ECDSA-AES256-SHA384', - 'issuer': '/CN=org/O=MyDom, Inc./C=US/ST=Oregon/L=Portland' - })), - (('*.*:ALL,REQUIRESSL,GRANT', { - 'subject': '/CN=alice/O=MyDom, Inc./C=US/ST=Oregon/L=Portland', - 'cipher': 'ECDHE-ECDSA-AES256-SHA384', - 'issuer': '/CN=org/O=MyDom, Inc./C=US/ST=Oregon/L=Portland' - }), ('*.*:ALL,GRANT', { - 'subject': '/CN=alice/O=MyDom, Inc./C=US/ST=Oregon/L=Portland', - 'cipher': 'ECDHE-ECDSA-AES256-SHA384', - 'issuer': '/CN=org/O=MyDom, Inc./C=US/ST=Oregon/L=Portland' - })), - (('*.*:ALL,REQUIRESSL,GRANT/a.b:USAGE', { - 'subject': '/CN=alice/O=MyDom, Inc./C=US/ST=Oregon/L=Portland', - 'cipher': 'ECDHE-ECDSA-AES256-SHA384', - 'issuer': '/CN=org/O=MyDom, Inc./C=US/ST=Oregon/L=Portland' - }), ('*.*:ALL,GRANT/a.b:USAGE', { - 'subject': '/CN=alice/O=MyDom, Inc./C=US/ST=Oregon/L=Portland', - 'cipher': 'ECDHE-ECDSA-AES256-SHA384', - 'issuer': '/CN=org/O=MyDom, Inc./C=US/ST=Oregon/L=Portland' - })) - ] -) -def test_handle_requiressl_in_priv_string(input_tuple, output_tuple): - """Tests the handle_requiressl_in_priv_string funciton.""" - assert handle_requiressl_in_priv_string(MagicMock(), *input_tuple) == output_tuple - - @pytest.mark.parametrize( 'input_,expected', [ From 1f796d9e812f77ec988234e120f88695bf65fd9a Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Wed, 1 Dec 2021 09:56:53 +0300 Subject: [PATCH 046/217] mysql_replication: remove mode values and ret values containing master / slave (#252) * mysql_replication: remove mode values containing master / slave * Add tests --- ...-mysql_replication_remove_master_slave.yml | 3 + plugins/modules/mysql_replication.py | 125 +++++------------- .../tasks/mysql_replication_initial.yml | 30 ++++- 3 files changed, 57 insertions(+), 101 deletions(-) create mode 100644 changelogs/fragments/300-mysql_replication_remove_master_slave.yml diff --git a/changelogs/fragments/300-mysql_replication_remove_master_slave.yml b/changelogs/fragments/300-mysql_replication_remove_master_slave.yml new file mode 100644 index 0000000..dd41831 --- /dev/null +++ b/changelogs/fragments/300-mysql_replication_remove_master_slave.yml @@ -0,0 +1,3 @@ +breaking_changes: +- mysql_replication - remove the mode options values containing ``master``/``slave`` and the master_use_gtid option ``slave_pos`` (were replaced with corresponding ``primary``/``replica`` values) (https://github.com/ansible-collections/community.mysql/issues/145). +- mysql_replication - remove ``Is_Slave`` and ``Is_Master`` return values (were replaced with ``Is_Primary`` and ``Is_Replica`` (https://github.com/ansible-collections /community.mysql/issues/145). diff --git a/plugins/modules/mysql_replication.py b/plugins/modules/mysql_replication.py index 12dffc4..820ceee 100644 --- a/plugins/modules/mysql_replication.py +++ b/plugins/modules/mysql_replication.py @@ -23,32 +23,24 @@ options: mode: description: - Module operating mode. Could be - C(changeprimary | changemaster) (CHANGE PRIMARY | MASTER TO), - C(getprimary | getmaster) (SHOW PRIMARY | MASTER STATUS), - C(getreplica | getslave) (SHOW REPLICA | SLAVE STATUS), - C(startreplica | startslave) (START REPLICA | SLAVE), - C(stopreplica | stopslave) (STOP REPLICA | SLAVE), - C(resetprimary | resetmaster) (RESET PRIMARY | MASTER) - supported since community.mysql 0.1.0, - C(resetreplica, resetslave) (RESET REPLICA | SLAVE), - C(resetreplicaall, resetslave) (RESET REPLICA | SLAVE ALL). + C(changeprimary) (CHANGE PRIMARY TO), + C(getprimary) (SHOW PRIMARY STATUS), + C(getreplica) (SHOW REPLICA), + C(startreplica) (START REPLICA), + C(stopreplica) (STOP REPLICA), + C(resetprimary) (RESET PRIMARY) - supported since community.mysql 0.1.0, + C(resetreplica) (RESET REPLICA), + C(resetreplicaall) (RESET REPLICA ALL). type: str choices: - changeprimary - - changemaster - getprimary - - getmaster - getreplica - - getslave - startreplica - - startslave - stopreplica - - stopslave - resetprimary - - resetmaster - resetreplica - - resetslave - resetreplicaall - - resetslaveall default: getreplica primary_host: description: @@ -153,9 +145,7 @@ options: - To find information about available values see U(https://mariadb.com/kb/en/library/change-master-to/#master_use_gtid). - Available since MariaDB 10.0.2. - - C(replica_pos) has been introduced in MariaDB 10.5.1 and - it is an alias for C(slave_pos). - choices: [current_pos, replica_pos, slave_pos, disabled] + choices: [current_pos, replica_pos, disabled] type: str version_added: '0.1.0' aliases: [master_use_gtid] @@ -441,14 +431,14 @@ def main(): argument_spec = mysql_common_argument_spec() argument_spec.update( mode=dict(type='str', default='getreplica', choices=[ - 'getprimary', 'getmaster', - 'getreplica', 'getslave', - 'changeprimary', 'changemaster', - 'stopreplica', 'stopslave', - 'startreplica', 'startslave', - 'resetprimary', 'resetmaster', - 'resetreplica', 'resetslave', - 'resetreplicaall', 'resetslaveall']), + 'getprimary', + 'getreplica', + 'changeprimary', + 'stopreplica', + 'startreplica', + 'resetprimary', + 'resetreplica', + 'resetreplicaall']), primary_auto_position=dict(type='bool', default=False, aliases=['master_auto_position']), primary_host=dict(type='str', aliases=['master_host']), primary_user=dict(type='str', aliases=['master_user']), @@ -466,7 +456,7 @@ def main(): primary_ssl_key=dict(type='str', no_log=False, aliases=['master_ssl_key']), primary_ssl_cipher=dict(type='str', aliases=['master_ssl_cipher']), primary_use_gtid=dict(type='str', choices=[ - 'current_pos', 'replica_pos', 'slave_pos', 'disabled'], aliases=['master_use_gtid']), + 'current_pos', 'replica_pos', 'disabled'], aliases=['master_use_gtid']), primary_delay=dict(type='int', aliases=['master_delay']), connection_name=dict(type='str'), channel=dict(type='str'), @@ -540,60 +530,29 @@ def main(): # "REPLICA" must be used instead of "SLAVE" if impl.uses_replica_terminology(cursor): replica_term = 'REPLICA' - if primary_use_gtid == 'slave_pos': - module.deprecate('primary_use_gtid | master_use_gtid "slave_pos" value is ' - 'deprecated, use "replica_pos" instead.', - version='3.0.0', collection_name='community.mysql') - primary_use_gtid = 'replica_pos' else: replica_term = 'SLAVE' - if primary_use_gtid == 'replica_pos': - primary_use_gtid = 'slave_pos' - - if mode in ('getprimary', 'getmaster'): - if mode == 'getmaster': - module.deprecate('"getmaster" option is deprecated, use "getprimary" instead.', - version='3.0.0', collection_name='community.mysql') + if mode == 'getprimary': status = get_primary_status(cursor) if not isinstance(status, dict): - # TODO: change the word master to primary in 3.0.0 - status = dict(Is_Master=False, Is_Primary=False, - msg="Server is not configured as mysql master") + status = dict(Is_Primary=False, + msg="Server is not configured as mysql primary") else: - status['Is_Master'] = True status['Is_Primary'] = True - module.deprecate('"Is_Master" and "Is_Slave" return values are deprecated ' - 'and will be replaced with "Is_Primary" and "Is_Replica" ' - 'in the next major release. Use "Is_Primary" and "Is_Replica" instead.', - version='3.0.0', collection_name='community.mysql') - module.exit_json(queries=executed_queries, **status) - elif mode in ("getreplica", "getslave"): - if mode == "getslave": - module.deprecate('"getslave" option is deprecated, use "getreplica" instead.', - version='3.0.0', collection_name='community.mysql') - + elif mode == "getreplica": status = get_replica_status(cursor, connection_name, channel, replica_term) if not isinstance(status, dict): - status = dict(Is_Slave=False, Is_Replica=False, msg="Server is not configured as mysql replica") + status = dict(Is_Replica=False, msg="Server is not configured as mysql replica") else: - status['Is_Slave'] = True status['Is_Replica'] = True - module.deprecate('"Is_Master" and "Is_Slave" return values are deprecated ' - 'and will be replaced with "Is_Primary" and "Is_Replica" ' - 'in the next major release. Use "Is_Primary" and "Is_Replica" instead.', - version='3.0.0', collection_name='community.mysql') - module.exit_json(queries=executed_queries, **status) - elif mode in ('changeprimary', 'changemaster'): - if mode == 'changemaster': - module.deprecate('"changemaster" option is deprecated, use "changeprimary" instead.', - version='3.0.0', collection_name='community.mysql') + elif mode == 'changeprimary': chm = [] result = {} if primary_host is not None: @@ -640,53 +599,31 @@ def main(): module.fail_json(msg='%s. Query == CHANGE MASTER TO %s' % (to_native(e), chm)) result['changed'] = True module.exit_json(queries=executed_queries, **result) - elif mode in ("startreplica", "startslave"): - if mode == "startslave": - module.deprecate('"startslave" option is deprecated, use "startreplica" instead.', - version='3.0.0', collection_name='community.mysql') - + elif mode == "startreplica": started = start_replica(module, cursor, connection_name, channel, fail_on_error, replica_term) if started is True: module.exit_json(msg="Replica started ", changed=True, queries=executed_queries) else: module.exit_json(msg="Replica already started (Or cannot be started)", changed=False, queries=executed_queries) - elif mode in ("stopreplica", "stopslave"): - if mode == "stopslave": - module.deprecate('"stopslave" option is deprecated, use "stopreplica" instead.', - version='3.0.0', collection_name='community.mysql') - + elif mode == "stopreplica": stopped = stop_replica(module, cursor, connection_name, channel, fail_on_error, replica_term) if stopped is True: module.exit_json(msg="Replica stopped", changed=True, queries=executed_queries) else: module.exit_json(msg="Replica already stopped", changed=False, queries=executed_queries) - elif mode in ('resetprimary', 'resetmaster'): - if mode == 'resetmaster': - module.deprecate('"resetmaster" option is deprecated, use "resetprimary" instead.', - version='3.0.0', collection_name='community.mysql') - + elif mode == 'resetprimary': reset = reset_primary(module, cursor, fail_on_error) if reset is True: - # TODO: Change "Master" to "Primary" in release 3.0.0 - module.exit_json(msg="Master reset", changed=True, queries=executed_queries) + module.exit_json(msg="Primary reset", changed=True, queries=executed_queries) else: - # TODO: Change "Master" to "Primary" in release 3.0.0 - module.exit_json(msg="Master already reset", changed=False, queries=executed_queries) - elif mode in ("resetreplica", "resetslave"): - if mode == "resetslave": - module.deprecate('"resetslave" option is deprecated, use "resetreplica" instead.', - version='3.0.0', collection_name='community.mysql') - + module.exit_json(msg="Primary already reset", changed=False, queries=executed_queries) + elif mode == "resetreplica": reset = reset_replica(module, cursor, connection_name, channel, fail_on_error, replica_term) if reset is True: module.exit_json(msg="Replica reset", changed=True, queries=executed_queries) else: module.exit_json(msg="Replica already reset", changed=False, queries=executed_queries) - elif mode in ("resetreplicaall", "resetslaveall"): - if mode == "resetslaveall": - module.deprecate('"resetslaveall" option is deprecated, use "resetreplicaall" instead.', - version='3.0.0', collection_name='community.mysql') - + elif mode == "resetreplicaall": reset = reset_replica_all(module, cursor, connection_name, channel, fail_on_error, replica_term) if reset is True: module.exit_json(msg="Replica reset", changed=True, queries=executed_queries) 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 c80bae4..3ff1d2a 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 @@ -64,11 +64,11 @@ - mysql_primary_status is not changed # Test startreplica fails without changeprimary first. This needs fail_on_error - - name: Start replica (using deprecated startslave choice) and fail because primary is not specified; failing on error as requested + - name: Start replica and fail because primary is not specified; failing on error as requested mysql_replication: <<: *mysql_params login_port: '{{ mysql_replica1_port }}' - mode: startslave + mode: startreplica fail_on_error: yes register: result ignore_errors: yes @@ -139,11 +139,11 @@ - result.queries == ["START SLAVE"] or result.queries == ["START REPLICA"] # Test getreplica mode: - - name: Get replica status using deprecated getslave choice + - name: Get replica status mysql_replication: <<: *mysql_params login_port: '{{ mysql_replica1_port }}' - mode: getslave + mode: getreplica register: replica_status - assert: @@ -159,7 +159,7 @@ - assert: that: - - replica_status.Is_Slave == true + - replica_status.Is_Replica == true - replica_status.Source_Host == '{{ mysql_host }}' - replica_status.Exec_Source_Log_Pos == mysql_primary_status.Position - replica_status.Source_Port == {{ mysql_primary_port }} @@ -216,11 +216,11 @@ when: (pymysql_version.stdout | default('1000', true)) is version('0.9.3', '<=') # Test stopreplica mode: - - name: Stop replica using deprecated stopslave choice + - name: Stop replica mysql_replication: <<: *mysql_params login_port: '{{ mysql_replica1_port }}' - mode: stopslave + mode: stopreplica register: result - assert: @@ -241,3 +241,19 @@ that: - result is not changed when: (pymysql_version.stdout | default('1000', true)) is version('0.9.3', '<=') + + # master / slave related choices were removed in 3.0.0 + # https://github.com/ansible-collections/community.mysql/pull/252 + - name: Test invoking the module with unsupported choice + mysql_replication: + <<: *mysql_params + login_port: '{{ mysql_replica1_port }}' + mode: stopslave + fail_on_error: true + register: result + ignore_errors: yes + + - assert: + that: + - result.msg == "value of mode must be one of{{ ":" }} getprimary, getreplica, changeprimary, stopreplica, startreplica, resetprimary, resetreplica, resetreplicaall, got{{ ":" }} stopslave" + - result is failed From 038a0b07b1d8defb10cd61b616356d8303c20f3e Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Wed, 1 Dec 2021 11:48:52 +0300 Subject: [PATCH 047/217] Release 3.0.0 commit (#255) --- changelogs/CHANGELOG.rst | 203 +----------------- changelogs/changelog.yaml | 26 ++- .../243-get-rid-of-privs-comparison.yml | 2 - .../244-remove-requiressl-privilege.yaml | 2 - ...-mysql_replication_remove_master_slave.yml | 3 - galaxy.yml | 2 +- 6 files changed, 37 insertions(+), 201 deletions(-) delete mode 100644 changelogs/fragments/243-get-rid-of-privs-comparison.yml delete mode 100644 changelogs/fragments/244-remove-requiressl-privilege.yaml delete mode 100644 changelogs/fragments/300-mysql_replication_remove_master_slave.yml diff --git a/changelogs/CHANGELOG.rst b/changelogs/CHANGELOG.rst index a80c771..ce4c0c7 100644 --- a/changelogs/CHANGELOG.rst +++ b/changelogs/CHANGELOG.rst @@ -4,203 +4,22 @@ Community MySQL Collection Release Notes .. contents:: Topics +This changelog describes changes after version 2.0.0. -v1.3.0 +v3.0.0 ====== Release Summary --------------- -This is the minor release of the ``community.mysql`` collection. -This changelog contains all changes to the modules in this collection -that have been added after the release of ``community.mysql`` 1.2.0. +This is the major release of the ``community.mysql`` collection. +This changelog contains all breaking changes to the modules in this collection +that have been added after the release of ``community.mysql`` 2.3.2. -Major Changes -------------- +Breaking Changes / Porting Guide +-------------------------------- -- mysql_replication - the mode options values ``getslave``, ``startslave``, ``stopslave``, ``resetslave``, ``resetslaveall` and the master_use_gtid option ``slave_pos`` are deprecated (see the alternative values) and will be removed in ``community.mysql`` 3.0.0 (https://github.com/ansible-collections/community.mysql/pull/97). -- mysql_replication - the word ``SLAVE`` in messages returned by the module will be changed to ``REPLICA`` in ``community.mysql`` 2.0.0 (https://github.com/ansible-collections/community.mysql/issues/98). - -Minor Changes -------------- - -- mysql_replication - deprecate offending terminology, add alternative choices to the ``mode`` option (https://github.com/ansible-collections/community.mysql/issues/78). - -Bugfixes --------- - -- mysql_user - fix handling of INSERT, UPDATE, REFERENCES on columns (https://github.com/ansible-collections/community.mysql/issues/106). -- mysql_user - the module is not idempotent when SELECT on columns granted (https://github.com/ansible-collections/community.mysql/issues/99). - -v1.2.0 -====== - -Release Summary ---------------- - -This is the minor release of the ``community.mysql`` collection. -This changelog contains all changes to the modules in this collection -that have been added after the release of ``community.mysql`` 1.1.2. - -Minor Changes -------------- - -- mysql_user - refactor to reduce cursor.execute() calls in preparation for adding query logging (https://github.com/ansible-collections/community.mysql/pull/76). - -Bugfixes --------- - -- mysql_user - add ``SHOW_ROUTINE`` privilege support (https://github.com/ansible-collections/community.mysql/issues/86). -- mysql_user - fixed creating user with encrypted password in MySQL 8.0 (https://github.com/ansible-collections/community.mysql/pull/79). - -v1.1.2 -====== - -Release Summary ---------------- - -This is the patch release of the ``community.mysql`` collection. -This changelog contains all changes to the modules in this collection that -have been added after the release of ``community.mysql`` 1.1.1. - -Minor Changes -------------- - -- mysql_query - simple refactoring of query type check (https://github.com/ansible-collections/community.mysql/pull/58). -- mysql_user - simple refactoring of priv type check (https://github.com/ansible-collections/community.mysql/pull/58). - -Bugfixes --------- - -- mysql_db - fix false warning related to ``unsafe_login_password`` option (https://github.com/ansible-collections/community.mysql/issues/33). -- mysql_replication - fix crashes of mariadb >= 10.5.1 and mysql >= 8.0.22 caused by using deprecated terminology (https://github.com/ansible-collections/community.mysql/issues/70). -- mysql_user - fixed change detection when using append_privs (https://github.com/ansible-collections/community.mysql/pull/72). - -v1.1.1 -====== - -Release Summary ---------------- - -This is the patch release of the ``community.mysql`` collection. -This changelog contains all changes to the modules in this collection that -have been added after the release of ``community.mysql`` 1.1.0. - - -Bugfixes --------- - -- mysql_query - fix failing when single-row query contains commas (https://github.com/ansible-collections/community.mysql/issues/51). - -v1.1.0 -====== - -Release Summary ---------------- - -This is the minor release of the ``community.mysql`` collection. -This changelog contains all changes to the modules in this collection that have been added after the release of ``community.mysql`` 1.0.2. - - -Minor Changes -------------- - -- mysql modules - add the ``check_hostname`` option (https://github.com/ansible-collections/community.mysql/issues/28). -- mysql modules - patch the ``Connection`` class to add a destructor that ensures connections to the server are explicitly closed (https://github.com/ansible-collections/community.mysql/pull/44). - -Bugfixes --------- - -- mysql modules - fix crash when ``!includedir`` option is in config file (https://github.com/ansible-collections/community.mysql/issues/46). - -v1.0.2 -====== - -Release Summary ---------------- - -This is the patch release of the ``community.mysql`` collection. -This changelog contains all changes to the modules in this collection that have been added after the release of ``community.mysql`` 1.0.1. - - -Bugfixes --------- - -- mysql_user - fix module's crash when modifying a user with ``host_all`` (https://github.com/ansible-collections/community.mysql/issues/39). - -v1.0.1 -====== - -Release Summary ---------------- - -This is the patch release of the ``community.mysql`` collection. -This changelog contains all changes to the modules in this collection that have been added after the release of ``community.mysql`` 1.0.0. - - -Bugfixes --------- - -- mysql_db - fix false warning related to ``unsafe_login_password`` option (https://github.com/ansible-collections/community.mysql/issues/33). -- mysql_user - added tests to verify that TLS requirements are removed with an empty ``tls_requires`` option (https://github.com/ansible-collections/community.mysql/issues/20). -- mysql_user - correct procedure to check existing TLS requirements (https://github.com/ansible-collections/community.mysql/pull/26). -- mysql_user - minor syntax fixes (https://github.com/ansible-collections/community.mysql/pull/26). - -v1.0.0 -====== - -Release Summary ---------------- - -This is the first proper release of the ``community.mysql`` collection. -This changelog contains all changes to the modules in this collection that were added after the release of Ansible 2.9.0. - - -Minor Changes -------------- - -- mysql_db - add ``master_data`` parameter (https://github.com/ansible/ansible/pull/66048). -- mysql_db - add ``skip_lock_tables`` option (https://github.com/ansible/ansible/pull/66688). -- mysql_db - add the ``check_implicit_admin`` parameter (https://github.com/ansible/ansible/issues/24418). -- mysql_db - add the ``dump_extra_args`` parameter (https://github.com/ansible/ansible/pull/67747). -- mysql_db - add the ``executed_commands`` returned value (https://github.com/ansible/ansible/pull/65498). -- mysql_db - add the ``force`` parameter (https://github.com/ansible/ansible/pull/65547). -- mysql_db - add the ``restrict_config_file`` parameter (https://github.com/ansible/ansible/issues/34488). -- mysql_db - add the ``unsafe_login_password`` parameter (https://github.com/ansible/ansible/issues/63955). -- mysql_db - add the ``use_shell`` parameter (https://github.com/ansible/ansible/issues/20196). -- mysql_info - add ``exclude_fields`` parameter (https://github.com/ansible/ansible/issues/63319). -- mysql_info - add ``global_status`` filter parameter option and return (https://github.com/ansible/ansible/pull/63189). -- mysql_info - add ``return_empty_dbs`` parameter to list empty databases (https://github.com/ansible/ansible/issues/65727). -- mysql_replication - add ``channel`` parameter (https://github.com/ansible/ansible/issues/29311). -- mysql_replication - add ``connection_name`` parameter (https://github.com/ansible/ansible/issues/46243). -- mysql_replication - add ``fail_on_error`` parameter (https://github.com/ansible/ansible/pull/66252). -- mysql_replication - add ``master_delay`` parameter (https://github.com/ansible/ansible/issues/51326). -- mysql_replication - add ``master_use_gtid`` parameter (https://github.com/ansible/ansible/pull/62648). -- mysql_replication - add ``queries`` return value (https://github.com/ansible/ansible/pull/63036). -- mysql_replication - add support of ``resetmaster`` choice to ``mode`` parameter (https://github.com/ansible/ansible/issues/42870). -- mysql_user - ``priv`` parameter can be string or dictionary (https://github.com/ansible/ansible/issues/57533). -- mysql_user - add TLS REQUIRES parameters (https://github.com/ansible-collections/community.mysql/pull/9). -- mysql_user - add ``plugin_auth_string`` parameter (https://github.com/ansible/ansible/pull/44267). -- mysql_user - add ``plugin_hash_string`` parameter (https://github.com/ansible/ansible/pull/44267). -- mysql_user - add ``plugin`` parameter (https://github.com/ansible/ansible/pull/44267). -- mysql_user - add the resource_limits parameter (https://github.com/ansible-collections/community.general/issues/133). -- mysql_variables - add ``mode`` parameter (https://github.com/ansible/ansible/issues/60119). - -Bugfixes --------- - -- mysql - dont mask ``mysql_connect`` function errors from modules (https://github.com/ansible/ansible/issues/64560). -- mysql_db - fix Broken pipe error appearance when state is import and the target file is compressed (https://github.com/ansible/ansible/issues/20196). -- mysql_db - fix bug in the ``db_import`` function introduced by https://github.com/ansible/ansible/pull/56721 (https://github.com/ansible/ansible/issues/65351). -- mysql_info - add parameter for __collect to get only what are wanted (https://github.com/ansible-collections/community.general/pull/136). -- mysql_replication - allow to pass empty values to parameters (https://github.com/ansible/ansible/issues/23976). -- mysql_user - Fix idempotence when long grant lists are used (https://github.com/ansible/ansible/issues/68044) -- mysql_user - Remove false positive ``no_log`` warning for ``update_password`` option -- mysql_user - add ``INVOKE LAMBDA`` privilege support (https://github.com/ansible-collections/community.general/issues/283). -- mysql_user - add missed privileges to support (https://github.com/ansible-collections/community.general/issues/617). -- mysql_user - fix ``host_all`` arguments conversion string formatting error (https://github.com/ansible/ansible/issues/29644). -- mysql_user - fix overriding password to the same (https://github.com/ansible-collections/community.general/issues/543). -- mysql_user - fix support privileges with underscore (https://github.com/ansible/ansible/issues/66974). -- mysql_user - fix the error No database selected (https://github.com/ansible/ansible/issues/68070). -- mysql_user - make sure current_pass_hash is a string before using it in comparison (https://github.com/ansible/ansible/issues/60567). -- mysql_variable - fix the module doesn't support variables name with dot (https://github.com/ansible/ansible/issues/54239). +- mysql_replication - remove ``Is_Slave`` and ``Is_Master`` return values (were replaced with ``Is_Primary`` and ``Is_Replica`` (https://github.com/ansible-collections /community.mysql/issues/145). +- mysql_replication - remove the mode options values containing ``master``/``slave`` and the master_use_gtid option ``slave_pos`` (were replaced with corresponding ``primary``/``replica`` values) (https://github.com/ansible-collections/community.mysql/issues/145). +- mysql_user - remove support for the `REQUIRESSL` special privilege as it has ben superseded by the `tls_requires` option (https://github.com/ansible-collections/community.mysql/discussions/121). +- mysql_user - validate privileges using database engine directly (https://github.com/ansible-collections/community.mysql/issues/234 https://github.com/ansible-collections/community.mysql/pull/243). Do not validate privileges in this module anymore. diff --git a/changelogs/changelog.yaml b/changelogs/changelog.yaml index e78468a..67264f4 100644 --- a/changelogs/changelog.yaml +++ b/changelogs/changelog.yaml @@ -1,2 +1,26 @@ ancestor: 2.0.0 -releases: {} +releases: + 3.0.0: + changes: + breaking_changes: + - mysql_replication - remove ``Is_Slave`` and ``Is_Master`` return values (were + replaced with ``Is_Primary`` and ``Is_Replica`` (https://github.com/ansible-collections /community.mysql/issues/145). + - mysql_replication - remove the mode options values containing ``master``/``slave`` + and the master_use_gtid option ``slave_pos`` (were replaced with corresponding + ``primary``/``replica`` values) (https://github.com/ansible-collections/community.mysql/issues/145). + - mysql_user - remove support for the `REQUIRESSL` special privilege as it has + ben superseded by the `tls_requires` option (https://github.com/ansible-collections/community.mysql/discussions/121). + - mysql_user - validate privileges using database engine directly (https://github.com/ansible-collections/community.mysql/issues/234 + https://github.com/ansible-collections/community.mysql/pull/243). Do not validate + privileges in this module anymore. + release_summary: 'This is the major release of the ``community.mysql`` collection. + + This changelog contains all breaking changes to the modules in this collection + + that have been added after the release of ``community.mysql`` 2.3.2.' + fragments: + - 243-get-rid-of-privs-comparison.yml + - 244-remove-requiressl-privilege.yaml + - 3.0.0.yml + - 300-mysql_replication_remove_master_slave.yml + release_date: '2021-12-01' diff --git a/changelogs/fragments/243-get-rid-of-privs-comparison.yml b/changelogs/fragments/243-get-rid-of-privs-comparison.yml deleted file mode 100644 index 6c29832..0000000 --- a/changelogs/fragments/243-get-rid-of-privs-comparison.yml +++ /dev/null @@ -1,2 +0,0 @@ -breaking_changes: - - mysql_user - validate privileges using database engine directly (https://github.com/ansible-collections/community.mysql/issues/234 https://github.com/ansible-collections/community.mysql/pull/243). Do not validate privileges in this module anymore. diff --git a/changelogs/fragments/244-remove-requiressl-privilege.yaml b/changelogs/fragments/244-remove-requiressl-privilege.yaml deleted file mode 100644 index 474b372..0000000 --- a/changelogs/fragments/244-remove-requiressl-privilege.yaml +++ /dev/null @@ -1,2 +0,0 @@ -breaking_changes: - - mysql_user - remove support for the `REQUIRESSL` special privilege as it has ben superseded by the `tls_requires` option (https://github.com/ansible-collections/community.mysql/discussions/121). diff --git a/changelogs/fragments/300-mysql_replication_remove_master_slave.yml b/changelogs/fragments/300-mysql_replication_remove_master_slave.yml deleted file mode 100644 index dd41831..0000000 --- a/changelogs/fragments/300-mysql_replication_remove_master_slave.yml +++ /dev/null @@ -1,3 +0,0 @@ -breaking_changes: -- mysql_replication - remove the mode options values containing ``master``/``slave`` and the master_use_gtid option ``slave_pos`` (were replaced with corresponding ``primary``/``replica`` values) (https://github.com/ansible-collections/community.mysql/issues/145). -- mysql_replication - remove ``Is_Slave`` and ``Is_Master`` return values (were replaced with ``Is_Primary`` and ``Is_Replica`` (https://github.com/ansible-collections /community.mysql/issues/145). diff --git a/galaxy.yml b/galaxy.yml index 5d44fe8..8e079f1 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -1,6 +1,6 @@ namespace: community name: mysql -version: 2.1.0 +version: 3.0.0 readme: README.md authors: - Ansible community From 1b061131dd5f65f13e25b7985deae71e9e656518 Mon Sep 17 00:00:00 2001 From: "R.Sicart" Date: Tue, 14 Dec 2021 09:30:46 +0100 Subject: [PATCH 048/217] Feature/mariadb integration ci (#246) * Replace matrix.mysql by matrix.db_engine_version * Specify db flavor * Upgrade dbdeployer to 1.56.0 See https://github.com/datacharmer/dbdeployer/issues/120 * Fix: github workflow syntax * Fix: mysql version file for mariadb engine * Do not test mysql_variables modes persist and persist_only on mariadb Those modes do not exist on mariadb. See https://mariadb.com/kb/en/set/ * Exclude integration tests for mariadb_10.5.4 with pymysql==0.7.10 * TLS on mariadb is disabled by default * Configure mariadb supported versions in matrix As discussed in https://github.com/ansible-collections/community.mysql/discussions/141#discussioncomment-643657 * Fix: test_mysql_db : assert that databases does not exist "assertion": "database1 not in mysql_result.stdout" * Fix: assertion mysql_version in result.version.full * Fix: test_mysql_user : Check that the module made a change and that the expected plugin type is set 'mysql_native_password' in show_create_user.stdout * Fix: test_mysql_replication : Create user for replication ERROR 1064 (42000) at line 1: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'BY 'replication_pass'' at line 1 https://dev.mysql.com/doc/mysql-replication-excerpt/5.7/en/replication-howto-repuser.html https://dev.mysql.com/doc/mysql-replication-excerpt/8.0/en/replication-howto-repuser.html https://mariadb.com/kb/en/setting-up-replication/#example-enabling-replication-for-mariadb Create user syntax compatible with auth plugin and password on both mysql and mariadb. https://dev.mysql.com/doc/refman/8.0/en/create-user.html https://mariadb.com/kb/en/create-user/ * Fix: test_mysql_replication: replica_status 'dict object' has no attribute 'Source_Host' * Do not test mysql_replication_channel.yml on mariadb * Do not test target 'test_mysql_role' with mariadb, too much errors to fix * Setup mysql_version_parts depending on install type (mysql or mariadb) * Install mariadb-client when install_type is mariadb To use the same client tools as the database engine. And to use a more updated mysqldump version, in order to fix this error: ERROR 1556 (HY000) at line 776: You can't use locks with log tables * Fix: mysql auth plugin is set on mariadb >10.2 * Fix: skip assertion on mariadb 10.2 * Do not execute test_user_plugin_auth.yml tests on mariadb, create/update useer sql syntax not compatible * Fix: test_mysql_user : assert user1 TLS requirements Remove test for oldd versions * Fix: typo * Fix: test_mysql_user : Test idempotency (expect ok) ignore mariadb 10.5 * [ci skip] Add changelog fragment * Delete changelog fragment --- .github/workflows/ansible-test-plugins.yml | 27 +++++++--- .../targets/setup_mysql/defaults/main.yml | 11 ++-- .../targets/setup_mysql/tasks/config.yml | 4 +- .../targets/setup_mysql/tasks/install.yml | 18 ++++++- .../targets/setup_mysql/tasks/setvars.yml | 7 ++- .../targets/setup_mysql/vars/main.yml | 6 +-- .../targets/test_mysql_db/tasks/issue-28.yml | 8 ++- .../tasks/multi_db_create_delete.yml | 12 +++++ .../test_mysql_info/tasks/issue-28.yml | 12 ++++- .../targets/test_mysql_info/tasks/main.yml | 2 +- .../test_mysql_query/tasks/issue-28.yml | 12 ++++- .../test_mysql_replication/tasks/issue-28.yml | 12 ++++- .../test_mysql_replication/tasks/main.yml | 2 + .../tasks/mysql_replication_initial.yml | 8 ++- .../tasks/mysql_role_initial.yml | 4 +- .../test_mysql_user/tasks/create_user.yml | 6 +++ .../test_mysql_user/tasks/issue-28.yml | 8 ++- .../targets/test_mysql_user/tasks/main.yml | 2 + .../test_mysql_user/tasks/test_privs.yml | 2 + .../tasks/test_user_plugin_auth.yml | 50 +++++++++++++++---- .../tasks/tls_requirements.yml | 28 ++++------- .../test_mysql_variables/tasks/issue-28.yml | 8 ++- .../tasks/mysql_variables.yml | 4 +- 23 files changed, 195 insertions(+), 58 deletions(-) diff --git a/.github/workflows/ansible-test-plugins.yml b/.github/workflows/ansible-test-plugins.yml index 64435cd..f39e171 100644 --- a/.github/workflows/ansible-test-plugins.yml +++ b/.github/workflows/ansible-test-plugins.yml @@ -50,14 +50,16 @@ jobs: working-directory: ./ansible_collections/community/mysql integration: - name: "Integration (Python: ${{ matrix.python }}, Ansible: ${{ matrix.ansible }}, MySQL: ${{ matrix.mysql }}, Connector: ${{ matrix.connector }})" + name: "Integration (Python: ${{ matrix.python }}, Ansible: ${{ matrix.ansible }}, MySQL: ${{ matrix.db_engine_version }}, Connector: ${{ matrix.connector }})" runs-on: ubuntu-latest strategy: fail-fast: false matrix: - mysql: - - 5.7.31 - - 8.0.22 + db_engine_version: + - mysql_5.7.31 + - mysql_8.0.22 + - mariadb_10.2.37 + - mariadb_10.5.9 ansible: - stable-2.9 - stable-2.10 @@ -72,7 +74,9 @@ jobs: - pymysql==0.9.3 - mysqlclient==2.0.1 exclude: - - mysql: 8.0.22 + - db_engine_version: mysql_8.0.22 + connector: pymysql==0.7.10 + - db_engine_version: mariadb_10.5.9 connector: pymysql==0.7.10 - python: 3.8 ansible: stable-2.9 @@ -100,8 +104,17 @@ jobs: - name: Install ansible-base (${{ matrix.ansible }}) run: pip install https://github.com/ansible/ansible/archive/${{ matrix.ansible }}.tar.gz --disable-pip-version-check - - name: Set MySQL version (${{ matrix.mysql }}) - run: "sed -i 's/^mysql_version:.*/mysql_version: \"${{ matrix.mysql }}\"/g' ${{ env.mysql_version_file }}" + - name: Set MySQL version (${{ matrix.db_engine_version }}) + run: | + export DB_VERSION=$(echo "${{ matrix.db_engine_version }}" | awk -F_ '{print $2}') + sed -i "s/^mysql_version:.*/mysql_version: $DB_VERSION/g" ${{ env.mysql_version_file }} + if: ${{ startsWith(matrix.db_engine_version, 'mysql') }} + + - name: Set MariaDB version (${{ matrix.db_engine_version }}) + run: | + export DB_VERSION=$(echo "${{ matrix.db_engine_version }}" | awk -F_ '{print $2}') + sed -i -e "s/^mariadb_version:.*/mariadb_version: $DB_VERSION/g" -e 's/^mariadb_install: false/mariadb_install: true/g' ${{ env.mysql_version_file }} + if: ${{ startsWith(matrix.db_engine_version, 'mariadb') }} - name: Set Connector version (${{ matrix.connector }}) run: "sed -i 's/^python_packages:.*/python_packages: [${{ matrix.connector }}]/' ${{ env.connector_version_file }}" diff --git a/tests/integration/targets/setup_mysql/defaults/main.yml b/tests/integration/targets/setup_mysql/defaults/main.yml index 7bcb2d2..68c60bc 100644 --- a/tests/integration/targets/setup_mysql/defaults/main.yml +++ b/tests/integration/targets/setup_mysql/defaults/main.yml @@ -1,13 +1,18 @@ -dbdeployer_version: 1.52.0 +dbdeployer_version: 1.56.0 dbdeployer_home_dir: /opt/dbdeployer home_dir: /root -percona_client_version: 5.7 - mariadb_install: false mysql_version: 8.0.22 mariadb_version: 10.5.4 mysql_base_port: 3306 + +percona_client_package: >- + {%- if mariadb_install -%} + mariadb-client + {%- else -%} + percona-server-client-5.7 + {%- endif -%} diff --git a/tests/integration/targets/setup_mysql/tasks/config.yml b/tests/integration/targets/setup_mysql/tasks/config.yml index 57be29a..2b27e27 100644 --- a/tests/integration/targets/setup_mysql/tasks/config.yml +++ b/tests/integration/targets/setup_mysql/tasks/config.yml @@ -6,10 +6,10 @@ - name: "{{ role_name }} | config | run unpack tarball" shell: - cmd: "dbdeployer unpack {{ dbdeployer_sandbox_download_dir }}/{{ install_tarball }}" + cmd: "dbdeployer unpack {{ dbdeployer_sandbox_download_dir }}/{{ install_tarball }} --flavor {{ install_type }}" creates: "{{ dbdeployer_sandbox_binary_dir }}/{{ install_version }}" - name: "{{ role_name }} | config | setup replication topology" shell: - cmd: "dbdeployer deploy multiple {{ install_version }} --base-port {{ mysql_base_port }} --my-cnf-options=\"master_info_repository='TABLE'\" --my-cnf-options=\"relay_log_info_repository='TABLE'\"" + cmd: "dbdeployer deploy multiple {{ install_version }} --flavor {{ install_type }} --base-port {{ mysql_base_port }} --my-cnf-options=\"master_info_repository='TABLE'\" --my-cnf-options=\"relay_log_info_repository='TABLE'\"" creates: "{{ dbdeployer_sandbox_home_dir }}/multi_msb_{{ install_version|replace('.','_') }}" diff --git a/tests/integration/targets/setup_mysql/tasks/install.yml b/tests/integration/targets/setup_mysql/tasks/install.yml index 57e4b31..b64af25 100644 --- a/tests/integration/targets/setup_mysql/tasks/install.yml +++ b/tests/integration/targets/setup_mysql/tasks/install.yml @@ -4,12 +4,26 @@ keyserver: keyserver.ubuntu.com id: 4D1BB29D63D98E422B2113B19334A25F8507EFA5 state: present + when: install_type == 'mysql' - name: "{{ role_name }} | install | add percona repositories" apt_repository: - repo: "{{ item }}" + repo: deb http://repo.percona.com/percona/apt {{ ansible_lsb.codename }} main state: present - loop: "{{ percona_mysql_repos }}" + when: install_type == 'mysql' + +- name: "{{ role_name }} | install | add apt signing key for mariadb" + apt_key: + keyserver: keyserver.ubuntu.com + id: F1656F24C74CD1D8 + state: present + when: install_type == 'mariadb' + +- name: "{{ role_name }} | install | add mariadb repositories" + apt_repository: + repo: "deb [arch=amd64,arm64] https://downloads.mariadb.com/MariaDB/mariadb-{{ mysql_major_version }}/repo/ubuntu {{ ansible_lsb.codename }} main" + state: present + when: install_type == 'mariadb' - name: "{{ role_name }} | install | install packages required by percona" apt: diff --git a/tests/integration/targets/setup_mysql/tasks/setvars.yml b/tests/integration/targets/setup_mysql/tasks/setvars.yml index 14a2c54..cfc90c1 100644 --- a/tests/integration/targets/setup_mysql/tasks/setvars.yml +++ b/tests/integration/targets/setup_mysql/tasks/setvars.yml @@ -1,7 +1,12 @@ --- - name: "{{ role_name }} | setvars | split mysql version in parts" set_fact: - mysql_version_parts: "{{ mysql_version.split('.') }}" + mysql_version_parts: >- + {%- if mariadb_install -%} + {{ mariadb_version.split('.') }} + {%- else -%} + {{ mysql_version.split('.') }} + {%- endif -%} - name: "{{ role_name }} | setvars | get mysql major version" set_fact: diff --git a/tests/integration/targets/setup_mysql/vars/main.yml b/tests/integration/targets/setup_mysql/vars/main.yml index e267307..db17f1e 100644 --- a/tests/integration/targets/setup_mysql/vars/main.yml +++ b/tests/integration/targets/setup_mysql/vars/main.yml @@ -7,12 +7,8 @@ dbdeployer_sandbox_download_dir: "{{ home_dir }}/downloads" dbdeployer_sandbox_binary_dir: "{{ home_dir }}/opt/mysql" dbdeployer_sandbox_home_dir: "{{ home_dir }}/sandboxes" -percona_mysql_repos: - - deb http://repo.percona.com/apt {{ ansible_lsb.codename }} main - - deb-src http://repo.percona.com/apt {{ ansible_lsb.codename }} main - percona_mysql_packages: - - percona-server-client-{{ percona_client_version }} + - "{{ percona_client_package }}" python_packages: [pymysql == 0.9.3] diff --git a/tests/integration/targets/test_mysql_db/tasks/issue-28.yml b/tests/integration/targets/test_mysql_db/tasks/issue-28.yml index a0b037f..74071e2 100644 --- a/tests/integration/targets/test_mysql_db/tasks/issue-28.yml +++ b/tests/integration/targets/test_mysql_db/tasks/issue-28.yml @@ -1,11 +1,17 @@ --- +- name: set fact tls_enabled + command: "{{ mysql_command }} \"-e SHOW VARIABLES LIKE 'have_ssl';\"" + register: result +- set_fact: + tls_enabled: "{{ 'YES' in result.stdout | bool | default('false', true) }}" + - vars: mysql_parameters: &mysql_params login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' login_host: 127.0.0.1 login_port: '{{ mysql_primary_port }}' - + when: tls_enabled block: # ============================================================ diff --git a/tests/integration/targets/test_mysql_db/tasks/multi_db_create_delete.yml b/tests/integration/targets/test_mysql_db/tasks/multi_db_create_delete.yml index 6bada1c..cb91d32 100644 --- a/tests/integration/targets/test_mysql_db/tasks/multi_db_create_delete.yml +++ b/tests/integration/targets/test_mysql_db/tasks/multi_db_create_delete.yml @@ -14,6 +14,18 @@ # # ========================================================================== # Initial check - To confirm that database does not exist before executing check mode tasks +- name: Drop databases before test + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: + - '{{ db1_name }}' + - '{{ db2_name }}' + - '{{ db3_name }}' + state: absent + - name: run command to list databases like specified database name command: "{{ mysql_command }} \"-e show databases like 'database%'\"" register: mysql_result diff --git a/tests/integration/targets/test_mysql_info/tasks/issue-28.yml b/tests/integration/targets/test_mysql_info/tasks/issue-28.yml index 289b8b3..ec2b493 100644 --- a/tests/integration/targets/test_mysql_info/tasks/issue-28.yml +++ b/tests/integration/targets/test_mysql_info/tasks/issue-28.yml @@ -1,11 +1,21 @@ --- +- name: alias mysql command to include default options + set_fact: + mysql_command: "mysql -u{{ mysql_user }} -p{{ mysql_password }} -P{{ mysql_primary_port }} --protocol=tcp" + +- name: set fact tls_enabled + command: "{{ mysql_command }} \"-e SHOW VARIABLES LIKE 'have_ssl';\"" + register: result +- set_fact: + tls_enabled: "{{ 'YES' in result.stdout | bool | default('false', true) }}" + - vars: mysql_parameters: &mysql_params login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' login_host: 127.0.0.1 login_port: '{{ mysql_primary_port }}' - + when: tls_enabled block: # ============================================================ diff --git a/tests/integration/targets/test_mysql_info/tasks/main.yml b/tests/integration/targets/test_mysql_info/tasks/main.yml index 0ed2af9..c3d601d 100644 --- a/tests/integration/targets/test_mysql_info/tasks/main.yml +++ b/tests/integration/targets/test_mysql_info/tasks/main.yml @@ -48,7 +48,7 @@ - assert: that: - result.changed == false - - "mysql_version in result.version.full" + - "mysql_version in result.version.full or mariadb_version in result.version.full" - result.settings != {} - result.global_status != {} - result.databases != {} diff --git a/tests/integration/targets/test_mysql_query/tasks/issue-28.yml b/tests/integration/targets/test_mysql_query/tasks/issue-28.yml index 0363834..61f086e 100644 --- a/tests/integration/targets/test_mysql_query/tasks/issue-28.yml +++ b/tests/integration/targets/test_mysql_query/tasks/issue-28.yml @@ -1,11 +1,21 @@ --- +- name: alias mysql command to include default options + set_fact: + mysql_command: "mysql -u{{ mysql_user }} -p{{ mysql_password }} -P{{ mysql_primary_port }} --protocol=tcp" + +- name: set fact tls_enabled + command: "{{ mysql_command }} \"-e SHOW VARIABLES LIKE 'have_ssl';\"" + register: result +- set_fact: + tls_enabled: "{{ 'YES' in result.stdout | bool | default('false', true) }}" + - vars: mysql_parameters: &mysql_params login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' login_host: 127.0.0.1 login_port: '{{ mysql_primary_port }}' - + when: tls_enabled block: # ============================================================ diff --git a/tests/integration/targets/test_mysql_replication/tasks/issue-28.yml b/tests/integration/targets/test_mysql_replication/tasks/issue-28.yml index 5baa384..11e457b 100644 --- a/tests/integration/targets/test_mysql_replication/tasks/issue-28.yml +++ b/tests/integration/targets/test_mysql_replication/tasks/issue-28.yml @@ -1,11 +1,21 @@ --- +- name: alias mysql command to include default options + set_fact: + mysql_command: "mysql -u{{ mysql_user }} -p{{ mysql_password }} -P{{ mysql_primary_port }} --protocol=tcp" + +- name: set fact tls_enabled + command: "{{ mysql_command }} \"-e SHOW VARIABLES LIKE 'have_ssl';\"" + register: result +- set_fact: + tls_enabled: "{{ 'YES' in result.stdout | bool | default('false', true) }}" + - vars: mysql_parameters: &mysql_params login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' login_host: 127.0.0.1 login_port: '{{ mysql_primary_port }}' - + when: tls_enabled block: # ============================================================ diff --git a/tests/integration/targets/test_mysql_replication/tasks/main.yml b/tests/integration/targets/test_mysql_replication/tasks/main.yml index d4c7150..32b59be 100644 --- a/tests/integration/targets/test_mysql_replication/tasks/main.yml +++ b/tests/integration/targets/test_mysql_replication/tasks/main.yml @@ -14,6 +14,8 @@ # Tests of channel parameter: - import_tasks: mysql_replication_channel.yml + when: + - install_type == 'mysql' # FIXME: mariadb introduces FOR CHANNEL in 10.7 # Tests of resetprimary mode: - import_tasks: mysql_replication_resetprimary_mode.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 3ff1d2a..050e952 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 @@ -24,14 +24,20 @@ mysql8022_and_higher: true when: - db.version.major > 8 or (db.version.major == 8 and db.version.minor > 0) or (db.version.major == 8 and db.version.minor == 0 and db.version.release >= 22) + - install_type == 'mysql' - name: alias mysql command to include default options set_fact: mysql_command: "mysql -u{{ mysql_user }} -p{{ mysql_password }} --protocol=tcp" # Preparation: - - name: Create user for replication + - name: Create user for mysql replication shell: "echo \"CREATE USER '{{ replication_user }}'@'localhost' IDENTIFIED WITH mysql_native_password BY '{{ replication_pass }}'; GRANT REPLICATION SLAVE ON *.* TO '{{ replication_user }}'@'localhost';\" | {{ mysql_command }} -P{{ mysql_primary_port }}" + when: install_type == 'mysql' + + - name: Create user for mariadb replication + shell: "echo \"CREATE USER '{{ replication_user }}'@'localhost' IDENTIFIED BY '{{ replication_pass }}'; GRANT REPLICATION SLAVE ON *.* TO '{{ replication_user }}'@'localhost';\" | {{ mysql_command }} -P{{ mysql_primary_port }}" + when: install_type == 'mariadb' - name: Create test database mysql_db: diff --git a/tests/integration/targets/test_mysql_role/tasks/mysql_role_initial.yml b/tests/integration/targets/test_mysql_role/tasks/mysql_role_initial.yml index ba3125c..5f47e7a 100644 --- a/tests/integration/targets/test_mysql_role/tasks/mysql_role_initial.yml +++ b/tests/integration/targets/test_mysql_role/tasks/mysql_role_initial.yml @@ -32,10 +32,10 @@ when: - srv['version']['major'] < 8 + # FIXME: tests should pass for both mysql >= 8 and mariadb # Skip unsupported versions - meta: end_play - when: - - srv['version']['major'] < 8 + when: srv['version']['major'] < 8 or install_type == 'mariadb' ######### # Prepare diff --git a/tests/integration/targets/test_mysql_user/tasks/create_user.yml b/tests/integration/targets/test_mysql_user/tasks/create_user.yml index 790d9bb..78c253d 100644 --- a/tests/integration/targets/test_mysql_user/tasks/create_user.yml +++ b/tests/integration/targets/test_mysql_user/tasks/create_user.yml @@ -24,6 +24,12 @@ login_port: '{{ mysql_primary_port }}' block: + - name: Drop mysql user if exists + mysql_user: + <<: *mysql_params + name: '{{ user_name_1 }}' + state: absent + ignore_errors: yes # ============================================================ - name: create mysql user {{user_name}} diff --git a/tests/integration/targets/test_mysql_user/tasks/issue-28.yml b/tests/integration/targets/test_mysql_user/tasks/issue-28.yml index 16eb47d..ae15865 100644 --- a/tests/integration/targets/test_mysql_user/tasks/issue-28.yml +++ b/tests/integration/targets/test_mysql_user/tasks/issue-28.yml @@ -1,11 +1,17 @@ --- +- name: set fact tls_enabled + command: "{{ mysql_command }} \"-e SHOW VARIABLES LIKE 'have_ssl';\"" + register: result +- set_fact: + tls_enabled: "{{ 'YES' in result.stdout | bool | default('false', true) }}" + - vars: mysql_parameters: &mysql_params login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' login_host: 127.0.0.1 login_port: '{{ mysql_primary_port }}' - + when: tls_enabled block: # ============================================================ diff --git a/tests/integration/targets/test_mysql_user/tasks/main.yml b/tests/integration/targets/test_mysql_user/tasks/main.yml index 93cb12d..9fd5ccd 100644 --- a/tests/integration/targets/test_mysql_user/tasks/main.yml +++ b/tests/integration/targets/test_mysql_user/tasks/main.yml @@ -242,7 +242,9 @@ # ============================================================ # Test plugin authentication scenarios. # + # FIXME: mariadb sql syntax for create/update user is not compatible - include: test_user_plugin_auth.yml + when: install_type == 'mysql' # ============================================================ # Assert create user with SELECT privileges, attempt to create database and update privileges to create database diff --git a/tests/integration/targets/test_mysql_user/tasks/test_privs.yml b/tests/integration/targets/test_mysql_user/tasks/test_privs.yml index 27beb77..89d9358 100644 --- a/tests/integration/targets/test_mysql_user/tasks/test_privs.yml +++ b/tests/integration/targets/test_mysql_user/tasks/test_privs.yml @@ -173,10 +173,12 @@ state: present register: result + # FIXME: on mariadb 10.5 there's always a change - name: Assert that priv did not change assert: that: - "result.changed == false" + when: install_type == 'mysql' or (install_type == 'mariadb' and mariadb_version is version('10.2', '==')) # ============================================================ - name: update user with invalid privileges diff --git a/tests/integration/targets/test_mysql_user/tasks/test_user_plugin_auth.yml b/tests/integration/targets/test_mysql_user/tasks/test_user_plugin_auth.yml index 3ce9f1b..3b95d17 100644 --- a/tests/integration/targets/test_mysql_user/tasks/test_user_plugin_auth.yml +++ b/tests/integration/targets/test_mysql_user/tasks/test_user_plugin_auth.yml @@ -31,14 +31,19 @@ register: result - name: Get user information - command: "{{ mysql_command }} -e \"SHOW CREATE USER '{{ test_user_name }}'@'localhost'\"" + command: "{{ mysql_command }} -e \"SELECT user, host, plugin FROM mysql.user WHERE user = '{{ test_user_name }}' and host = 'localhost'\"" register: show_create_user - - name: Check that the module made a change and that the expected plugin type is set + - name: Check that the module made a change assert: that: - "result.changed == true" + + - name: Check that the expected plugin type is set + assert: + that: - "'{{ test_plugin_type }}' in show_create_user.stdout" + when: install_type == 'mysql' or (install_type == 'mariadb' and mariadb_version is version('10.3', '>=')) - include: assert_user.yml user_name={{ test_user_name }} priv={{ test_default_priv_type }} @@ -102,14 +107,19 @@ register: result - name: Get user information - command: "{{ mysql_command }} -e \"SHOW CREATE USER '{{ test_user_name }}'@'localhost'\"" + command: "{{ mysql_command }} -e \"SELECT user, host, plugin FROM mysql.user WHERE user = '{{ test_user_name }}' and host = 'localhost'\"" register: show_create_user - - name: Check that the module made a change and that the expected plugin type is set + - name: Check that the module made a change assert: that: - "result.changed == true" + + - name: Check that the expected plugin type is set + assert: + that: - "'{{ test_plugin_type }}' in show_create_user.stdout" + when: install_type == 'mysql' or (install_type == 'mariadb' and mariadb_version is version('10.3', '>=')) - include: assert_user.yml user_name={{ test_user_name }} priv={{ test_default_priv_type }} @@ -135,10 +145,12 @@ plugin_hash_string: '{{ test_plugin_hash }}' register: result + # FIXME: on mariadb 10.2 there's always a change - name: Check that the module doesn't make a change when the same hash is passed in assert: that: - "result.changed == false" + when: install_type == 'mysql' or (install_type == 'mariadb' and mariadb_version is version('10.3', '>=')) - include: assert_user.yml user_name={{ test_user_name }} priv={{ test_default_priv_type }} @@ -190,11 +202,16 @@ command: "{{ mysql_command }} -e \"SHOW CREATE USER '{{ test_user_name }}'@'localhost'\"" register: show_create_user - - name: Check that the module made a change and that the expected plugin type is set + - name: Check that the module made a change assert: that: - "result.changed == true" + + - name: Check that the expected plugin type is set + assert: + that: - "'{{ test_plugin_type }}' in show_create_user.stdout" + when: install_type == 'mysql' or (install_type == 'mariadb' and mariadb_version is version('10.3', '>=')) - include: assert_user.yml user_name={{ test_user_name }} priv={{ test_default_priv_type }} @@ -275,11 +292,16 @@ command: "{{ mysql_command }} -e \"SHOW CREATE USER '{{ test_user_name }}'@'localhost'\"" register: show_create_user - - name: Check that the module made a change and that the expected plugin type is set + - name: Check that the module made a change assert: that: - "result.changed == true" + + - name: Check that the expected plugin type is set + assert: + that: - "'{{ test_plugin_type }}' in show_create_user.stdout" + when: install_type == 'mysql' or (install_type == 'mariadb' and mariadb_version is version('10.3', '>=')) - include: assert_user.yml user_name={{ test_user_name }} priv={{ test_default_priv_type }} @@ -354,11 +376,16 @@ command: "{{ mysql_command }} -e \"SHOW CREATE USER '{{ test_user_name }}'@'localhost'\"" register: show_create_user - - name: Check that the module made a change and that the expected plugin type is set + - name: Check that the module made a change assert: that: - "result.changed == true" + + - name: Check that the expected plugin type is set + assert: + that: - "'{{ test_plugin_type }}' in show_create_user.stdout" + when: install_type == 'mysql' or (install_type == 'mariadb' and mariadb_version is version('10.3', '>=')) - include: assert_user.yml user_name={{ test_user_name }} priv={{ test_default_priv_type }} @@ -374,11 +401,16 @@ command: "{{ mysql_command }} -e \"SHOW CREATE USER '{{ test_user_name }}'@'localhost'\"" register: show_create_user - - name: Check that the module made a change and that the expected plugin type is set + - name: Check that the module made a change assert: that: - "result.changed == true" - - "'sha256_password' in show_create_user.stdout" + + - name: Check that the expected plugin type is set + assert: + that: + - "'sha256_password' in show_create_user.stdout" + when: install_type == 'mysql' or (install_type == 'mariadb' and mariadb_version is version('10.3', '>=')) - include: assert_user.yml user_name={{ test_user_name }} priv={{ test_default_priv_type }} diff --git a/tests/integration/targets/test_mysql_user/tasks/tls_requirements.yml b/tests/integration/targets/test_mysql_user/tasks/tls_requirements.yml index 8de9401..7bf142e 100644 --- a/tests/integration/targets/test_mysql_user/tasks/tls_requirements.yml +++ b/tests/integration/targets/test_mysql_user/tasks/tls_requirements.yml @@ -55,7 +55,7 @@ issuer: '/CN=org/O=MyDom, Inc./C=US/ST=Oregon/L=Portland' - block: - - name: retrieve TLS requiremets for users in old database version + - name: retrieve TLS requirements for users in old database version command: "{{ mysql_command }} -L -N -s -e \"SHOW GRANTS for '{{ item }}'@'localhost'\"" register: old_result with_items: ['{{ user_name_1 }}', '{{ user_name_2 }}', '{{ user_name_3 }}'] @@ -67,7 +67,7 @@ when: db_version.version.major <= 5 and db_version.version.minor <= 6 or db_version.version.major == 10 and db_version.version.minor < 2 - block: - - name: retrieve TLS requiremets for users in new database version + - name: retrieve TLS requirements for users in new database version command: "{{ mysql_command }} -L -N -s -e \"SHOW CREATE USER '{{ item }}'@'localhost'\"" register: new_result with_items: ['{{ user_name_1 }}', '{{ user_name_2 }}', '{{ user_name_3 }}'] @@ -119,12 +119,12 @@ that: - result is changed - - name: retrieve TLS requiremets for users in old database version + - name: retrieve TLS requirements for users in old database version command: "{{ mysql_command }} -L -N -s -e \"SHOW GRANTS for '{{ user_name_1 }}'@'localhost'\"" register: old_result when: db_version.version.major <= 5 and db_version.version.minor <= 6 or db_version.version.major == 10 and db_version.version.minor < 2 - - name: retrieve TLS requiremets for users in new database version + - name: retrieve TLS requirements for users in new database version command: "{{ mysql_command }} -L -N -s -e \"SHOW CREATE USER '{{ user_name_1 }}'@'localhost'\"" register: new_result when: db_version.version.major == 5 and db_version.version.minor >= 7 or db_version.version.major > 5 and db_version.version.major < 10 or db_version.version.major == 10 and db_version.version.minor >= 2 @@ -143,12 +143,12 @@ tls_requires: X509: - - name: retrieve TLS requiremets for users in old database version + - name: retrieve TLS requirements for users in old database version command: "{{ mysql_command }} -L -N -s -e \"SHOW GRANTS for '{{ user_name_1 }}'@'localhost'\"" register: old_result when: db_version.version.major <= 5 and db_version.version.minor <= 6 or db_version.version.major == 10 and db_version.version.minor < 2 - - name: retrieve TLS requiremets for users in new database version + - name: retrieve TLS requirements for users in new database version command: "{{ mysql_command }} -L -N -s -e \"SHOW CREATE USER '{{ user_name_1 }}'@'localhost'\"" register: new_result when: db_version.version.major == 5 and db_version.version.minor >= 7 or db_version.version.major > 5 and db_version.version.major < 10 or db_version.version.major == 10 and db_version.version.minor >= 2 @@ -159,28 +159,20 @@ vars: - reqs: "{{(old_result is skipped | ternary(new_result, old_result)).stdout.split('REQUIRE')[1].split(separator)[0].strip()}}" - - name: remove TLS requiremets from user (expect changed=true) + - name: remove TLS requirements from user (expect changed=true) mysql_user: <<: *mysql_params name: '{{ user_name_1 }}' password: '{{ user_password_1 }}' tls_requires: - - name: retrieve TLS requiremets for users in old database version - command: "{{ mysql_command }} -L -N -s -e \"SHOW GRANTS for '{{ user_name_1 }}'@'localhost'\"" - register: old_result - when: db_version.version.major <= 5 and db_version.version.minor <= 6 or db_version.version.major == 10 and db_version.version.minor < 2 - - - name: retrieve TLS requiremets for users in new database version + - name: retrieve TLS requirements for users command: "{{ mysql_command }} -L -N -s -e \"SHOW CREATE USER '{{ user_name_1 }}'@'localhost'\"" - register: new_result - when: db_version.version.major == 5 and db_version.version.minor >= 7 or db_version.version.major > 5 and db_version.version.major < 10 or db_version.version.major == 10 and db_version.version.minor >= 2 + register: result - name: assert user1 TLS requirements assert: - that: "'NONE' in reqs" - vars: - - reqs: "{{(old_result is skipped | ternary(new_result, old_result)).stdout.split('REQUIRE')[1].split(separator)[0].strip() | default('NONE') }}" + that: "'REQUIRE ' not in result.stdout or 'REQUIRE NONE' in result.stdout" - include: remove_user.yml user_name={{user_name_1}} user_password={{ user_password_1 }} diff --git a/tests/integration/targets/test_mysql_variables/tasks/issue-28.yml b/tests/integration/targets/test_mysql_variables/tasks/issue-28.yml index c33c52d..93c2125 100644 --- a/tests/integration/targets/test_mysql_variables/tasks/issue-28.yml +++ b/tests/integration/targets/test_mysql_variables/tasks/issue-28.yml @@ -1,11 +1,17 @@ --- +- name: set fact tls_enabled + command: "{{ mysql_command }} \"-e SHOW VARIABLES LIKE 'have_ssl';\"" + register: result +- set_fact: + tls_enabled: "{{ 'YES' in result.stdout | bool | default('false', true) }}" + - vars: mysql_parameters: &mysql_params login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' login_host: 127.0.0.1 login_port: '{{ mysql_primary_port }}' - + when: tls_enabled block: # ============================================================ 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 390bc69..a857f12 100644 --- a/tests/integration/targets/test_mysql_variables/tasks/mysql_variables.yml +++ b/tests/integration/targets/test_mysql_variables/tasks/mysql_variables.yml @@ -359,7 +359,9 @@ - include: assert_var.yml changed=true output={{ result }} var_name={{ set_name }} var_value='{{ def_val }}' - when: mysql_version is not version('8.0', '<') + when: + - mysql_version is version('8.0', '>=') + - install_type == 'mysql' # Bugfix of https://github.com/ansible/ansible/issues/54239 # - name: set variable containing dot From ce3192b34a188d270ee2d80c6f98fc87ca26b45a Mon Sep 17 00:00:00 2001 From: "Jorge Rodriguez (A.K.A. Tiriel)" Date: Wed, 15 Dec 2021 09:55:23 +0100 Subject: [PATCH 049/217] Update README with supported versions (#250) --- README.md | 19 +++++++++++++++++-- ...orted-database-and-connector-versions.yaml | 2 ++ 2 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 changelogs/fragments/141-supported-database-and-connector-versions.yaml diff --git a/README.md b/README.md index 11727ee..cafd510 100644 --- a/README.md +++ b/README.md @@ -59,19 +59,34 @@ Every voice is important and every idea is valuable. If you have something on yo - [mysql_user](https://docs.ansible.com/ansible/latest/collections/community/mysql/mysql_user_module.html) - [mysql_variables](https://docs.ansible.com/ansible/latest/collections/community/mysql/mysql_variables_module.html) -## Tested with Ansible +## Tested with +### Ansible - 2.9 - 2.10 - 2.11 +- 2.12 - devel +### Databases + +- mysql 5.7.31 +- mysql 8.0.22 +- mariadb 10.2.37 +- mariadb 10.5.9 + +### Database connectors + +- pymysql 0.7.10 +- pymysql 0.9.3 +- mysqlclient 2.0.1 + ## External requirements The MySQL modules rely on a MySQL connector. The list of supported drivers is below: - [PyMySQL](https://github.com/PyMySQL/PyMySQL) -- [MySQLdb](https://github.com/PyMySQL/mysqlclient-python) +- [mysqlclient](https://github.com/PyMySQL/mysqlclient) - Support for other Python MySQL connectors may be added in a future release. ## Using this collection diff --git a/changelogs/fragments/141-supported-database-and-connector-versions.yaml b/changelogs/fragments/141-supported-database-and-connector-versions.yaml new file mode 100644 index 0000000..1527239 --- /dev/null +++ b/changelogs/fragments/141-supported-database-and-connector-versions.yaml @@ -0,0 +1,2 @@ +minor_changes: + - Added explicit description of the supported versions of databases and connectors. Changes to the collection are **NOT** tested against database versions older than `mysql 5.7.31` and `mariadb 10.2.37` or connector versions older than `pymysql 0.7.10` and `mysqlclient 2.0.1`. (https://github.com/ansible-collections/community.mysql/discussions/141) From d9e12b85ad8f73b83de6a1a039ae2fe37e021225 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Thu, 23 Dec 2021 14:45:21 +0300 Subject: [PATCH 050/217] Prepare for distutils.version being removed in Python 3.12 (#267) * Prepare for distutils.version being removed in Python 3.12 * Update plugins/module_utils/version.py Co-authored-by: Felix Fontein Co-authored-by: Felix Fontein --- .../267-prepare_for_distutils_be_removed.yml | 2 ++ .../implementations/mariadb/replication.py | 2 +- .../implementations/mariadb/role.py | 2 +- .../implementations/mariadb/user.py | 2 +- .../implementations/mysql/replication.py | 2 +- .../implementations/mysql/role.py | 2 +- .../implementations/mysql/user.py | 2 +- plugins/module_utils/version.py | 21 +++++++++++++++++++ plugins/modules/mysql_replication.py | 2 +- 9 files changed, 30 insertions(+), 7 deletions(-) create mode 100644 changelogs/fragments/267-prepare_for_distutils_be_removed.yml create mode 100644 plugins/module_utils/version.py diff --git a/changelogs/fragments/267-prepare_for_distutils_be_removed.yml b/changelogs/fragments/267-prepare_for_distutils_be_removed.yml new file mode 100644 index 0000000..6c73582 --- /dev/null +++ b/changelogs/fragments/267-prepare_for_distutils_be_removed.yml @@ -0,0 +1,2 @@ +bugfixes: + - "Collection core functions - use vendored version of ``distutils.version`` included in ansible-core 2.12 if available. This avoids breakage when ``distutils`` is removed from the standard library of Python 3.12. Note that ansible-core 2.11, ansible-base 2.10 and Ansible 2.9 are right now not compatible with Python 3.12, hence this fix does not target these ansible-core/-base/2.9 versions (https://github.com/ansible-collections/community.mysql/pull/267)." diff --git a/plugins/module_utils/implementations/mariadb/replication.py b/plugins/module_utils/implementations/mariadb/replication.py index 7968e92..cee4967 100644 --- a/plugins/module_utils/implementations/mariadb/replication.py +++ b/plugins/module_utils/implementations/mariadb/replication.py @@ -2,7 +2,7 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type from ansible_collections.community.mysql.plugins.module_utils.mysql import get_server_version -from distutils.version import LooseVersion +from ansible_collections.community.mysql.plugins.module_utils.version import LooseVersion def uses_replica_terminology(cursor): diff --git a/plugins/module_utils/implementations/mariadb/role.py b/plugins/module_utils/implementations/mariadb/role.py index 3b4c777..a3c9ea5 100644 --- a/plugins/module_utils/implementations/mariadb/role.py +++ b/plugins/module_utils/implementations/mariadb/role.py @@ -1,7 +1,7 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -from distutils.version import LooseVersion +from ansible_collections.community.mysql.plugins.module_utils.version import LooseVersion from ansible_collections.community.mysql.plugins.module_utils.mysql import get_server_version diff --git a/plugins/module_utils/implementations/mariadb/user.py b/plugins/module_utils/implementations/mariadb/user.py index fa2cac6..7579157 100644 --- a/plugins/module_utils/implementations/mariadb/user.py +++ b/plugins/module_utils/implementations/mariadb/user.py @@ -1,7 +1,7 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -from distutils.version import LooseVersion +from ansible_collections.community.mysql.plugins.module_utils.version import LooseVersion from ansible_collections.community.mysql.plugins.module_utils.mysql import get_server_version diff --git a/plugins/module_utils/implementations/mysql/replication.py b/plugins/module_utils/implementations/mysql/replication.py index 2fd3d6c..c5324da 100644 --- a/plugins/module_utils/implementations/mysql/replication.py +++ b/plugins/module_utils/implementations/mysql/replication.py @@ -2,7 +2,7 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type from ansible_collections.community.mysql.plugins.module_utils.mysql import get_server_version -from distutils.version import LooseVersion +from ansible_collections.community.mysql.plugins.module_utils.version import LooseVersion def uses_replica_terminology(cursor): diff --git a/plugins/module_utils/implementations/mysql/role.py b/plugins/module_utils/implementations/mysql/role.py index 42276ff..f9686c5 100644 --- a/plugins/module_utils/implementations/mysql/role.py +++ b/plugins/module_utils/implementations/mysql/role.py @@ -1,7 +1,7 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -from distutils.version import LooseVersion +from ansible_collections.community.mysql.plugins.module_utils.version import LooseVersion from ansible_collections.community.mysql.plugins.module_utils.mysql import get_server_version diff --git a/plugins/module_utils/implementations/mysql/user.py b/plugins/module_utils/implementations/mysql/user.py index ce7f5b8..43e400b 100644 --- a/plugins/module_utils/implementations/mysql/user.py +++ b/plugins/module_utils/implementations/mysql/user.py @@ -1,7 +1,7 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -from distutils.version import LooseVersion +from ansible_collections.community.mysql.plugins.module_utils.version import LooseVersion from ansible_collections.community.mysql.plugins.module_utils.mysql import get_server_version diff --git a/plugins/module_utils/version.py b/plugins/module_utils/version.py new file mode 100644 index 0000000..95f9b16 --- /dev/null +++ b/plugins/module_utils/version.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- + +# Copyright: (c) 2021, Felix Fontein +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +"""Provide version object to compare version numbers.""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +from ansible.module_utils.six import raise_from + +try: + from ansible.module_utils.compat.version import LooseVersion +except ImportError: + try: + from distutils.version import LooseVersion + except ImportError as exc: + msg = ('To use this plugin or module with ansible-core < 2.11, ' + 'you need to use Python < 3.12 with distutils.version present') + raise_from(ImportError(msg), exc) diff --git a/plugins/modules/mysql_replication.py b/plugins/modules/mysql_replication.py index 820ceee..3316694 100644 --- a/plugins/modules/mysql_replication.py +++ b/plugins/modules/mysql_replication.py @@ -283,7 +283,7 @@ from ansible_collections.community.mysql.plugins.module_utils.mysql import ( mysql_common_argument_spec, ) from ansible.module_utils._text import to_native -from distutils.version import LooseVersion +from ansible_collections.community.mysql.plugins.module_utils.version import LooseVersion executed_queries = [] From 9c575b4762d7906c0d6d1611c3d0d3681807cdd4 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Fri, 7 Jan 2022 16:59:19 +0300 Subject: [PATCH 051/217] Use vendored version of distutils.version (#269) * Use vendored version of distutils.version * Correct fragment * Update plugins/module_utils/version.py Co-authored-by: Felix Fontein Co-authored-by: Felix Fontein --- .../267-prepare_for_distutils_be_removed.yml | 2 +- plugins/module_utils/_version.py | 343 ++++++++++++++++++ plugins/module_utils/version.py | 15 +- 3 files changed, 349 insertions(+), 11 deletions(-) create mode 100644 plugins/module_utils/_version.py diff --git a/changelogs/fragments/267-prepare_for_distutils_be_removed.yml b/changelogs/fragments/267-prepare_for_distutils_be_removed.yml index 6c73582..190b178 100644 --- a/changelogs/fragments/267-prepare_for_distutils_be_removed.yml +++ b/changelogs/fragments/267-prepare_for_distutils_be_removed.yml @@ -1,2 +1,2 @@ bugfixes: - - "Collection core functions - use vendored version of ``distutils.version`` included in ansible-core 2.12 if available. This avoids breakage when ``distutils`` is removed from the standard library of Python 3.12. Note that ansible-core 2.11, ansible-base 2.10 and Ansible 2.9 are right now not compatible with Python 3.12, hence this fix does not target these ansible-core/-base/2.9 versions (https://github.com/ansible-collections/community.mysql/pull/267)." +- Collection core functions - use vendored version of ``distutils.version`` instead of the deprecated Python standard library ``distutils`` (https://github.com/ansible-collections/community.mysql/pull/269). diff --git a/plugins/module_utils/_version.py b/plugins/module_utils/_version.py new file mode 100644 index 0000000..59ee9db --- /dev/null +++ b/plugins/module_utils/_version.py @@ -0,0 +1,343 @@ +# Vendored copy of distutils/version.py from CPython 3.9.5 +# +# Implements multiple version numbering conventions for the +# Python Module Distribution Utilities. +# +# PSF License (see licenses/PSF-license.txt or https://opensource.org/licenses/Python-2.0) +# + +"""Provides classes to represent module version numbers (one class for +each style of version numbering). There are currently two such classes +implemented: StrictVersion and LooseVersion. + +Every version number class implements the following interface: + * the 'parse' method takes a string and parses it to some internal + representation; if the string is an invalid version number, + 'parse' raises a ValueError exception + * the class constructor takes an optional string argument which, + if supplied, is passed to 'parse' + * __str__ reconstructs the string that was passed to 'parse' (or + an equivalent string -- ie. one that will generate an equivalent + version number instance) + * __repr__ generates Python code to recreate the version number instance + * _cmp compares the current instance with either another instance + of the same class or a string (which will be parsed to an instance + of the same class, thus must follow the same rules) +""" + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import re + +try: + RE_FLAGS = re.VERBOSE | re.ASCII +except AttributeError: + RE_FLAGS = re.VERBOSE + + +class Version: + """Abstract base class for version numbering classes. Just provides + constructor (__init__) and reproducer (__repr__), because those + seem to be the same for all version numbering classes; and route + rich comparisons to _cmp. + """ + + def __init__(self, vstring=None): + if vstring: + self.parse(vstring) + + def __repr__(self): + return "%s ('%s')" % (self.__class__.__name__, str(self)) + + def __eq__(self, other): + c = self._cmp(other) + if c is NotImplemented: + return c + return c == 0 + + def __lt__(self, other): + c = self._cmp(other) + if c is NotImplemented: + return c + return c < 0 + + def __le__(self, other): + c = self._cmp(other) + if c is NotImplemented: + return c + return c <= 0 + + def __gt__(self, other): + c = self._cmp(other) + if c is NotImplemented: + return c + return c > 0 + + def __ge__(self, other): + c = self._cmp(other) + if c is NotImplemented: + return c + return c >= 0 + + +# Interface for version-number classes -- must be implemented +# by the following classes (the concrete ones -- Version should +# be treated as an abstract class). +# __init__ (string) - create and take same action as 'parse' +# (string parameter is optional) +# parse (string) - convert a string representation to whatever +# internal representation is appropriate for +# this style of version numbering +# __str__ (self) - convert back to a string; should be very similar +# (if not identical to) the string supplied to parse +# __repr__ (self) - generate Python code to recreate +# the instance +# _cmp (self, other) - compare two version numbers ('other' may +# be an unparsed version string, or another +# instance of your version class) + + +class StrictVersion(Version): + """Version numbering for anal retentives and software idealists. + Implements the standard interface for version number classes as + described above. A version number consists of two or three + dot-separated numeric components, with an optional "pre-release" tag + on the end. The pre-release tag consists of the letter 'a' or 'b' + followed by a number. If the numeric components of two version + numbers are equal, then one with a pre-release tag will always + be deemed earlier (lesser) than one without. + + The following are valid version numbers (shown in the order that + would be obtained by sorting according to the supplied cmp function): + + 0.4 0.4.0 (these two are equivalent) + 0.4.1 + 0.5a1 + 0.5b3 + 0.5 + 0.9.6 + 1.0 + 1.0.4a3 + 1.0.4b1 + 1.0.4 + + The following are examples of invalid version numbers: + + 1 + 2.7.2.2 + 1.3.a4 + 1.3pl1 + 1.3c4 + + The rationale for this version numbering system will be explained + in the distutils documentation. + """ + + version_re = re.compile(r'^(\d+) \. (\d+) (\. (\d+))? ([ab](\d+))?$', + RE_FLAGS) + + def parse(self, vstring): + match = self.version_re.match(vstring) + if not match: + raise ValueError("invalid version number '%s'" % vstring) + + (major, minor, patch, prerelease, prerelease_num) = \ + match.group(1, 2, 4, 5, 6) + + if patch: + self.version = tuple(map(int, [major, minor, patch])) + else: + self.version = tuple(map(int, [major, minor])) + (0,) + + if prerelease: + self.prerelease = (prerelease[0], int(prerelease_num)) + else: + self.prerelease = None + + def __str__(self): + if self.version[2] == 0: + vstring = '.'.join(map(str, self.version[0:2])) + else: + vstring = '.'.join(map(str, self.version)) + + if self.prerelease: + vstring = vstring + self.prerelease[0] + str(self.prerelease[1]) + + return vstring + + def _cmp(self, other): + if isinstance(other, str): + other = StrictVersion(other) + elif not isinstance(other, StrictVersion): + return NotImplemented + + if self.version != other.version: + # numeric versions don't match + # prerelease stuff doesn't matter + if self.version < other.version: + return -1 + else: + return 1 + + # have to compare prerelease + # case 1: neither has prerelease; they're equal + # case 2: self has prerelease, other doesn't; other is greater + # case 3: self doesn't have prerelease, other does: self is greater + # case 4: both have prerelease: must compare them! + + if (not self.prerelease and not other.prerelease): + return 0 + elif (self.prerelease and not other.prerelease): + return -1 + elif (not self.prerelease and other.prerelease): + return 1 + elif (self.prerelease and other.prerelease): + if self.prerelease == other.prerelease: + return 0 + elif self.prerelease < other.prerelease: + return -1 + else: + return 1 + else: + raise AssertionError("never get here") + +# end class StrictVersion + +# The rules according to Greg Stein: +# 1) a version number has 1 or more numbers separated by a period or by +# sequences of letters. If only periods, then these are compared +# left-to-right to determine an ordering. +# 2) sequences of letters are part of the tuple for comparison and are +# compared lexicographically +# 3) recognize the numeric components may have leading zeroes +# +# The LooseVersion class below implements these rules: a version number +# string is split up into a tuple of integer and string components, and +# comparison is a simple tuple comparison. This means that version +# numbers behave in a predictable and obvious way, but a way that might +# not necessarily be how people *want* version numbers to behave. There +# wouldn't be a problem if people could stick to purely numeric version +# numbers: just split on period and compare the numbers as tuples. +# However, people insist on putting letters into their version numbers; +# the most common purpose seems to be: +# - indicating a "pre-release" version +# ('alpha', 'beta', 'a', 'b', 'pre', 'p') +# - indicating a post-release patch ('p', 'pl', 'patch') +# but of course this can't cover all version number schemes, and there's +# no way to know what a programmer means without asking him. +# +# The problem is what to do with letters (and other non-numeric +# characters) in a version number. The current implementation does the +# obvious and predictable thing: keep them as strings and compare +# lexically within a tuple comparison. This has the desired effect if +# an appended letter sequence implies something "post-release": +# eg. "0.99" < "0.99pl14" < "1.0", and "5.001" < "5.001m" < "5.002". +# +# However, if letters in a version number imply a pre-release version, +# the "obvious" thing isn't correct. Eg. you would expect that +# "1.5.1" < "1.5.2a2" < "1.5.2", but under the tuple/lexical comparison +# implemented here, this just isn't so. +# +# Two possible solutions come to mind. The first is to tie the +# comparison algorithm to a particular set of semantic rules, as has +# been done in the StrictVersion class above. This works great as long +# as everyone can go along with bondage and discipline. Hopefully a +# (large) subset of Python module programmers will agree that the +# particular flavour of bondage and discipline provided by StrictVersion +# provides enough benefit to be worth using, and will submit their +# version numbering scheme to its domination. The free-thinking +# anarchists in the lot will never give in, though, and something needs +# to be done to accommodate them. +# +# Perhaps a "moderately strict" version class could be implemented that +# lets almost anything slide (syntactically), and makes some heuristic +# assumptions about non-digits in version number strings. This could +# sink into special-case-hell, though; if I was as talented and +# idiosyncratic as Larry Wall, I'd go ahead and implement a class that +# somehow knows that "1.2.1" < "1.2.2a2" < "1.2.2" < "1.2.2pl3", and is +# just as happy dealing with things like "2g6" and "1.13++". I don't +# think I'm smart enough to do it right though. +# +# In any case, I've coded the test suite for this module (see +# ../test/test_version.py) specifically to fail on things like comparing +# "1.2a2" and "1.2". That's not because the *code* is doing anything +# wrong, it's because the simple, obvious design doesn't match my +# complicated, hairy expectations for real-world version numbers. It +# would be a snap to fix the test suite to say, "Yep, LooseVersion does +# the Right Thing" (ie. the code matches the conception). But I'd rather +# have a conception that matches common notions about version numbers. + + +class LooseVersion(Version): + """Version numbering for anarchists and software realists. + Implements the standard interface for version number classes as + described above. A version number consists of a series of numbers, + separated by either periods or strings of letters. When comparing + version numbers, the numeric components will be compared + numerically, and the alphabetic components lexically. The following + are all valid version numbers, in no particular order: + + 1.5.1 + 1.5.2b2 + 161 + 3.10a + 8.02 + 3.4j + 1996.07.12 + 3.2.pl0 + 3.1.1.6 + 2g6 + 11g + 0.960923 + 2.2beta29 + 1.13++ + 5.5.kw + 2.0b1pl0 + + In fact, there is no such thing as an invalid version number under + this scheme; the rules for comparison are simple and predictable, + but may not always give the results you want (for some definition + of "want"). + """ + + component_re = re.compile(r'(\d+ | [a-z]+ | \.)', re.VERBOSE) + + def __init__(self, vstring=None): + if vstring: + self.parse(vstring) + + def parse(self, vstring): + # I've given up on thinking I can reconstruct the version string + # from the parsed tuple -- so I just store the string here for + # use by __str__ + self.vstring = vstring + components = [x for x in self.component_re.split(vstring) if x and x != '.'] + for i, obj in enumerate(components): + try: + components[i] = int(obj) + except ValueError: + pass + + self.version = components + + def __str__(self): + return self.vstring + + def __repr__(self): + return "LooseVersion ('%s')" % str(self) + + def _cmp(self, other): + if isinstance(other, str): + other = LooseVersion(other) + elif not isinstance(other, LooseVersion): + return NotImplemented + + if self.version == other.version: + return 0 + if self.version < other.version: + return -1 + if self.version > other.version: + return 1 + +# end class LooseVersion diff --git a/plugins/module_utils/version.py b/plugins/module_utils/version.py index 95f9b16..359cceb 100644 --- a/plugins/module_utils/version.py +++ b/plugins/module_utils/version.py @@ -8,14 +8,9 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type -from ansible.module_utils.six import raise_from +# Once we drop support for Ansible 2.9, ansible-base 2.10, and ansible-core 2.11, we can +# remove the _version.py file, and replace the following import by +# +# from ansible.module_utils.compat.version import LooseVersion -try: - from ansible.module_utils.compat.version import LooseVersion -except ImportError: - try: - from distutils.version import LooseVersion - except ImportError as exc: - msg = ('To use this plugin or module with ansible-core < 2.11, ' - 'you need to use Python < 3.12 with distutils.version present') - raise_from(ImportError(msg), exc) +from ._version import LooseVersion From f5e8fbb3f549c47de68a881867bace8d554949b3 Mon Sep 17 00:00:00 2001 From: Daniel Rupp Date: Mon, 10 Jan 2022 16:03:25 +0100 Subject: [PATCH 052/217] Add default database "mysql" to mysql_user (#266) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add default database "mysql" to mysql_user Since permissions are stored in the "mysql" database anyway this should not change the behaviour of the module. But replication / binlog filters which rely on the current database will be able to filter the statements correctly afterwards. Prior to this change they were not executed in any database context and could not be filtered in any way by the existing methods in MySQL. * Added changelog fragment * Update changelogs/fragments/266-default-database-for-mysql-user Thanks! Co-authored-by: Andrew Klychkov * Update mysql_user.py Make the change a configureable boolean * Update 266-default-database-for-mysql-user update changelog fragment * Update 266-default-database-for-mysql-user it´s not a bugfix anymore * Update plugins/modules/mysql_user.py Co-authored-by: Andrew Klychkov * Update plugins/modules/mysql_user.py Co-authored-by: Andrew Klychkov * Update plugins/modules/mysql_user.py Co-authored-by: Andrew Klychkov * Update plugins/modules/mysql_user.py Co-authored-by: Andrew Klychkov * renamed new option to force_context enhanced description added tests * fixed changelog * Update plugins/modules/mysql_user.py Co-authored-by: Andrew Klychkov * Update plugins/modules/mysql_user.py Co-authored-by: Andrew Klychkov * added more tests * removed first test attempts again (from issue-28.yml) created new tests for testing with and without replication * added force_context: no testing * forgot to add the new part to main.yml * found a copy&paste issue * fix include naming * Made sure the tests work in local testing * MariaDB handles online replication filters differently * fix changelog * Update changelogs/fragments/266-default-database-for-mysql-user.yml Co-authored-by: Andrew Klychkov * Update changelogs/fragments/266-default-database-for-mysql-user.yml Co-authored-by: Andrew Klychkov Co-authored-by: Andrew Klychkov --- .../266-default-database-for-mysql-user.yml | 2 + plugins/modules/mysql_user.py | 16 ++ .../tasks/issue-265.yml | 165 +++++++++++++++++ .../test_mysql_replication/tasks/main.yml | 3 + .../test_mysql_user/tasks/issue-265.yml | 168 ++++++++++++++++++ .../targets/test_mysql_user/tasks/main.yml | 4 + 6 files changed, 358 insertions(+) create mode 100644 changelogs/fragments/266-default-database-for-mysql-user.yml create mode 100644 tests/integration/targets/test_mysql_replication/tasks/issue-265.yml create mode 100644 tests/integration/targets/test_mysql_user/tasks/issue-265.yml diff --git a/changelogs/fragments/266-default-database-for-mysql-user.yml b/changelogs/fragments/266-default-database-for-mysql-user.yml new file mode 100644 index 0000000..b552a6e --- /dev/null +++ b/changelogs/fragments/266-default-database-for-mysql-user.yml @@ -0,0 +1,2 @@ +minor_changes: +- mysql_user - added the ``force_context`` boolean option to set the default database context for the queries to be the ``mysql`` database. This way replication/binlog filters can catch the statements (https://github.com/ansible-collections/community.mysql/issues/265). diff --git a/plugins/modules/mysql_user.py b/plugins/modules/mysql_user.py index 10e37fc..e1d0a92 100644 --- a/plugins/modules/mysql_user.py +++ b/plugins/modules/mysql_user.py @@ -74,6 +74,19 @@ options: - Whether binary logging should be enabled or disabled for the connection. type: bool default: yes + force_context: + description: + - Sets the С(mysql) system database as context for the executed statements (it will be used + as a database to connect to). Useful if you use binlog / replication filters in MySQL as + per default the statements can not be caught by a binlog / replication filter, they require + a database to be set to work, otherwise the replication can break down. + - See U(https://dev.mysql.com/doc/refman/8.0/en/replication-options-binary-log.html#option_mysqld_binlog-ignore-db) + for a description on how binlog filters work (filtering on the primary). + - See U(https://dev.mysql.com/doc/refman/8.0/en/replication-options-replica.html#option_mysqld_replicate-ignore-db) + for a description on how replication filters work (filtering on the replica). + type: bool + default: no + version_added: '3.1.0' state: description: - Whether the user should exist. @@ -341,6 +354,7 @@ def main(): plugin_hash_string=dict(default=None, type='str'), plugin_auth_string=dict(default=None, type='str'), resource_limits=dict(type='dict'), + force_context=dict(type='bool', default=False), ) module = AnsibleModule( argument_spec=argument_spec, @@ -366,6 +380,8 @@ def main(): ssl_ca = module.params["ca_cert"] check_hostname = module.params["check_hostname"] db = '' + if module.params["force_context"]: + db = 'mysql' sql_log_bin = module.params["sql_log_bin"] plugin = module.params["plugin"] plugin_hash_string = module.params["plugin_hash_string"] diff --git a/tests/integration/targets/test_mysql_replication/tasks/issue-265.yml b/tests/integration/targets/test_mysql_replication/tasks/issue-265.yml new file mode 100644 index 0000000..24232f3 --- /dev/null +++ b/tests/integration/targets/test_mysql_replication/tasks/issue-265.yml @@ -0,0 +1,165 @@ +--- +- name: alias mysql command to include default options + set_fact: + mysql_command: "mysql -u{{ mysql_user }} -p{{ mysql_password }} --protocol=tcp" + +- 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: + + # start replica so it is available for testing + + - name: Start replica + mysql_replication: + <<: *mysql_params + login_port: '{{ mysql_replica1_port }}' + mode: startreplica + register: result + + - assert: + that: + - result is changed + - result.queries == ["START SLAVE"] or result.queries == ["START REPLICA"] + + - name: Drop {{ user_name_1 }} if exists + mysql_user: + <<: *mysql_params + name: '{{ user_name_1 }}' + state: absent + ignore_errors: yes + + # First test + # check if user creation works with force_context and is replicated + - name: create user with force_context + mysql_user: + <<: *mysql_params + name: "{{ user_name_1 }}" + password: "{{ user_password_1 }}" + priv: '*.*:ALL,GRANT' + force_context: yes + + - name: attempt connection on replica1 with newly created user (expect success) + mysql_replication: + mode: getprimary + login_user: '{{ user_name_1 }}' + login_password: '{{ user_password_1 }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_replica1_port }}' + register: result + ignore_errors: yes + + - assert: + that: + - result is succeeded + + - name: Drop user + mysql_user: + <<: *mysql_params + name: '{{ user_name_1 }}' + state: absent + force_context: yes + + - name: attempt connection on replica with freshly removed user (expect failure) + mysql_replication: + mode: getprimary + login_user: '{{ user_name_1 }}' + login_password: '{{ user_password_1 }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_replica1_port }}' + register: result + ignore_errors: yes + + - assert: + that: + - result is failed + + # Prepare replica1 for testing with a replication filter in place + # Stop replication, create a filter and restart replication on replica1. + - name: Stop replica + mysql_replication: + <<: *mysql_params + login_port: '{{ mysql_replica1_port }}' + mode: stopreplica + register: result + + - assert: + that: + - result is changed + - result.queries == ["STOP SLAVE"] or result.queries == ["STOP REPLICA"] + + - name: Create replication filter MySQL + shell: "echo \"CHANGE REPLICATION FILTER REPLICATE_IGNORE_DB = (mysql);\" | {{ mysql_command }} -P{{ mysql_replica1_port }}" + when: install_type == 'mysql' + + - name: Create replication filter MariaDB + shell: "echo \"SET GLOBAL replicate_ignore_db = 'mysql';\" | {{ mysql_command }} -P{{ mysql_replica1_port }}" + when: install_type == 'mariadb' + + - name: Start replica + mysql_replication: + <<: *mysql_params + login_port: '{{ mysql_replica1_port }}' + mode: startreplica + register: result + + - assert: + that: + - result is changed + - result.queries == ["START SLAVE"] or result.queries == ["START REPLICA"] + + # Second test + # Filter in place, ready to test if user creation is filtered with force_context + - name: create user with force_context + mysql_user: + <<: *mysql_params + name: "{{ user_name_1 }}" + password: "{{ user_password_1 }}" + priv: '*.*:ALL,GRANT' + force_context: yes + + - name: attempt connection on replica with newly created user (expect failure) + mysql_replication: + mode: getprimary + login_user: '{{ user_name_1 }}' + login_password: '{{ user_password_1 }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_replica1_port }}' + register: result + ignore_errors: yes + + - assert: + that: + - result is failed + + - name: Drop user + mysql_user: + <<: *mysql_params + name: '{{ user_name_1 }}' + state: absent + force_context: yes + + # restore normal replica1 operation + # Stop replication and remove the filter + - name: Stop replica + mysql_replication: + <<: *mysql_params + login_port: '{{ mysql_replica1_port }}' + mode: stopreplica + register: result + + - assert: + that: + - result is changed + - result.queries == ["STOP SLAVE"] or result.queries == ["STOP REPLICA"] + + - name: Remove replication filter MySQL + shell: "echo \"CHANGE REPLICATION FILTER REPLICATE_IGNORE_DB = ();\" | {{ mysql_command }} -P{{ mysql_replica1_port }}" + when: install_type == 'mysql' + + - name: Remove replication filter MariaDB + shell: "echo \"SET GLOBAL replicate_ignore_db = '';\" | {{ mysql_command }} -P{{ mysql_replica1_port }}" + when: install_type == 'mariadb' diff --git a/tests/integration/targets/test_mysql_replication/tasks/main.yml b/tests/integration/targets/test_mysql_replication/tasks/main.yml index 32b59be..044787a 100644 --- a/tests/integration/targets/test_mysql_replication/tasks/main.yml +++ b/tests/integration/targets/test_mysql_replication/tasks/main.yml @@ -9,6 +9,9 @@ # Initial CI tests of mysql_replication module: - import_tasks: mysql_replication_initial.yml +# Tests of replication filters and force_context +- include: issue-265.yml + # Tests of primary_delay parameter: - import_tasks: mysql_replication_primary_delay.yml diff --git a/tests/integration/targets/test_mysql_user/tasks/issue-265.yml b/tests/integration/targets/test_mysql_user/tasks/issue-265.yml new file mode 100644 index 0000000..6c91803 --- /dev/null +++ b/tests/integration/targets/test_mysql_user/tasks/issue-265.yml @@ -0,0 +1,168 @@ +--- +- 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: Drop mysql user if exists + mysql_user: + <<: *mysql_params + name: '{{ user_name_1 }}' + state: absent + ignore_errors: yes + + # Tests with force_context: yes + # Test user creation + - name: create mysql user {{user_name_1}} + mysql_user: + <<: *mysql_params + name: '{{ user_name_1 }}' + password: '{{ user_password_1 }}' + state: present + force_context: yes + register: result + + - name: assert output message mysql user was created + assert: + that: + - "result.changed == true" + + - include: assert_user.yml user_name={{user_name_1}} + + # Test user removal + - name: remove mysql user {{user_name_1}} + mysql_user: + <<: *mysql_params + name: '{{user_name_1}}' + password: '{{user_password_1}}' + state: absent + force_context: yes + register: result + + - name: assert output message mysql user was removed + assert: + that: + - "result.changed == true" + + # Test blank user removal + - name: create blank mysql user to be removed later + mysql_user: + <<: *mysql_params + name: "" + state: present + force_context: yes + password: 'KJFDY&D*Sfuydsgf' + + - name: remove blank mysql user with hosts=all (expect changed) + mysql_user: + <<: *mysql_params + user: "" + host_all: true + state: absent + force_context: yes + register: result + + - name: assert changed is true for removing all blank users + assert: + that: + - "result.changed == true" + + - name: remove blank mysql user with hosts=all (expect ok) + mysql_user: + <<: *mysql_params + user: "" + host_all: true + force_context: yes + state: absent + register: result + + - name: assert changed is true for removing all blank users + assert: + that: + - "result.changed == false" + + - include: assert_no_user.yml user_name={{user_name_1}} + + # Tests with force_context: no + # Test user creation + - name: Drop mysql user if exists + mysql_user: + <<: *mysql_params + name: '{{ user_name_1 }}' + state: absent + ignore_errors: yes + + # Tests with force_context: yes + # Test user creation + - name: create mysql user {{user_name_1}} + mysql_user: + <<: *mysql_params + name: '{{ user_name_1 }}' + password: '{{ user_password_1 }}' + state: present + force_context: yes + register: result + + - name: assert output message mysql user was created + assert: + that: + - "result.changed == true" + + - include: assert_user.yml user_name={{user_name_1}} + + # Test user removal + - name: remove mysql user {{user_name_1}} + mysql_user: + <<: *mysql_params + name: '{{user_name_1}}' + password: '{{user_password_1}}' + state: absent + force_context: no + register: result + + - name: assert output message mysql user was removed + assert: + that: + - "result.changed == true" + + # Test blank user removal + - name: create blank mysql user to be removed later + mysql_user: + <<: *mysql_params + name: "" + state: present + force_context: no + password: 'KJFDY&D*Sfuydsgf' + + - name: remove blank mysql user with hosts=all (expect changed) + mysql_user: + <<: *mysql_params + user: "" + host_all: true + state: absent + force_context: no + register: result + + - name: assert changed is true for removing all blank users + assert: + that: + - "result.changed == true" + + - name: remove blank mysql user with hosts=all (expect ok) + mysql_user: + <<: *mysql_params + user: "" + host_all: true + force_context: no + state: absent + register: result + + - name: assert changed is true for removing all blank users + assert: + that: + - "result.changed == false" + + - include: assert_no_user.yml user_name={{user_name_1}} diff --git a/tests/integration/targets/test_mysql_user/tasks/main.yml b/tests/integration/targets/test_mysql_user/tasks/main.yml index 9fd5ccd..e949fe6 100644 --- a/tests/integration/targets/test_mysql_user/tasks/main.yml +++ b/tests/integration/targets/test_mysql_user/tasks/main.yml @@ -284,3 +284,7 @@ - import_tasks: issue-64560.yaml tags: - issue-64560 + + # Test that mysql_user still works with force_context enabled (database set to "mysql") + # (https://github.com/ansible-collections/community.mysql/issues/265) + - include: issue-265.yml From c0619821deec556fdfed29ec89180107032e7d9d Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Tue, 18 Jan 2022 12:59:38 +0300 Subject: [PATCH 053/217] Release 3.1.0 commit (#275) --- changelogs/CHANGELOG.rst | 21 ++++++++++++++++ changelogs/changelog.yaml | 24 +++++++++++++++++++ ...orted-database-and-connector-versions.yaml | 2 -- .../266-default-database-for-mysql-user.yml | 2 -- .../267-prepare_for_distutils_be_removed.yml | 2 -- galaxy.yml | 2 +- 6 files changed, 46 insertions(+), 7 deletions(-) delete mode 100644 changelogs/fragments/141-supported-database-and-connector-versions.yaml delete mode 100644 changelogs/fragments/266-default-database-for-mysql-user.yml delete mode 100644 changelogs/fragments/267-prepare_for_distutils_be_removed.yml diff --git a/changelogs/CHANGELOG.rst b/changelogs/CHANGELOG.rst index ce4c0c7..6a1872c 100644 --- a/changelogs/CHANGELOG.rst +++ b/changelogs/CHANGELOG.rst @@ -6,6 +6,27 @@ Community MySQL Collection Release Notes This changelog describes changes after version 2.0.0. +v3.1.0 +====== + +Release Summary +--------------- + +This is the minor release of the ``community.mysql`` collection. +This changelog contains all changes to the modules in this collection +that have been added after the release of ``community.mysql`` 3.0.0. + +Minor Changes +------------- + +- Added explicit description of the supported versions of databases and connectors. Changes to the collection are **NOT** tested against database versions older than `mysql 5.7.31` and `mariadb 10.2.37` or connector versions older than `pymysql 0.7.10` and `mysqlclient 2.0.1`. (https://github.com/ansible-collections/community.mysql/discussions/141) +- mysql_user - added the ``force_context`` boolean option to set the default database context for the queries to be the ``mysql`` database. This way replication/binlog filters can catch the statements (https://github.com/ansible-collections/community.mysql/issues/265). + +Bugfixes +-------- + +- Collection core functions - use vendored version of ``distutils.version`` instead of the deprecated Python standard library ``distutils`` (https://github.com/ansible-collections/community.mysql/pull/269). + v3.0.0 ====== diff --git a/changelogs/changelog.yaml b/changelogs/changelog.yaml index 67264f4..e131686 100644 --- a/changelogs/changelog.yaml +++ b/changelogs/changelog.yaml @@ -24,3 +24,27 @@ releases: - 3.0.0.yml - 300-mysql_replication_remove_master_slave.yml release_date: '2021-12-01' + 3.1.0: + changes: + bugfixes: + - Collection core functions - use vendored version of ``distutils.version`` + instead of the deprecated Python standard library ``distutils`` (https://github.com/ansible-collections/community.mysql/pull/269). + minor_changes: + - Added explicit description of the supported versions of databases and connectors. + Changes to the collection are **NOT** tested against database versions older + than `mysql 5.7.31` and `mariadb 10.2.37` or connector versions older than + `pymysql 0.7.10` and `mysqlclient 2.0.1`. (https://github.com/ansible-collections/community.mysql/discussions/141) + - mysql_user - added the ``force_context`` boolean option to set the default + database context for the queries to be the ``mysql`` database. This way replication/binlog + filters can catch the statements (https://github.com/ansible-collections/community.mysql/issues/265). + release_summary: 'This is the minor release of the ``community.mysql`` collection. + + This changelog contains all changes to the modules in this collection + + that have been added after the release of ``community.mysql`` 3.0.0.' + fragments: + - 141-supported-database-and-connector-versions.yaml + - 266-default-database-for-mysql-user.yml + - 267-prepare_for_distutils_be_removed.yml + - 3.1.0.yml + release_date: '2022-01-18' diff --git a/changelogs/fragments/141-supported-database-and-connector-versions.yaml b/changelogs/fragments/141-supported-database-and-connector-versions.yaml deleted file mode 100644 index 1527239..0000000 --- a/changelogs/fragments/141-supported-database-and-connector-versions.yaml +++ /dev/null @@ -1,2 +0,0 @@ -minor_changes: - - Added explicit description of the supported versions of databases and connectors. Changes to the collection are **NOT** tested against database versions older than `mysql 5.7.31` and `mariadb 10.2.37` or connector versions older than `pymysql 0.7.10` and `mysqlclient 2.0.1`. (https://github.com/ansible-collections/community.mysql/discussions/141) diff --git a/changelogs/fragments/266-default-database-for-mysql-user.yml b/changelogs/fragments/266-default-database-for-mysql-user.yml deleted file mode 100644 index b552a6e..0000000 --- a/changelogs/fragments/266-default-database-for-mysql-user.yml +++ /dev/null @@ -1,2 +0,0 @@ -minor_changes: -- mysql_user - added the ``force_context`` boolean option to set the default database context for the queries to be the ``mysql`` database. This way replication/binlog filters can catch the statements (https://github.com/ansible-collections/community.mysql/issues/265). diff --git a/changelogs/fragments/267-prepare_for_distutils_be_removed.yml b/changelogs/fragments/267-prepare_for_distutils_be_removed.yml deleted file mode 100644 index 190b178..0000000 --- a/changelogs/fragments/267-prepare_for_distutils_be_removed.yml +++ /dev/null @@ -1,2 +0,0 @@ -bugfixes: -- Collection core functions - use vendored version of ``distutils.version`` instead of the deprecated Python standard library ``distutils`` (https://github.com/ansible-collections/community.mysql/pull/269). diff --git a/galaxy.yml b/galaxy.yml index 8e079f1..d7c45a5 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -1,6 +1,6 @@ namespace: community name: mysql -version: 3.0.0 +version: 3.1.0 readme: README.md authors: - Ansible community From cbdf51234a33f616292b0dd40622897e5bc4443c Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Tue, 18 Jan 2022 14:30:49 +0300 Subject: [PATCH 054/217] Update CONTRIBUTORS file (#278) --- CONTRIBUTORS | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 718caa2..cacb4ff 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -36,6 +36,7 @@ Berbe bjne bmalynovytch bmildren +bmillemathias boreal321 brutus burner1024 @@ -60,6 +61,7 @@ Davidffry denisemauldin diclophis d-lee +d-rupp dmp1ce dnelson dramaley @@ -100,6 +102,7 @@ hyperfocus1338 igormukhingmailcom imjoseangel infigoKriti +int32bit ipergenitsa iredmail ivandigiusto @@ -109,6 +112,7 @@ jamescassell janosmiko jarnold-timeout JaSafieddine +jb-2197 jborean93 jctanner jean-christophe-manciot @@ -136,10 +140,12 @@ kalaisubbiah kenichi-ogawa-1988 kkeane klingac +koleo kotso kuntalFreshBooks kurtdavis larsks +laurent-indermuehle ldesgrange leeadh LeonB @@ -218,6 +224,7 @@ robpblake rokka-n Roxyrob roysmith +rsicart rthouvenin ruudk samccann @@ -242,6 +249,7 @@ stoned strixBE SWADESNA tapologo +tarunm97 tejatsk14 tersmitten the @@ -252,6 +260,7 @@ timorunge Tomasthanes tomdymond Tronde +tuhoanganh tvlooy tyll UncertaintyP From f547b66d353eeefe35650e392a68cab4eba79e20 Mon Sep 17 00:00:00 2001 From: Benoit Garret Date: Fri, 4 Feb 2022 10:43:24 +0100 Subject: [PATCH 055/217] Honor the set_default_role_all parameter (#282) The set_default_role_all parameter is documented, but does nothing. This PR fixes this. --- plugins/modules/mysql_role.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/modules/mysql_role.py b/plugins/modules/mysql_role.py index 80d0144..8525ac6 100644 --- a/plugins/modules/mysql_role.py +++ b/plugins/modules/mysql_role.py @@ -769,7 +769,8 @@ class Role(): self.cursor.execute(*self.q_builder.role_grant(user)) - self.role_impl.set_default_role_all(user) + if set_default_role_all: + self.role_impl.set_default_role_all(user) changed = True From 952e1666d85d94d60847cb223de4f9e413c384eb Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Fri, 4 Feb 2022 13:25:16 +0300 Subject: [PATCH 056/217] Add a changelog fragment to PR 282 (#283) --- .../282-mysql_role_fix_set_default_role_all_argument.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 changelogs/fragments/282-mysql_role_fix_set_default_role_all_argument.yml diff --git a/changelogs/fragments/282-mysql_role_fix_set_default_role_all_argument.yml b/changelogs/fragments/282-mysql_role_fix_set_default_role_all_argument.yml new file mode 100644 index 0000000..06fe4b6 --- /dev/null +++ b/changelogs/fragments/282-mysql_role_fix_set_default_role_all_argument.yml @@ -0,0 +1,2 @@ +bugfixes: +- mysql_role - make the ``set_default_role_all`` parameter actually working (https://github.com/ansible-collections/community.mysql/pull/282). From 8052563a5351934af1d4893108f8f82dbddab46d Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Wed, 16 Feb 2022 13:38:07 +0300 Subject: [PATCH 057/217] Release 3.1.1 commit (#289) --- changelogs/CHANGELOG.rst | 15 +++++++++++++++ changelogs/changelog.yaml | 14 ++++++++++++++ ...sql_role_fix_set_default_role_all_argument.yml | 2 -- galaxy.yml | 2 +- 4 files changed, 30 insertions(+), 3 deletions(-) delete mode 100644 changelogs/fragments/282-mysql_role_fix_set_default_role_all_argument.yml diff --git a/changelogs/CHANGELOG.rst b/changelogs/CHANGELOG.rst index 6a1872c..3b97d9a 100644 --- a/changelogs/CHANGELOG.rst +++ b/changelogs/CHANGELOG.rst @@ -6,6 +6,21 @@ Community MySQL Collection Release Notes This changelog describes changes after version 2.0.0. +v3.1.1 +====== + +Release Summary +--------------- + +This is the patch release of the ``community.mysql`` collection. +This changelog contains all changes to the modules in this collection +that have been added after the release of ``community.mysql`` 3.1.0. + +Bugfixes +-------- + +- mysql_role - make the ``set_default_role_all`` parameter actually working (https://github.com/ansible-collections/community.mysql/pull/282). + v3.1.0 ====== diff --git a/changelogs/changelog.yaml b/changelogs/changelog.yaml index e131686..da04ced 100644 --- a/changelogs/changelog.yaml +++ b/changelogs/changelog.yaml @@ -48,3 +48,17 @@ releases: - 267-prepare_for_distutils_be_removed.yml - 3.1.0.yml release_date: '2022-01-18' + 3.1.1: + changes: + bugfixes: + - mysql_role - make the ``set_default_role_all`` parameter actually working + (https://github.com/ansible-collections/community.mysql/pull/282). + release_summary: 'This is the patch release of the ``community.mysql`` collection. + + This changelog contains all changes to the modules in this collection + + that have been added after the release of ``community.mysql`` 3.1.0.' + fragments: + - 282-mysql_role_fix_set_default_role_all_argument.yml + - 3.1.1.yml + release_date: '2022-02-16' diff --git a/changelogs/fragments/282-mysql_role_fix_set_default_role_all_argument.yml b/changelogs/fragments/282-mysql_role_fix_set_default_role_all_argument.yml deleted file mode 100644 index 06fe4b6..0000000 --- a/changelogs/fragments/282-mysql_role_fix_set_default_role_all_argument.yml +++ /dev/null @@ -1,2 +0,0 @@ -bugfixes: -- mysql_role - make the ``set_default_role_all`` parameter actually working (https://github.com/ansible-collections/community.mysql/pull/282). diff --git a/galaxy.yml b/galaxy.yml index d7c45a5..c96b8c3 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -1,6 +1,6 @@ namespace: community name: mysql -version: 3.1.0 +version: 3.1.1 readme: README.md authors: - Ansible community From c273ee36a7db58a2298cd95a762dbce93a4d03fe Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Tue, 1 Mar 2022 19:57:20 +0300 Subject: [PATCH 058/217] Enable and fix integration tests for devel (#290) * Enable and fix integration tests for devel * Update repo targets * Remove useless directive * default container -> ubuntu1804 * revert last commit * 10.2 does not support 20.04 focal; increase to 10.3 --- .github/workflows/ansible-test-plugins.yml | 4 ++-- tests/integration/targets/setup_mysql/defaults/main.yml | 2 +- tests/integration/targets/setup_mysql/vars/main.yml | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ansible-test-plugins.yml b/.github/workflows/ansible-test-plugins.yml index f39e171..f3e7839 100644 --- a/.github/workflows/ansible-test-plugins.yml +++ b/.github/workflows/ansible-test-plugins.yml @@ -58,14 +58,14 @@ jobs: db_engine_version: - mysql_5.7.31 - mysql_8.0.22 - - mariadb_10.2.37 + - mariadb_10.3.34 - mariadb_10.5.9 ansible: - stable-2.9 - stable-2.10 - stable-2.11 - stable-2.12 - #- devel + - devel python: - 3.6 - 3.8 diff --git a/tests/integration/targets/setup_mysql/defaults/main.yml b/tests/integration/targets/setup_mysql/defaults/main.yml index 68c60bc..cceb8f5 100644 --- a/tests/integration/targets/setup_mysql/defaults/main.yml +++ b/tests/integration/targets/setup_mysql/defaults/main.yml @@ -1,4 +1,4 @@ -dbdeployer_version: 1.56.0 +dbdeployer_version: 1.64.0 dbdeployer_home_dir: /opt/dbdeployer home_dir: /root diff --git a/tests/integration/targets/setup_mysql/vars/main.yml b/tests/integration/targets/setup_mysql/vars/main.yml index db17f1e..94b43b4 100644 --- a/tests/integration/targets/setup_mysql/vars/main.yml +++ b/tests/integration/targets/setup_mysql/vars/main.yml @@ -15,6 +15,7 @@ python_packages: [pymysql == 0.9.3] install_prereqs: - libaio1 - libnuma1 + - libncurses5 install_python_prereqs: - python3-dev From 82cedf85108af7238d4e5bb7a0229424d7490893 Mon Sep 17 00:00:00 2001 From: "R.Sicart" Date: Fri, 4 Mar 2022 10:52:43 +0100 Subject: [PATCH 059/217] Fix role integration tests for mariadb (#291) * Fix: test_mysql_role : Check that the user have no active roles assertion * Fix: test_mysql_role : Check in DB assertion * Fix: test_mysql_role : Check in DB, if not granted, the query will fail for mariadb * Fix: test_mysql_role : Check that the role is active assertion * test_mysql_role : create checks for mariadb * Make 'when' conditions uniform * Fix: test_mysql_role : admin option tests * test_mysql_role : create checks for mariadb * Fix: add fixme on test which create role in check mode 'again' It changes only on mariadb 10.2 (doesn't change on mysql 8 or mariadb 10.5) * Fix: add fixme on test which create role 'again' * Simplify test without checking Host part * Fix 'show grant' checks for mariadb * Enable mariadb tests for test_mysql_role target * Add tests to verify role grants for roles * Add FIXME comments for tests which do not pass on mariadb 10.2 * Fix FIXME tests related to mariadb 10.2 * Exclude mysqlclient==2.0.1 with mariadb from integration tests --- .github/workflows/ansible-test-plugins.yml | 4 + .../tasks/mysql_role_initial.yml | 465 ++++++++++++++++-- 2 files changed, 432 insertions(+), 37 deletions(-) diff --git a/.github/workflows/ansible-test-plugins.yml b/.github/workflows/ansible-test-plugins.yml index f3e7839..3c34ee6 100644 --- a/.github/workflows/ansible-test-plugins.yml +++ b/.github/workflows/ansible-test-plugins.yml @@ -78,6 +78,10 @@ jobs: connector: pymysql==0.7.10 - db_engine_version: mariadb_10.5.9 connector: pymysql==0.7.10 + - db_engine_version: mariadb_10.3.34 + connector: mysqlclient==2.0.1 + - db_engine_version: mariadb_10.5.9 + connector: mysqlclient==2.0.1 - python: 3.8 ansible: stable-2.9 - python: 3.8 diff --git a/tests/integration/targets/test_mysql_role/tasks/mysql_role_initial.yml b/tests/integration/targets/test_mysql_role/tasks/mysql_role_initial.yml index 5f47e7a..1bca3ae 100644 --- a/tests/integration/targets/test_mysql_role/tasks/mysql_role_initial.yml +++ b/tests/integration/targets/test_mysql_role/tasks/mysql_role_initial.yml @@ -32,10 +32,9 @@ when: - srv['version']['major'] < 8 - # FIXME: tests should pass for both mysql >= 8 and mariadb # Skip unsupported versions - meta: end_play - when: srv['version']['major'] < 8 or install_type == 'mariadb' + when: srv['version']['major'] < 8 ######### # Prepare @@ -92,7 +91,7 @@ <<: *task_params mysql_query: <<: *mysql_params - query: "SELECT 1 FROM mysql.user WHERE User = '{{ role0 }}' AND Host = '%'" + query: "SELECT 1 FROM mysql.user WHERE User = '{{ role0 }}'" - name: Check assert: @@ -106,11 +105,26 @@ <<: *mysql_params query: "SHOW GRANTS FOR {{ user0 }}@localhost USING '{{ role0 }}'" ignore_errors: yes + when: install_type == 'mysql' - name: Check assert: that: - result is failed + when: install_type == 'mysql' + + - name: Check in DB (mariadb) + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = '{{ user0 }}' AND Host = 'localhost' AND Role = '{{ role0 }}'" + when: install_type == 'mariadb' + + - name: Check (mariadb) + assert: + that: + - result.query_result.0.0['user_roles'] == 0 + when: install_type == 'mariadb' #===================== @@ -121,7 +135,7 @@ login_password: '{{ mysql_password }}' login_host: 127.0.0.1 login_port: '{{ mysql_primary_port }}' - query: 'SELECT current_role()' + query: 'SELECT COALESCE(current_role(), "NONE") as "current_role()"' - name: Check assert: @@ -146,7 +160,7 @@ <<: *task_params mysql_query: <<: *mysql_params - query: "SELECT 1 FROM mysql.user WHERE User = '{{ role0 }}' AND Host = '%'" + query: "SELECT 1 FROM mysql.user WHERE User = '{{ role0 }}'" - name: Check assert: @@ -158,11 +172,26 @@ mysql_query: <<: *mysql_params query: "SHOW GRANTS FOR {{ user0 }}@localhost USING '{{ role0 }}'" + when: install_type == 'mysql' - name: Check assert: that: - result is succeeded + when: install_type == 'mysql' + + - name: Check in DB (mariadb) + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = '{{ user0 }}' AND Host = 'localhost' AND Role = '{{ role0 }}'" + when: install_type == 'mariadb' + + - name: Check (mariadb) + assert: + that: + - result.query_result.0.0['user_roles'] == 1 + when: install_type == 'mariadb' - name: Check that the role is active <<: *task_params @@ -172,11 +201,31 @@ login_host: 127.0.0.1 login_port: '{{ mysql_primary_port }}' query: 'SELECT current_role()' + when: install_type == 'mysql' - name: Check assert: that: - - result.query_result.0.0["current_role()"] == "`{{ role0 }}`@`%`" + - "'{{ role0 }}' in result.query_result.0.0['current_role()']" + when: install_type == 'mysql' + + - name: Check that the role is active (mariadb) + <<: *task_params + mysql_query: + login_user: '{{ user0 }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + query: + - 'SET ROLE {{ role0 }}' + - 'SELECT current_role()' + when: install_type == 'mariadb' + + - name: Check (mariadb) + assert: + that: + - "'{{ role0 }}' in result.query_result.1.0['current_role()']" + when: install_type == 'mariadb' #======================== @@ -197,7 +246,7 @@ <<: *task_params mysql_query: <<: *mysql_params - query: "SELECT 1 FROM mysql.user WHERE User = '{{ role0 }}' AND Host = '%'" + query: "SELECT 1 FROM mysql.user WHERE User = '{{ role0 }}'" - name: Check assert: @@ -209,11 +258,26 @@ mysql_query: <<: *mysql_params query: "SHOW GRANTS FOR {{ user0 }}@localhost USING '{{ role0 }}'" + when: install_type == 'mysql' - name: Check assert: that: - result is succeeded + when: install_type == 'mysql' + + - name: Check in DB (mariadb) + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = '{{ user0 }}' AND Host = 'localhost' AND Role = '{{ role0 }}'" + when: install_type == 'mariadb' + + - name: Check (mariadb) + assert: + that: + - result.query_result.0.0['user_roles'] == 1 + when: install_type == 'mariadb' #======================== @@ -233,7 +297,7 @@ <<: *task_params mysql_query: <<: *mysql_params - query: "SELECT 1 FROM mysql.user WHERE User = '{{ role0 }}' AND Host = '%'" + query: "SELECT 1 FROM mysql.user WHERE User = '{{ role0 }}'" - name: Check assert: @@ -259,7 +323,7 @@ <<: *task_params mysql_query: <<: *mysql_params - query: "SELECT 1 FROM mysql.user WHERE User = '{{ role0 }}' AND Host = '%'" + query: "SELECT 1 FROM mysql.user WHERE User = '{{ role0 }}'" - name: Check assert: @@ -272,11 +336,27 @@ mysql_query: <<: *mysql_params query: "SHOW GRANTS FOR {{ user0 }}@localhost USING '{{ role0 }}'" + when: install_type == 'mysql' - name: Check assert: that: - result is succeeded + when: install_type == 'mysql' + + # Must pass because of check_mode + - name: Check in DB (mariadb) + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = '{{ user0 }}' AND Host = 'localhost' AND Role = '{{ role0 }}'" + when: install_type == 'mariadb' + + - name: Check (mariadb) + assert: + that: + - result.query_result.0.0['user_roles'] == 1 + when: install_type == 'mariadb' #======================== @@ -296,7 +376,7 @@ <<: *task_params mysql_query: <<: *mysql_params - query: "SELECT 1 FROM mysql.user WHERE User = '{{ role0 }}' AND Host = '%'" + query: "SELECT 1 FROM mysql.user WHERE User = '{{ role0 }}'" - name: Check assert: @@ -309,11 +389,27 @@ <<: *mysql_params query: "SHOW GRANTS FOR {{ user0 }}@localhost USING '{{ role0 }}'" ignore_errors: yes + when: install_type == 'mysql' - name: Check assert: that: - result is failed + when: install_type == 'mysql' + + - name: Check in DB (mariadb) + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = '{{ user0 }}' AND Host = 'localhost' AND Role = '{{ role0 }}'" + ignore_errors: yes + when: install_type == 'mariadb' + + - name: Check (mariadb) + assert: + that: + - result.query_result.0.0['user_roles'] == 0 + when: install_type == 'mariadb' #======================== @@ -366,7 +462,7 @@ <<: *task_params mysql_query: <<: *mysql_params - query: "SELECT 1 FROM mysql.user WHERE User = '{{ role0 }}' AND Host = '%'" + query: "SELECT 1 FROM mysql.user WHERE User = '{{ role0 }}'" - name: Check assert: @@ -396,7 +492,7 @@ <<: *task_params mysql_query: <<: *mysql_params - query: "SELECT 1 FROM mysql.user WHERE User = '{{ role0 }}' AND Host = '%'" + query: "SELECT 1 FROM mysql.user WHERE User = '{{ role0 }}'" - name: Check assert: @@ -477,11 +573,27 @@ mysql_query: <<: *mysql_params query: "SHOW GRANTS FOR {{ user0 }}@localhost USING '{{ role0 }}'" + when: install_type == 'mysql' - name: Check assert: that: - result is succeeded + when: install_type == 'mysql' + + # user0 is still a member because of check_mode + - name: Check in DB (mariadb) + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = '{{ user0 }}' AND Host = 'localhost' AND Role = '{{ role0 }}'" + when: install_type == 'mariadb' + + - name: Check (mariadb) + assert: + that: + - result.query_result.0.0['user_roles'] == 1 + when: install_type == 'mariadb' # user1, user2, and role1 are not members because of check_mode - name: Check in DB, if not granted, the query will fail @@ -490,11 +602,26 @@ <<: *mysql_params query: "SHOW GRANTS FOR {{ user1 }}@localhost USING '{{ role0 }}'" ignore_errors: yes + when: install_type == 'mysql' - name: Check assert: that: - result is failed + when: install_type == 'mysql' + + - name: Check in DB (mariadb) + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = '{{ user1 }}' AND Host = 'localhost' AND Role = '{{ role0 }}'" + when: install_type == 'mariadb' + + - name: Check (mariadb) + assert: + that: + - result.query_result.0.0['user_roles'] == 0 + when: install_type == 'mariadb' - name: Check in DB, if not granted, the query will fail <<: *task_params @@ -502,11 +629,26 @@ <<: *mysql_params query: "SHOW GRANTS FOR {{ user2 }}@localhost USING '{{ role0 }}'" ignore_errors: yes + when: install_type == 'mysql' - name: Check assert: that: - result is failed + when: install_type == 'mysql' + + - name: Check in DB (mariadb) + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = '{{ user2 }}' AND Host = 'localhost' AND Role = '{{ role0 }}'" + when: install_type == 'mariadb' + + - name: Check (mariadb) + assert: + that: + - result.query_result.0.0['user_roles'] == 0 + when: install_type == 'mariadb' - name: Check in DB, if not granted, the query will fail <<: *task_params @@ -514,11 +656,27 @@ <<: *mysql_params query: "SHOW GRANTS FOR {{ role1 }} USING '{{ role0 }}'" ignore_errors: yes + when: install_type == 'mysql' - name: Check assert: that: - result is failed + when: install_type == 'mysql' + + - name: Check in DB (mariadb) + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = '{{ role1 }}' AND Role = '{{ role0 }}'" + when: install_type == 'mariadb' + + - name: Check (mariadb) + assert: + that: + - result.query_result.0.0['user_roles'] == 0 + when: install_type == 'mariadb' + #======================== - name: Rewrite members @@ -544,33 +702,79 @@ <<: *mysql_params query: "SHOW GRANTS FOR {{ user0 }}@localhost USING '{{ role0 }}'" ignore_errors: yes + when: install_type == 'mysql' - name: Check assert: that: - result is failed + when: install_type == 'mysql' + + # user0 is not a member any more + - name: Check in DB (mariadb) + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SHOW GRANTS FOR {{ user0 }}@localhost" + when: install_type == 'mariadb' + + - name: Check (mariadb) + assert: + that: + - "'{{ role0 }}' not in result.query_result.0.0['Grants for user0@localhost']" + when: install_type == 'mariadb' - name: Check in DB, if not granted, the query will fail <<: *task_params mysql_query: <<: *mysql_params query: "SHOW GRANTS FOR {{ user1 }}@localhost USING '{{ role0 }}'" + when: install_type == 'mysql' - name: Check assert: that: - result is succeeded + when: install_type == 'mysql' + + - name: Check in DB (mariadb) + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = '{{ user1 }}' AND Host = 'localhost' AND Role = '{{ role0 }}'" + when: install_type == 'mariadb' + + - name: Check (mariadb) + assert: + that: + - result.query_result.0.0['user_roles'] == 1 + when: install_type == 'mariadb' - name: Check in DB, if not granted, the query will fail <<: *task_params mysql_query: <<: *mysql_params query: "SHOW GRANTS FOR {{ user2 }}@localhost USING '{{ role0 }}'" + when: install_type == 'mysql' - name: Check assert: that: - result is succeeded + when: install_type == 'mysql' + + - name: Check in DB (mariadb) + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = '{{ user2 }}' AND Host = 'localhost' AND Role = '{{ role0 }}'" + when: install_type == 'mariadb' + + - name: Check (mariadb) + assert: + that: + - result.query_result.0.0['user_roles'] == 1 + when: install_type == 'mariadb' - name: Check in DB, if not granted, the query will fail <<: *task_params @@ -578,11 +782,27 @@ <<: *mysql_params query: "SHOW GRANTS FOR {{ role1 }} USING '{{ role0 }}'" ignore_errors: yes + when: install_type == 'mysql' - name: Check assert: that: - result is succeeded + when: install_type == 'mysql' + + - name: Check in DB (mariadb) + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = '{{ role1 }}' AND Role = '{{ role0 }}'" + when: install_type == 'mariadb' + + - name: Check (mariadb) + assert: + that: + - result.query_result.0.0['user_roles'] == 1 + when: install_type == 'mariadb' + #========================== @@ -646,12 +866,26 @@ <<: *mysql_params query: "SHOW GRANTS FOR {{ user0 }}@localhost USING '{{ role0 }}'" ignore_errors: yes + when: install_type == 'mysql' - name: Check assert: that: - result is failed + when: install_type == 'mysql' + - name: Check in DB (mariadb) + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = '{{ user0 }}' AND Host = 'localhost' AND Role = '{{ role0 }}'" + when: install_type == 'mariadb' + + - name: Check (mariadb) + assert: + that: + - result.query_result.0.0['user_roles'] == 0 + when: install_type == 'mariadb' #===================== - name: Append a member @@ -674,11 +908,26 @@ mysql_query: <<: *mysql_params query: "SHOW GRANTS FOR {{ user0 }}@localhost USING '{{ role0 }}'" + when: install_type == 'mysql' - name: Check assert: that: - result is succeeded + when: install_type == 'mysql' + + - name: Check in DB (mariadb) + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = '{{ user0 }}' AND Host = 'localhost' AND Role = '{{ role0 }}'" + when: install_type == 'mariadb' + + - name: Check (mariadb) + assert: + that: + - result.query_result.0.0['user_roles'] == 1 + when: install_type == 'mariadb' # user1 and user2 must still be in DB because we are appending - name: Check in DB, if not granted, the query will fail @@ -686,22 +935,52 @@ mysql_query: <<: *mysql_params query: "SHOW GRANTS FOR {{ user1 }}@localhost USING '{{ role0 }}'" + when: install_type == 'mysql' - name: Check assert: that: - result is succeeded + when: install_type == 'mysql' + + - name: Check in DB (mariadb) + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = '{{ user1 }}' AND Host = 'localhost' AND Role = '{{ role0 }}'" + when: install_type == 'mariadb' + + - name: Check (mariadb) + assert: + that: + - result.query_result.0.0['user_roles'] == 1 + when: install_type == 'mariadb' - name: Check in DB, if not granted, the query will fail <<: *task_params mysql_query: <<: *mysql_params query: "SHOW GRANTS FOR {{ user2 }}@localhost USING '{{ role0 }}'" + when: install_type == 'mysql' - name: Check assert: that: - result is succeeded + when: install_type == 'mysql' + + - name: Check in DB (mariadb) + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = '{{ user2 }}' AND Host = 'localhost' AND Role = '{{ role0 }}'" + when: install_type == 'mariadb' + + - name: Check (mariadb) + assert: + that: + - result.query_result.0.0['user_roles'] == 1 + when: install_type == 'mariadb' #======================== @@ -763,33 +1042,78 @@ mysql_query: <<: *mysql_params query: "SHOW GRANTS FOR {{ user0 }}@localhost USING '{{ role0 }}'" + when: install_type == 'mysql' - name: Check assert: that: - result is succeeded + when: install_type == 'mysql' + + - name: Check in DB (mariadb) + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = '{{ user0 }}' AND Host = 'localhost' AND Role = '{{ role0 }}'" + when: install_type == 'mariadb' + + - name: Check (mariadb) + assert: + that: + - result.query_result.0.0['user_roles'] == 1 + when: install_type == 'mariadb' - name: Check in DB, if not granted, the query will fail <<: *task_params mysql_query: <<: *mysql_params query: "SHOW GRANTS FOR {{ user1 }}@localhost USING '{{ role0 }}'" + when: install_type == 'mysql' - name: Check assert: that: - result is succeeded + when: install_type == 'mysql' + + - name: Check in DB (mariadb) + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = '{{ user1 }}' AND Host = 'localhost' AND Role = '{{ role0 }}'" + when: install_type == 'mariadb' + + - name: Check (mariadb) + assert: + that: + - result.query_result.0.0['user_roles'] == 1 + when: install_type == 'mariadb' - name: Check in DB, if not granted, the query will fail <<: *task_params mysql_query: <<: *mysql_params query: "SHOW GRANTS FOR {{ user2 }}@localhost USING '{{ role0 }}'" + when: install_type == 'mysql' - name: Check assert: that: - result is succeeded + when: install_type == 'mysql' + + - name: Check in DB (mariadb) + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = '{{ user2 }}' AND Host = 'localhost' AND Role = '{{ role0 }}'" + when: install_type == 'mariadb' + + - name: Check (mariadb) + assert: + that: + - result.query_result.0.0['user_roles'] == 1 + when: install_type == 'mariadb' #======================== @@ -814,11 +1138,26 @@ mysql_query: <<: *mysql_params query: "SHOW GRANTS FOR {{ user0 }}@localhost USING '{{ role0 }}'" + when: install_type == 'mysql' - name: Check assert: that: - result is succeeded + when: install_type == 'mysql' + + - name: Check in DB (mariadb) + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = '{{ user0 }}' AND Host = 'localhost' AND Role = '{{ role0 }}'" + when: install_type == 'mariadb' + + - name: Check (mariadb) + assert: + that: + - result.query_result.0.0['user_roles'] == 1 + when: install_type == 'mariadb' - name: Check in DB, if not granted, the query will fail <<: *task_params @@ -826,11 +1165,26 @@ <<: *mysql_params query: "SHOW GRANTS FOR {{ user1 }}@localhost USING '{{ role0 }}'" ignore_errors: yes + when: install_type == 'mysql' - name: Check assert: that: - result is failed + when: install_type == 'mysql' + + - name: Check in DB (mariadb) + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = '{{ user1 }}' AND Host = 'localhost' AND Role = '{{ role0 }}'" + when: install_type == 'mariadb' + + - name: Check (mariadb) + assert: + that: + - result.query_result.0.0['user_roles'] == 0 + when: install_type == 'mariadb' - name: Check in DB, if not granted, the query will fail <<: *task_params @@ -838,11 +1192,26 @@ <<: *mysql_params query: "SHOW GRANTS FOR {{ user2 }}@localhost USING '{{ role0 }}'" ignore_errors: yes + when: install_type == 'mysql' - name: Check assert: that: - result is failed + when: install_type == 'mysql' + + - name: Check in DB (mariadb) + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = '{{ user2 }}' AND Host = 'localhost' AND Role = '{{ role0 }}'" + when: install_type == 'mariadb' + + - name: Check (mariadb) + assert: + that: + - result.query_result.0.0['user_roles'] == 0 + when: install_type == 'mariadb' #===================== @@ -914,6 +1283,15 @@ - result.query_result.0.0["Grants for role0@%"] == "GRANT SELECT, INSERT ON *.* TO `role0`@`%`" - result.query_result.0.1["Grants for role0@%"] == "GRANT UPDATE ON `mysql`.* TO `role0`@`%`" - result.rowcount.0 == 2 + when: install_type == 'mysql' + + - name: Check (mariadb) + assert: + that: + - result.query_result.0.0["Grants for role0"] == "GRANT SELECT, INSERT ON *.* TO `role0`" + - result.query_result.0.1["Grants for role0"] == "GRANT UPDATE ON `mysql`.* TO `role0`" + - result.rowcount.0 == 2 + when: install_type == 'mariadb' - name: Append privs in check_mode <<: *task_params @@ -942,6 +1320,15 @@ - result.query_result.0.0["Grants for role0@%"] == "GRANT SELECT, INSERT ON *.* TO `role0`@`%`" - result.query_result.0.1["Grants for role0@%"] == "GRANT UPDATE ON `mysql`.* TO `role0`@`%`" - result.rowcount.0 == 2 + when: install_type == 'mysql' + + - name: Check (mariadb) + assert: + that: + - result.query_result.0.0["Grants for role0"] == "GRANT SELECT, INSERT ON *.* TO `role0`" + - result.query_result.0.1["Grants for role0"] == "GRANT UPDATE ON `mysql`.* TO `role0`" + - result.rowcount.0 == 2 + when: install_type == 'mariadb' - name: Append privs <<: *task_params @@ -971,6 +1358,17 @@ - result.query_result.0.2["Grants for role0@%"] == "GRANT SELECT, INSERT ON `test_db1`.`test_table` TO `role0`@`%`" - result.query_result.0.3["Grants for role0@%"] == "GRANT DELETE ON `test_db2`.`test_table` TO `role0`@`%`" - result.rowcount.0 == 4 + when: install_type == 'mysql' + + - name: Check (mariadb) + assert: + that: + - result.query_result.0.0["Grants for role0"] == "GRANT SELECT, INSERT ON *.* TO `role0`" + - result.query_result.0.1["Grants for role0"] == "GRANT UPDATE ON `mysql`.* TO `role0`" + - result.query_result.0.2["Grants for role0"] == "GRANT SELECT, INSERT ON `test_db1`.`test_table` TO `role0`" + - result.query_result.0.3["Grants for role0"] == "GRANT DELETE ON `test_db2`.`test_table` TO `role0`" + - result.rowcount.0 == 4 + when: install_type == 'mariadb' - name: Append privs again in check_mode <<: *task_params @@ -1038,6 +1436,14 @@ that: - result.query_result.0.0["Grants for role0@%"] == "GRANT SELECT ON *.* TO `role0`@`%`" - result.rowcount.0 == 1 + when: install_type == 'mysql' + + - name: Check (mariadb) + assert: + that: + - result.query_result.0.0["Grants for role0"] == "GRANT SELECT ON *.* TO `role0`" + - result.rowcount.0 == 1 + when: install_type == 'mariadb' # ################# # Test admin option @@ -1056,7 +1462,7 @@ <<: *mysql_params name: '{{ role0 }}' state: present - admin: '{{ user0 }}' + admin: '{{ user0 }}@localhost' ignore_errors: yes - name: Check with MySQL @@ -1064,34 +1470,26 @@ that: - result is failed - result.msg is search('option can be used only with MariaDB') - when: - # Semantically, when there's MySQL - - srv['version']['major'] < 10 + when: install_type == 'mysql' - name: Check with MariaDB assert: that: - result is changed - when: - # Semantically, when there's MariaDB - - srv['version']['major'] >= 10 + when: install_type == 'mariadb' - name: Check in DB <<: *task_params mysql_query: <<: *mysql_params - query: "SELECT 1 FROM mysql.user WHERE User = '{{ role0 }}' AND Host = '%'" - when: - # Semantically, when there's MariaDB - - srv['version']['major'] >= 10 + query: "SELECT 1 FROM mysql.user WHERE User = '{{ role0 }}' AND Host = ''" + when: install_type == 'mariadb' - name: Check assert: that: - result.rowcount.0 == 1 - when: - # Semantically, when there's MariaDB - - srv['version']['major'] >= 10 + when: install_type == 'mariadb' - name: Create role with admin again <<: *task_params @@ -1099,7 +1497,7 @@ <<: *mysql_params name: '{{ role0 }}' state: present - admin: '{{ user0 }}' + admin: '{{ user0 }}@localhost' ignore_errors: yes - name: Check with MySQL @@ -1107,17 +1505,13 @@ that: - result is failed - result.msg is search('option can be used only with MariaDB') - when: - # Semantically, when there's MySQL - - srv['version']['major'] < 10 + when: install_type == 'mysql' - name: Check with MariaDB assert: that: - result is not changed - # Semantically, when there's MariaDB - when: - - srv['version']['major'] >= 10 + when: install_type == 'mariadb' # Try to grant a role to a user who does not exist - name: Create role with admin again @@ -1130,14 +1524,11 @@ - '{{ nonexistent }}@localhost' ignore_errors: yes - - name: Check with MySQL + - name: Check assert: that: - result is failed - result.msg is search('does not exist') - when: - # Semantically, when there's MySQL - - srv['version']['major'] < 10 always: # Clean up From 71a628f82da4bbaf413a50f6becdaf24b8c80665 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Fri, 11 Mar 2022 19:58:12 +0300 Subject: [PATCH 060/217] Update requirements (#296) --- README.md | 2 +- plugins/doc_fragments/mysql.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cafd510..9eec168 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ Every voice is important and every idea is valuable. If you have something on yo - mysql 5.7.31 - mysql 8.0.22 -- mariadb 10.2.37 +- mariadb 10.3.34 - mariadb 10.5.9 ### Database connectors diff --git a/plugins/doc_fragments/mysql.py b/plugins/doc_fragments/mysql.py index b7a9152..9cc5bce 100644 --- a/plugins/doc_fragments/mysql.py +++ b/plugins/doc_fragments/mysql.py @@ -71,7 +71,8 @@ options: type: bool version_added: '1.1.0' requirements: - - PyMySQL (Python 2.7 and Python 3.X), or + - mysqlclient (Python 3.5+) or + - PyMySQL (Python 2.7 and Python 3.x) or - MySQLdb (Python 2.x) notes: - Requires the PyMySQL (Python 2.7 and Python 3.X) or MySQL-python (Python 2.X) package installed on the remote host. From f00f06df764172a7f841ec3bb9a94b385ddd4e47 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Mon, 14 Mar 2022 12:03:47 +0300 Subject: [PATCH 061/217] Fix the collection to work with mysqlclient connector (#293) * Integration tests: restrict exclusion mysqlclient + MariaDB only when testing the mysql_role module * Fix * Fix underlying issue * Fix units * Add changelog fragment --- .github/workflows/ansible-test-plugins.yml | 4 ---- changelogs/fragments/0-mysqlclient.yml | 2 ++ plugins/module_utils/user.py | 8 ++++++-- plugins/modules/mysql_role.py | 2 +- tests/unit/plugins/modules/test_mysql_role.py | 4 ++-- 5 files changed, 11 insertions(+), 9 deletions(-) create mode 100644 changelogs/fragments/0-mysqlclient.yml diff --git a/.github/workflows/ansible-test-plugins.yml b/.github/workflows/ansible-test-plugins.yml index 3c34ee6..f3e7839 100644 --- a/.github/workflows/ansible-test-plugins.yml +++ b/.github/workflows/ansible-test-plugins.yml @@ -78,10 +78,6 @@ jobs: connector: pymysql==0.7.10 - db_engine_version: mariadb_10.5.9 connector: pymysql==0.7.10 - - db_engine_version: mariadb_10.3.34 - connector: mysqlclient==2.0.1 - - db_engine_version: mariadb_10.5.9 - connector: mysqlclient==2.0.1 - python: 3.8 ansible: stable-2.9 - python: 3.8 diff --git a/changelogs/fragments/0-mysqlclient.yml b/changelogs/fragments/0-mysqlclient.yml new file mode 100644 index 0000000..8c058d1 --- /dev/null +++ b/changelogs/fragments/0-mysqlclient.yml @@ -0,0 +1,2 @@ +bugfixes: +- Collection core functions - fixes related to the mysqlclient Python connector (https://github.com/ansible-collections/community.mysql/issues/292). diff --git a/plugins/module_utils/user.py b/plugins/module_utils/user.py index a523534..13b0f25 100644 --- a/plugins/module_utils/user.py +++ b/plugins/module_utils/user.py @@ -398,7 +398,7 @@ def privileges_get(cursor, user, host, maria_role=False): if not maria_role: cursor.execute("SHOW GRANTS FOR %s@%s", (user, host)) else: - cursor.execute("SHOW GRANTS FOR %s", (user)) + cursor.execute("SHOW GRANTS FOR %s", (user,)) grants = cursor.fetchall() def pick(x): @@ -618,7 +618,7 @@ def privileges_revoke(cursor, user, host, db_table, priv, grant_option, maria_ro params = (user, host) else: query.append("FROM %s") - params = (user) + params = (user,) query = ' '.join(query) cursor.execute(query, params) @@ -644,6 +644,10 @@ def privileges_grant(cursor, user, host, db_table, priv, tls_requires, maria_rol if 'GRANT' in priv: query.append("WITH GRANT OPTION") query = ' '.join(query) + + if isinstance(params, str): + params = (params,) + try: cursor.execute(query, params) except (mysql_driver.ProgrammingError, mysql_driver.OperationalError, mysql_driver.InternalError) as e: diff --git a/plugins/modules/mysql_role.py b/plugins/modules/mysql_role.py index 8525ac6..34cccd3 100644 --- a/plugins/modules/mysql_role.py +++ b/plugins/modules/mysql_role.py @@ -481,7 +481,7 @@ class MariaDBQueryBuilder(): Returns: tuple: (query_string, tuple_containing_parameters). """ - return "SELECT count(*) FROM mysql.user WHERE user = %s AND is_role = 'Y'", (self.name) + return "SELECT count(*) FROM mysql.user WHERE user = %s AND is_role = 'Y'", (self.name,) def role_grant(self, user): """Return a query to grant a role to a user or role. diff --git a/tests/unit/plugins/modules/test_mysql_role.py b/tests/unit/plugins/modules/test_mysql_role.py index 2ed5a2c..3c24719 100644 --- a/tests/unit/plugins/modules/test_mysql_role.py +++ b/tests/unit/plugins/modules/test_mysql_role.py @@ -28,9 +28,9 @@ module = Module() @pytest.mark.parametrize( 'builder,output', [ - (MariaDBQueryBuilder('role0'), ("SELECT count(*) FROM mysql.user WHERE user = %s AND is_role = 'Y'", ('role0'))), + (MariaDBQueryBuilder('role0'), ("SELECT count(*) FROM mysql.user WHERE user = %s AND is_role = 'Y'", ('role0',))), (MySQLQueryBuilder('role0', '%'), ('SELECT count(*) FROM mysql.user WHERE user = %s AND host = %s', ('role0', '%'))), - (MariaDBQueryBuilder('role1'), ("SELECT count(*) FROM mysql.user WHERE user = %s AND is_role = 'Y'", ('role1'))), + (MariaDBQueryBuilder('role1'), ("SELECT count(*) FROM mysql.user WHERE user = %s AND is_role = 'Y'", ('role1',))), (MySQLQueryBuilder('role1', 'fake'), ('SELECT count(*) FROM mysql.user WHERE user = %s AND host = %s', ('role1', 'fake'))), ] ) From 5b1f1fe46929bf335062039589c55b5d79013d7e Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Mon, 14 Mar 2022 17:34:02 +0300 Subject: [PATCH 062/217] Release 3.1.2 commit (#304) --- galaxy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/galaxy.yml b/galaxy.yml index c96b8c3..4b0da15 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -1,6 +1,6 @@ namespace: community name: mysql -version: 3.1.1 +version: 3.1.2 readme: README.md authors: - Ansible community From 1f16e65dfc670b9df68b35bf67d309074867be8e Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Mon, 14 Mar 2022 17:45:53 +0300 Subject: [PATCH 063/217] Release 3.1.2 commit #2 (#306) --- changelogs/CHANGELOG.rst | 15 +++++++++++++++ changelogs/changelog.yaml | 14 ++++++++++++++ changelogs/fragments/0-mysqlclient.yml | 2 -- 3 files changed, 29 insertions(+), 2 deletions(-) delete mode 100644 changelogs/fragments/0-mysqlclient.yml diff --git a/changelogs/CHANGELOG.rst b/changelogs/CHANGELOG.rst index 3b97d9a..4129b5e 100644 --- a/changelogs/CHANGELOG.rst +++ b/changelogs/CHANGELOG.rst @@ -6,6 +6,21 @@ Community MySQL Collection Release Notes This changelog describes changes after version 2.0.0. +v3.1.2 +====== + +Release Summary +--------------- + +This is the patch release of the ``community.mysql`` collection. +This changelog contains all changes to the modules in this collection +that have been added after the release of ``community.mysql`` 3.1.1. + +Bugfixes +-------- + +- Collection core functions - fixes related to the mysqlclient Python connector (https://github.com/ansible-collections/community.mysql/issues/292). + v3.1.1 ====== diff --git a/changelogs/changelog.yaml b/changelogs/changelog.yaml index da04ced..e8af856 100644 --- a/changelogs/changelog.yaml +++ b/changelogs/changelog.yaml @@ -62,3 +62,17 @@ releases: - 282-mysql_role_fix_set_default_role_all_argument.yml - 3.1.1.yml release_date: '2022-02-16' + 3.1.2: + changes: + bugfixes: + - Collection core functions - fixes related to the mysqlclient Python connector + (https://github.com/ansible-collections/community.mysql/issues/292). + release_summary: 'This is the patch release of the ``community.mysql`` collection. + + This changelog contains all changes to the modules in this collection + + that have been added after the release of ``community.mysql`` 3.1.1.' + fragments: + - 0-mysqlclient.yml + - 3.1.2.yml + release_date: '2022-03-14' diff --git a/changelogs/fragments/0-mysqlclient.yml b/changelogs/fragments/0-mysqlclient.yml deleted file mode 100644 index 8c058d1..0000000 --- a/changelogs/fragments/0-mysqlclient.yml +++ /dev/null @@ -1,2 +0,0 @@ -bugfixes: -- Collection core functions - fixes related to the mysqlclient Python connector (https://github.com/ansible-collections/community.mysql/issues/292). From 3a452faeb07884a6547b62f76f4c0743de907115 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Tue, 15 Mar 2022 15:41:55 +0300 Subject: [PATCH 064/217] Add IF EXISTS clause to DROP USER statement (#307) * Add IF EXISTS clause to DROP USER statement * Add a changelog fragment * Fix exception --- .../fragments/307-mysql_user_add_if_exists_to_drop.yml | 2 ++ plugins/module_utils/user.py | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 changelogs/fragments/307-mysql_user_add_if_exists_to_drop.yml diff --git a/changelogs/fragments/307-mysql_user_add_if_exists_to_drop.yml b/changelogs/fragments/307-mysql_user_add_if_exists_to_drop.yml new file mode 100644 index 0000000..8de1b17 --- /dev/null +++ b/changelogs/fragments/307-mysql_user_add_if_exists_to_drop.yml @@ -0,0 +1,2 @@ +bugfixes: +- "mysql_user - fix the possibility for a race condition that breaks certain (circular) replication configurations when ``DROP USER`` is executed on multiple nodes in the replica set. Adding ``IF EXISTS`` avoids the need to use ``sql_log_bin: no`` making the statement always replication safe (https://github.com/ansible-collections/community.mysql/pull/287)." diff --git a/plugins/module_utils/user.py b/plugins/module_utils/user.py index 13b0f25..8fe0629 100644 --- a/plugins/module_utils/user.py +++ b/plugins/module_utils/user.py @@ -368,7 +368,10 @@ def user_delete(cursor, user, host, host_all, check_mode): hostnames = [host] for hostname in hostnames: - cursor.execute("DROP USER %s@%s", (user, hostname)) + try: + cursor.execute("DROP USER IF EXISTS %s@%s", (user, hostname)) + except Exception: + cursor.execute("DROP USER %s@%s", (user, hostname)) return True From 55458f5b0b40f7e1810b62f24ba27b972cf2ff1a Mon Sep 17 00:00:00 2001 From: "R.Sicart" Date: Thu, 17 Mar 2022 09:30:29 +0100 Subject: [PATCH 065/217] Setup patchback bot config file (resolves #310) (#311) --- .github/patchback.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .github/patchback.yml diff --git a/.github/patchback.yml b/.github/patchback.yml new file mode 100644 index 0000000..33ad6e8 --- /dev/null +++ b/.github/patchback.yml @@ -0,0 +1,5 @@ +--- +backport_branch_prefix: patchback/backports/ +backport_label_prefix: backport- +target_branch_prefix: stable- +... From e6e661b87f182f4a9486a19594b640388b0d6d8f Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Wed, 23 Mar 2022 12:00:26 +0300 Subject: [PATCH 066/217] Fix roles CI (#316) --- .github/workflows/ansible-test-roles.yml | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ansible-test-roles.yml b/.github/workflows/ansible-test-roles.yml index 0bc32f6..4f85d26 100644 --- a/.github/workflows/ansible-test-roles.yml +++ b/.github/workflows/ansible-test-roles.yml @@ -24,13 +24,24 @@ jobs: - 2.0.12 ansible: - stable-2.9 - ### it looks like there's errors for 2.10+ with ansible-lint (https://github.com/ansible/ansible-lint/pull/878) - ### and molecule (_maybe_ relating to https://github.com/ansible-community/molecule/pull/2547) - # - stable-2.10 - # - devel + - stable-2.10 + - stable-2.11 + - stable-2.12 + - devel python: - - 2.7 + - 3.6 - 3.8 + exclude: + - python: 3.8 + ansible: stable-2.9 + - python: 3.8 + ansible: stable-2.10 + - python: 3.8 + ansible: stable-2.11 + - python: 3.6 + ansible: stable-2.12 + - python: 3.6 + ansible: devel steps: @@ -44,7 +55,7 @@ jobs: with: python-version: ${{ matrix.python }} - - name: Install ansible-base (${{ matrix.ansible }}) + - name: Install ansible-core (${{ matrix.ansible }}) run: pip install https://github.com/ansible/ansible/archive/${{ matrix.ansible }}.tar.gz --disable-pip-version-check - name: Install molecule and related dependencies From 5afae459dc35b4507d25a34d7cc901231a3d07f4 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Fri, 25 Mar 2022 09:40:31 +0300 Subject: [PATCH 067/217] mysql_user: clarify behavior of priv parameter (#319) --- plugins/modules/mysql_user.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/plugins/modules/mysql_user.py b/plugins/modules/mysql_user.py index e1d0a92..326c1fe 100644 --- a/plugins/modules/mysql_user.py +++ b/plugins/modules/mysql_user.py @@ -45,7 +45,7 @@ options: description: - "MySQL privileges string in the format: C(db.table:priv1,priv2)." - "Multiple privileges can be specified by separating each one using - a forward slash: C(db.table:priv/db.table:priv)." + a forward slash: C(db.table1:priv/db.table2:priv)." - The format is based on MySQL C(GRANT) statement. - Database and table names can be quoted, MySQL-style. - If column privileges are used, the C(priv1,priv2) part must be @@ -54,6 +54,11 @@ options: by permission (C(SELECT(col1,col2)) instead of C(SELECT(col1),SELECT(col2))). - Can be passed as a dictionary (see the examples). - Supports GRANTs for procedures and functions (see the examples). + - "Note: If you pass the same C(db.table) combination to this parameter + two or more times with different privileges, + for example, C('*.*:SELECT/*.*:SHOW VIEW'), only the last one will be applied, + in this example, it will be C(SHOW VIEW) respectively. + Use C('*.*:SELECT,SHOW VIEW') instead to apply both." type: raw append_privs: description: From 82baf7508ce757b102e229b51f902c46cf18bebc Mon Sep 17 00:00:00 2001 From: Matthew Exon Date: Fri, 1 Apr 2022 17:19:19 +0800 Subject: [PATCH 068/217] Clarified error message about missing python modules (#279) * Clarified error message about missing python modules, and tweak documentation to suggest overriding interpreter. * Mention mysqlclient as another option * Correct mysqlclient suggestions from python2 to python3 Co-authored-by: Matthew Exon --- plugins/doc_fragments/mysql.py | 11 +++++++---- plugins/module_utils/mysql.py | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/plugins/doc_fragments/mysql.py b/plugins/doc_fragments/mysql.py index 9cc5bce..4b531d4 100644 --- a/plugins/doc_fragments/mysql.py +++ b/plugins/doc_fragments/mysql.py @@ -79,10 +79,13 @@ notes: The Python package may be installed with apt-get install python-pymysql (Ubuntu; see M(ansible.builtin.apt)) or yum install python2-PyMySQL (RHEL/CentOS/Fedora; see M(ansible.builtin.yum)). You can also use dnf install python2-PyMySQL for newer versions of Fedora; see M(ansible.builtin.dnf). - - Be sure you have PyMySQL or MySQLdb library installed on the target machine - for the Python interpreter Ansible uses, for example, if it is Python 3, - you must install the library for Python 3. You can also change the interpreter. - For more information, see U(https://docs.ansible.com/ansible/latest/reference_appendices/interpreter_discovery.html). + - Be sure you have mysqlclient, PyMySQL, or MySQLdb library installed on the target machine + for the Python interpreter Ansible discovers. For example if ansible discovers and uses Python 3, you need to install + the Python 3 version of PyMySQL or mysqlclient. If ansible discovers and uses Python 2, you need to install the Python 2 + version of either PyMySQL or MySQL-python. + - If you have trouble, it may help to force Ansible to use the Python interpreter you need by specifying + C(ansible_python_interpreter). For more information, see + U(https://docs.ansible.com/ansible/latest/reference_appendices/interpreter_discovery.html). - Both C(login_password) and C(login_user) are required when you are passing credentials. If none are present, the module will attempt to read the credentials from C(~/.my.cnf), and finally fall back to using the MySQL diff --git a/plugins/module_utils/mysql.py b/plugins/module_utils/mysql.py index 5af9c20..c62863a 100644 --- a/plugins/module_utils/mysql.py +++ b/plugins/module_utils/mysql.py @@ -29,7 +29,7 @@ except ImportError: except ImportError: mysql_driver = None -mysql_driver_fail_msg = 'The PyMySQL (Python 2.7 and Python 3.X) or MySQL-python (Python 2.X) module is required.' +mysql_driver_fail_msg = 'A MySQL module is required: for Python 2.7 either PyMySQL, or MySQL-python, or for Python 3.X mysqlclient or PyMySQL. Consider setting ansible_python_interpreter to use the intended Python version.' def parse_from_mysql_config_file(cnf): From c16b2428e80f8f837469d41fca48f6b0015fab8f Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Fri, 1 Apr 2022 17:40:02 +0300 Subject: [PATCH 069/217] Copy ignore-2.13.txt to ignore-2.14.txt (#323) * Copy ignore-2.13.txt to ignore-2.14.txt * Fix sanity --- plugins/module_utils/mysql.py | 5 ++++- tests/sanity/ignore-2.14.txt | 8 ++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 tests/sanity/ignore-2.14.txt diff --git a/plugins/module_utils/mysql.py b/plugins/module_utils/mysql.py index c62863a..9492ea8 100644 --- a/plugins/module_utils/mysql.py +++ b/plugins/module_utils/mysql.py @@ -29,7 +29,10 @@ except ImportError: except ImportError: mysql_driver = None -mysql_driver_fail_msg = 'A MySQL module is required: for Python 2.7 either PyMySQL, or MySQL-python, or for Python 3.X mysqlclient or PyMySQL. Consider setting ansible_python_interpreter to use the intended Python version.' +mysql_driver_fail_msg = ('A MySQL module is required: for Python 2.7 either PyMySQL, or ' + 'MySQL-python, or for Python 3.X mysqlclient or PyMySQL. ' + 'Consider setting ansible_python_interpreter to use ' + 'the intended Python version.') def parse_from_mysql_config_file(cnf): diff --git a/tests/sanity/ignore-2.14.txt b/tests/sanity/ignore-2.14.txt new file mode 100644 index 0000000..c0323af --- /dev/null +++ b/tests/sanity/ignore-2.14.txt @@ -0,0 +1,8 @@ +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_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_variables.py validate-modules:doc-required-mismatch From e319ac082ef24524d8c0cc59f135f3081d43b850 Mon Sep 17 00:00:00 2001 From: "R.Sicart" Date: Sat, 2 Apr 2022 17:53:21 +0200 Subject: [PATCH 070/217] CI: add testing against ansible-core 2.13 (#326) Relates to https://github.com/ansible-collections/news-for-maintainers/issues/14 --- .github/workflows/ansible-test-plugins.yml | 5 +++++ .github/workflows/ansible-test-roles.yml | 3 +++ README.md | 1 + 3 files changed, 9 insertions(+) diff --git a/.github/workflows/ansible-test-plugins.yml b/.github/workflows/ansible-test-plugins.yml index f3e7839..b7be934 100644 --- a/.github/workflows/ansible-test-plugins.yml +++ b/.github/workflows/ansible-test-plugins.yml @@ -29,6 +29,7 @@ jobs: - stable-2.10 - stable-2.11 - stable-2.12 + - stable-2.13 - devel steps: @@ -65,6 +66,7 @@ jobs: - stable-2.10 - stable-2.11 - stable-2.12 + - stable-2.13 - devel python: - 3.6 @@ -86,6 +88,8 @@ jobs: ansible: stable-2.11 - python: 3.6 ansible: stable-2.12 + - python: 3.6 + ansible: stable-2.13 - python: 3.6 ansible: devel @@ -144,6 +148,7 @@ jobs: - stable-2.10 - stable-2.11 - stable-2.12 + - stable-2.13 - devel steps: diff --git a/.github/workflows/ansible-test-roles.yml b/.github/workflows/ansible-test-roles.yml index 4f85d26..5c27416 100644 --- a/.github/workflows/ansible-test-roles.yml +++ b/.github/workflows/ansible-test-roles.yml @@ -27,6 +27,7 @@ jobs: - stable-2.10 - stable-2.11 - stable-2.12 + - stable-2.13 - devel python: - 3.6 @@ -40,6 +41,8 @@ jobs: ansible: stable-2.11 - python: 3.6 ansible: stable-2.12 + - python: 3.6 + ansible: stable-2.13 - python: 3.6 ansible: devel diff --git a/README.md b/README.md index 9eec168..e01d83b 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,7 @@ Every voice is important and every idea is valuable. If you have something on yo - 2.10 - 2.11 - 2.12 +- 2.13 - devel ### Databases From 641894e6e8fa0c02384e64e7eaaaf5e5e45b82a0 Mon Sep 17 00:00:00 2001 From: betanummeric <40263343+betanummeric@users.noreply.github.com> Date: Tue, 5 Apr 2022 09:35:46 +0200 Subject: [PATCH 071/217] mysql_role: remove redundant connection closing (fixes #329) (#330) * mysql_role: remove redundant connection closing (fixes #329) * add changelog fragment for pull request #330 Co-authored-by: Felix Hamme --- .../329-mysql_role-remove-redudant-connection-closing.yml | 2 ++ plugins/modules/mysql_role.py | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 changelogs/fragments/329-mysql_role-remove-redudant-connection-closing.yml diff --git a/changelogs/fragments/329-mysql_role-remove-redudant-connection-closing.yml b/changelogs/fragments/329-mysql_role-remove-redudant-connection-closing.yml new file mode 100644 index 0000000..8035b32 --- /dev/null +++ b/changelogs/fragments/329-mysql_role-remove-redudant-connection-closing.yml @@ -0,0 +1,2 @@ +bugfixes: + - "mysql_role - remove redundant connection closing (https://github.com/ansible-collections/community.mysql/pull/330)." diff --git a/plugins/modules/mysql_role.py b/plugins/modules/mysql_role.py index 34cccd3..7641b07 100644 --- a/plugins/modules/mysql_role.py +++ b/plugins/modules/mysql_role.py @@ -1057,8 +1057,6 @@ def main(): except Exception as e: module.fail_json(msg=to_native(e)) - # Exit - db_conn.close() module.exit_json(changed=changed) From 450cb19027aa4385b798df39cf6d91086f4816bd Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Thu, 21 Apr 2022 18:43:08 +0300 Subject: [PATCH 072/217] mysql_replication: fix failing when using primary_use_gtid with replica_ or slave_pos (#336) --- changelogs/fragments/0-mysql_replication_replica_pos.yml | 2 ++ plugins/modules/mysql_replication.py | 2 ++ .../test_mysql_replication/tasks/mysql_replication_initial.yml | 1 + 3 files changed, 5 insertions(+) create mode 100644 changelogs/fragments/0-mysql_replication_replica_pos.yml diff --git a/changelogs/fragments/0-mysql_replication_replica_pos.yml b/changelogs/fragments/0-mysql_replication_replica_pos.yml new file mode 100644 index 0000000..db59f3a --- /dev/null +++ b/changelogs/fragments/0-mysql_replication_replica_pos.yml @@ -0,0 +1,2 @@ +bugfixes: +- mysql_replication - fails when using the `primary_use_gtid` option with `slave_pos` or `replica_pos` (https://github.com/ansible-collections/community.mysql/issues/335). diff --git a/plugins/modules/mysql_replication.py b/plugins/modules/mysql_replication.py index 3316694..46895e3 100644 --- a/plugins/modules/mysql_replication.py +++ b/plugins/modules/mysql_replication.py @@ -532,6 +532,8 @@ def main(): replica_term = 'REPLICA' else: replica_term = 'SLAVE' + if primary_use_gtid == 'replica_pos': + primary_use_gtid = 'slave_pos' if mode == 'getprimary': status = get_primary_status(cursor) 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 050e952..7f6e554 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 @@ -75,6 +75,7 @@ <<: *mysql_params login_port: '{{ mysql_replica1_port }}' mode: startreplica + primary_use_gtid: replica_pos fail_on_error: yes register: result ignore_errors: yes From 4aab8ac808a1584bdfaaf15040f362fdb9f278ef Mon Sep 17 00:00:00 2001 From: "R.Sicart" Date: Tue, 26 Apr 2022 17:07:21 +0200 Subject: [PATCH 073/217] Release 3.1.3 commit (#337) --- changelogs/CHANGELOG.rst | 17 +++++++++++++++ changelogs/changelog.yaml | 21 +++++++++++++++++++ .../0-mysql_replication_replica_pos.yml | 2 -- .../307-mysql_user_add_if_exists_to_drop.yml | 2 -- ...ole-remove-redudant-connection-closing.yml | 2 -- galaxy.yml | 2 +- 6 files changed, 39 insertions(+), 7 deletions(-) delete mode 100644 changelogs/fragments/0-mysql_replication_replica_pos.yml delete mode 100644 changelogs/fragments/307-mysql_user_add_if_exists_to_drop.yml delete mode 100644 changelogs/fragments/329-mysql_role-remove-redudant-connection-closing.yml diff --git a/changelogs/CHANGELOG.rst b/changelogs/CHANGELOG.rst index 4129b5e..68a2d2e 100644 --- a/changelogs/CHANGELOG.rst +++ b/changelogs/CHANGELOG.rst @@ -6,6 +6,23 @@ Community MySQL Collection Release Notes This changelog describes changes after version 2.0.0. +v3.1.3 +====== + +Release Summary +--------------- + +This is the patch release of the ``community.mysql`` collection. +This changelog contains all changes to the modules in this collection +that have been added after the release of ``community.mysql`` 3.1.2. + +Bugfixes +-------- + +- mysql_replication - fails when using the `primary_use_gtid` option with `slave_pos` or `replica_pos` (https://github.com/ansible-collections/community.mysql/issues/335). +- mysql_role - remove redundant connection closing (https://github.com/ansible-collections/community.mysql/pull/330). +- mysql_user - fix the possibility for a race condition that breaks certain (circular) replication configurations when ``DROP USER`` is executed on multiple nodes in the replica set. Adding ``IF EXISTS`` avoids the need to use ``sql_log_bin: no`` making the statement always replication safe (https://github.com/ansible-collections/community.mysql/pull/287). + v3.1.2 ====== diff --git a/changelogs/changelog.yaml b/changelogs/changelog.yaml index e8af856..ceeb833 100644 --- a/changelogs/changelog.yaml +++ b/changelogs/changelog.yaml @@ -76,3 +76,24 @@ releases: - 0-mysqlclient.yml - 3.1.2.yml release_date: '2022-03-14' + 3.1.3: + changes: + bugfixes: + - mysql_replication - fails when using the `primary_use_gtid` option with `slave_pos` + or `replica_pos` (https://github.com/ansible-collections/community.mysql/issues/335). + - mysql_role - remove redundant connection closing (https://github.com/ansible-collections/community.mysql/pull/330). + - 'mysql_user - fix the possibility for a race condition that breaks certain + (circular) replication configurations when ``DROP USER`` is executed on multiple + nodes in the replica set. Adding ``IF EXISTS`` avoids the need to use ``sql_log_bin: + no`` making the statement always replication safe (https://github.com/ansible-collections/community.mysql/pull/287).' + release_summary: 'This is the patch release of the ``community.mysql`` collection. + + This changelog contains all changes to the modules in this collection + + that have been added after the release of ``community.mysql`` 3.1.2.' + fragments: + - 0-mysql_replication_replica_pos.yml + - 3.1.3.yml + - 307-mysql_user_add_if_exists_to_drop.yml + - 329-mysql_role-remove-redudant-connection-closing.yml + release_date: '2022-04-26' diff --git a/changelogs/fragments/0-mysql_replication_replica_pos.yml b/changelogs/fragments/0-mysql_replication_replica_pos.yml deleted file mode 100644 index db59f3a..0000000 --- a/changelogs/fragments/0-mysql_replication_replica_pos.yml +++ /dev/null @@ -1,2 +0,0 @@ -bugfixes: -- mysql_replication - fails when using the `primary_use_gtid` option with `slave_pos` or `replica_pos` (https://github.com/ansible-collections/community.mysql/issues/335). diff --git a/changelogs/fragments/307-mysql_user_add_if_exists_to_drop.yml b/changelogs/fragments/307-mysql_user_add_if_exists_to_drop.yml deleted file mode 100644 index 8de1b17..0000000 --- a/changelogs/fragments/307-mysql_user_add_if_exists_to_drop.yml +++ /dev/null @@ -1,2 +0,0 @@ -bugfixes: -- "mysql_user - fix the possibility for a race condition that breaks certain (circular) replication configurations when ``DROP USER`` is executed on multiple nodes in the replica set. Adding ``IF EXISTS`` avoids the need to use ``sql_log_bin: no`` making the statement always replication safe (https://github.com/ansible-collections/community.mysql/pull/287)." diff --git a/changelogs/fragments/329-mysql_role-remove-redudant-connection-closing.yml b/changelogs/fragments/329-mysql_role-remove-redudant-connection-closing.yml deleted file mode 100644 index 8035b32..0000000 --- a/changelogs/fragments/329-mysql_role-remove-redudant-connection-closing.yml +++ /dev/null @@ -1,2 +0,0 @@ -bugfixes: - - "mysql_role - remove redundant connection closing (https://github.com/ansible-collections/community.mysql/pull/330)." diff --git a/galaxy.yml b/galaxy.yml index 4b0da15..cbf2b3a 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -1,6 +1,6 @@ namespace: community name: mysql -version: 3.1.2 +version: 3.1.3 readme: README.md authors: - Ansible community From 1dcc5ec086434e707d0ad122ffd9b612187b1132 Mon Sep 17 00:00:00 2001 From: bigo8525 <53953606+bigo8525@users.noreply.github.com> Date: Fri, 29 Apr 2022 12:38:12 +0200 Subject: [PATCH 074/217] mysql_user: added flush privileges to write dynamic privs into db (#338) * added flush privileges to write dynamic privs into db Fixes https://github.com/ansible-collections/community.mysql/issues/120 * added changelog fragment * Update changelogs/fragments/338-mysql_user_fix_missing_dynamic_privileges.yml Co-authored-by: Andrew Klychkov Co-authored-by: Andrew Klychkov --- .../fragments/338-mysql_user_fix_missing_dynamic_privileges.yml | 2 ++ plugins/module_utils/user.py | 1 + 2 files changed, 3 insertions(+) create mode 100644 changelogs/fragments/338-mysql_user_fix_missing_dynamic_privileges.yml diff --git a/changelogs/fragments/338-mysql_user_fix_missing_dynamic_privileges.yml b/changelogs/fragments/338-mysql_user_fix_missing_dynamic_privileges.yml new file mode 100644 index 0000000..1054ea6 --- /dev/null +++ b/changelogs/fragments/338-mysql_user_fix_missing_dynamic_privileges.yml @@ -0,0 +1,2 @@ +bugfixes: + - "mysql_user - fix missing dynamic privileges after revoke and grant privileges to user (https://github.com/ansible-collections/community.mysql/issues/120)." \ No newline at end of file diff --git a/plugins/module_utils/user.py b/plugins/module_utils/user.py index 8fe0629..dc82a60 100644 --- a/plugins/module_utils/user.py +++ b/plugins/module_utils/user.py @@ -625,6 +625,7 @@ def privileges_revoke(cursor, user, host, db_table, priv, grant_option, maria_ro query = ' '.join(query) cursor.execute(query, params) + cursor.execute("FLUSH PRIVILEGES") def privileges_grant(cursor, user, host, db_table, priv, tls_requires, maria_role=False): From ba4fea67b1121f11aa1882df80955294911c4302 Mon Sep 17 00:00:00 2001 From: betanummeric <40263343+betanummeric@users.noreply.github.com> Date: Mon, 9 May 2022 09:50:49 +0200 Subject: [PATCH 075/217] mysql_user, mysql_role: add argument subtract_privs to revoke privileges explicitly (#333) * add option subtract_privs to mysql_role and mysql_user see https://github.com/ansible-collections/community.mysql/issues/331 * add integration tests for subtract_privs for mysql_role and mysql_user * add changelog fragment for PR #333 * mysql_role, mysql_user: when subtract_privileges, don't grant unwanted privileges and don't revoke USAGE implicitly * fix integration tests * mysql_role, mysql_user: invalid privileges are ignored when subtract_privs is true -> document that and fix integration tests * fix mysql_role integration tests * fix mysql_role, mysql_user integration tests * formatting make the PEP8 check happy * mysql_user and mysql_role: fix granting privileges when only the GRANT OPTION needs to be added * mysql_user and mysql_role: log some updated privileges; explain integration test blind spot * mysql_user and mysql_role: don't grant too much privileges If only the grant option needs to be granted, at least one privilege needs to be granted to get valid syntax. USAGE is better for that than the existing privileges, because unwanted privileges would be re-added after revokation. * mysql_user and mysql_role: fix type error * Update changelogs/fragments/333-mysql_user-mysql_role-add-subtract_privileges-argument.yml Co-authored-by: Andrew Klychkov * Update plugins/modules/mysql_role.py Co-authored-by: Andrew Klychkov * Update plugins/modules/mysql_user.py Co-authored-by: Andrew Klychkov Co-authored-by: Felix Hamme Co-authored-by: Andrew Klychkov --- ..._role-add-subtract_privileges-argument.yml | 2 + plugins/module_utils/user.py | 74 +++++--- plugins/modules/mysql_role.py | 38 +++- plugins/modules/mysql_user.py | 28 ++- .../targets/test_mysql_role/defaults/main.yml | 1 + .../targets/test_mysql_role/tasks/main.yml | 10 + .../tasks/test_priv_subtract.yml | 168 +++++++++++++++++ .../targets/test_mysql_user/tasks/main.yml | 5 + .../tasks/test_priv_subtract.yml | 173 ++++++++++++++++++ .../test_mysql_user/tasks/test_privs.yml | 2 +- 10 files changed, 459 insertions(+), 42 deletions(-) create mode 100644 changelogs/fragments/333-mysql_user-mysql_role-add-subtract_privileges-argument.yml create mode 100644 tests/integration/targets/test_mysql_role/tasks/test_priv_subtract.yml create mode 100644 tests/integration/targets/test_mysql_user/tasks/test_priv_subtract.yml diff --git a/changelogs/fragments/333-mysql_user-mysql_role-add-subtract_privileges-argument.yml b/changelogs/fragments/333-mysql_user-mysql_role-add-subtract_privileges-argument.yml new file mode 100644 index 0000000..3e6e632 --- /dev/null +++ b/changelogs/fragments/333-mysql_user-mysql_role-add-subtract_privileges-argument.yml @@ -0,0 +1,2 @@ +minor_changes: + - "mysql_user and mysql_role: Add the argument ``subtract_privs`` (boolean, default false, mutually exclusive with ``append_privs``). If set, the privileges given in ``priv`` are revoked and existing privileges are kept (https://github.com/ansible-collections/community.mysql/pull/333)." diff --git a/plugins/module_utils/user.py b/plugins/module_utils/user.py index dc82a60..35f701d 100644 --- a/plugins/module_utils/user.py +++ b/plugins/module_utils/user.py @@ -169,7 +169,7 @@ def is_hash(password): def user_mod(cursor, user, host, host_all, password, encrypted, plugin, plugin_hash_string, plugin_auth_string, new_priv, - append_privs, tls_requires, module, role=False, maria_role=False): + append_privs, subtract_privs, tls_requires, module, role=False, maria_role=False): changed = False msg = "User unchanged" grant_option = False @@ -288,47 +288,61 @@ def user_mod(cursor, user, host, host_all, password, encrypted, # If the user has privileges on a db.table that doesn't appear at all in # the new specification, then revoke all privileges on it. - for db_table, priv in iteritems(curr_priv): - # If the user has the GRANT OPTION on a db.table, revoke it first. - if "GRANT" in priv: - grant_option = True - if db_table not in new_priv: - if user != "root" and "PROXY" not in priv and not append_privs: - msg = "Privileges updated" - if module.check_mode: - return (True, msg) - privileges_revoke(cursor, user, host, db_table, priv, grant_option, maria_role) - changed = True + if not append_privs and not subtract_privs: + for db_table, priv in iteritems(curr_priv): + # If the user has the GRANT OPTION on a db.table, revoke it first. + if "GRANT" in priv: + grant_option = True + if db_table not in new_priv: + if user != "root" and "PROXY" not in priv: + msg = "Privileges updated" + if module.check_mode: + return (True, msg) + privileges_revoke(cursor, user, host, db_table, priv, grant_option, maria_role) + changed = True # If the user doesn't currently have any privileges on a db.table, then # we can perform a straight grant operation. - for db_table, priv in iteritems(new_priv): - if db_table not in curr_priv: - msg = "New privileges granted" - if module.check_mode: - return (True, msg) - privileges_grant(cursor, user, host, db_table, priv, tls_requires, maria_role) - changed = True + if not subtract_privs: + for db_table, priv in iteritems(new_priv): + if db_table not in curr_priv: + msg = "New privileges granted" + if module.check_mode: + return (True, msg) + privileges_grant(cursor, user, host, db_table, priv, tls_requires, maria_role) + changed = True # If the db.table specification exists in both the user's current privileges # and in the new privileges, then we need to see if there's a difference. db_table_intersect = set(new_priv.keys()) & set(curr_priv.keys()) for db_table in db_table_intersect: - # If appending privileges, only the set difference between new privileges and current privileges matter. - # The symmetric difference isn't relevant for append because existing privileges will not be revoked. + grant_privs = [] + revoke_privs = [] if append_privs: - priv_diff = set(new_priv[db_table]) - set(curr_priv[db_table]) + # When appending privileges, only missing privileges need to be granted. Nothing is revoked. + grant_privs = list(set(new_priv[db_table]) - set(curr_priv[db_table])) + elif subtract_privs: + # When subtracting privileges, revoke only the intersection of requested and current privileges. + # No privileges are granted. + revoke_privs = list(set(new_priv[db_table]) & set(curr_priv[db_table])) else: - priv_diff = set(new_priv[db_table]) ^ set(curr_priv[db_table]) + # When replacing (neither append_privs nor subtract_privs), grant all missing privileges + # and revoke existing privileges that were not requested. + grant_privs = list(set(new_priv[db_table]) - set(curr_priv[db_table])) + revoke_privs = list(set(curr_priv[db_table]) - set(new_priv[db_table])) + if grant_privs == ['GRANT']: + # USAGE grants no privileges, it is only needed because 'WITH GRANT OPTION' cannot stand alone + grant_privs.append('USAGE') - if len(priv_diff) > 0: - msg = "Privileges updated" + if len(grant_privs) + len(revoke_privs) > 0: + msg = "Privileges updated: granted %s, revoked %s" % (grant_privs, revoke_privs) if module.check_mode: return (True, msg) - if not append_privs: - privileges_revoke(cursor, user, host, db_table, curr_priv[db_table], grant_option, maria_role) - privileges_grant(cursor, user, host, db_table, new_priv[db_table], tls_requires, maria_role) + if len(revoke_privs) > 0: + privileges_revoke(cursor, user, host, db_table, revoke_privs, grant_option, maria_role) + if len(grant_privs) > 0: + privileges_grant(cursor, user, host, db_table, grant_privs, tls_requires, maria_role) changed = True if role: @@ -549,7 +563,7 @@ def sort_column_order(statement): return '%s(%s)' % (priv_name, ', '.join(columns)) -def privileges_unpack(priv, mode): +def privileges_unpack(priv, mode, ensure_usage=True): """ Take a privileges string, typically passed as a parameter, and unserialize it into a dictionary, the same format as privileges_get() above. We have this custom format to avoid using YAML/JSON strings inside YAML playbooks. Example @@ -595,7 +609,7 @@ def privileges_unpack(priv, mode): # Handle cases when there's privs like GRANT SELECT (colA, ...) in privs. output[pieces[0]] = normalize_col_grants(output[pieces[0]]) - if '*.*' not in output: + if ensure_usage and '*.*' not in output: output['*.*'] = ['USAGE'] return output diff --git a/plugins/modules/mysql_role.py b/plugins/modules/mysql_role.py index 7641b07..d036541 100644 --- a/plugins/modules/mysql_role.py +++ b/plugins/modules/mysql_role.py @@ -51,7 +51,16 @@ options: append_privs: description: - Append the privileges defined by the I(priv) option to the existing ones - for this role instead of overwriting them. + for this role instead of overwriting them. Mutually exclusive with I(subtract_privs). + type: bool + default: no + + subtract_privs: + description: + - Revoke the privileges defined by the I(priv) option and keep other existing privileges. + If set, invalid privileges in I(priv) are ignored. + Mutually exclusive with I(append_privs). + version_added: '3.2.0' type: bool default: no @@ -233,6 +242,14 @@ EXAMPLES = r''' name: business members: - marketing + +- name: Ensure the role foo does not have the DELETE privilege + community.mysql.mysql_role: + state: present + name: foo + subtract_privs: yes + priv: + 'db1.*': DELETE ''' RETURN = '''#''' @@ -821,9 +838,9 @@ class Role(): return True def update(self, users, privs, check_mode=False, - append_privs=False, append_members=False, - detach_members=False, admin=False, - set_default_role_all=True): + append_privs=False, subtract_privs=False, + append_members=False, detach_members=False, + admin=False, set_default_role_all=True): """Update a role. Update a role if needed. @@ -837,6 +854,8 @@ class Role(): check_mode (bool): If True, just checks and does nothing. append_privs (bool): If True, adds new privileges passed through privs not touching current privileges. + subtract_privs (bool): If True, revoke the privileges passed through privs + not touching other existing privileges. append_members (bool): If True, adds new members passed through users not touching current members. detach_members (bool): If True, removes members passed through users from a role. @@ -861,7 +880,7 @@ class Role(): if privs: changed, msg = user_mod(self.cursor, self.name, self.host, None, None, None, None, None, None, - privs, append_privs, None, + privs, append_privs, subtract_privs, None, self.module, role=True, maria_role=self.is_mariadb) if admin: @@ -931,6 +950,7 @@ def main(): admin=dict(type='str'), priv=dict(type='raw'), append_privs=dict(type='bool', default=False), + subtract_privs=dict(type='bool', default=False), members=dict(type='list', elements='str'), append_members=dict(type='bool', default=False), detach_members=dict(type='bool', default=False), @@ -945,6 +965,7 @@ def main(): ('admin', 'members'), ('admin', 'append_members'), ('admin', 'detach_members'), + ('append_privs', 'subtract_privs'), ), ) @@ -958,6 +979,7 @@ def main(): connect_timeout = module.params['connect_timeout'] config_file = module.params['config_file'] append_privs = module.params['append_privs'] + subtract_privs = module.boolean(module.params['subtract_privs']) members = module.params['members'] append_members = module.params['append_members'] detach_members = module.params['detach_members'] @@ -1014,7 +1036,7 @@ def main(): module.fail_json(msg=to_native(e)) try: - priv = privileges_unpack(priv, mode) + priv = privileges_unpack(priv, mode, ensure_usage=not subtract_privs) except Exception as e: module.fail_json(msg='Invalid privileges string: %s' % to_native(e)) @@ -1043,11 +1065,13 @@ def main(): try: if state == 'present': if not role.exists: + if subtract_privs: + priv = None # avoid granting unwanted privileges changed = role.add(members, priv, module.check_mode, admin, set_default_role_all) else: - changed = role.update(members, priv, module.check_mode, append_privs, + changed = role.update(members, priv, module.check_mode, append_privs, subtract_privs, append_members, detach_members, admin, set_default_role_all) diff --git a/plugins/modules/mysql_user.py b/plugins/modules/mysql_user.py index 326c1fe..9299eaf 100644 --- a/plugins/modules/mysql_user.py +++ b/plugins/modules/mysql_user.py @@ -63,7 +63,15 @@ options: append_privs: description: - Append the privileges defined by priv to the existing ones for this - user instead of overwriting existing ones. + user instead of overwriting existing ones. Mutually exclusive with I(subtract_privs). + type: bool + default: no + subtract_privs: + description: + - Revoke the privileges defined by the I(priv) option and keep other existing privileges. + If set, invalid privileges in I(priv) are ignored. + Mutually exclusive with I(append_privs). + version_added: '3.2.0' type: bool default: no tls_requires: @@ -306,6 +314,13 @@ EXAMPLES = r''' MAX_QUERIES_PER_HOUR: 10 MAX_CONNECTIONS_PER_HOUR: 5 +- name: Ensure bob does not have the DELETE privilege + community.mysql.mysql_user: + name: bob + subtract_privs: yes + priv: + 'db1.*': DELETE + # Example .my.cnf file for setting the root password # [client] # user=root @@ -352,6 +367,7 @@ def main(): priv=dict(type='raw'), tls_requires=dict(type='dict'), append_privs=dict(type='bool', default=False), + subtract_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), @@ -364,6 +380,7 @@ def main(): module = AnsibleModule( argument_spec=argument_spec, supports_check_mode=True, + mutually_exclusive=(('append_privs', 'subtract_privs'),) ) login_user = module.params["login_user"] login_password = module.params["login_password"] @@ -379,6 +396,7 @@ def main(): connect_timeout = module.params["connect_timeout"] config_file = module.params["config_file"] append_privs = module.boolean(module.params["append_privs"]) + subtract_privs = module.boolean(module.params['subtract_privs']) update_password = module.params['update_password'] ssl_cert = module.params["client_cert"] ssl_key = module.params["client_key"] @@ -427,7 +445,7 @@ def main(): mode = get_mode(cursor) except Exception as e: module.fail_json(msg=to_native(e)) - priv = privileges_unpack(priv, mode) + priv = privileges_unpack(priv, mode, ensure_usage=not subtract_privs) if state == "present": if user_exists(cursor, user, host, host_all): @@ -435,11 +453,11 @@ def main(): if update_password == "always": changed, msg = user_mod(cursor, user, host, host_all, password, encrypted, plugin, plugin_hash_string, plugin_auth_string, - priv, append_privs, tls_requires, module) + priv, append_privs, subtract_privs, tls_requires, module) else: changed, msg = user_mod(cursor, user, host, host_all, None, encrypted, plugin, plugin_hash_string, plugin_auth_string, - priv, append_privs, tls_requires, module) + priv, append_privs, subtract_privs, tls_requires, module) except (SQLParseError, InvalidPrivsError, mysql_driver.Error) as e: module.fail_json(msg=to_native(e)) @@ -447,6 +465,8 @@ def main(): if host_all: module.fail_json(msg="host_all parameter cannot be used when adding a user") try: + if subtract_privs: + priv = None # avoid granting unwanted privileges changed = user_add(cursor, user, host, host_all, password, encrypted, plugin, plugin_hash_string, plugin_auth_string, priv, tls_requires, module.check_mode) diff --git a/tests/integration/targets/test_mysql_role/defaults/main.yml b/tests/integration/targets/test_mysql_role/defaults/main.yml index 744ba34..53544bf 100644 --- a/tests/integration/targets/test_mysql_role/defaults/main.yml +++ b/tests/integration/targets/test_mysql_role/defaults/main.yml @@ -14,3 +14,4 @@ nonexistent: user3 role0: role0 role1: role1 +role2: role2 diff --git a/tests/integration/targets/test_mysql_role/tasks/main.yml b/tests/integration/targets/test_mysql_role/tasks/main.yml index 5bcd5ec..952bf6f 100644 --- a/tests/integration/targets/test_mysql_role/tasks/main.yml +++ b/tests/integration/targets/test_mysql_role/tasks/main.yml @@ -3,5 +3,15 @@ # and should not be used as examples of how to write Ansible roles # #################################################################### +- name: alias mysql command to include default options + set_fact: + mysql_command: "mysql -u{{ mysql_user }} -p{{ mysql_password }} -P{{ mysql_primary_port }} --protocol=tcp" + + # mysql_role module initial CI tests - import_tasks: mysql_role_initial.yml + +# Test that subtract_privs will only revoke the grants given by priv +# (https://github.com/ansible-collections/community.mysql/issues/331) +- include: test_priv_subtract.yml enable_check_mode=no +- include: test_priv_subtract.yml enable_check_mode=yes diff --git a/tests/integration/targets/test_mysql_role/tasks/test_priv_subtract.yml b/tests/integration/targets/test_mysql_role/tasks/test_priv_subtract.yml new file mode 100644 index 0000000..d5fe69c --- /dev/null +++ b/tests/integration/targets/test_mysql_role/tasks/test_priv_subtract.yml @@ -0,0 +1,168 @@ +# Test code to ensure that subtracting privileges will not result in unnecessary changes. +- 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: Create test databases + mysql_db: + <<: *mysql_params + name: '{{ item }}' + state: present + loop: + - data1 + + - name: Create a role with an initial set of privileges + mysql_role: + <<: *mysql_params + name: '{{ role2 }}' + priv: 'data1.*:SELECT,INSERT' + state: present + + - name: Run command to show privileges for role (expect privileges in stdout) + command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ role2 }}'\"" + register: result + + - name: Assert that the initial set of privileges matches what is expected + assert: + that: + - "'GRANT SELECT, INSERT ON `data1`.*' in result.stdout" + + - name: Subtract privileges that are not in the current privileges, which should be a no-op + mysql_role: + <<: *mysql_params + name: '{{ role2 }}' + priv: 'data1.*:DELETE' + subtract_privs: yes + state: present + check_mode: '{{ enable_check_mode }}' + register: result + + - name: Assert that there wasn't a change in permissions + assert: + that: + - "result.changed == false" + + - name: Run command to show privileges for role (expect privileges in stdout) + command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ role2 }}'\"" + register: result + + - name: Assert that the permissions still match what was originally granted + assert: + that: + - "'GRANT SELECT, INSERT ON `data1`.*' in result.stdout" + + - name: Subtract existing and not-existing privileges, but not all + mysql_role: + <<: *mysql_params + name: '{{ role2 }}' + priv: 'data1.*:INSERT,DELETE' + subtract_privs: yes + state: present + check_mode: '{{ enable_check_mode }}' + register: result + + - name: Assert that there was a change because permissions were/would be revoked on data1.* + assert: + that: + - "result.changed == true" + + - name: Run command to show privileges for role (expect privileges in stdout) + command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ role2 }}'\"" + register: result + + - name: Assert that the permissions were not changed if check_mode is set to 'yes' + assert: + that: + - "'GRANT SELECT, INSERT ON `data1`.*' in result.stdout" + when: enable_check_mode == 'yes' + + - name: Assert that only DELETE was revoked if check_mode is set to 'no' + assert: + that: + - "'GRANT SELECT ON `data1`.*' in result.stdout" + when: enable_check_mode == 'no' + + - name: Try to subtract invalid privileges + mysql_role: + <<: *mysql_params + name: '{{ role2 }}' + priv: 'data1.*:INVALID' + subtract_privs: yes + state: present + check_mode: '{{ enable_check_mode }}' + register: result + + - name: Assert that there was no change because invalid permissions are ignored + assert: + that: + - "result.changed == false" + + - name: Run command to show privileges for role (expect privileges in stdout) + command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ role2 }}'\"" + register: result + + - name: Assert that the permissions were not changed with check_mode=='yes' + assert: + that: + - "'GRANT SELECT, INSERT ON `data1`.*' in result.stdout" + when: enable_check_mode == 'yes' + + - name: Assert that the permissions were not changed with check_mode=='no' + assert: + that: + - "'GRANT SELECT ON `data1`.*' in result.stdout" + when: enable_check_mode == 'no' + + - name: trigger failure by trying to subtract and append privileges at the same time + mysql_role: + <<: *mysql_params + name: '{{ role2 }}' + priv: 'data1.*:SELECT' + subtract_privs: yes + append_privs: yes + state: present + check_mode: '{{ enable_check_mode }}' + register: result + ignore_errors: true + + - name: Assert the previous execution failed + assert: + that: + - result is failed + + - name: Run command to show privileges for role (expect privileges in stdout) + command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ role2 }}'\"" + register: result + + - name: Assert that the permissions stayed the same, with check_mode=='yes' + assert: + that: + - "'GRANT SELECT, INSERT ON `data1`.*' in result.stdout" + when: enable_check_mode == 'yes' + + - name: Assert that the permissions stayed the same, with check_mode=='no' + assert: + that: + - "'GRANT SELECT ON `data1`.*' in result.stdout" + when: enable_check_mode == 'no' + + ########## + # Clean up + - name: Drop test databases + mysql_db: + <<: *mysql_params + name: '{{ item }}' + state: present + loop: + - data1 + + - name: Drop test role + mysql_role: + <<: *mysql_params + name: '{{ role2 }}' + state: absent diff --git a/tests/integration/targets/test_mysql_user/tasks/main.yml b/tests/integration/targets/test_mysql_user/tasks/main.yml index e949fe6..645ea6c 100644 --- a/tests/integration/targets/test_mysql_user/tasks/main.yml +++ b/tests/integration/targets/test_mysql_user/tasks/main.yml @@ -274,6 +274,11 @@ - include: test_priv_append.yml enable_check_mode=no - include: test_priv_append.yml enable_check_mode=yes + # Test that subtract_privs will only revoke the grants given by priv + # (https://github.com/ansible-collections/community.mysql/issues/331) + - include: test_priv_subtract.yml enable_check_mode=no + - include: test_priv_subtract.yml enable_check_mode=yes + # Tests for the TLS requires dictionary - include: tls_requirements.yml diff --git a/tests/integration/targets/test_mysql_user/tasks/test_priv_subtract.yml b/tests/integration/targets/test_mysql_user/tasks/test_priv_subtract.yml new file mode 100644 index 0000000..c8d08c7 --- /dev/null +++ b/tests/integration/targets/test_mysql_user/tasks/test_priv_subtract.yml @@ -0,0 +1,173 @@ +# Test code to ensure that subtracting privileges will not result in unnecessary changes. +- 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: Create test databases + mysql_db: + <<: *mysql_params + name: '{{ item }}' + state: present + loop: + - data1 + + - name: Create a user with an initial set of privileges + mysql_user: + <<: *mysql_params + name: '{{ user_name_4 }}' + password: '{{ user_password_4 }}' + priv: 'data1.*:SELECT,INSERT' + state: present + + - name: Run command to show privileges for user (expect privileges in stdout) + command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ user_name_4 }}'@'localhost'\"" + register: result + + - name: Assert that the initial set of privileges matches what is expected + assert: + that: + - "'GRANT SELECT, INSERT ON `data1`.*' in result.stdout" + + - name: Subtract privileges that are not in the current privileges, which should be a no-op + mysql_user: + <<: *mysql_params + name: '{{ user_name_4 }}' + password: '{{ user_password_4 }}' + priv: 'data1.*:DELETE' + subtract_privs: yes + state: present + check_mode: '{{ enable_check_mode }}' + register: result + + - name: Assert that there wasn't a change in permissions + assert: + that: + - "result.changed == false" + + - name: Run command to show privileges for user (expect privileges in stdout) + command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ user_name_4 }}'@'localhost'\"" + register: result + + - name: Assert that the permissions still match what was originally granted + assert: + that: + - "'GRANT SELECT, INSERT ON `data1`.*' in result.stdout" + + - name: Subtract existing and not-existing privileges, but not all + mysql_user: + <<: *mysql_params + name: '{{ user_name_4 }}' + password: '{{ user_password_4 }}' + priv: 'data1.*:INSERT,DELETE' + subtract_privs: yes + state: present + check_mode: '{{ enable_check_mode }}' + register: result + + - name: Assert that there was a change because permissions were/would be revoked on data1.* + assert: + that: + - "result.changed == true" + + - name: Run command to show privileges for user (expect privileges in stdout) + command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ user_name_4 }}'@'localhost'\"" + register: result + + - name: Assert that the permissions were not changed if check_mode is set to 'yes' + assert: + that: + - "'GRANT SELECT, INSERT ON `data1`.*' in result.stdout" + when: enable_check_mode == 'yes' + + - name: Assert that only DELETE was revoked if check_mode is set to 'no' + assert: + that: + - "'GRANT SELECT ON `data1`.*' in result.stdout" + when: enable_check_mode == 'no' + + - name: Try to subtract invalid privileges + mysql_user: + <<: *mysql_params + name: '{{ user_name_4 }}' + password: '{{ user_password_4 }}' + priv: 'data1.*:INVALID' + subtract_privs: yes + state: present + check_mode: '{{ enable_check_mode }}' + register: result + + - name: Assert that there was no change because invalid permissions are ignored + assert: + that: + - "result.changed == false" + + - name: Run command to show privileges for user (expect privileges in stdout) + command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ user_name_4 }}'@'localhost'\"" + register: result + + - name: Assert that the permissions were not changed with check_mode=='yes' + assert: + that: + - "'GRANT SELECT, INSERT ON `data1`.*' in result.stdout" + when: enable_check_mode == 'yes' + + - name: Assert that the permissions were not changed with check_mode=='no' + assert: + that: + - "'GRANT SELECT ON `data1`.*' in result.stdout" + when: enable_check_mode == 'no' + + - name: trigger failure by trying to subtract and append privileges at the same time + mysql_user: + <<: *mysql_params + name: '{{ user_name_4 }}' + password: '{{ user_password_4 }}' + priv: 'data1.*:SELECT' + subtract_privs: yes + append_privs: yes + state: present + check_mode: '{{ enable_check_mode }}' + register: result + ignore_errors: true + + - name: Assert the previous execution failed + assert: + that: + - result is failed + + - name: Run command to show privileges for user (expect privileges in stdout) + command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ user_name_4 }}'@'localhost'\"" + register: result + + - name: Assert that the permissions stayed the same, with check_mode=='yes' + assert: + that: + - "'GRANT SELECT, INSERT ON `data1`.*' in result.stdout" + when: enable_check_mode == 'yes' + + - name: Assert that the permissions stayed the same, with check_mode=='no' + assert: + that: + - "'GRANT SELECT ON `data1`.*' in result.stdout" + when: enable_check_mode == 'no' + + ########## + # Clean up + - name: Drop test databases + mysql_db: + <<: *mysql_params + name: '{{ item }}' + state: present + loop: + - data1 + + - name: Drop test user + mysql_user: + <<: *mysql_params + name: '{{ user_name_4 }}' + state: absent diff --git a/tests/integration/targets/test_mysql_user/tasks/test_privs.yml b/tests/integration/targets/test_mysql_user/tasks/test_privs.yml index 89d9358..68025ac 100644 --- a/tests/integration/targets/test_mysql_user/tasks/test_privs.yml +++ b/tests/integration/targets/test_mysql_user/tasks/test_privs.yml @@ -173,7 +173,7 @@ state: present register: result - # FIXME: on mariadb 10.5 there's always a change + # FIXME: on mariadb >=10.5.2 there's always a change because the REPLICATION CLIENT privilege was renamed to BINLOG MONITOR - name: Assert that priv did not change assert: that: From 8d114c7e39d8ec8b9b3592e6bb9e259db92af6e3 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Thu, 12 May 2022 10:36:21 +0300 Subject: [PATCH 076/217] mysql_user: fix parsing privs when a user has roles assigned to it (#341) * mysql_user: fix parsing errors when a user has roles assigned * Add a changelog fragment * Fix a typo * Fix CI --- .gitignore | 1 + ...ser_fix_pars_users_with_roles_assigned.yml | 2 + plugins/module_utils/user.py | 11 +++ .../tasks/mysql_role_initial.yml | 21 ++++ .../targets/test_mysql_user/tasks/main.yml | 3 + .../test_user_grants_with_roles_applied.yml | 95 +++++++++++++++++++ 6 files changed, 133 insertions(+) create mode 100644 changelogs/fragments/001-mysql_user_fix_pars_users_with_roles_assigned.yml create mode 100644 tests/integration/targets/test_mysql_user/tasks/test_user_grants_with_roles_applied.yml diff --git a/.gitignore b/.gitignore index f440722..6bbe85a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ /tests/output/ /changelogs/.plugin-cache.yaml +*.swp # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/changelogs/fragments/001-mysql_user_fix_pars_users_with_roles_assigned.yml b/changelogs/fragments/001-mysql_user_fix_pars_users_with_roles_assigned.yml new file mode 100644 index 0000000..121bc46 --- /dev/null +++ b/changelogs/fragments/001-mysql_user_fix_pars_users_with_roles_assigned.yml @@ -0,0 +1,2 @@ +bugfixes: +- mysql_user - fix parsing privs when a user has roles assigned (https://github.com/ansible-collections/community.mysql/issues/231). diff --git a/plugins/module_utils/user.py b/plugins/module_utils/user.py index 35f701d..dd0509b 100644 --- a/plugins/module_utils/user.py +++ b/plugins/module_utils/user.py @@ -429,8 +429,19 @@ def privileges_get(cursor, user, host, maria_role=False): res = re.match("""GRANT (.+) ON (.+) TO (['`"]).*\\3@(['`"]).*\\4( IDENTIFIED BY PASSWORD (['`"]).+\\6)? ?(.*)""", grant[0]) else: res = re.match("""GRANT (.+) ON (.+) TO (['`"]).*\\3""", grant[0]) + if res is None: + # If a user has roles assigned, we'll have one of priv tuples looking like + # GRANT `admin`@`%` TO `user1`@`localhost` + # which will result None as res value. + # As we use the mysql_role module to manipulate roles + # we just ignore such privs below: + res = re.match("""GRANT (.+) TO (['`"]).*""", grant[0]) + if not maria_role and res: + continue + raise InvalidPrivsError('unable to parse the MySQL grant string: %s' % grant[0]) + privileges = res.group(1).split(",") privileges = [pick(x.strip()) for x in privileges] diff --git a/tests/integration/targets/test_mysql_role/tasks/mysql_role_initial.yml b/tests/integration/targets/test_mysql_role/tasks/mysql_role_initial.yml index 1bca3ae..a2167c6 100644 --- a/tests/integration/targets/test_mysql_role/tasks/mysql_role_initial.yml +++ b/tests/integration/targets/test_mysql_role/tasks/mysql_role_initial.yml @@ -1540,3 +1540,24 @@ - '{{ test_db }}' - '{{ test_db1 }}' - '{{ test_db2 }}' + + - name: Drop users + <<: *task_params + mysql_user: + <<: *mysql_params + name: '{{ item }}' + state: absent + loop: + - '{{ user0 }}' + - '{{ user1 }}' + - '{{ user2 }}' + + - name: Drop roles + <<: *task_params + mysql_role: + <<: *mysql_params + name: '{{ item }}' + state: absent + loop: + - '{{ role0 }}' + - test diff --git a/tests/integration/targets/test_mysql_user/tasks/main.yml b/tests/integration/targets/test_mysql_user/tasks/main.yml index 645ea6c..1d36b40 100644 --- a/tests/integration/targets/test_mysql_user/tasks/main.yml +++ b/tests/integration/targets/test_mysql_user/tasks/main.yml @@ -293,3 +293,6 @@ # Test that mysql_user still works with force_context enabled (database set to "mysql") # (https://github.com/ansible-collections/community.mysql/issues/265) - include: issue-265.yml + + # https://github.com/ansible-collections/community.mysql/issues/231 + - include: test_user_grants_with_roles_applied.yml diff --git a/tests/integration/targets/test_mysql_user/tasks/test_user_grants_with_roles_applied.yml b/tests/integration/targets/test_mysql_user/tasks/test_user_grants_with_roles_applied.yml new file mode 100644 index 0000000..8ee738e --- /dev/null +++ b/tests/integration/targets/test_mysql_user/tasks/test_user_grants_with_roles_applied.yml @@ -0,0 +1,95 @@ +# https://github.com/ansible-collections/community.mysql/issues/231 +- 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: Get server version + mysql_info: + <<: *mysql_params + register: srv + + # Skip unsupported versions + - meta: end_play + when: srv['version']['major'] < 8 + + - name: Create test databases + mysql_db: + <<: *mysql_params + name: '{{ item }}' + state: present + loop: + - data1 + - data2 + + - name: Create user with privileges + mysql_user: + <<: *mysql_params + name: '{{ user_name_3 }}' + password: '{{ user_password_3 }}' + priv: + "data1.*": "SELECT" + "data2.*": "SELECT" + state: present + + - name: Run command to show privileges for user (expect privileges in stdout) + command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ user_name_3 }}'@'localhost'\"" + register: result + + - name: Assert user has giving privileges + assert: + that: + - "'GRANT SELECT ON `data1`.*' in result.stdout" + - "'GRANT SELECT ON `data2`.*' in result.stdout" + + - name: Create role + mysql_role: + <<: *mysql_params + name: test231 + members: + - '{{ user_name_3 }}@localhost' + + - name: Try to change privs + mysql_user: + <<: *mysql_params + name: '{{ user_name_3 }}' + priv: + "data1.*": "INSERT" + "data2.*": "INSERT" + state: present + + - name: Run command to show privileges for user (expect privileges in stdout) + command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ user_name_3 }}'@'localhost'\"" + register: result + + - name: Assert user has giving privileges + assert: + that: + - "'GRANT INSERT ON `data1`.*' in result.stdout" + - "'GRANT INSERT ON `data2`.*' in result.stdout" + + ########## + # Clean up + - name: Drop test databases + mysql_db: + <<: *mysql_params + name: '{{ item }}' + state: present + loop: + - data1 + - data2 + + - name: Drop test user + mysql_user: + <<: *mysql_params + name: '{{ user_name_3 }}' + state: absent + + - name: Drop test role + mysql_role: + <<: *mysql_params + name: test231 + state: absent From eff87f952bf8adb543bd77eedf7ecd518054f2ca Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Thu, 12 May 2022 11:50:26 +0300 Subject: [PATCH 077/217] Drop support for Ansible 2.9 and ansible-base 2.10 (#343) * Drop support for Ansible 2.9 and ansible-base 2.10 * Improve README --- .github/workflows/ansible-test-plugins.yml | 10 ---------- .github/workflows/ansible-test-roles.yml | 6 ------ README.md | 7 +++---- changelogs/fragments/drop_support_of_2.9-2.10.yml | 2 ++ plugins/module_utils/version.py | 2 +- 5 files changed, 6 insertions(+), 21 deletions(-) create mode 100644 changelogs/fragments/drop_support_of_2.9-2.10.yml diff --git a/.github/workflows/ansible-test-plugins.yml b/.github/workflows/ansible-test-plugins.yml index b7be934..c6363b1 100644 --- a/.github/workflows/ansible-test-plugins.yml +++ b/.github/workflows/ansible-test-plugins.yml @@ -25,8 +25,6 @@ jobs: strategy: matrix: ansible: - - stable-2.9 - - stable-2.10 - stable-2.11 - stable-2.12 - stable-2.13 @@ -62,8 +60,6 @@ jobs: - mariadb_10.3.34 - mariadb_10.5.9 ansible: - - stable-2.9 - - stable-2.10 - stable-2.11 - stable-2.12 - stable-2.13 @@ -80,10 +76,6 @@ jobs: connector: pymysql==0.7.10 - db_engine_version: mariadb_10.5.9 connector: pymysql==0.7.10 - - python: 3.8 - ansible: stable-2.9 - - python: 3.8 - ansible: stable-2.10 - python: 3.8 ansible: stable-2.11 - python: 3.6 @@ -144,8 +136,6 @@ jobs: fail-fast: true matrix: ansible: - - stable-2.9 - - stable-2.10 - stable-2.11 - stable-2.12 - stable-2.13 diff --git a/.github/workflows/ansible-test-roles.yml b/.github/workflows/ansible-test-roles.yml index 5c27416..bda6986 100644 --- a/.github/workflows/ansible-test-roles.yml +++ b/.github/workflows/ansible-test-roles.yml @@ -23,8 +23,6 @@ jobs: mysql: - 2.0.12 ansible: - - stable-2.9 - - stable-2.10 - stable-2.11 - stable-2.12 - stable-2.13 @@ -33,10 +31,6 @@ jobs: - 3.6 - 3.8 exclude: - - python: 3.8 - ansible: stable-2.9 - - python: 3.8 - ansible: stable-2.10 - python: 3.8 ansible: stable-2.11 - python: 3.6 diff --git a/README.md b/README.md index e01d83b..15db6a9 100644 --- a/README.md +++ b/README.md @@ -60,14 +60,13 @@ Every voice is important and every idea is valuable. If you have something on yo - [mysql_variables](https://docs.ansible.com/ansible/latest/collections/community/mysql/mysql_variables_module.html) ## Tested with -### Ansible -- 2.9 -- 2.10 +### ansible-core + - 2.11 - 2.12 - 2.13 -- devel +- current development version ### Databases diff --git a/changelogs/fragments/drop_support_of_2.9-2.10.yml b/changelogs/fragments/drop_support_of_2.9-2.10.yml new file mode 100644 index 0000000..8570210 --- /dev/null +++ b/changelogs/fragments/drop_support_of_2.9-2.10.yml @@ -0,0 +1,2 @@ +major_changes: +- The community.mysql collection no longer supports ``Ansible 2.9`` and ``ansible-base 2.10``. While we take no active measures to prevent usage and there are no plans to introduce incompatible code to the modules, we will stop testing against ``Ansible 2.9`` and ``ansible-base 2.10``. Both will very soon be End of Life and if you are still using them, you should consider upgrading to the ``latest Ansible / ansible-core 2.11 or later`` as soon as possible (https://github.com/ansible-collections/community.mysql/pull/343). diff --git a/plugins/module_utils/version.py b/plugins/module_utils/version.py index 359cceb..9473134 100644 --- a/plugins/module_utils/version.py +++ b/plugins/module_utils/version.py @@ -8,7 +8,7 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type -# Once we drop support for Ansible 2.9, ansible-base 2.10, and ansible-core 2.11, we can +# Once we drop support for ansible-core 2.11, we can # remove the _version.py file, and replace the following import by # # from ansible.module_utils.compat.version import LooseVersion From f57ed38beb46f25aeb37eb33cdce262f8f5ed5b1 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Thu, 12 May 2022 12:42:30 +0300 Subject: [PATCH 078/217] Move CHANGELOG.rst at top level (#349) --- changelogs/CHANGELOG.rst => CHANGELOG.rst | 0 changelogs/config.yaml | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename changelogs/CHANGELOG.rst => CHANGELOG.rst (100%) diff --git a/changelogs/CHANGELOG.rst b/CHANGELOG.rst similarity index 100% rename from changelogs/CHANGELOG.rst rename to CHANGELOG.rst diff --git a/changelogs/config.yaml b/changelogs/config.yaml index 559e6c4..70ab036 100644 --- a/changelogs/config.yaml +++ b/changelogs/config.yaml @@ -1,4 +1,4 @@ -changelog_filename_template: CHANGELOG.rst +changelog_filename_template: ../CHANGELOG.rst changelog_filename_version_depth: 0 changes_file: changelog.yaml changes_format: combined From f62d708bcf1170bfbe7a43239dc187aa420fbd4c Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Fri, 13 May 2022 10:16:24 +0300 Subject: [PATCH 079/217] Release 3.2.0 commit (#355) --- CHANGELOG.rst | 26 ++++++++++++++++ changelogs/changelog.yaml | 30 +++++++++++++++++++ ...ser_fix_pars_users_with_roles_assigned.yml | 2 -- ..._role-add-subtract_privileges-argument.yml | 2 -- ...ql_user_fix_missing_dynamic_privileges.yml | 2 -- .../fragments/drop_support_of_2.9-2.10.yml | 2 -- galaxy.yml | 2 +- 7 files changed, 57 insertions(+), 9 deletions(-) delete mode 100644 changelogs/fragments/001-mysql_user_fix_pars_users_with_roles_assigned.yml delete mode 100644 changelogs/fragments/333-mysql_user-mysql_role-add-subtract_privileges-argument.yml delete mode 100644 changelogs/fragments/338-mysql_user_fix_missing_dynamic_privileges.yml delete mode 100644 changelogs/fragments/drop_support_of_2.9-2.10.yml diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 68a2d2e..dec20f7 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,32 @@ Community MySQL Collection Release Notes This changelog describes changes after version 2.0.0. +v3.2.0 +====== + +Release Summary +--------------- + +This is the minor release of the ``community.mysql`` collection. +This changelog contains all changes to the modules in this collection +that have been added after the release of ``community.mysql`` 3.1.3. + +Major Changes +------------- + +- The community.mysql collection no longer supports ``Ansible 2.9`` and ``ansible-base 2.10``. While we take no active measures to prevent usage and there are no plans to introduce incompatible code to the modules, we will stop testing against ``Ansible 2.9`` and ``ansible-base 2.10``. Both will very soon be End of Life and if you are still using them, you should consider upgrading to the ``latest Ansible / ansible-core 2.11 or later`` as soon as possible (https://github.com/ansible-collections/community.mysql/pull/343). + +Minor Changes +------------- + +- mysql_user and mysql_role: Add the argument ``subtract_privs`` (boolean, default false, mutually exclusive with ``append_privs``). If set, the privileges given in ``priv`` are revoked and existing privileges are kept (https://github.com/ansible-collections/community.mysql/pull/333). + +Bugfixes +-------- + +- mysql_user - fix missing dynamic privileges after revoke and grant privileges to user (https://github.com/ansible-collections/community.mysql/issues/120). +- mysql_user - fix parsing privs when a user has roles assigned (https://github.com/ansible-collections/community.mysql/issues/231). + v3.1.3 ====== diff --git a/changelogs/changelog.yaml b/changelogs/changelog.yaml index ceeb833..9a428c6 100644 --- a/changelogs/changelog.yaml +++ b/changelogs/changelog.yaml @@ -97,3 +97,33 @@ releases: - 307-mysql_user_add_if_exists_to_drop.yml - 329-mysql_role-remove-redudant-connection-closing.yml release_date: '2022-04-26' + 3.2.0: + changes: + bugfixes: + - mysql_user - fix missing dynamic privileges after revoke and grant privileges + to user (https://github.com/ansible-collections/community.mysql/issues/120). + - mysql_user - fix parsing privs when a user has roles assigned (https://github.com/ansible-collections/community.mysql/issues/231). + major_changes: + - The community.mysql collection no longer supports ``Ansible 2.9`` and ``ansible-base + 2.10``. While we take no active measures to prevent usage and there are no + plans to introduce incompatible code to the modules, we will stop testing + against ``Ansible 2.9`` and ``ansible-base 2.10``. Both will very soon be + End of Life and if you are still using them, you should consider upgrading + to the ``latest Ansible / ansible-core 2.11 or later`` as soon as possible + (https://github.com/ansible-collections/community.mysql/pull/343). + minor_changes: + - 'mysql_user and mysql_role: Add the argument ``subtract_privs`` (boolean, + default false, mutually exclusive with ``append_privs``). If set, the privileges + given in ``priv`` are revoked and existing privileges are kept (https://github.com/ansible-collections/community.mysql/pull/333).' + release_summary: 'This is the minor release of the ``community.mysql`` collection. + + This changelog contains all changes to the modules in this collection + + that have been added after the release of ``community.mysql`` 3.1.3.' + fragments: + - 001-mysql_user_fix_pars_users_with_roles_assigned.yml + - 3.2.0.yml + - 333-mysql_user-mysql_role-add-subtract_privileges-argument.yml + - 338-mysql_user_fix_missing_dynamic_privileges.yml + - drop_support_of_2.9-2.10.yml + release_date: '2022-05-13' diff --git a/changelogs/fragments/001-mysql_user_fix_pars_users_with_roles_assigned.yml b/changelogs/fragments/001-mysql_user_fix_pars_users_with_roles_assigned.yml deleted file mode 100644 index 121bc46..0000000 --- a/changelogs/fragments/001-mysql_user_fix_pars_users_with_roles_assigned.yml +++ /dev/null @@ -1,2 +0,0 @@ -bugfixes: -- mysql_user - fix parsing privs when a user has roles assigned (https://github.com/ansible-collections/community.mysql/issues/231). diff --git a/changelogs/fragments/333-mysql_user-mysql_role-add-subtract_privileges-argument.yml b/changelogs/fragments/333-mysql_user-mysql_role-add-subtract_privileges-argument.yml deleted file mode 100644 index 3e6e632..0000000 --- a/changelogs/fragments/333-mysql_user-mysql_role-add-subtract_privileges-argument.yml +++ /dev/null @@ -1,2 +0,0 @@ -minor_changes: - - "mysql_user and mysql_role: Add the argument ``subtract_privs`` (boolean, default false, mutually exclusive with ``append_privs``). If set, the privileges given in ``priv`` are revoked and existing privileges are kept (https://github.com/ansible-collections/community.mysql/pull/333)." diff --git a/changelogs/fragments/338-mysql_user_fix_missing_dynamic_privileges.yml b/changelogs/fragments/338-mysql_user_fix_missing_dynamic_privileges.yml deleted file mode 100644 index 1054ea6..0000000 --- a/changelogs/fragments/338-mysql_user_fix_missing_dynamic_privileges.yml +++ /dev/null @@ -1,2 +0,0 @@ -bugfixes: - - "mysql_user - fix missing dynamic privileges after revoke and grant privileges to user (https://github.com/ansible-collections/community.mysql/issues/120)." \ No newline at end of file diff --git a/changelogs/fragments/drop_support_of_2.9-2.10.yml b/changelogs/fragments/drop_support_of_2.9-2.10.yml deleted file mode 100644 index 8570210..0000000 --- a/changelogs/fragments/drop_support_of_2.9-2.10.yml +++ /dev/null @@ -1,2 +0,0 @@ -major_changes: -- The community.mysql collection no longer supports ``Ansible 2.9`` and ``ansible-base 2.10``. While we take no active measures to prevent usage and there are no plans to introduce incompatible code to the modules, we will stop testing against ``Ansible 2.9`` and ``ansible-base 2.10``. Both will very soon be End of Life and if you are still using them, you should consider upgrading to the ``latest Ansible / ansible-core 2.11 or later`` as soon as possible (https://github.com/ansible-collections/community.mysql/pull/343). diff --git a/galaxy.yml b/galaxy.yml index cbf2b3a..efb541e 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -1,6 +1,6 @@ namespace: community name: mysql -version: 3.1.3 +version: 3.2.0 readme: README.md authors: - Ansible community From b2e476cb1a5b1723238e75e7c674a7abd540d75f Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Mon, 16 May 2022 09:41:48 +0200 Subject: [PATCH 080/217] Add PSF-license.txt file (#356) * Add PSF-license.txt file. * Update with actual CPython 3.9.5 license. --- PSF-license.txt | 48 ++++++++++++++++++++++++++++ changelogs/fragments/psf-license.yml | 2 ++ plugins/module_utils/_version.py | 2 +- 3 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 PSF-license.txt create mode 100644 changelogs/fragments/psf-license.yml diff --git a/PSF-license.txt b/PSF-license.txt new file mode 100644 index 0000000..35acd7f --- /dev/null +++ b/PSF-license.txt @@ -0,0 +1,48 @@ +PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 +-------------------------------------------- + +1. This LICENSE AGREEMENT is between the Python Software Foundation +("PSF"), and the Individual or Organization ("Licensee") accessing and +otherwise using this software ("Python") in source or binary form and +its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, PSF hereby +grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, +analyze, test, perform and/or display publicly, prepare derivative works, +distribute, and otherwise use Python alone or in any derivative version, +provided, however, that PSF's License Agreement and PSF's notice of copyright, +i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, +2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Python Software Foundation; +All Rights Reserved" are retained in Python alone or in any derivative version +prepared by Licensee. + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python. + +4. PSF is making Python available to Licensee on an "AS IS" +basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. Nothing in this License Agreement shall be deemed to create any +relationship of agency, partnership, or joint venture between PSF and +Licensee. This License Agreement does not grant permission to use PSF +trademarks or trade name in a trademark sense to endorse or promote +products or services of Licensee, or any third party. + +8. By copying, installing or otherwise using Python, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. diff --git a/changelogs/fragments/psf-license.yml b/changelogs/fragments/psf-license.yml new file mode 100644 index 0000000..f8fbc0b --- /dev/null +++ b/changelogs/fragments/psf-license.yml @@ -0,0 +1,2 @@ +bugfixes: + - Include ``PSF-license.txt`` file for ``plugins/module_utils/_version.py``. diff --git a/plugins/module_utils/_version.py b/plugins/module_utils/_version.py index 59ee9db..ce02717 100644 --- a/plugins/module_utils/_version.py +++ b/plugins/module_utils/_version.py @@ -3,7 +3,7 @@ # Implements multiple version numbering conventions for the # Python Module Distribution Utilities. # -# PSF License (see licenses/PSF-license.txt or https://opensource.org/licenses/Python-2.0) +# PSF License (see PSF-license.txt or https://opensource.org/licenses/Python-2.0) # """Provides classes to represent module version numbers (one class for From cc950cb53a5b4f75cc3fbe2e4f41b2ff9164bc09 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Tue, 17 May 2022 13:18:41 +0300 Subject: [PATCH 081/217] Release 3.2.1 commit (#363) --- CHANGELOG.rst | 15 +++++++++++++++ changelogs/changelog.yaml | 13 +++++++++++++ changelogs/fragments/psf-license.yml | 2 -- galaxy.yml | 2 +- 4 files changed, 29 insertions(+), 3 deletions(-) delete mode 100644 changelogs/fragments/psf-license.yml diff --git a/CHANGELOG.rst b/CHANGELOG.rst index dec20f7..0e11f40 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,21 @@ Community MySQL Collection Release Notes This changelog describes changes after version 2.0.0. +v3.2.1 +====== + +Release Summary +--------------- + +This is the patch release of the ``community.mysql`` collection. +This changelog contains all changes to the modules in this collection +that have been added after the release of ``community.mysql`` 3.2.0. + +Bugfixes +-------- + +- Include ``PSF-license.txt`` file for ``plugins/module_utils/_version.py``. + v3.2.0 ====== diff --git a/changelogs/changelog.yaml b/changelogs/changelog.yaml index 9a428c6..e128bd9 100644 --- a/changelogs/changelog.yaml +++ b/changelogs/changelog.yaml @@ -127,3 +127,16 @@ releases: - 338-mysql_user_fix_missing_dynamic_privileges.yml - drop_support_of_2.9-2.10.yml release_date: '2022-05-13' + 3.2.1: + changes: + bugfixes: + - Include ``PSF-license.txt`` file for ``plugins/module_utils/_version.py``. + release_summary: 'This is the patch release of the ``community.mysql`` collection. + + This changelog contains all changes to the modules in this collection + + that have been added after the release of ``community.mysql`` 3.2.0.' + fragments: + - 3.2.1.yml + - psf-license.yml + release_date: '2022-05-17' diff --git a/changelogs/fragments/psf-license.yml b/changelogs/fragments/psf-license.yml deleted file mode 100644 index f8fbc0b..0000000 --- a/changelogs/fragments/psf-license.yml +++ /dev/null @@ -1,2 +0,0 @@ -bugfixes: - - Include ``PSF-license.txt`` file for ``plugins/module_utils/_version.py``. diff --git a/galaxy.yml b/galaxy.yml index efb541e..294d37d 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -1,6 +1,6 @@ namespace: community name: mysql -version: 3.2.0 +version: 3.2.1 readme: README.md authors: - Ansible community From c489cf1a37aaf30553714c17db7930aac1b05206 Mon Sep 17 00:00:00 2001 From: Per Lundberg Date: Wed, 18 May 2022 13:52:05 +0300 Subject: [PATCH 082/217] Update CHANGELOG.rst (#364) Fix broken link --- CHANGELOG.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 0e11f40..9897fa2 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -128,7 +128,7 @@ that have been added after the release of ``community.mysql`` 2.3.2. Breaking Changes / Porting Guide -------------------------------- -- mysql_replication - remove ``Is_Slave`` and ``Is_Master`` return values (were replaced with ``Is_Primary`` and ``Is_Replica`` (https://github.com/ansible-collections /community.mysql/issues/145). +- mysql_replication - remove ``Is_Slave`` and ``Is_Master`` return values (were replaced with ``Is_Primary`` and ``Is_Replica`` (https://github.com/ansible-collections/community.mysql/issues/145). - mysql_replication - remove the mode options values containing ``master``/``slave`` and the master_use_gtid option ``slave_pos`` (were replaced with corresponding ``primary``/``replica`` values) (https://github.com/ansible-collections/community.mysql/issues/145). - mysql_user - remove support for the `REQUIRESSL` special privilege as it has ben superseded by the `tls_requires` option (https://github.com/ansible-collections/community.mysql/discussions/121). - mysql_user - validate privileges using database engine directly (https://github.com/ansible-collections/community.mysql/issues/234 https://github.com/ansible-collections/community.mysql/pull/243). Do not validate privileges in this module anymore. From 07a72865f7ef67017e1acb3d0bf898f8273bcec0 Mon Sep 17 00:00:00 2001 From: betanummeric <40263343+betanummeric@users.noreply.github.com> Date: Wed, 25 May 2022 10:16:50 +0200 Subject: [PATCH 083/217] mysql_role: fix and simplify role member detection (#368) * mysql_role: fix and simplify role membership detection * add changelog fragment * Update changelogs/fragments/368-mysql_role-fix-member-detection.yml Co-authored-by: Andrew Klychkov Co-authored-by: Felix Hamme Co-authored-by: Andrew Klychkov --- .../368-mysql_role-fix-member-detection.yml | 6 +++ plugins/modules/mysql_role.py | 49 ++----------------- 2 files changed, 11 insertions(+), 44 deletions(-) create mode 100644 changelogs/fragments/368-mysql_role-fix-member-detection.yml diff --git a/changelogs/fragments/368-mysql_role-fix-member-detection.yml b/changelogs/fragments/368-mysql_role-fix-member-detection.yml new file mode 100644 index 0000000..b7cbd3e --- /dev/null +++ b/changelogs/fragments/368-mysql_role-fix-member-detection.yml @@ -0,0 +1,6 @@ +bugfixes: + - > + mysql_role - in some cases (when "SHOW GRANTS" did not use backticks for quotes), no unwanted members were detached + from the role (and redundant "GRANT" statements were executed for wanted members). This is fixed by querying the + existing role members from the mysql.role_edges (MySQL) or mysql.roles_mapping (MariaDB) tables instead of parsing + the "SHOW GRANTS" output (https://github.com/ansible-collections/community.mysql/pull/368). diff --git a/plugins/modules/mysql_role.py b/plugins/modules/mysql_role.py index d036541..ffff026 100644 --- a/plugins/modules/mysql_role.py +++ b/plugins/modules/mysql_role.py @@ -896,50 +896,11 @@ class Role(): Returns: set: Members. """ - members = set() - - for user, host in self.server.get_users(): - # Don't handle itself - if user == self.name and host == self.host: - continue - - grants = self.server.get_grants(user, host) - - if self.__is_member(grants): - members.add((user, host)) - - return members - - def __is_member(self, grants): - """Check if a user / role is a member of a role. - - To check if a user is a member of a role, - we parse their grants looking for the role name in them. - In the following grants, we can see that test@% is a member of readers. - +---------------------------------------------------+ - | Grants for test@% | - +---------------------------------------------------+ - | GRANT SELECT, INSERT, UPDATE ON *.* TO `test`@`%` | - | GRANT ALL PRIVILEGES ON `mysql`.* TO `test`@`%` | - | GRANT INSERT ON `mysql`.`user` TO `test`@`%` | - | GRANT `readers`@`%` TO `test`@`%` | - +---------------------------------------------------+ - - Args: - grants (list): Grants of a user to parse. - - Returns: - bool: True if the self.full_name has been found in grants, - otherwise returns False. - """ - if not grants: - return False - - for grant in grants: - if self.full_name in grant[0]: - return True - - return False + if self.is_mariadb: + self.cursor.execute('select user, host from mysql.roles_mapping where role = %s', (self.name,)) + else: + self.cursor.execute('select TO_USER as user, TO_HOST as host from mysql.role_edges where FROM_USER = %s', (self.name,)) + return set(self.cursor.fetchall()) def main(): From ceda7662d0282d7973c7d0d983bbb963048eb9a4 Mon Sep 17 00:00:00 2001 From: betanummeric <40263343+betanummeric@users.noreply.github.com> Date: Wed, 25 May 2022 11:47:39 +0200 Subject: [PATCH 084/217] mysql_role: don't add members to a role when creating the role and "detach_members: true" is set (#367) * mysql_role: don't add members to a role when creating the role and "detach_members: true" is set, add integration test * add changelog fragment * mysql_role: add author betanummeric * Update changelogs/fragments/367-mysql_role-fix-deatch-members.yml Co-authored-by: Andrew Klychkov Co-authored-by: Felix Hamme Co-authored-by: Andrew Klychkov --- .../367-mysql_role-fix-deatch-members.yml | 2 ++ plugins/modules/mysql_role.py | 3 +++ .../targets/test_mysql_role/defaults/main.yml | 1 + .../tasks/mysql_role_initial.yml | 27 +++++++++++++++++++ 4 files changed, 33 insertions(+) create mode 100644 changelogs/fragments/367-mysql_role-fix-deatch-members.yml diff --git a/changelogs/fragments/367-mysql_role-fix-deatch-members.yml b/changelogs/fragments/367-mysql_role-fix-deatch-members.yml new file mode 100644 index 0000000..5a4d414 --- /dev/null +++ b/changelogs/fragments/367-mysql_role-fix-deatch-members.yml @@ -0,0 +1,2 @@ +bugfixes: + - "mysql_role - don't add members to a role when creating the role and ``detach_members: true`` is set (https://github.com/ansible-collections/community.mysql/pull/367)." diff --git a/plugins/modules/mysql_role.py b/plugins/modules/mysql_role.py index ffff026..8265f9a 100644 --- a/plugins/modules/mysql_role.py +++ b/plugins/modules/mysql_role.py @@ -128,6 +128,7 @@ seealso: author: - Andrew Klychkov (@Andersson007) + - Felix Hamme (@betanummeric) extends_documentation_fragment: - community.mysql.mysql @@ -1028,6 +1029,8 @@ def main(): if not role.exists: if subtract_privs: priv = None # avoid granting unwanted privileges + if detach_members: + members = None # avoid adding unwanted members changed = role.add(members, priv, module.check_mode, admin, set_default_role_all) diff --git a/tests/integration/targets/test_mysql_role/defaults/main.yml b/tests/integration/targets/test_mysql_role/defaults/main.yml index 53544bf..544f098 100644 --- a/tests/integration/targets/test_mysql_role/defaults/main.yml +++ b/tests/integration/targets/test_mysql_role/defaults/main.yml @@ -15,3 +15,4 @@ nonexistent: user3 role0: role0 role1: role1 role2: role2 +role3: role3 \ No newline at end of file diff --git a/tests/integration/targets/test_mysql_role/tasks/mysql_role_initial.yml b/tests/integration/targets/test_mysql_role/tasks/mysql_role_initial.yml index a2167c6..95616df 100644 --- a/tests/integration/targets/test_mysql_role/tasks/mysql_role_initial.yml +++ b/tests/integration/targets/test_mysql_role/tasks/mysql_role_initial.yml @@ -1248,6 +1248,32 @@ that: - result is not changed + - name: '"detach" users when creating a new role' + <<: *task_params + mysql_role: + <<: *mysql_params + name: '{{ role3 }}' + state: present + detach_members: yes + members: + - '{{ user1 }}@localhost' + + - name: Check the role was created + assert: + that: + - result is changed + + - name: Check grants + <<: *task_params + mysql_query: + <<: *mysql_params + query: "SHOW GRANTS FOR {{ user1 }}@localhost" + + - name: asssert detach_members did not add a user to the role + assert: + that: + - "'{{ role3 }}' not in result.query_result.0.0['Grants for {{ user1 }}@localhost']" + # ########## # Test privs # ########## @@ -1561,3 +1587,4 @@ loop: - '{{ role0 }}' - test + - '{{ role3 }}' From 647461010db5af4fb68164eeacbfda3dce6a41c8 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Wed, 25 May 2022 17:19:31 +0300 Subject: [PATCH 085/217] mysql_query: fix false change reports when IF NOT EXISTS clause is used (#322) * mysql_query: fix false change reports when IF NOT EXISTS clause is used * Fix * Fix doc, add fragment * Improve doc --- ...22-mysql_query_fix_false_change_report.yml | 2 ++ plugins/modules/mysql_query.py | 32 +++++++++++++++--- .../tasks/mysql_query_initial.yml | 33 +++++++++++++++++++ 3 files changed, 63 insertions(+), 4 deletions(-) create mode 100644 changelogs/fragments/322-mysql_query_fix_false_change_report.yml diff --git a/changelogs/fragments/322-mysql_query_fix_false_change_report.yml b/changelogs/fragments/322-mysql_query_fix_false_change_report.yml new file mode 100644 index 0000000..db53922 --- /dev/null +++ b/changelogs/fragments/322-mysql_query_fix_false_change_report.yml @@ -0,0 +1,2 @@ +bugfixes: +- mysql_query - fix false change reports when ``IF EXISTS/IF NOT EXISTS`` clause is used (https://github.com/ansible-collections/community.mysql/issues/268). diff --git a/plugins/modules/mysql_query.py b/plugins/modules/mysql_query.py index fc789c5..a91335b 100644 --- a/plugins/modules/mysql_query.py +++ b/plugins/modules/mysql_query.py @@ -22,6 +22,10 @@ options: description: - SQL query to run. Multiple queries can be passed using YAML list syntax. - Must be a string or YAML list containing strings. + - Note that if you use the C(IF EXISTS/IF NOT EXISTS) clauses in your query + and C(mysqlclient) connector, the module will report that + the state has been changed even if it has not. If it is important in your + workflow, use the C(PyMySQL) connector instead. type: raw required: yes positional_args: @@ -103,6 +107,8 @@ rowcount: sample: [5, 1] ''' +import warnings + from ansible.module_utils.basic import AnsibleModule from ansible_collections.community.mysql.plugins.module_utils.mysql import ( mysql_connect, @@ -196,9 +202,22 @@ def main(): executed_queries = [] rowcount = [] + already_exists = False for q in query: try: - cursor.execute(q, arguments) + with warnings.catch_warnings(): + warnings.filterwarnings(action='error', + message='.*already exists*', + category=mysql_driver.Warning) + + try: + cursor.execute(q, arguments) + except mysql_driver.Warning: + # When something is run with IF NOT EXISTS + # and there's "already exists" MySQL warning, + # set the flag as True. + # PyMySQL throws the warning, mysqlclinet does NOT. + already_exists = True except Exception as e: if not autocommit: @@ -208,7 +227,8 @@ def main(): module.fail_json(msg="Cannot execute SQL '%s' args [%s]: %s" % (q, arguments, to_native(e))) try: - query_result.append([dict(row) for row in cursor.fetchall()]) + if not already_exists: + query_result.append([dict(row) for row in cursor.fetchall()]) except Exception as e: if not autocommit: @@ -224,8 +244,12 @@ def main(): for keyword in DDL_QUERY_KEYWORDS: if keyword in q: - changed = True - + if already_exists: + # Indicates the entity already exists + changed = False + already_exists = False # Reset flag + else: + changed = True try: executed_queries.append(cursor._last_executed) except AttributeError: diff --git a/tests/integration/targets/test_mysql_query/tasks/mysql_query_initial.yml b/tests/integration/targets/test_mysql_query/tasks/mysql_query_initial.yml index 30182fe..2d971ab 100644 --- a/tests/integration/targets/test_mysql_query/tasks/mysql_query_initial.yml +++ b/tests/integration/targets/test_mysql_query/tasks/mysql_query_initial.yml @@ -321,6 +321,39 @@ - result is changed - result.rowcount == [2] + # Issue https://github.com/ansible-collections/community.mysql/issues/268 + - name: Create table + mysql_query: + <<: *mysql_params + login_db: '{{ test_db }}' + query: "CREATE TABLE issue268 (id int)" + single_transaction: yes + + # Issue https://github.com/ansible-collections/community.mysql/issues/268 + - name: Create table with IF NOT EXISTS + mysql_query: + <<: *mysql_params + login_db: '{{ test_db }}' + query: "CREATE TABLE IF NOT EXISTS issue268 (id int)" + single_transaction: yes + register: result + + # Issue https://github.com/ansible-collections/community.mysql/issues/268 + - assert: + that: + # PyMySQL driver throws a warning, so the following is correct + - result is not changed + when: connector.name.0 is search('pymysql') + + # Issue https://github.com/ansible-collections/community.mysql/issues/268 + - assert: + that: + # mysqlclient driver throws nothing, so it's impossible to figure out + # if the state was changed or not. + # We assume that it was for DDL queryes by default in the code + - result is changed + when: connector.name.0 is search('mysqlclient') + - name: Drop db {{ test_db }} mysql_query: <<: *mysql_params From bf5086d19d377e6c2ad976aa43304bd34d1879ae Mon Sep 17 00:00:00 2001 From: betanummeric <40263343+betanummeric@users.noreply.github.com> Date: Fri, 27 May 2022 12:11:17 +0200 Subject: [PATCH 086/217] mysql_role: add argument "members_must_exist" (#369) * mysql_role: add argument "members_must_exist" (boolean, default true) The assertion that the users supplied in the "members" argument exist is only executed when the new argument "members_must_exist" is true, to allow opt-out. * mysql_role: add integration tests for argument members_must_exist * add changelog fragment * mysql_role: fix behavior of members_must_exist argument * Update plugins/modules/mysql_role.py Co-authored-by: Andrew Klychkov * Update changelogs/fragments/369_mysql_role-add-members_must_exist.yml Co-authored-by: Andrew Klychkov Co-authored-by: Felix Hamme Co-authored-by: Andrew Klychkov --- .../369_mysql_role-add-members_must_exist.yml | 4 ++ plugins/modules/mysql_role.py | 19 +++++- .../tasks/mysql_role_initial.yml | 65 +++++++++++++++++++ 3 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 changelogs/fragments/369_mysql_role-add-members_must_exist.yml diff --git a/changelogs/fragments/369_mysql_role-add-members_must_exist.yml b/changelogs/fragments/369_mysql_role-add-members_must_exist.yml new file mode 100644 index 0000000..c2d420c --- /dev/null +++ b/changelogs/fragments/369_mysql_role-add-members_must_exist.yml @@ -0,0 +1,4 @@ +minor_changes: + - > + mysql_role - add the argument ``members_must_exist`` (boolean, default true). The assertion that the users supplied in + the ``members`` argument exist is only executed when the new argument ``members_must_exist`` is ``true``, to allow opt-out (https://github.com/ansible-collections/community.mysql/pull/369). diff --git a/plugins/modules/mysql_role.py b/plugins/modules/mysql_role.py index 8265f9a..97fabe8 100644 --- a/plugins/modules/mysql_role.py +++ b/plugins/modules/mysql_role.py @@ -114,6 +114,13 @@ options: type: bool default: no + members_must_exist: + description: + - When C(yes), the module fails if any user in I(members) does not exist. + - When C(no), users in I(members) which don't exist are simply skipped. + type: bool + default: yes + notes: - Pay attention that the module runs C(SET DEFAULT ROLE ALL TO) all the I(members) passed by default when the state has changed. @@ -382,6 +389,11 @@ class DbServer(): msg = 'User / role `%s` with host `%s` does not exist' % (user[0], user[1]) self.module.fail_json(msg=msg) + def filter_existing_users(self, users): + for user in users: + if user in self.users: + yield user + def __get_users(self): """Get users. @@ -918,6 +930,7 @@ def main(): detach_members=dict(type='bool', default=False), check_implicit_admin=dict(type='bool', default=False), set_default_role_all=dict(type='bool', default=True), + members_must_exist=dict(type='bool', default=True) ) module = AnsibleModule( argument_spec=argument_spec, @@ -951,6 +964,7 @@ def main(): check_hostname = module.params['check_hostname'] db = '' set_default_role_all = module.params['set_default_role_all'] + members_must_exist = module.params['members_must_exist'] if priv and not isinstance(priv, (str, dict)): msg = ('The "priv" parameter must be str or dict ' @@ -1019,7 +1033,10 @@ def main(): if members: members = normalize_users(module, members, server.is_mariadb()) - server.check_users_in_db(members) + if members_must_exist: + server.check_users_in_db(members) + else: + members = list(server.filter_existing_users(members)) # Main job starts here role = Role(module, cursor, name, server) diff --git a/tests/integration/targets/test_mysql_role/tasks/mysql_role_initial.yml b/tests/integration/targets/test_mysql_role/tasks/mysql_role_initial.yml index 95616df..8c81a75 100644 --- a/tests/integration/targets/test_mysql_role/tasks/mysql_role_initial.yml +++ b/tests/integration/targets/test_mysql_role/tasks/mysql_role_initial.yml @@ -1274,6 +1274,71 @@ that: - "'{{ role3 }}' not in result.query_result.0.0['Grants for {{ user1 }}@localhost']" + # test members_must_exist + - name: try failing on not-existing user in check-mode + <<: *task_params + mysql_role: + <<: *mysql_params + name: '{{ role0 }}' + state: present + members_must_exist: yes + append_members: yes + members: + - 'not_existent@localhost' + ignore_errors: yes + check_mode: yes + - name: assert failure + assert: + that: + - result is failed + + - name: try failing on not-existing user in check-mode + <<: *task_params + mysql_role: + <<: *mysql_params + name: '{{ role0 }}' + state: present + members_must_exist: no + append_members: yes + members: + - 'not_existent@localhost' + check_mode: yes + - name: Check for lack of change + assert: + that: + - result is not changed + + - name: try failing on not-existing user + <<: *task_params + mysql_role: + <<: *mysql_params + name: '{{ role0 }}' + state: present + members_must_exist: yes + append_members: yes + members: + - 'not_existent@localhost' + ignore_errors: yes + - name: assert failure + assert: + that: + - result is failed + + - name: try failing on not-existing user + <<: *task_params + mysql_role: + <<: *mysql_params + name: '{{ role0 }}' + state: present + members_must_exist: no + append_members: yes + members: + - 'not_existent@localhost' + - name: Check for lack of change + assert: + that: + - result is not changed + # ########## # Test privs # ########## From 05eccd9a1d98e213ad904b102a44164bd1d50d06 Mon Sep 17 00:00:00 2001 From: betanummeric <40263343+betanummeric@users.noreply.github.com> Date: Mon, 30 May 2022 09:59:20 +0200 Subject: [PATCH 087/217] mysql_role: add examples for "members_must_exist" argument (#376) * mysql_role: add examples for "members_must_exist" argument * mysql_role: fix syntax in example * Update plugins/modules/mysql_role.py Co-authored-by: Andrew Klychkov * Update plugins/modules/mysql_role.py Co-authored-by: Andrew Klychkov Co-authored-by: Felix Hamme Co-authored-by: Andrew Klychkov --- plugins/modules/mysql_role.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/plugins/modules/mysql_role.py b/plugins/modules/mysql_role.py index 97fabe8..790c0eb 100644 --- a/plugins/modules/mysql_role.py +++ b/plugins/modules/mysql_role.py @@ -258,6 +258,26 @@ EXAMPLES = r''' subtract_privs: yes priv: 'db1.*': DELETE + +- name: Add some members to a role and skip not-existent users + community.mysql.mysql_role: + state: present + name: foo + append_members: yes + members_must_exist: no + members: + - 'existing_user@localhost' + - 'not_existing_user@localhost' + +- name: Detach some members from a role and ignore not-existent users + community.mysql.mysql_role: + state: present + name: foo + detach_members: yes + members_must_exist: no + members: + - 'existing_user@localhost' + - 'not_existing_user@localhost' ''' RETURN = '''#''' From 51a38840d977e6184be3ef1d6f427c8cb3dc4545 Mon Sep 17 00:00:00 2001 From: hubiongithub <79990207+hubiongithub@users.noreply.github.com> Date: Tue, 31 May 2022 07:40:32 +0200 Subject: [PATCH 088/217] =?UTF-8?q?mysql=5Fuser:=20prevent=20password=20ge?= =?UTF-8?q?tting=20set=20for=20existing=20users=20on=20on=5Fcre=E2=80=A6?= =?UTF-8?q?=20(#342)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * mysql_user: prevent password getting set for existing users on on_create when plugin is used * added changelog fragment * format fix * added substract_privs, to t list of arguments * clarify the documetation * additional documentation to password,plugin,plugin_hash_string,plugin_auth_string options, format fix on changelog * Update plugins/modules/mysql_user.py Co-authored-by: Andrew Klychkov * Update plugins/modules/mysql_user.py Co-authored-by: Andrew Klychkov * Update plugins/modules/mysql_user.py Co-authored-by: Andrew Klychkov * linting * linting * linting * linting Co-authored-by: Andrew Klychkov --- .../fragments/334-mysql_user_fix_logic_on_oncreate.yml | 2 ++ plugins/modules/mysql_user.py | 9 +++++---- 2 files changed, 7 insertions(+), 4 deletions(-) create mode 100644 changelogs/fragments/334-mysql_user_fix_logic_on_oncreate.yml diff --git a/changelogs/fragments/334-mysql_user_fix_logic_on_oncreate.yml b/changelogs/fragments/334-mysql_user_fix_logic_on_oncreate.yml new file mode 100644 index 0000000..4ac88a1 --- /dev/null +++ b/changelogs/fragments/334-mysql_user_fix_logic_on_oncreate.yml @@ -0,0 +1,2 @@ +bugfixes: + - "mysql_user - fix logic when ``update_password`` is set to ``on_create`` for users using ``plugin*`` arguments (https://github.com/ansible-collections/community.mysql/issues/334). The ``on_create`` sets ``password`` to None for old mysql_native_authentication but not for authentiation methods which uses the ``plugin*`` arguments. This PR changes this so ``on_create`` also exchange ``plugin``, ``plugin_hash_string``, ``plugin_auth_string`` to None in the list of arguments to change" diff --git a/plugins/modules/mysql_user.py b/plugins/modules/mysql_user.py index 9299eaf..292179a 100644 --- a/plugins/modules/mysql_user.py +++ b/plugins/modules/mysql_user.py @@ -22,7 +22,8 @@ options: required: true password: description: - - Set the user's password. + - Set the user's password. Only for C(mysql_native_password) authentication. + For other authentication plugins see the combination of I(plugin), I(plugin_hash_string), I(plugin_auth_string). type: str encrypted: description: @@ -115,8 +116,8 @@ options: default: no update_password: description: - - C(always) will update passwords if they differ. - - C(on_create) will only set the password for newly created users. + - C(always) will update passwords if they differ. This affects I(password) and the combination of I(plugin), I(plugin_hash_string), I(plugin_auth_string). + - C(on_create) will only set the password or the combination of plugin, plugin_hash_string, plugin_auth_string for newly created users. type: str choices: [ always, on_create ] default: always @@ -456,7 +457,7 @@ def main(): priv, append_privs, subtract_privs, tls_requires, module) else: changed, msg = user_mod(cursor, user, host, host_all, None, encrypted, - plugin, plugin_hash_string, plugin_auth_string, + None, None, None, priv, append_privs, subtract_privs, tls_requires, module) except (SQLParseError, InvalidPrivsError, mysql_driver.Error) as e: From ed3935abec07696b4f171ab5b7475735452f6d26 Mon Sep 17 00:00:00 2001 From: betanummeric <40263343+betanummeric@users.noreply.github.com> Date: Tue, 31 May 2022 16:00:24 +0200 Subject: [PATCH 089/217] mysql_user: add "update_password: on_new_username" argument, "password_changed" result field (#365) * mysql_user: add value 'on_new_username' to argument 'update_password' * mysql_user: return "password_changed" boolean (true if the user got a new password) * mysql_user: optimize queries for existing passwords * mysql_user: add integration tests for update_password argument * mysql_user: add description for "update_password: on_new_username" argument * add changelog fragment * formatting (PEP8) * Update changelogs/fragments/365-mysql_user-add-on_new_username-and-password_changed.yml Co-authored-by: Benjamin MALYNOVYTCH * Update changelogs/fragments/365-mysql_user-add-on_new_username-and-password_changed.yml Co-authored-by: Benjamin MALYNOVYTCH * Update plugins/modules/mysql_user.py Co-authored-by: Andrew Klychkov * Update changelogs/fragments/365-mysql_user-add-on_new_username-and-password_changed.yml Co-authored-by: Andrew Klychkov * Update changelogs/fragments/365-mysql_user-add-on_new_username-and-password_changed.yml Co-authored-by: Andrew Klychkov Co-authored-by: Felix Hamme Co-authored-by: Benjamin MALYNOVYTCH Co-authored-by: Andrew Klychkov --- ...d-on_new_username-and-password_changed.yml | 10 ++ plugins/module_utils/user.py | 51 +++++-- plugins/modules/mysql_role.py | 9 +- plugins/modules/mysql_user.py | 37 +++-- .../tasks/assert_user_password.yml | 24 ++++ .../tasks/test_update_password.yml | 128 ++++++++++++++++++ 6 files changed, 232 insertions(+), 27 deletions(-) create mode 100644 changelogs/fragments/365-mysql_user-add-on_new_username-and-password_changed.yml create mode 100644 tests/integration/targets/test_mysql_user/tasks/assert_user_password.yml create mode 100644 tests/integration/targets/test_mysql_user/tasks/test_update_password.yml diff --git a/changelogs/fragments/365-mysql_user-add-on_new_username-and-password_changed.yml b/changelogs/fragments/365-mysql_user-add-on_new_username-and-password_changed.yml new file mode 100644 index 0000000..2796776 --- /dev/null +++ b/changelogs/fragments/365-mysql_user-add-on_new_username-and-password_changed.yml @@ -0,0 +1,10 @@ +minor_changes: + - > + mysql_user - Add the option ``on_new_username`` to argument ``update_password`` to reuse the password (plugin and + authentication_string) when creating a new user if some user with the same name already exists. + If the existing user with the same name have varying passwords, the password from the arguments is used like with + ``update_password: always`` (https://github.com/ansible-collections/community.mysql/pull/365). + - > + mysql_user - Add the result field ``password_changed`` (boolean). It is true, when the user got a new password. + When the user was created with ``update_password: on_new_username`` and an existing password was reused, + ``password_changed`` is false (https://github.com/ansible-collections/community.mysql/pull/365). diff --git a/plugins/module_utils/user.py b/plugins/module_utils/user.py index dd0509b..655d847 100644 --- a/plugins/module_utils/user.py +++ b/plugins/module_utils/user.py @@ -112,21 +112,49 @@ def get_grants(cursor, user, host): return grants.split(", ") +def get_existing_authentication(cursor, user): + # Return the plugin and auth_string if there is exactly one distinct existing plugin and auth_string. + cursor.execute("SELECT VERSION()") + if 'mariadb' in cursor.fetchone()[0].lower(): + # before MariaDB 10.2.19 and 10.3.11, "password" and "authentication_string" can differ + # when using mysql_native_password + cursor.execute("""select plugin, auth from ( + select plugin, password as auth from mysql.user where user=%(user)s + union select plugin, authentication_string as auth from mysql.user where user=%(user)s + ) x group by plugin, auth limit 2 + """, {'user': user}) + else: + cursor.execute("""select plugin, authentication_string as auth from mysql.user where user=%(user)s + group by plugin, authentication_string limit 2""", {'user': user}) + rows = cursor.fetchall() + if len(rows) == 1: + return {'plugin': rows[0][0], 'auth_string': rows[0][1]} + return None + + def user_add(cursor, user, host, host_all, password, encrypted, plugin, plugin_hash_string, plugin_auth_string, new_priv, - tls_requires, check_mode): + tls_requires, check_mode, reuse_existing_password): # we cannot create users without a proper hostname if host_all: - return False + return {'changed': False, 'password_changed': False} if check_mode: - return True + return {'changed': True, 'password_changed': None} # Determine what user management method server uses old_user_mgmt = impl.use_old_user_mgmt(cursor) mogrify = do_not_mogrify_requires if old_user_mgmt else mogrify_requires + used_existing_password = False + if reuse_existing_password: + existing_auth = get_existing_authentication(cursor, user) + if existing_auth: + plugin = existing_auth['plugin'] + plugin_hash_string = existing_auth['auth_string'] + password = None + used_existing_password = True if password and encrypted: if impl.supports_identified_by_password(cursor): query_with_args = "CREATE USER %s@%s IDENTIFIED BY PASSWORD %s", (user, host, password) @@ -156,7 +184,7 @@ def user_add(cursor, user, host, host_all, password, encrypted, privileges_grant(cursor, user, host, db_table, priv, tls_requires) if tls_requires is not None: privileges_grant(cursor, user, host, "*.*", get_grants(cursor, user, host), tls_requires) - return True + return {'changed': True, 'password_changed': not used_existing_password} def is_hash(password): @@ -182,6 +210,7 @@ def user_mod(cursor, user, host, host_all, password, encrypted, else: hostnames = [host] + password_changed = False for host in hostnames: # Handle clear text and hashed passwords. if not role: @@ -226,9 +255,10 @@ def user_mod(cursor, user, host, host_all, password, encrypted, encrypted_password = cursor.fetchone()[0] if current_pass_hash != encrypted_password: + password_changed = True msg = "Password updated" if module.check_mode: - return (True, msg) + return {'changed': True, 'msg': msg, 'password_changed': password_changed} if old_user_mgmt: cursor.execute("SET PASSWORD FOR %s@%s = %s", (user, host, encrypted_password)) msg = "Password updated (old style)" @@ -280,6 +310,7 @@ def user_mod(cursor, user, host, host_all, password, encrypted, query_with_args = "ALTER USER %s@%s IDENTIFIED WITH %s", (user, host, plugin) cursor.execute(*query_with_args) + password_changed = True changed = True # Handle privileges @@ -297,7 +328,7 @@ def user_mod(cursor, user, host, host_all, password, encrypted, if user != "root" and "PROXY" not in priv: msg = "Privileges updated" if module.check_mode: - return (True, msg) + return {'changed': True, 'msg': msg, 'password_changed': password_changed} privileges_revoke(cursor, user, host, db_table, priv, grant_option, maria_role) changed = True @@ -308,7 +339,7 @@ def user_mod(cursor, user, host, host_all, password, encrypted, if db_table not in curr_priv: msg = "New privileges granted" if module.check_mode: - return (True, msg) + return {'changed': True, 'msg': msg, 'password_changed': password_changed} privileges_grant(cursor, user, host, db_table, priv, tls_requires, maria_role) changed = True @@ -338,7 +369,7 @@ def user_mod(cursor, user, host, host_all, password, encrypted, if len(grant_privs) + len(revoke_privs) > 0: msg = "Privileges updated: granted %s, revoked %s" % (grant_privs, revoke_privs) if module.check_mode: - return (True, msg) + return {'changed': True, 'msg': msg, 'password_changed': password_changed} if len(revoke_privs) > 0: privileges_revoke(cursor, user, host, db_table, revoke_privs, grant_option, maria_role) if len(grant_privs) > 0: @@ -353,7 +384,7 @@ def user_mod(cursor, user, host, host_all, password, encrypted, if current_requires != tls_requires: msg = "TLS requires updated" if module.check_mode: - return (True, msg) + return {'changed': True, 'msg': msg, 'password_changed': password_changed} if not old_user_mgmt: pre_query = "ALTER USER" else: @@ -369,7 +400,7 @@ def user_mod(cursor, user, host, host_all, password, encrypted, cursor.execute(*query_with_args) changed = True - return (changed, msg) + return {'changed': changed, 'msg': msg, 'password_changed': password_changed} def user_delete(cursor, user, host, host_all, check_mode): diff --git a/plugins/modules/mysql_role.py b/plugins/modules/mysql_role.py index 790c0eb..b37d70d 100644 --- a/plugins/modules/mysql_role.py +++ b/plugins/modules/mysql_role.py @@ -911,10 +911,11 @@ class Role(): set_default_role_all=set_default_role_all) if privs: - changed, msg = user_mod(self.cursor, self.name, self.host, - None, None, None, None, None, None, - privs, append_privs, subtract_privs, None, - self.module, role=True, maria_role=self.is_mariadb) + result = user_mod(self.cursor, self.name, self.host, + None, None, None, None, None, None, + privs, append_privs, subtract_privs, None, + self.module, role=True, maria_role=self.is_mariadb) + changed = result['changed'] if admin: self.role_impl.set_admin(admin) diff --git a/plugins/modules/mysql_user.py b/plugins/modules/mysql_user.py index 292179a..c85a910 100644 --- a/plugins/modules/mysql_user.py +++ b/plugins/modules/mysql_user.py @@ -118,8 +118,12 @@ options: description: - C(always) will update passwords if they differ. This affects I(password) and the combination of I(plugin), I(plugin_hash_string), I(plugin_auth_string). - C(on_create) will only set the password or the combination of plugin, plugin_hash_string, plugin_auth_string for newly created users. + - "C(on_new_username) works like C(on_create), but it tries to reuse an existing password: If one different user + with the same username exists, or multiple different users with the same username and equal C(plugin) and + C(authentication_string) attribute, the existing C(plugin) and C(authentication_string) are used for the + new user instead of the I(password), I(plugin), I(plugin_hash_string) or I(plugin_auth_string) argument." type: str - choices: [ always, on_create ] + choices: [ always, on_create, on_new_username ] default: always plugin: description: @@ -370,7 +374,7 @@ def main(): append_privs=dict(type='bool', default=False), subtract_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), + 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), plugin=dict(default=None, type='str'), plugin_hash_string=dict(default=None, type='str'), @@ -447,18 +451,22 @@ def main(): except Exception as e: module.fail_json(msg=to_native(e)) priv = privileges_unpack(priv, mode, ensure_usage=not subtract_privs) - + password_changed = False if state == "present": if user_exists(cursor, user, host, host_all): try: if update_password == "always": - changed, msg = user_mod(cursor, user, host, host_all, password, encrypted, - plugin, plugin_hash_string, plugin_auth_string, - priv, append_privs, subtract_privs, tls_requires, module) + result = user_mod(cursor, user, host, host_all, password, encrypted, + plugin, plugin_hash_string, plugin_auth_string, + priv, append_privs, subtract_privs, tls_requires, module) + else: - changed, msg = user_mod(cursor, user, host, host_all, None, encrypted, - None, None, None, - priv, append_privs, subtract_privs, tls_requires, module) + result = user_mod(cursor, user, host, host_all, None, encrypted, + None, None, None, + priv, append_privs, subtract_privs, tls_requires, module) + changed = result['changed'] + msg = result['msg'] + password_changed = result['password_changed'] except (SQLParseError, InvalidPrivsError, mysql_driver.Error) as e: module.fail_json(msg=to_native(e)) @@ -468,9 +476,12 @@ def main(): try: if subtract_privs: priv = None # avoid granting unwanted privileges - changed = user_add(cursor, user, host, host_all, password, encrypted, - plugin, plugin_hash_string, plugin_auth_string, - priv, tls_requires, module.check_mode) + reuse_existing_password = update_password == 'on_new_username' + result = user_add(cursor, user, host, host_all, password, encrypted, + plugin, plugin_hash_string, plugin_auth_string, + priv, tls_requires, module.check_mode, reuse_existing_password) + changed = result['changed'] + password_changed = result['password_changed'] if changed: msg = "User added" @@ -487,7 +498,7 @@ def main(): else: changed = False msg = "User doesn't exist" - module.exit_json(changed=changed, user=user, msg=msg) + module.exit_json(changed=changed, user=user, msg=msg, password_changed=password_changed) if __name__ == '__main__': diff --git a/tests/integration/targets/test_mysql_user/tasks/assert_user_password.yml b/tests/integration/targets/test_mysql_user/tasks/assert_user_password.yml new file mode 100644 index 0000000..fd7e281 --- /dev/null +++ b/tests/integration/targets/test_mysql_user/tasks/assert_user_password.yml @@ -0,0 +1,24 @@ +- name: "applying user {{ username }}@{{ host }} with update_password={{ update_password }}" + mysql_user: + login_user: '{{ mysql_parameters.login_user }}' + login_password: '{{ mysql_parameters.login_password }}' + login_host: '{{ mysql_parameters.login_host }}' + login_port: '{{ mysql_parameters.login_port }}' + state: present + name: "{{ username }}" + host: "{{ host }}" + password: "{{ password }}" + update_password: "{{ update_password }}" + register: result +- name: assert a change occurred + assert: + that: + - "result.changed == {{ expect_change }}" + - "result.password_changed == {{ expect_password_change }}" +- name: query the user + command: "{{ mysql_command }} -BNe \"SELECT plugin, authentication_string FROM mysql.user where user='{{ username }}' and host='{{ host }}'\"" + register: existing_user +- name: assert the password is as set to expect_hash + assert: + that: + - "'mysql_native_password\t{{ expect_password_hash }}' in existing_user.stdout_lines" diff --git a/tests/integration/targets/test_mysql_user/tasks/test_update_password.yml b/tests/integration/targets/test_mysql_user/tasks/test_update_password.yml new file mode 100644 index 0000000..c9b74bb --- /dev/null +++ b/tests/integration/targets/test_mysql_user/tasks/test_update_password.yml @@ -0,0 +1,128 @@ +# Tests scenarios for both plaintext and encrypted user passwords. + +- vars: + mysql_parameters: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + test_password1: kbB9tcx5WOGVGfzV + test_password1_hash: '*AF6A7F9D038475C17EE46564F154104877EE5037' + test_password2: XBYjpHmjIctMxl1y + test_password2_hash: '*9E22D1B35C68BDDF398B8F28AE482E5A865BAC0A' + test_password3: tem33JfR5Yx98BB + test_password3_hash: '*C7E7C2710702F20336F8D93BC0670C8FB66BDBC7' + + + block: + - include_tasks: assert_user_password.yml + vars: + username: "{{ item.username }}" + host: '127.0.0.1' + update_password: "{{ item.update_password }}" + password: "{{ test_password1 }}" + expect_change: "{{ item.expect_change }}" + expect_password_change: "{{ item.expect_change }}" + expect_password_hash: "{{ test_password1_hash }}" + loop: + # all variants set the password when nothing exists + - username: test1 + update_password: always + expect_change: true + - username: test2 + update_password: on_create + expect_change: true + - username: test3 + update_password: on_new_username + expect_change: true + + # assert idempotency + - username: test1 + update_password: always + expect_change: false + - username: test2 + update_password: on_create + expect_change: false + - username: test3 + update_password: on_new_username + expect_change: false + + # same user, new password + - include_tasks: assert_user_password.yml + vars: + username: "{{ item.username }}" + host: '127.0.0.1' + update_password: "{{ item.update_password }}" + password: "{{ test_password2 }}" + expect_change: "{{ item.expect_change }}" + expect_password_change: "{{ item.expect_change }}" + expect_password_hash: "{{ item.expect_password_hash }}" + loop: + - username: test1 + update_password: always + expect_change: true + expect_password_hash: "{{ test_password2_hash }}" + - username: test2 + update_password: on_create + expect_change: false + expect_password_hash: "{{ test_password1_hash }}" + - username: test3 + update_password: on_new_username + expect_change: false + expect_password_hash: "{{ test_password1_hash }}" + + # new user, new password + - include_tasks: assert_user_password.yml + vars: + username: "{{ item.username }}" + host: '::1' + update_password: "{{ item.update_password }}" + password: "{{ item.password }}" + expect_change: "{{ item.expect_change }}" + expect_password_change: "{{ item.expect_password_change }}" + expect_password_hash: "{{ item.expect_password_hash }}" + loop: + - username: test1 + update_password: always + expect_change: true + expect_password_change: true + password: "{{ test_password1 }}" + expect_password_hash: "{{ test_password1_hash }}" + - username: test2 + update_password: on_create + expect_change: true + expect_password_change: true + password: "{{ test_password2 }}" + expect_password_hash: "{{ test_password2_hash }}" + - username: test3 + update_password: on_new_username + expect_change: true + expect_password_change: false + password: "{{ test_password2 }}" + expect_password_hash: "{{ test_password1_hash }}" + + # prepare for next test: ensure all users have varying passwords + - username: test3 + update_password: always + expect_change: true + expect_password_change: true + password: "{{ test_password2 }}" + expect_password_hash: "{{ test_password2_hash }}" + + # another new user, another new password and multiple existing users with varying passwords + - include_tasks: assert_user_password.yml + vars: + username: "{{ item.username }}" + host: '2001:db8::1' + update_password: "{{ item.update_password }}" + password: "{{ test_password3 }}" + expect_change: true + expect_password_change: true + expect_password_hash: "{{ test_password3_hash }}" + loop: + - username: test1 + update_password: always + - username: test2 + update_password: on_create + - username: test3 + update_password: on_new_username From 2e9d50f27476bb46dea152fb4c1383022ae68860 Mon Sep 17 00:00:00 2001 From: Maciej Date: Tue, 31 May 2022 17:44:14 +0200 Subject: [PATCH 090/217] Changed += to append because cmd is a list (#377) Using += on a list cause some problems druing creation of mysql command: /usr/bin/mysql - - u s e r = r o o t - - p a s s w o r d = ' ' --socket=/run/mysqld/mysqld.sock --- plugins/modules/mysql_db.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/mysql_db.py b/plugins/modules/mysql_db.py index c2a6fd8..207b118 100644 --- a/plugins/modules/mysql_db.py +++ b/plugins/modules/mysql_db.py @@ -442,7 +442,7 @@ def db_import(module, host, user, password, db_name, target, all_databases, port cmd.append("--defaults-extra-file=%s" % shlex_quote(config_file)) if check_implicit_admin: - cmd += " --user=root --password=''" + cmd.append("--user=root --password=''") else: if user: cmd.append("--user=%s" % shlex_quote(user)) From 482a0d8ee96679613fef86b10598b4f1a78269a4 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Thu, 2 Jun 2022 09:23:25 +0300 Subject: [PATCH 091/217] Release 3.3.0 commit (#389) --- CHANGELOG.rst | 27 +++++++++- changelogs/changelog.yaml | 54 +++++++++++++++++++ ...22-mysql_query_fix_false_change_report.yml | 2 - .../334-mysql_user_fix_logic_on_oncreate.yml | 2 - ...d-on_new_username-and-password_changed.yml | 10 ---- .../367-mysql_role-fix-deatch-members.yml | 2 - .../368-mysql_role-fix-member-detection.yml | 6 --- .../369_mysql_role-add-members_must_exist.yml | 4 -- galaxy.yml | 2 +- 9 files changed, 81 insertions(+), 28 deletions(-) delete mode 100644 changelogs/fragments/322-mysql_query_fix_false_change_report.yml delete mode 100644 changelogs/fragments/334-mysql_user_fix_logic_on_oncreate.yml delete mode 100644 changelogs/fragments/365-mysql_user-add-on_new_username-and-password_changed.yml delete mode 100644 changelogs/fragments/367-mysql_role-fix-deatch-members.yml delete mode 100644 changelogs/fragments/368-mysql_role-fix-member-detection.yml delete mode 100644 changelogs/fragments/369_mysql_role-add-members_must_exist.yml diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 9897fa2..3179e87 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,31 @@ Community MySQL Collection Release Notes This changelog describes changes after version 2.0.0. +v3.3.0 +====== + +Release Summary +--------------- + +This is the minor release of the ``community.mysql`` collection. +This changelog contains all changes to the modules in this collection +that have been added after the release of ``community.mysql`` 3.2.1. + +Minor Changes +------------- + +- mysql_role - add the argument ``members_must_exist`` (boolean, default true). The assertion that the users supplied in the ``members`` argument exist is only executed when the new argument ``members_must_exist`` is ``true``, to allow opt-out (https://github.com/ansible-collections/community.mysql/pull/369). +- mysql_user - Add the option ``on_new_username`` to argument ``update_password`` to reuse the password (plugin and authentication_string) when creating a new user if some user with the same name already exists. If the existing user with the same name have varying passwords, the password from the arguments is used like with ``update_password: always`` (https://github.com/ansible-collections/community.mysql/pull/365). +- mysql_user - Add the result field ``password_changed`` (boolean). It is true, when the user got a new password. When the user was created with ``update_password: on_new_username`` and an existing password was reused, ``password_changed`` is false (https://github.com/ansible-collections/community.mysql/pull/365). + +Bugfixes +-------- + +- mysql_query - fix false change reports when ``IF EXISTS/IF NOT EXISTS`` clause is used (https://github.com/ansible-collections/community.mysql/issues/268). +- mysql_role - don't add members to a role when creating the role and ``detach_members: true`` is set (https://github.com/ansible-collections/community.mysql/pull/367). +- mysql_role - in some cases (when "SHOW GRANTS" did not use backticks for quotes), no unwanted members were detached from the role (and redundant "GRANT" statements were executed for wanted members). This is fixed by querying the existing role members from the mysql.role_edges (MySQL) or mysql.roles_mapping (MariaDB) tables instead of parsing the "SHOW GRANTS" output (https://github.com/ansible-collections/community.mysql/pull/368). +- mysql_user - fix logic when ``update_password`` is set to ``on_create`` for users using ``plugin*`` arguments (https://github.com/ansible-collections/community.mysql/issues/334). The ``on_create`` sets ``password`` to None for old mysql_native_authentication but not for authentiation methods which uses the ``plugin*`` arguments. This PR changes this so ``on_create`` also exchange ``plugin``, ``plugin_hash_string``, ``plugin_auth_string`` to None in the list of arguments to change + v3.2.1 ====== @@ -128,7 +153,7 @@ that have been added after the release of ``community.mysql`` 2.3.2. Breaking Changes / Porting Guide -------------------------------- -- mysql_replication - remove ``Is_Slave`` and ``Is_Master`` return values (were replaced with ``Is_Primary`` and ``Is_Replica`` (https://github.com/ansible-collections/community.mysql/issues/145). +- mysql_replication - remove ``Is_Slave`` and ``Is_Master`` return values (were replaced with ``Is_Primary`` and ``Is_Replica`` (https://github.com/ansible-collections /community.mysql/issues/145). - mysql_replication - remove the mode options values containing ``master``/``slave`` and the master_use_gtid option ``slave_pos`` (were replaced with corresponding ``primary``/``replica`` values) (https://github.com/ansible-collections/community.mysql/issues/145). - mysql_user - remove support for the `REQUIRESSL` special privilege as it has ben superseded by the `tls_requires` option (https://github.com/ansible-collections/community.mysql/discussions/121). - mysql_user - validate privileges using database engine directly (https://github.com/ansible-collections/community.mysql/issues/234 https://github.com/ansible-collections/community.mysql/pull/243). Do not validate privileges in this module anymore. diff --git a/changelogs/changelog.yaml b/changelogs/changelog.yaml index e128bd9..ce4140f 100644 --- a/changelogs/changelog.yaml +++ b/changelogs/changelog.yaml @@ -140,3 +140,57 @@ releases: - 3.2.1.yml - psf-license.yml release_date: '2022-05-17' + 3.3.0: + changes: + bugfixes: + - mysql_query - fix false change reports when ``IF EXISTS/IF NOT EXISTS`` clause + is used (https://github.com/ansible-collections/community.mysql/issues/268). + - 'mysql_role - don''t add members to a role when creating the role and ``detach_members: + true`` is set (https://github.com/ansible-collections/community.mysql/pull/367).' + - 'mysql_role - in some cases (when "SHOW GRANTS" did not use backticks for + quotes), no unwanted members were detached from the role (and redundant "GRANT" + statements were executed for wanted members). This is fixed by querying the + existing role members from the mysql.role_edges (MySQL) or mysql.roles_mapping + (MariaDB) tables instead of parsing the "SHOW GRANTS" output (https://github.com/ansible-collections/community.mysql/pull/368). + + ' + - mysql_user - fix logic when ``update_password`` is set to ``on_create`` for + users using ``plugin*`` arguments (https://github.com/ansible-collections/community.mysql/issues/334). + The ``on_create`` sets ``password`` to None for old mysql_native_authentication + but not for authentiation methods which uses the ``plugin*`` arguments. This + PR changes this so ``on_create`` also exchange ``plugin``, ``plugin_hash_string``, + ``plugin_auth_string`` to None in the list of arguments to change + minor_changes: + - 'mysql_role - add the argument ``members_must_exist`` (boolean, default true). + The assertion that the users supplied in the ``members`` argument exist is + only executed when the new argument ``members_must_exist`` is ``true``, to + allow opt-out (https://github.com/ansible-collections/community.mysql/pull/369). + + ' + - 'mysql_user - Add the option ``on_new_username`` to argument ``update_password`` + to reuse the password (plugin and authentication_string) when creating a new + user if some user with the same name already exists. If the existing user + with the same name have varying passwords, the password from the arguments + is used like with ``update_password: always`` (https://github.com/ansible-collections/community.mysql/pull/365). + + ' + - 'mysql_user - Add the result field ``password_changed`` (boolean). It is true, + when the user got a new password. When the user was created with ``update_password: + on_new_username`` and an existing password was reused, ``password_changed`` + is false (https://github.com/ansible-collections/community.mysql/pull/365). + + ' + release_summary: 'This is the minor release of the ``community.mysql`` collection. + + This changelog contains all changes to the modules in this collection + + that have been added after the release of ``community.mysql`` 3.2.1.' + fragments: + - 3.3.0.yml + - 322-mysql_query_fix_false_change_report.yml + - 334-mysql_user_fix_logic_on_oncreate.yml + - 365-mysql_user-add-on_new_username-and-password_changed.yml + - 367-mysql_role-fix-deatch-members.yml + - 368-mysql_role-fix-member-detection.yml + - 369_mysql_role-add-members_must_exist.yml + release_date: '2022-06-02' diff --git a/changelogs/fragments/322-mysql_query_fix_false_change_report.yml b/changelogs/fragments/322-mysql_query_fix_false_change_report.yml deleted file mode 100644 index db53922..0000000 --- a/changelogs/fragments/322-mysql_query_fix_false_change_report.yml +++ /dev/null @@ -1,2 +0,0 @@ -bugfixes: -- mysql_query - fix false change reports when ``IF EXISTS/IF NOT EXISTS`` clause is used (https://github.com/ansible-collections/community.mysql/issues/268). diff --git a/changelogs/fragments/334-mysql_user_fix_logic_on_oncreate.yml b/changelogs/fragments/334-mysql_user_fix_logic_on_oncreate.yml deleted file mode 100644 index 4ac88a1..0000000 --- a/changelogs/fragments/334-mysql_user_fix_logic_on_oncreate.yml +++ /dev/null @@ -1,2 +0,0 @@ -bugfixes: - - "mysql_user - fix logic when ``update_password`` is set to ``on_create`` for users using ``plugin*`` arguments (https://github.com/ansible-collections/community.mysql/issues/334). The ``on_create`` sets ``password`` to None for old mysql_native_authentication but not for authentiation methods which uses the ``plugin*`` arguments. This PR changes this so ``on_create`` also exchange ``plugin``, ``plugin_hash_string``, ``plugin_auth_string`` to None in the list of arguments to change" diff --git a/changelogs/fragments/365-mysql_user-add-on_new_username-and-password_changed.yml b/changelogs/fragments/365-mysql_user-add-on_new_username-and-password_changed.yml deleted file mode 100644 index 2796776..0000000 --- a/changelogs/fragments/365-mysql_user-add-on_new_username-and-password_changed.yml +++ /dev/null @@ -1,10 +0,0 @@ -minor_changes: - - > - mysql_user - Add the option ``on_new_username`` to argument ``update_password`` to reuse the password (plugin and - authentication_string) when creating a new user if some user with the same name already exists. - If the existing user with the same name have varying passwords, the password from the arguments is used like with - ``update_password: always`` (https://github.com/ansible-collections/community.mysql/pull/365). - - > - mysql_user - Add the result field ``password_changed`` (boolean). It is true, when the user got a new password. - When the user was created with ``update_password: on_new_username`` and an existing password was reused, - ``password_changed`` is false (https://github.com/ansible-collections/community.mysql/pull/365). diff --git a/changelogs/fragments/367-mysql_role-fix-deatch-members.yml b/changelogs/fragments/367-mysql_role-fix-deatch-members.yml deleted file mode 100644 index 5a4d414..0000000 --- a/changelogs/fragments/367-mysql_role-fix-deatch-members.yml +++ /dev/null @@ -1,2 +0,0 @@ -bugfixes: - - "mysql_role - don't add members to a role when creating the role and ``detach_members: true`` is set (https://github.com/ansible-collections/community.mysql/pull/367)." diff --git a/changelogs/fragments/368-mysql_role-fix-member-detection.yml b/changelogs/fragments/368-mysql_role-fix-member-detection.yml deleted file mode 100644 index b7cbd3e..0000000 --- a/changelogs/fragments/368-mysql_role-fix-member-detection.yml +++ /dev/null @@ -1,6 +0,0 @@ -bugfixes: - - > - mysql_role - in some cases (when "SHOW GRANTS" did not use backticks for quotes), no unwanted members were detached - from the role (and redundant "GRANT" statements were executed for wanted members). This is fixed by querying the - existing role members from the mysql.role_edges (MySQL) or mysql.roles_mapping (MariaDB) tables instead of parsing - the "SHOW GRANTS" output (https://github.com/ansible-collections/community.mysql/pull/368). diff --git a/changelogs/fragments/369_mysql_role-add-members_must_exist.yml b/changelogs/fragments/369_mysql_role-add-members_must_exist.yml deleted file mode 100644 index c2d420c..0000000 --- a/changelogs/fragments/369_mysql_role-add-members_must_exist.yml +++ /dev/null @@ -1,4 +0,0 @@ -minor_changes: - - > - mysql_role - add the argument ``members_must_exist`` (boolean, default true). The assertion that the users supplied in - the ``members`` argument exist is only executed when the new argument ``members_must_exist`` is ``true``, to allow opt-out (https://github.com/ansible-collections/community.mysql/pull/369). diff --git a/galaxy.yml b/galaxy.yml index 294d37d..262677d 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -1,6 +1,6 @@ namespace: community name: mysql -version: 3.2.1 +version: 3.3.0 readme: README.md authors: - Ansible community From 2a3f8f6506ffa173bbb4d1401312e11bcf6d2a12 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Fri, 3 Jun 2022 12:47:03 +0300 Subject: [PATCH 092/217] Update licensing information (#390) --- changelogs/fragments/simplified-bsd-license.yml | 2 ++ plugins/module_utils/database.py | 2 +- .../module_utils/implementations/mariadb/replication.py | 4 ++++ plugins/module_utils/implementations/mariadb/role.py | 4 ++++ plugins/module_utils/implementations/mariadb/user.py | 4 ++++ plugins/module_utils/implementations/mysql/replication.py | 4 ++++ plugins/module_utils/implementations/mysql/role.py | 4 ++++ plugins/module_utils/implementations/mysql/user.py | 4 ++++ plugins/module_utils/mysql.py | 2 +- plugins/module_utils/user.py | 2 +- simplified_bsd.txt | 8 ++++++++ 11 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 changelogs/fragments/simplified-bsd-license.yml create mode 100644 simplified_bsd.txt diff --git a/changelogs/fragments/simplified-bsd-license.yml b/changelogs/fragments/simplified-bsd-license.yml new file mode 100644 index 0000000..574a695 --- /dev/null +++ b/changelogs/fragments/simplified-bsd-license.yml @@ -0,0 +1,2 @@ +bugfixes: + - Include ``simplified_bsd.txt`` license file for various module utils. diff --git a/plugins/module_utils/database.py b/plugins/module_utils/database.py index 6785030..da0375d 100644 --- a/plugins/module_utils/database.py +++ b/plugins/module_utils/database.py @@ -6,7 +6,7 @@ # # Copyright (c) 2014, Toshio Kuratomi # -# Simplified BSD License (see licenses/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) from __future__ import (absolute_import, division, print_function) __metaclass__ = type diff --git a/plugins/module_utils/implementations/mariadb/replication.py b/plugins/module_utils/implementations/mariadb/replication.py index cee4967..a1733e7 100644 --- a/plugins/module_utils/implementations/mariadb/replication.py +++ b/plugins/module_utils/implementations/mariadb/replication.py @@ -1,3 +1,7 @@ +# -*- coding: utf-8 -*- + +# 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) __metaclass__ = type diff --git a/plugins/module_utils/implementations/mariadb/role.py b/plugins/module_utils/implementations/mariadb/role.py index a3c9ea5..d227d59 100644 --- a/plugins/module_utils/implementations/mariadb/role.py +++ b/plugins/module_utils/implementations/mariadb/role.py @@ -1,3 +1,7 @@ +# -*- coding: utf-8 -*- + +# 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) __metaclass__ = type diff --git a/plugins/module_utils/implementations/mariadb/user.py b/plugins/module_utils/implementations/mariadb/user.py index 7579157..b87ff69 100644 --- a/plugins/module_utils/implementations/mariadb/user.py +++ b/plugins/module_utils/implementations/mariadb/user.py @@ -1,3 +1,7 @@ +# -*- coding: utf-8 -*- + +# 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) __metaclass__ = type diff --git a/plugins/module_utils/implementations/mysql/replication.py b/plugins/module_utils/implementations/mysql/replication.py index c5324da..2e50bea 100644 --- a/plugins/module_utils/implementations/mysql/replication.py +++ b/plugins/module_utils/implementations/mysql/replication.py @@ -1,3 +1,7 @@ +# -*- coding: utf-8 -*- + +# 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) __metaclass__ = type diff --git a/plugins/module_utils/implementations/mysql/role.py b/plugins/module_utils/implementations/mysql/role.py index f9686c5..932d74a 100644 --- a/plugins/module_utils/implementations/mysql/role.py +++ b/plugins/module_utils/implementations/mysql/role.py @@ -1,3 +1,7 @@ +# -*- coding: utf-8 -*- + +# 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) __metaclass__ = type diff --git a/plugins/module_utils/implementations/mysql/user.py b/plugins/module_utils/implementations/mysql/user.py index 43e400b..b141903 100644 --- a/plugins/module_utils/implementations/mysql/user.py +++ b/plugins/module_utils/implementations/mysql/user.py @@ -1,3 +1,7 @@ +# -*- coding: utf-8 -*- + +# 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) __metaclass__ = type diff --git a/plugins/module_utils/mysql.py b/plugins/module_utils/mysql.py index 9492ea8..d256599 100644 --- a/plugins/module_utils/mysql.py +++ b/plugins/module_utils/mysql.py @@ -7,7 +7,7 @@ # Copyright (c), Jonathan Mainguy , 2015 # Most of this was originally added by Sven Schliesing @muffl0n in the mysql_user.py module # -# Simplified BSD License (see licenses/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) from __future__ import (absolute_import, division, print_function) from functools import reduce diff --git a/plugins/module_utils/user.py b/plugins/module_utils/user.py index 655d847..7e27d13 100644 --- a/plugins/module_utils/user.py +++ b/plugins/module_utils/user.py @@ -7,7 +7,7 @@ __metaclass__ = type # still belong to the author of the module, and may assign their own license # to the complete work. # -# Simplified BSD License (see licenses/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 re diff --git a/simplified_bsd.txt b/simplified_bsd.txt new file mode 100644 index 0000000..6810e04 --- /dev/null +++ b/simplified_bsd.txt @@ -0,0 +1,8 @@ +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + From 8e79690a0213c6b85e535f7d8b4f35ecca5c7dca Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Mon, 13 Jun 2022 09:11:18 +0300 Subject: [PATCH 093/217] mysql_db: add chdir argument (#396) --- .../0-mysql_db_add_chdir_argument.yml | 2 + plugins/modules/mysql_db.py | 14 ++++++ .../test_mysql_db/tasks/state_dump_import.yml | 45 +++++++++++++++++++ 3 files changed, 61 insertions(+) create mode 100644 changelogs/fragments/0-mysql_db_add_chdir_argument.yml diff --git a/changelogs/fragments/0-mysql_db_add_chdir_argument.yml b/changelogs/fragments/0-mysql_db_add_chdir_argument.yml new file mode 100644 index 0000000..26ce3dd --- /dev/null +++ b/changelogs/fragments/0-mysql_db_add_chdir_argument.yml @@ -0,0 +1,2 @@ +minor_changes: +- mysql_db - add the ``chdir`` argument to avoid failings when a dump file contains relative paths (https://github.com/ansible-collections/community.mysql/issues/395). diff --git a/plugins/modules/mysql_db.py b/plugins/modules/mysql_db.py index 207b118..5acdb65 100644 --- a/plugins/modules/mysql_db.py +++ b/plugins/modules/mysql_db.py @@ -150,6 +150,12 @@ options: type: bool default: no version_added: '0.1.0' + chdir: + description: + - Changes the current working directory. + - Can be useful, for example, when I(state=import) and a dump file contains relative paths. + type: path + version_added: '3.4.0' seealso: - module: community.mysql.mysql_info @@ -562,6 +568,7 @@ def main(): restrict_config_file=dict(type='bool', default=False), check_implicit_admin=dict(type='bool', default=False), config_overrides_defaults=dict(type='bool', default=False), + chdir=dict(type='path'), ) module = AnsibleModule( @@ -610,6 +617,13 @@ def main(): restrict_config_file = module.params["restrict_config_file"] check_implicit_admin = module.params['check_implicit_admin'] config_overrides_defaults = module.params['config_overrides_defaults'] + chdir = module.params['chdir'] + + if chdir: + try: + os.chdir(chdir) + except Exception as e: + module.fail_json("Cannot change the current directory to %s: %s" % (chdir, e)) if len(db) > 1 and state == 'import': module.fail_json(msg="Multiple databases are not supported with state=import") diff --git a/tests/integration/targets/test_mysql_db/tasks/state_dump_import.yml b/tests/integration/targets/test_mysql_db/tasks/state_dump_import.yml index 1de7439..008721c 100644 --- a/tests/integration/targets/test_mysql_db/tasks/state_dump_import.yml +++ b/tests/integration/targets/test_mysql_db/tasks/state_dump_import.yml @@ -416,6 +416,51 @@ that: - result is changed +######################## +# Test import with chdir + +- name: Create dir + file: + path: ~/subdir + state: directory + +- name: Create test dump + shell: 'echo "SOURCE ./subdir_test.sql" > ~/original_test.sql' + +- name: Create test source + shell: 'echo "SELECT 1" > ~/subdir/subdir_test.sql' + +- name: Try to restore without chdir argument, must fail + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: '{{ db_name }}' + state: import + target: '~/original_test.sql' + ignore_errors: yes + register: result +- assert: + that: + - result is failed + - result.msg is search('Failed to open file') + +- name: Restore with chdir argument, must pass + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: '{{ db_name }}' + state: import + target: '~/original_test.sql' + chdir: ~/subdir + register: result +- assert: + that: + - result is succeeded + ########## # Clean up ########## From 04aa13f6d686e08457c5dc88f89141dd2e43f09a Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Mon, 13 Jun 2022 09:13:58 +0300 Subject: [PATCH 094/217] mysql_replication: set MASTER_SSL=0 when primary_ssl is set to no (#397) * mysql_replication: set MASTER_SSL=0 when primary_ssl is set to no * Improve doc --- .../1-mysql_replication_can_disable_master_ssl.yml | 2 ++ plugins/modules/mysql_replication.py | 11 +++++++---- .../tasks/mysql_replication_initial.yml | 3 ++- 3 files changed, 11 insertions(+), 5 deletions(-) create mode 100644 changelogs/fragments/1-mysql_replication_can_disable_master_ssl.yml diff --git a/changelogs/fragments/1-mysql_replication_can_disable_master_ssl.yml b/changelogs/fragments/1-mysql_replication_can_disable_master_ssl.yml new file mode 100644 index 0000000..ceb0d5a --- /dev/null +++ b/changelogs/fragments/1-mysql_replication_can_disable_master_ssl.yml @@ -0,0 +1,2 @@ +bugfixes: +- mysql_replication - when the ``primary_ssl`` argument is set to ``no``, the module will turn off SSL (https://github.com/ansible-collections/community.mysql/issues/393). diff --git a/plugins/modules/mysql_replication.py b/plugins/modules/mysql_replication.py index 46895e3..f4c21b9 100644 --- a/plugins/modules/mysql_replication.py +++ b/plugins/modules/mysql_replication.py @@ -92,8 +92,8 @@ options: if an encrypted connection can be established. - For details, refer to L(MySQL encrypted replication documentation,https://dev.mysql.com/doc/refman/8.0/en/replication-solutions-encrypted-connections.html). + - The default is C(false). type: bool - default: false aliases: [master_ssl] primary_ssl_ca: description: @@ -449,7 +449,7 @@ def main(): primary_log_pos=dict(type='int', aliases=['master_log_pos']), relay_log_file=dict(type='str'), relay_log_pos=dict(type='int'), - primary_ssl=dict(type='bool', default=False, aliases=['master_ssl']), + primary_ssl=dict(type='bool', aliases=['master_ssl']), primary_ssl_ca=dict(type='str', aliases=['master_ssl_ca']), primary_ssl_capath=dict(type='str', aliases=['master_ssl_capath']), primary_ssl_cert=dict(type='str', aliases=['master_ssl_cert']), @@ -577,8 +577,11 @@ def main(): chm.append("RELAY_LOG_FILE='%s'" % relay_log_file) if relay_log_pos is not None: chm.append("RELAY_LOG_POS=%s" % relay_log_pos) - if primary_ssl: - chm.append("MASTER_SSL=1") + if primary_ssl is not None: + if primary_ssl: + chm.append("MASTER_SSL=1") + else: + chm.append("MASTER_SSL=0") if primary_ssl_ca is not None: chm.append("MASTER_SSL_CA='%s'" % primary_ssl_ca) if primary_ssl_capath is not None: 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 7f6e554..8272307 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 @@ -125,12 +125,13 @@ primary_log_file: '{{ mysql_primary_status.File }}' primary_log_pos: '{{ mysql_primary_status.Position }}' primary_ssl_ca: '' + primary_ssl: no register: result - assert: that: - result is changed - - result.queries == ["CHANGE MASTER TO MASTER_HOST='{{ mysql_host }}',MASTER_USER='{{ replication_user }}',MASTER_PASSWORD='********',MASTER_PORT={{ mysql_primary_port }},MASTER_LOG_FILE='{{ mysql_primary_status.File }}',MASTER_LOG_POS={{ mysql_primary_status.Position }},MASTER_SSL_CA=''"] + - result.queries == ["CHANGE MASTER TO MASTER_HOST='{{ mysql_host }}',MASTER_USER='{{ replication_user }}',MASTER_PASSWORD='********',MASTER_PORT={{ mysql_primary_port }},MASTER_LOG_FILE='{{ mysql_primary_status.File }}',MASTER_LOG_POS={{ mysql_primary_status.Position }},MASTER_SSL=0,MASTER_SSL_CA=''"] # Test startreplica mode: - name: Start replica From 0df46e0e673935423e55f5f31682ab96f55fc6ae Mon Sep 17 00:00:00 2001 From: Chris Croome Date: Thu, 16 Jun 2022 14:17:45 +0100 Subject: [PATCH 095/217] Note added regarding the default config file, ~/.my.cnf (#400) * Note added for https://github.com/ansible-collections/community.mysql/issues/394 * Update config file notes as discussed * Update plugins/doc_fragments/mysql.py Co-authored-by: Andrew Klychkov Co-authored-by: Andrew Klychkov --- plugins/doc_fragments/mysql.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/plugins/doc_fragments/mysql.py b/plugins/doc_fragments/mysql.py index 4b531d4..66809c4 100644 --- a/plugins/doc_fragments/mysql.py +++ b/plugins/doc_fragments/mysql.py @@ -44,6 +44,9 @@ options: config_file: description: - Specify a config file from which user and password are to be read. + - The default config file, C(~/.my.cnf), if it exists, will be read, even if I(config_file) is not specified. + - The default config file, C(~/.my.cnf), if it exists, must contain a C([client]) section as a MySQL connector requirement. + - To prevent the default config file from being read, set I(config_file) to be an empty string. type: path default: '~/.my.cnf' ca_cert: @@ -98,4 +101,7 @@ notes: - Alternatively, you can use the mysqlclient library instead of MySQL-python (MySQLdb) which supports both Python 2.X and Python >=3.5. See U(https://pypi.org/project/mysqlclient/) how to install it. + - "If credentials from the config file (for example, C(/root/.my.cnf)) are not needed to connect to a database server, but + the file exists and does not contain a C([client]) section, before any other valid directives, it will be read and this + will cause the connection to fail, to prevent this set it to an empty string, (for example C(config_file: ''))." ''' From b62a59cf5ae39eba2110ae7385350337478a4f4d Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Fri, 24 Jun 2022 13:50:19 +0200 Subject: [PATCH 096/217] Update mariadb to 10.6.8 in test matrix (#370) * Update mariadb to 10.6.8 in test matrix * try 10.8.3 * change tarball path * Change tarball name * Add mariadb 10.8 * Fix * Fix * Fix --- .github/workflows/ansible-test-plugins.yml | 11 +++++++++-- tests/integration/targets/setup_mysql/vars/main.yml | 5 +++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ansible-test-plugins.yml b/.github/workflows/ansible-test-plugins.yml index c6363b1..1acca5d 100644 --- a/.github/workflows/ansible-test-plugins.yml +++ b/.github/workflows/ansible-test-plugins.yml @@ -58,7 +58,9 @@ jobs: - mysql_5.7.31 - mysql_8.0.22 - mariadb_10.3.34 - - mariadb_10.5.9 + # When adding later versions below, + # also change the "Set MariaDB URL sub dir" task + - mariadb_10.8.3 ansible: - stable-2.11 - stable-2.12 @@ -74,7 +76,7 @@ jobs: exclude: - db_engine_version: mysql_8.0.22 connector: pymysql==0.7.10 - - db_engine_version: mariadb_10.5.9 + - db_engine_version: mariadb_10.8.3 connector: pymysql==0.7.10 - python: 3.8 ansible: stable-2.11 @@ -112,6 +114,11 @@ jobs: sed -i -e "s/^mariadb_version:.*/mariadb_version: $DB_VERSION/g" -e 's/^mariadb_install: false/mariadb_install: true/g' ${{ env.mysql_version_file }} if: ${{ startsWith(matrix.db_engine_version, 'mariadb') }} + - name: Set MariaDB URL sub dir + run: | + sed -i -e "s/^mariadb_url_subdir:.*/mariadb_url_subdir: linux-systemd/g" ${{ env.connector_version_file }} + if: matrix.db_engine_version == 'mariadb_10.8.3' + - name: Set Connector version (${{ matrix.connector }}) run: "sed -i 's/^python_packages:.*/python_packages: [${{ matrix.connector }}]/' ${{ env.connector_version_file }}" diff --git a/tests/integration/targets/setup_mysql/vars/main.yml b/tests/integration/targets/setup_mysql/vars/main.yml index 94b43b4..ba316f7 100644 --- a/tests/integration/targets/setup_mysql/vars/main.yml +++ b/tests/integration/targets/setup_mysql/vars/main.yml @@ -24,5 +24,6 @@ install_python_prereqs: 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" -mariadb_src: "https://downloads.mariadb.com/MariaDB/mariadb-{{ mariadb_version }}/bintar-linux-x86_64/{{ mariadb_tarball }}" +mariadb_url_subdir: "linux" +mariadb_tarball: "mariadb-{{ mariadb_version }}-{{ mariadb_url_subdir }}-x86_64.tar.gz" +mariadb_src: "https://downloads.mariadb.com/MariaDB/mariadb-{{ mariadb_version }}/bintar-{{ mariadb_url_subdir }}-x86_64/{{ mariadb_tarball }}" From 6f87620d9bf61ea4f6168c0839d2c28f78d09bc9 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Fri, 24 Jun 2022 14:32:32 +0200 Subject: [PATCH 097/217] README: update MariaDB versions we test against (#404) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 15db6a9..266db1d 100644 --- a/README.md +++ b/README.md @@ -72,8 +72,8 @@ Every voice is important and every idea is valuable. If you have something on yo - mysql 5.7.31 - mysql 8.0.22 -- mariadb 10.3.34 -- mariadb 10.5.9 +- mariadb 10.3.34 (only collection version >= 3) +- mariadb 10.8.3 (only collection version >= 3) ### Database connectors From 5108ca5e66bf4d49c1c3e2f29968385f738200f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20Inderm=C3=BChle?= Date: Thu, 30 Jun 2022 06:54:26 +0200 Subject: [PATCH 098/217] Fix mysqldump ignoring errors (#403) * Add schema and tables for the tests * Add tests for full dump with and without compression * Add test for distinct dump with and without compression * Fix sh not seeing errors for command before the pipe sh is missing the pipefail flag. We must use bash for this. * Add cleanup to prevent the following tests from failing * Fix fqcn in module_defaults * Add changelog fragment * Add check to the error message to ensure we captured the right one * Add option to activate the fix on systems with bash * Fix errors when data schema is already absent * Update changelogs/fragments/fix-256-mysql_dump-errors.yml Co-authored-by: Andrew Klychkov * Add markup for commands in the documentation string Co-authored-by: Andrew Klychkov * Add markup and next release version in the documentation string Co-authored-by: Andrew Klychkov * Fix missing dependency for MySQL 8 * Add pipefail to tests of uncompressed dumps to enure it still works * Fix "bash command not found" if pipefail is used for uncompressed dump * Fix sanity pep8 * Document example of dump with pipefail * Add dedpulication to command construct Co-authored-by: Andrew Klychkov Co-authored-by: Andrew Klychkov --- .../fragments/fix-256-mysql_dump-errors.yml | 7 + plugins/modules/mysql_db.py | 30 +++- .../targets/setup_mysql/vars/main.yml | 1 + .../tasks/issue_256_mysqldump_errors.yml | 148 ++++++++++++++++++ .../targets/test_mysql_db/tasks/main.yml | 3 + .../tasks/state_present_absent.yml | 4 +- 6 files changed, 188 insertions(+), 5 deletions(-) create mode 100644 changelogs/fragments/fix-256-mysql_dump-errors.yml create mode 100644 tests/integration/targets/test_mysql_db/tasks/issue_256_mysqldump_errors.yml diff --git a/changelogs/fragments/fix-256-mysql_dump-errors.yml b/changelogs/fragments/fix-256-mysql_dump-errors.yml new file mode 100644 index 0000000..85fc0af --- /dev/null +++ b/changelogs/fragments/fix-256-mysql_dump-errors.yml @@ -0,0 +1,7 @@ +--- + +bugfixes: + - mysql_dump - Fixes issue 256. Using compression masks errors messages from + mysql_dump. By default the fix is inactiv to ensure retro-compatibility + with system without bash. To activate the fix, use the module option + ``pipefail=true`` (https://github.com/ansible-collections/community.mysql/issues/256). diff --git a/plugins/modules/mysql_db.py b/plugins/modules/mysql_db.py index 5acdb65..0830a12 100644 --- a/plugins/modules/mysql_db.py +++ b/plugins/modules/mysql_db.py @@ -156,6 +156,14 @@ options: - Can be useful, for example, when I(state=import) and a dump file contains relative paths. type: path version_added: '3.4.0' + pipefail: + description: + - Use C(bash) instead of C(sh) and add C(-o pipefail) to catch errors from the + mysql_dump command when I(state=import) and compression is used. The default is I(false) to + prevent issue on system without bash. The default may change in a future release. + type: bool + default: no + version_added: '3.4.0' seealso: - module: community.mysql.mysql_info @@ -295,6 +303,13 @@ EXAMPLES = r''' login_password: 123456 name: bobdata state: present + +- name: Dump a database with compression and catch errors from mysqldump with bash pipefail + community.mysql.mysql_db: + state: dump + name: foo + target: /tmp/dump.sql.gz + pipefail: true ''' RETURN = r''' @@ -355,7 +370,7 @@ def db_dump(module, host, user, password, db_name, target, all_databases, port, single_transaction=None, quick=None, ignore_tables=None, hex_blob=None, encoding=None, force=False, master_data=0, skip_lock_tables=False, dump_extra_args=None, unsafe_password=False, restrict_config_file=False, - check_implicit_admin=False): + check_implicit_admin=False, pipefail=False): cmd = module.get_bin_path('mysqldump', True) # If defined, mysqldump demands --defaults-extra-file be the first option if config_file: @@ -424,11 +439,18 @@ def db_dump(module, host, user, password, db_name, target, all_databases, port, if path: cmd = '%s | %s > %s' % (cmd, path, shlex_quote(target)) + if pipefail: + cmd = 'set -o pipefail && ' + cmd else: cmd += " > %s" % shlex_quote(target) executed_commands.append(cmd) - rc, stdout, stderr = module.run_command(cmd, use_unsafe_shell=True) + + if pipefail: + rc, stdout, stderr = module.run_command(cmd, use_unsafe_shell=True, executable='bash') + else: + rc, stdout, stderr = module.run_command(cmd, use_unsafe_shell=True) + return rc, stdout, stderr @@ -569,6 +591,7 @@ def main(): check_implicit_admin=dict(type='bool', default=False), config_overrides_defaults=dict(type='bool', default=False), chdir=dict(type='path'), + pipefail=dict(type='bool', default=False), ) module = AnsibleModule( @@ -618,6 +641,7 @@ def main(): check_implicit_admin = module.params['check_implicit_admin'] config_overrides_defaults = module.params['config_overrides_defaults'] chdir = module.params['chdir'] + pipefail = module.params['pipefail'] if chdir: try: @@ -704,7 +728,7 @@ def main(): ssl_ca, single_transaction, quick, ignore_tables, hex_blob, encoding, force, master_data, skip_lock_tables, dump_extra_args, unsafe_login_password, restrict_config_file, - check_implicit_admin) + check_implicit_admin, pipefail) if rc != 0: module.fail_json(msg="%s" % stderr) module.exit_json(changed=True, db=db_name, db_list=db, msg=stdout, diff --git a/tests/integration/targets/setup_mysql/vars/main.yml b/tests/integration/targets/setup_mysql/vars/main.yml index ba316f7..4aa52a2 100644 --- a/tests/integration/targets/setup_mysql/vars/main.yml +++ b/tests/integration/targets/setup_mysql/vars/main.yml @@ -19,6 +19,7 @@ install_prereqs: install_python_prereqs: - python3-dev + - python3-cryptography - default-libmysqlclient-dev - build-essential diff --git a/tests/integration/targets/test_mysql_db/tasks/issue_256_mysqldump_errors.yml b/tests/integration/targets/test_mysql_db/tasks/issue_256_mysqldump_errors.yml new file mode 100644 index 0000000..58285b3 --- /dev/null +++ b/tests/integration/targets/test_mysql_db/tasks/issue_256_mysqldump_errors.yml @@ -0,0 +1,148 @@ +--- + +# When mysqldump encountered an issue, mysql_db was still happy. But the +# dump produced was empty or worse, only contained `DROP TABLE IF EXISTS...` + +- module_defaults: + community.mysql.mysql_db: &mysql_defaults + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + community.mysql.mysql_query: *mysql_defaults + + block: + + - name: Dumps errors | Setup test | Create 2 schemas + community.mysql.mysql_db: + name: + - "db1" + - "db2" + state: present + + - name: Dumps errors | Setup test | Create 2 tables + community.mysql.mysql_query: + query: + - "CREATE TABLE db1.t1 (id int)" + - "CREATE TABLE db1.t2 (id int)" + - "CREATE VIEW db2.v1 AS SELECT id from db1.t1" + + - name: Dumps errors | Full dump without compression + community.mysql.mysql_db: + state: dump + name: all + target: /tmp/full-dump.sql + register: full_dump + + - name: Dumps errors | Full dump with gunzip + community.mysql.mysql_db: + state: dump + name: all + target: /tmp/full-dump.sql.gz + register: full_dump_gz + + - name: Dumps errors | Distinct dump without compression + community.mysql.mysql_db: + state: dump + name: db2 + target: /tmp/dump-db2.sql + register: dump_db2 + + - name: Dumps errors | Distinct dump with gunzip + community.mysql.mysql_db: + state: dump + name: db2 + target: /tmp/dump-db2.sql.gz + register: dump_db2_gz + + - name: Dumps errors | Check distinct dumps are changed + ansible.builtin.assert: + that: + - dump_db2 is changed + - dump_db2_gz is changed + + # Now db2.v1 targets an inexistant table so mysqldump will fail + - name: Dumps errors | Drop t1 + community.mysql.mysql_query: + query: + - "DROP TABLE db1.t1" + + - name: Dumps errors | Full dump after drop t1 without compression + community.mysql.mysql_db: + state: dump + name: all + target: /tmp/full-dump-without-t1.sql + pipefail: true # This should do nothing + register: full_dump_without_t1 + ignore_errors: true + + - name: Dumps errors | Full dump after drop t1 with gzip without the fix + community.mysql.mysql_db: + state: dump + name: all + target: /tmp/full-dump-without-t1.sql.gz + register: full_dump_without_t1_gz_without_fix + ignore_errors: true + + - name: Dumps errors | Full dump after drop t1 with gzip with the fix + community.mysql.mysql_db: + state: dump + name: all + target: /tmp/full-dump-without-t1.sql.gz + pipefail: true + register: full_dump_without_t1_gz_with_fix + ignore_errors: true + + - name: Dumps errors | Check full dump + ansible.builtin.assert: + that: + - full_dump_without_t1 is failed + - full_dump_without_t1.msg is search( + 'references invalid table') + - full_dump_without_t1_gz_without_fix is changed + - full_dump_without_t1_gz_with_fix is failed + - full_dump_without_t1_gz_with_fix.msg is search( + 'references invalid table') + + - name: Dumps errors | Distinct dump after drop t1 without compression + community.mysql.mysql_db: + state: dump + name: db2 + target: /tmp/dump-db2-without_t1.sql + pipefail: true # This should do nothing + register: dump_db2_without_t1 + ignore_errors: true + + - name: Dumps errors | Distinct dump after drop t1 with gzip without the fix + community.mysql.mysql_db: + state: dump + name: db2 + target: /tmp/dump-db2-without_t1.sql.gz + register: dump_db2_without_t1_gz_without_fix + ignore_errors: true + + - name: Dumps errors | Distinct dump after drop t1 with gzip with the fix + community.mysql.mysql_db: + state: dump + name: db2 + target: /tmp/dump-db2-without_t1.sql.gz + pipefail: true + register: dump_db2_without_t1_gz_with_fix + ignore_errors: true + + - name: Dumps errors | Check distinct dump + ansible.builtin.assert: + that: + - dump_db2_without_t1 is failed + - dump_db2_without_t1.msg is search( + 'references invalid table') + - dump_db2_without_t1_gz_without_fix is changed + - dump_db2_without_t1_gz_with_fix is failed + - dump_db2_without_t1_gz_with_fix.msg is search( + 'references invalid table') + - name: Dumps errors | Cleanup + community.mysql.mysql_db: + name: + - "db1" + - "db2" + 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 958e341..df6bb07 100644 --- a/tests/integration/targets/test_mysql_db/tasks/main.yml +++ b/tests/integration/targets/test_mysql_db/tasks/main.yml @@ -63,3 +63,6 @@ vars: db_name: "{{ item }}" loop: "{{ db_names }}" + +- name: Check errors from mysqldump are seen issue 256 + ansible.builtin.include_tasks: issue_256_mysqldump_errors.yml diff --git a/tests/integration/targets/test_mysql_db/tasks/state_present_absent.yml b/tests/integration/targets/test_mysql_db/tasks/state_present_absent.yml index 02411f0..e5c5f33 100644 --- a/tests/integration/targets/test_mysql_db/tasks/state_present_absent.yml +++ b/tests/integration/targets/test_mysql_db/tasks/state_present_absent.yml @@ -18,8 +18,8 @@ # ============================================================ - name: remove database if it exists command: > - "{{ mysql_command }} -sse 'drop database {{ db_name }}'" - ignore_errors: True + "{{ mysql_command }} -sse 'DROP DATABASE IF EXISTS {{ db_name }}'" + ignore_errors: true - name: make sure the test database is not there command: "{{ mysql_command }} {{ db_name }}" From 1776702b9d11b7d205a2ef0b1b6422b5b790c0da Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Thu, 30 Jun 2022 11:38:21 +0200 Subject: [PATCH 099/217] Announce pipefail default change in community.mysql 4.0.0 (#408) --- changelogs/fragments/2-mysql_db_announce.yml | 6 ++++++ changelogs/fragments/fix-256-mysql_dump-errors.yml | 4 ++-- plugins/modules/mysql_db.py | 5 +++-- 3 files changed, 11 insertions(+), 4 deletions(-) create mode 100644 changelogs/fragments/2-mysql_db_announce.yml diff --git a/changelogs/fragments/2-mysql_db_announce.yml b/changelogs/fragments/2-mysql_db_announce.yml new file mode 100644 index 0000000..87d3c60 --- /dev/null +++ b/changelogs/fragments/2-mysql_db_announce.yml @@ -0,0 +1,6 @@ +--- +minor_changes: +- mysql_db - add the ``pipefail`` argument to avoid broken dumps when ``state`` is ``dump`` and compression is used (https://github.com/ansible-collections/community.mysql/issues/256). + +major_changes: +- mysql_db - the ``pipefail`` argument's default value will be changed to ``true`` in community.mysql 4.0.0. If your target machines do not use ``bash`` as a default interpreter, set ``pipefail`` to ``false`` explicitly. However, we strongly recommend setting up ``bash`` as a default and ``pipefail=true`` as it will protect you from getting broken dumps you don't know about (https://github.com/ansible-collections/community.mysql/issues/407). diff --git a/changelogs/fragments/fix-256-mysql_dump-errors.yml b/changelogs/fragments/fix-256-mysql_dump-errors.yml index 85fc0af..f3dccc0 100644 --- a/changelogs/fragments/fix-256-mysql_dump-errors.yml +++ b/changelogs/fragments/fix-256-mysql_dump-errors.yml @@ -1,7 +1,7 @@ --- bugfixes: - - mysql_dump - Fixes issue 256. Using compression masks errors messages from - mysql_dump. By default the fix is inactiv to ensure retro-compatibility + - mysql_dump - using compression masks errors messages from + mysql_dump. By default the fix is inactive to ensure retro-compatibility with system without bash. To activate the fix, use the module option ``pipefail=true`` (https://github.com/ansible-collections/community.mysql/issues/256). diff --git a/plugins/modules/mysql_db.py b/plugins/modules/mysql_db.py index 0830a12..bf681fe 100644 --- a/plugins/modules/mysql_db.py +++ b/plugins/modules/mysql_db.py @@ -159,8 +159,9 @@ options: pipefail: description: - Use C(bash) instead of C(sh) and add C(-o pipefail) to catch errors from the - mysql_dump command when I(state=import) and compression is used. The default is I(false) to - prevent issue on system without bash. The default may change in a future release. + mysql_dump command when I(state=import) and compression is used. + - The default is C(no) to prevent issues on systems without bash as a default interpreter. + - The default will change to C(yes) in community.mysql 4.0.0. type: bool default: no version_added: '3.4.0' From af73fa0d76512b8852354b768455a07f8be41fa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20Inderm=C3=BChle?= Date: Wed, 3 Aug 2022 11:03:17 +0200 Subject: [PATCH 100/217] Release 3.4.0 commit (#414) * Changelog: Fix module name * Release 3.4.0 commit --- CHANGELOG.rst | 28 ++++++++++++++++ changelogs/changelog.yaml | 33 +++++++++++++++++++ .../0-mysql_db_add_chdir_argument.yml | 2 -- ...sql_replication_can_disable_master_ssl.yml | 2 -- changelogs/fragments/2-mysql_db_announce.yml | 6 ---- .../fragments/fix-256-mysql_dump-errors.yml | 7 ---- .../fragments/simplified-bsd-license.yml | 2 -- galaxy.yml | 2 +- 8 files changed, 62 insertions(+), 20 deletions(-) delete mode 100644 changelogs/fragments/0-mysql_db_add_chdir_argument.yml delete mode 100644 changelogs/fragments/1-mysql_replication_can_disable_master_ssl.yml delete mode 100644 changelogs/fragments/2-mysql_db_announce.yml delete mode 100644 changelogs/fragments/fix-256-mysql_dump-errors.yml delete mode 100644 changelogs/fragments/simplified-bsd-license.yml diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 3179e87..31c62a2 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,34 @@ Community MySQL Collection Release Notes This changelog describes changes after version 2.0.0. +v3.4.0 +====== + +Release Summary +--------------- + +This is the minor release of the ``community.mysql`` collection. +This changelog contains all changes to the modules in this collection +that have been added after the release of ``community.mysql`` 3.3.0. + +Major Changes +------------- + +- mysql_db - the ``pipefail`` argument's default value will be changed to ``true`` in community.mysql 4.0.0. If your target machines do not use ``bash`` as a default interpreter, set ``pipefail`` to ``false`` explicitly. However, we strongly recommend setting up ``bash`` as a default and ``pipefail=true`` as it will protect you from getting broken dumps you don't know about (https://github.com/ansible-collections/community.mysql/issues/407). + +Minor Changes +------------- + +- mysql_db - add the ``chdir`` argument to avoid failings when a dump file contains relative paths (https://github.com/ansible-collections/community.mysql/issues/395). +- mysql_db - add the ``pipefail`` argument to avoid broken dumps when ``state`` is ``dump`` and compression is used (https://github.com/ansible-collections/community.mysql/issues/256). + +Bugfixes +-------- + +- Include ``simplified_bsd.txt`` license file for various module utils. +- mysql_db - Using compression masks errors messages from mysql_dump. By default the fix is inactive to ensure retro-compatibility with system without bash. To activate the fix, use the module option ``pipefail=true`` (https://github.com/ansible-collections/community.mysql/issues/256). +- mysql_replication - when the ``primary_ssl`` argument is set to ``no``, the module will turn off SSL (https://github.com/ansible-collections/community.mysql/issues/393). + v3.3.0 ====== diff --git a/changelogs/changelog.yaml b/changelogs/changelog.yaml index ce4140f..99d7227 100644 --- a/changelogs/changelog.yaml +++ b/changelogs/changelog.yaml @@ -194,3 +194,36 @@ releases: - 368-mysql_role-fix-member-detection.yml - 369_mysql_role-add-members_must_exist.yml release_date: '2022-06-02' + 3.4.0: + changes: + bugfixes: + - Include ``simplified_bsd.txt`` license file for various module utils. + - mysql_db - Using compression masks errors messages from mysql_dump. By default + the fix is inactive to ensure retro-compatibility with system without bash. + To activate the fix, use the module option ``pipefail=true`` (https://github.com/ansible-collections/community.mysql/issues/256). + - mysql_replication - when the ``primary_ssl`` argument is set to ``no``, the + module will turn off SSL (https://github.com/ansible-collections/community.mysql/issues/393). + major_changes: + - mysql_db - the ``pipefail`` argument's default value will be changed to ``true`` + in community.mysql 4.0.0. If your target machines do not use ``bash`` as a + default interpreter, set ``pipefail`` to ``false`` explicitly. However, we + strongly recommend setting up ``bash`` as a default and ``pipefail=true`` + as it will protect you from getting broken dumps you don't know about (https://github.com/ansible-collections/community.mysql/issues/407). + minor_changes: + - mysql_db - add the ``chdir`` argument to avoid failings when a dump file contains + relative paths (https://github.com/ansible-collections/community.mysql/issues/395). + - mysql_db - add the ``pipefail`` argument to avoid broken dumps when ``state`` + is ``dump`` and compression is used (https://github.com/ansible-collections/community.mysql/issues/256). + release_summary: 'This is the minor release of the ``community.mysql`` collection. + + This changelog contains all changes to the modules in this collection + + that have been added after the release of ``community.mysql`` 3.3.0.' + fragments: + - 0-mysql_db_add_chdir_argument.yml + - 1-mysql_replication_can_disable_master_ssl.yml + - 2-mysql_db_announce.yml + - 3.4.0.yml + - fix-256-mysql_dump-errors.yml + - simplified-bsd-license.yml + release_date: '2022-08-02' diff --git a/changelogs/fragments/0-mysql_db_add_chdir_argument.yml b/changelogs/fragments/0-mysql_db_add_chdir_argument.yml deleted file mode 100644 index 26ce3dd..0000000 --- a/changelogs/fragments/0-mysql_db_add_chdir_argument.yml +++ /dev/null @@ -1,2 +0,0 @@ -minor_changes: -- mysql_db - add the ``chdir`` argument to avoid failings when a dump file contains relative paths (https://github.com/ansible-collections/community.mysql/issues/395). diff --git a/changelogs/fragments/1-mysql_replication_can_disable_master_ssl.yml b/changelogs/fragments/1-mysql_replication_can_disable_master_ssl.yml deleted file mode 100644 index ceb0d5a..0000000 --- a/changelogs/fragments/1-mysql_replication_can_disable_master_ssl.yml +++ /dev/null @@ -1,2 +0,0 @@ -bugfixes: -- mysql_replication - when the ``primary_ssl`` argument is set to ``no``, the module will turn off SSL (https://github.com/ansible-collections/community.mysql/issues/393). diff --git a/changelogs/fragments/2-mysql_db_announce.yml b/changelogs/fragments/2-mysql_db_announce.yml deleted file mode 100644 index 87d3c60..0000000 --- a/changelogs/fragments/2-mysql_db_announce.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -minor_changes: -- mysql_db - add the ``pipefail`` argument to avoid broken dumps when ``state`` is ``dump`` and compression is used (https://github.com/ansible-collections/community.mysql/issues/256). - -major_changes: -- mysql_db - the ``pipefail`` argument's default value will be changed to ``true`` in community.mysql 4.0.0. If your target machines do not use ``bash`` as a default interpreter, set ``pipefail`` to ``false`` explicitly. However, we strongly recommend setting up ``bash`` as a default and ``pipefail=true`` as it will protect you from getting broken dumps you don't know about (https://github.com/ansible-collections/community.mysql/issues/407). diff --git a/changelogs/fragments/fix-256-mysql_dump-errors.yml b/changelogs/fragments/fix-256-mysql_dump-errors.yml deleted file mode 100644 index f3dccc0..0000000 --- a/changelogs/fragments/fix-256-mysql_dump-errors.yml +++ /dev/null @@ -1,7 +0,0 @@ ---- - -bugfixes: - - mysql_dump - using compression masks errors messages from - mysql_dump. By default the fix is inactive to ensure retro-compatibility - with system without bash. To activate the fix, use the module option - ``pipefail=true`` (https://github.com/ansible-collections/community.mysql/issues/256). diff --git a/changelogs/fragments/simplified-bsd-license.yml b/changelogs/fragments/simplified-bsd-license.yml deleted file mode 100644 index 574a695..0000000 --- a/changelogs/fragments/simplified-bsd-license.yml +++ /dev/null @@ -1,2 +0,0 @@ -bugfixes: - - Include ``simplified_bsd.txt`` license file for various module utils. diff --git a/galaxy.yml b/galaxy.yml index 262677d..d877dea 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -1,6 +1,6 @@ namespace: community name: mysql -version: 3.3.0 +version: 3.4.0 readme: README.md authors: - Ansible community From c4e90f087df824a0f79f1d434e6961f8fc368c9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20Inderm=C3=BChle?= Date: Wed, 3 Aug 2022 11:20:22 +0200 Subject: [PATCH 101/217] Update galaxy.yml to the next expected version (#415) --- galaxy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/galaxy.yml b/galaxy.yml index d877dea..b30a3f9 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -1,6 +1,6 @@ namespace: community name: mysql -version: 3.4.0 +version: 3.4.1 readme: README.md authors: - Ansible community From 97318559e5aa976dce668a62daaab258014d75a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20Inderm=C3=BChle?= Date: Fri, 5 Aug 2022 09:25:14 +0200 Subject: [PATCH 102/217] Fix ci python requirements (#416) * Add matrix for python and ansible-core versions for sanity tests * Add python 3.9 to integrations tests * Add python 3.9 to unit tests * Reformat sort by python version first --- .github/workflows/ansible-test-plugins.yml | 43 +++++++++++++++++++--- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ansible-test-plugins.yml b/.github/workflows/ansible-test-plugins.yml index 1acca5d..3056760 100644 --- a/.github/workflows/ansible-test-plugins.yml +++ b/.github/workflows/ansible-test-plugins.yml @@ -29,6 +29,18 @@ jobs: - stable-2.12 - stable-2.13 - devel + python: + - 3.8 + - 3.9 + exclude: + - python: 3.8 + ansible: stable-2.13 + - python: 3.8 + ansible: devel + - python: 3.9 + ansible: stable-2.11 + - python: 3.9 + ansible: stable-2.12 steps: - name: Check out code @@ -39,7 +51,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v2 with: - python-version: 3.8 + python-version: ${{ matrix.python }} - name: Install ansible-base (${{ matrix.ansible }}) run: pip install https://github.com/ansible/ansible/archive/${{ matrix.ansible }}.tar.gz --disable-pip-version-check @@ -69,6 +81,7 @@ jobs: python: - 3.6 - 3.8 + - 3.9 connector: - pymysql==0.7.10 - pymysql==0.9.3 @@ -78,14 +91,22 @@ jobs: connector: pymysql==0.7.10 - db_engine_version: mariadb_10.8.3 connector: pymysql==0.7.10 - - python: 3.8 - ansible: stable-2.11 - python: 3.6 ansible: stable-2.12 - python: 3.6 ansible: stable-2.13 - python: 3.6 ansible: devel + - python: 3.8 + ansible: stable-2.11 + - python: 3.8 + ansible: stable-2.13 + - python: 3.8 + ansible: devel + - python: 3.9 + ansible: stable-2.11 + - python: 3.9 + ansible: stable-2.12 steps: @@ -97,7 +118,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v2 with: - python-version: 3.8 + python-version: ${{ matrix.python }} - name: Install ansible-base (${{ matrix.ansible }}) run: pip install https://github.com/ansible/ansible/archive/${{ matrix.ansible }}.tar.gz --disable-pip-version-check @@ -147,6 +168,18 @@ jobs: - stable-2.12 - stable-2.13 - devel + python: + - 3.8 + - 3.9 + exclude: + - python: 3.8 + ansible: stable-2.13 + - python: 3.8 + ansible: devel + - python: 3.9 + ansible: stable-2.11 + - python: 3.9 + ansible: stable-2.12 steps: - name: Check out code @@ -157,7 +190,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v2 with: - python-version: 3.8 + python-version: ${{ matrix.python }} - name: Install ansible-base (${{matrix.ansible}}) run: pip install https://github.com/ansible/ansible/archive/${{ matrix.ansible }}.tar.gz --disable-pip-version-check From 057f81711110d40fb46c2a66e9a9987f8dc70068 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Fri, 12 Aug 2022 22:41:26 +0200 Subject: [PATCH 103/217] MAINTAINERS file: add a new maintainer (#419) --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS b/MAINTAINERS index 97d0030..597aa6c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1,4 +1,5 @@ bmalynovytch Jorge-Rodriguez rsicart +laurent-indermuehle Andersson007 (andersson007_ in #ansible-community IRC/Matrix) From 61586ae4cc169119cb3fa9824fdfe9f5aeb46308 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20Inderm=C3=BChle?= Date: Tue, 16 Aug 2022 09:15:50 +0200 Subject: [PATCH 104/217] Port stable 1 ci changes (#423) * Add changes from stable-1 integrations tests (PR 418) * Refactor to use connectors' info declared in setup_mysql * Fix 2nd replication stop marked changed by mysqlclient --- .../tasks/config_overrides_defaults.yml | 21 ++++++++++++++++--- .../targets/test_mysql_db/tasks/issue-28.yml | 4 ++-- .../test_mysql_info/tasks/issue-28.yml | 4 ++-- .../test_mysql_query/tasks/issue-28.yml | 4 ++-- .../tasks/mysql_query_initial.yml | 4 ++-- .../test_mysql_replication/tasks/issue-28.yml | 4 ++-- .../tasks/mysql_replication_initial.yml | 20 ++++++++++++------ .../test_mysql_user/tasks/issue-121.yml | 2 -- .../test_mysql_user/tasks/issue-28.yml | 4 ++-- .../tasks/test_user_plugin_auth.yml | 12 ++++++----- .../tasks/assert_fail_msg.yml | 2 +- .../test_mysql_variables/tasks/assert_var.yml | 4 ++-- .../tasks/assert_var_output.yml | 4 ++-- .../test_mysql_variables/tasks/issue-28.yml | 4 ++-- .../tasks/mysql_variables.yml | 6 +++--- 15 files changed, 61 insertions(+), 38 deletions(-) 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 90c72b5..c2fda2a 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 @@ -14,7 +14,12 @@ - name: Add blank line shell: 'echo "" >> {{ config_file }}' when: - - (connector.name.0 is search('pymysql') and connector_ver is version('0.9.3', '>=')) or connector.name.0 is not search('pymysql') + - > + connector_name is not search('pymysql') + or ( + connector_name is search('pymysql') + and connector_ver is version('0.9.3', '>=') + ) - name: Create include_dir file: @@ -22,7 +27,12 @@ state: directory mode: '0777' when: - - (connector.name.0 is search('pymysql') and connector_ver is version('0.9.3', '>=')) or connector.name.0 is not search('pymysql') + - > + connector_name is not search('pymysql') + or ( + connector_name is search('pymysql') + and connector_ver is version('0.9.3', '>=') + ) - name: Add include_dir lineinfile: @@ -30,7 +40,12 @@ line: '!includedir {{ include_dir }}' insertafter: EOF when: - - (connector.name.0 is search('pymysql') and connector_ver is version('0.9.3', '>=')) or connector.name.0 is not search('pymysql') + - > + connector_name is not search('pymysql') + or ( + connector_name is search('pymysql') + and connector_ver is version('0.9.3', '>=') + ) - name: Create database using fake port to connect to, must fail mysql_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 index 74071e2..64fe9d5 100644 --- a/tests/integration/targets/test_mysql_db/tasks/issue-28.yml +++ b/tests/integration/targets/test_mysql_db/tasks/issue-28.yml @@ -52,12 +52,12 @@ - assert: that: - result is failed - when: connector.name.0 is search('pymysql') + when: connector_name is search('pymysql') - assert: that: - result is succeeded - when: connector.name.0 is not search('pymysql') + when: connector_name is not search('pymysql') - name: attempt connection with newly created user ignoring hostname mysql_db: diff --git a/tests/integration/targets/test_mysql_info/tasks/issue-28.yml b/tests/integration/targets/test_mysql_info/tasks/issue-28.yml index ec2b493..bf4576f 100644 --- a/tests/integration/targets/test_mysql_info/tasks/issue-28.yml +++ b/tests/integration/targets/test_mysql_info/tasks/issue-28.yml @@ -54,12 +54,12 @@ - assert: that: - result is failed - when: connector.name.0 is search('pymysql') + when: connector_name is search('pymysql') - assert: that: - result is succeeded - when: connector.name.0 is not search('pymysql') + when: connector_name is not search('pymysql') - name: attempt connection with newly created user ignoring hostname mysql_info: diff --git a/tests/integration/targets/test_mysql_query/tasks/issue-28.yml b/tests/integration/targets/test_mysql_query/tasks/issue-28.yml index 61f086e..a61e07f 100644 --- a/tests/integration/targets/test_mysql_query/tasks/issue-28.yml +++ b/tests/integration/targets/test_mysql_query/tasks/issue-28.yml @@ -54,12 +54,12 @@ - assert: that: - result is failed - when: connector.name.0 is search('pymysql') + when: connector_name is search('pymysql') - assert: that: - result is succeeded - when: connector.name.0 is not search('pymysql') + when: connector_name is not search('pymysql') - name: attempt connection with newly created user ignoring hostname mysql_query: diff --git a/tests/integration/targets/test_mysql_query/tasks/mysql_query_initial.yml b/tests/integration/targets/test_mysql_query/tasks/mysql_query_initial.yml index 2d971ab..5bf379f 100644 --- a/tests/integration/targets/test_mysql_query/tasks/mysql_query_initial.yml +++ b/tests/integration/targets/test_mysql_query/tasks/mysql_query_initial.yml @@ -343,7 +343,7 @@ that: # PyMySQL driver throws a warning, so the following is correct - result is not changed - when: connector.name.0 is search('pymysql') + when: connector_name is search('pymysql') # Issue https://github.com/ansible-collections/community.mysql/issues/268 - assert: @@ -352,7 +352,7 @@ # if the state was changed or not. # We assume that it was for DDL queryes by default in the code - result is changed - when: connector.name.0 is search('mysqlclient') + when: connector_name is search('mysqlclient') - name: Drop db {{ test_db }} mysql_query: diff --git a/tests/integration/targets/test_mysql_replication/tasks/issue-28.yml b/tests/integration/targets/test_mysql_replication/tasks/issue-28.yml index 11e457b..e6333f0 100644 --- a/tests/integration/targets/test_mysql_replication/tasks/issue-28.yml +++ b/tests/integration/targets/test_mysql_replication/tasks/issue-28.yml @@ -55,12 +55,12 @@ - assert: that: - result is failed - when: connector.name.0 is search('pymysql') + when: connector_name is search('pymysql') - assert: that: - result is succeeded - when: connector.name.0 is not search('pymysql') + when: connector_name is not search('pymysql') - name: attempt connection with newly created user ignoring hostname mysql_replication: 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 8272307..78206fc 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 @@ -184,8 +184,8 @@ shell: "echo \"INSERT INTO {{ test_table }} (id) VALUES (1), (2), (3); FLUSH LOGS;\" | {{ mysql_command }} -P{{ mysql_primary_port }} {{ test_db }}" - name: Small pause to be sure the bin log, which was flushed previously, reached the replica - pause: - seconds: 2 + ansible.builtin.wait_for: + timeout: 2 # Test primary log pos has been changed: - name: Get replica status @@ -218,10 +218,12 @@ fail_on_error: true register: result + # mysqlclient 2.0.1 always return "changed" - assert: that: - - result is not changed - when: (pymysql_version.stdout | default('1000', true)) is version('0.9.3', '<=') + - result is not changed + when: + - connector_name == 'pymysql' # Test stopreplica mode: - name: Stop replica @@ -236,7 +238,12 @@ - result is changed - result.queries == ["STOP SLAVE"] or result.queries == ["STOP REPLICA"] + - name: Pause for 2 seconds to let the replication stop + ansible.builtin.wait_for: + timeout: 2 + # Test stopreplica mode: + # mysqlclient 2.0.1 always return "changed" - name: Stop replica that is no longer running mysql_replication: <<: *mysql_params @@ -247,8 +254,9 @@ - assert: that: - - result is not changed - when: (pymysql_version.stdout | default('1000', true)) is version('0.9.3', '<=') + - result is not changed + when: + - connector_name == 'pymysql' # master / slave related choices were removed in 3.0.0 # https://github.com/ansible-collections/community.mysql/pull/252 diff --git a/tests/integration/targets/test_mysql_user/tasks/issue-121.yml b/tests/integration/targets/test_mysql_user/tasks/issue-121.yml index fb5bef6..7d789ef 100644 --- a/tests/integration/targets/test_mysql_user/tasks/issue-121.yml +++ b/tests/integration/targets/test_mysql_user/tasks/issue-121.yml @@ -9,8 +9,6 @@ block: # ============================================================ - - shell: pip show pymysql | awk '/Version/ {print $2}' - register: pymysql_version - name: get server certificate copy: diff --git a/tests/integration/targets/test_mysql_user/tasks/issue-28.yml b/tests/integration/targets/test_mysql_user/tasks/issue-28.yml index ae15865..d56965a 100644 --- a/tests/integration/targets/test_mysql_user/tasks/issue-28.yml +++ b/tests/integration/targets/test_mysql_user/tasks/issue-28.yml @@ -53,12 +53,12 @@ - assert: that: - result is failed - when: connector.name.0 is search('pymysql') + when: connector_name is search('pymysql') - assert: that: - result is succeeded - when: connector.name.0 is not search('pymysql') + when: connector_name is not search('pymysql') - name: attempt connection with newly created user ignoring hostname mysql_user: diff --git a/tests/integration/targets/test_mysql_user/tasks/test_user_plugin_auth.yml b/tests/integration/targets/test_mysql_user/tasks/test_user_plugin_auth.yml index 3b95d17..a4884d8 100644 --- a/tests/integration/targets/test_mysql_user/tasks/test_user_plugin_auth.yml +++ b/tests/integration/targets/test_mysql_user/tasks/test_user_plugin_auth.yml @@ -356,12 +356,14 @@ # plugins that are loaded by default are sha2*, but these aren't compatible with pymysql < 0.9, so skip these tests # for those versions. # - - name: Get pymysql version - shell: pip show pymysql | awk '/Version/ {print $2}' - register: pymysql_version - - name: Test plugin auth switching which doesn't work on pymysql < 0.9 - when: pymysql_version.stdout == "" or (pymysql_version.stdout != "" and pymysql_version.stdout is version('0.9', '>=')) + when: + - > + connector_name is not search('pymysql') + or ( + connector_name is search('pymysql') + and connector_ver is version('0.9', '>=') + ) block: - name: Create user with plugin auth (empty auth string) diff --git a/tests/integration/targets/test_mysql_variables/tasks/assert_fail_msg.yml b/tests/integration/targets/test_mysql_variables/tasks/assert_fail_msg.yml index 4a840b9..e7e0885 100644 --- a/tests/integration/targets/test_mysql_variables/tasks/assert_fail_msg.yml +++ b/tests/integration/targets/test_mysql_variables/tasks/assert_fail_msg.yml @@ -22,4 +22,4 @@ - name: assert message failure (expect failed=true) assert: that: - - "output.failed == true" + - "output.failed | bool == true" diff --git a/tests/integration/targets/test_mysql_variables/tasks/assert_var.yml b/tests/integration/targets/test_mysql_variables/tasks/assert_var.yml index 5419f34..704f069 100644 --- a/tests/integration/targets/test_mysql_variables/tasks/assert_var.yml +++ b/tests/integration/targets/test_mysql_variables/tasks/assert_var.yml @@ -22,7 +22,7 @@ - name: assert output message changed value assert: that: - - "output.changed == {{ changed }}" + - "output.changed | bool == changed | bool" - name: run mysql command to show variable command: "{{ mysql_command }} \"-e show variables like '{{ var_name }}'\"" @@ -31,6 +31,6 @@ - name: assert output mysql variable name and value assert: that: - - "result.changed == true" + - "result.changed | bool == true" - "'{{ var_name }}' in result.stdout" - "'{{ var_value }}' in result.stdout" diff --git a/tests/integration/targets/test_mysql_variables/tasks/assert_var_output.yml b/tests/integration/targets/test_mysql_variables/tasks/assert_var_output.yml index f84a468..01362ef 100644 --- a/tests/integration/targets/test_mysql_variables/tasks/assert_var_output.yml +++ b/tests/integration/targets/test_mysql_variables/tasks/assert_var_output.yml @@ -22,7 +22,7 @@ - name: assert output message changed value assert: that: - - "output.changed == {{ changed }}" + - "output.changed | bool == changed | bool" - set_fact: key_name: "{{ var_name }}" @@ -35,6 +35,6 @@ - name: assert output variable info match mysql variable info assert: that: - - "result.changed == true" + - "result.changed | bool == true" - "key_name in result.stdout" - "key_value in result.stdout" diff --git a/tests/integration/targets/test_mysql_variables/tasks/issue-28.yml b/tests/integration/targets/test_mysql_variables/tasks/issue-28.yml index 93c2125..aa01ddb 100644 --- a/tests/integration/targets/test_mysql_variables/tasks/issue-28.yml +++ b/tests/integration/targets/test_mysql_variables/tasks/issue-28.yml @@ -51,12 +51,12 @@ - assert: that: - result is failed - when: connector.name.0 is search('pymysql') + when: connector_name is search('pymysql') - assert: that: - result is succeeded - when: connector.name.0 is not search('pymysql') + when: connector_name is not search('pymysql') - name: attempt connection with newly created user ignoring hostname mysql_variables: 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 a857f12..ed34966 100644 --- a/tests/integration/targets/test_mysql_variables/tasks/mysql_variables.yml +++ b/tests/integration/targets/test_mysql_variables/tasks/mysql_variables.yml @@ -155,15 +155,15 @@ mysql_variables: <<: *mysql_params variable: max_connect_errors - value: -1 + value: '-1' register: oor_result ignore_errors: true - include: assert_var.yml changed=true output={{ oor_result }} var_name=max_connect_errors var_value=1 - when: connector.name.0 is not search('pymysql') + when: connector_name is not search('pymysql') - include: assert_fail_msg.yml output={{ oor_result }} msg='Truncated incorrect' - when: connector.name.0 is search('pymysql') + when: connector_name is search('pymysql') # ============================================================ # Verify mysql_variable fails when setting an incorrect value (incorrect type) From 0a68bb270f64957fc30d263080ca10c9f4d20f6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20Inderm=C3=BChle?= Date: Tue, 23 Aug 2022 09:11:55 +0200 Subject: [PATCH 105/217] Is changed (#427) * Refactor tests to use "is" and "is not" changed * Refactor tests to use is succeeded or is failed * Reformat indentation * Add filter "bool" to prevent issues --- .../tasks/multi_db_create_delete.yml | 30 ++-- .../test_mysql_db/tasks/state_dump_import.yml | 16 +-- .../tasks/state_present_absent.yml | 8 +- .../targets/test_mysql_info/tasks/main.yml | 56 ++++---- .../tasks/mysql_query_initial.yml | 128 +++++++++--------- .../tasks/test_priv_subtract.yml | 6 +- .../tasks/assert_user_password.yml | 2 +- .../test_mysql_user/tasks/create_user.yml | 2 +- .../test_mysql_user/tasks/issue-265.yml | 16 +-- .../test_mysql_user/tasks/issue-64560.yaml | 8 +- .../targets/test_mysql_user/tasks/main.yml | 8 +- .../test_mysql_user/tasks/remove_user.yml | 6 +- .../tasks/test_priv_append.yml | 4 +- .../tasks/test_priv_subtract.yml | 6 +- .../test_mysql_user/tasks/test_privs.yml | 10 +- .../tasks/test_user_password.yml | 30 ++-- .../tasks/test_user_plugin_auth.yml | 40 +++--- .../tasks/assert_fail_msg.yml | 2 +- .../test_mysql_variables/tasks/assert_var.yml | 2 +- .../tasks/assert_var_output.yml | 2 +- 20 files changed, 194 insertions(+), 188 deletions(-) diff --git a/tests/integration/targets/test_mysql_db/tasks/multi_db_create_delete.yml b/tests/integration/targets/test_mysql_db/tasks/multi_db_create_delete.yml index cb91d32..c2eb13c 100644 --- a/tests/integration/targets/test_mysql_db/tasks/multi_db_create_delete.yml +++ b/tests/integration/targets/test_mysql_db/tasks/multi_db_create_delete.yml @@ -56,7 +56,7 @@ - name: assert successful completion of create database using check_mode since databases does not exist prior assert: that: - - check_mode_result.changed == true + - check_mode_result is changed - name: run command to list databases like specified database name command: "{{ mysql_command }} \"-e show databases like 'database%'\"" @@ -87,7 +87,7 @@ - name: assert successful completion of create database assert: that: - - result.changed == true + - result is changed - result.db_list == ['{{ db1_name }}', '{{ db2_name }}', '{{ db3_name }}'] - name: run command to list databases like specified database name @@ -120,7 +120,7 @@ - name: assert that recreation of existing databases does not make change (since recreated using check mode) assert: that: - - check_mode_result.changed == false + - check_mode_result is not changed - name: run command to list databases like specified database name command: "{{ mysql_command }} \"-e show databases like 'database%'\"" @@ -151,7 +151,7 @@ - name: assert that recreation of existing databases does not make change assert: that: - - result.changed == false + - result is not changed - name: run command to list databases like specified database name command: "{{ mysql_command }} \"-e show databases like 'database%'\"" @@ -180,7 +180,7 @@ - name: assert successful completion of deleting database assert: that: - - result.changed == true + - result is changed - name: run command to list databases like specified database name command: "{{ mysql_command }} \"-e show databases like 'database%'\"" @@ -212,7 +212,7 @@ - name: assert successful completion of recreation of partially existing database using check mode assert: that: - - check_mode_result.changed == true + - check_mode_result is changed - name: run command to list databases like specified database name command: "{{ mysql_command }} \"-e show databases like 'database%'\"" @@ -243,7 +243,7 @@ - name: assert successful completion of create database assert: that: - - result.changed == true + - result is changed - name: run command to list databases like specified database name command: "{{ mysql_command }} \"-e show databases like 'database%'\"" @@ -284,7 +284,7 @@ - name: assert successful completion of dump operation using check mode assert: that: - - check_mode_dump_result.changed == true + - check_mode_dump_result is changed - name: run command to list databases like specified database name command: "{{ mysql_command }} \"-e show databases like 'database%'\"" @@ -401,7 +401,7 @@ - name: assert successful completion of dump operation assert: that: - - dump_result.changed == true + - dump_result is changed - dump_result.db_list == ['{{ db1_name }}', '{{ db2_name }}', '{{ db3_name }}'] - name: run command to list databases like specified database name @@ -451,7 +451,7 @@ - name: assert successful completion of dump operation assert: that: - - dump_result.changed == true + - dump_result is changed - name: run command to list databases like specified database name command: "{{ mysql_command }} \"-e show databases like 'database%'\"" @@ -491,7 +491,7 @@ - name: assert successful completion of delete databases which already exists using check mode assert: that: - - check_mode_result.changed == true + - check_mode_result is changed - name: run command to test state=absent for a database name command: "{{ mysql_command }} \"-e show databases like 'database%'\"" @@ -520,7 +520,7 @@ - name: assert successful completion of deleting database assert: that: - - result.changed == true + - result is changed - result.db_list == ['{{ db2_name }}', '{{ db3_name }}'] - name: run command to list databases like specified database name @@ -551,7 +551,7 @@ - name: assert that deletion of non existing databases does not make change (using check mode) assert: that: - - check_mode_result.changed == false + - check_mode_result is not changed - name: run command to test state=absent for a database name command: "{{ mysql_command }} \"-e show databases like 'database%'\"" @@ -580,7 +580,7 @@ - name: assert that deletion of non existing databases does not make change assert: that: - - result.changed == false + - result is not changed - name: run command to list databases like specified database name command: "{{ mysql_command }} \"-e show databases like 'database%'\"" @@ -612,7 +612,7 @@ - name: assert successful completion of deleting database assert: that: - - result.changed == true + - result is changed - name: run command to list databases like specified database name command: "{{ mysql_command }} \"-e show databases like 'database%'\"" diff --git a/tests/integration/targets/test_mysql_db/tasks/state_dump_import.yml b/tests/integration/targets/test_mysql_db/tasks/state_dump_import.yml index 008721c..724dd18 100644 --- a/tests/integration/targets/test_mysql_db/tasks/state_dump_import.yml +++ b/tests/integration/targets/test_mysql_db/tasks/state_dump_import.yml @@ -159,7 +159,7 @@ - name: assert successful completion of dump operation (with multiple databases in list form) via check mode assert: that: - - "dump_result.changed == true" + - dump_result is changed - name: database dump file2 should not exist stat: @@ -187,7 +187,7 @@ - name: assert successful completion of dump operation (with multiple databases in list form) assert: that: - - "dump_result2.changed == true" + - dump_result2 is changed - name: state dump - dump file2 should exist file: @@ -249,7 +249,7 @@ - name: assert output message restored a database from dump file1 assert: that: - - "import_result.changed == true" + - import_result is changed - name: remove database mysql_db: @@ -284,7 +284,7 @@ - name: assert output message restored a database from dump file2 (check mode) assert: that: - - "check_import_result.changed == true" + - check_import_result is changed - name: run command to list databases command: "{{ mysql_command }} \"-e show databases like 'data%'\"" @@ -309,7 +309,7 @@ - name: assert output message restored a database from dump file2 assert: that: - - import_result2.changed == true + - import_result2 is changed - import_result2.db_list == ['{{ db_name2 }}'] - name: run command to list databases @@ -335,7 +335,7 @@ - name: assert output message backup the database assert: that: - - "result.changed == true" + - result is changed - "result.db =='{{ db_name }}'" # - name: assert database was backed up successfully @@ -364,7 +364,7 @@ - name: assert output message restore the database assert: that: - - "result.changed == true" + - result is changed - name: select data from table employee command: "{{ mysql_command }} {{ db_name }} \"-e select * from employee\"" @@ -398,7 +398,7 @@ - assert: that: - - result.failed == true + - result is failed - name: try to import with force parameter mysql_db: diff --git a/tests/integration/targets/test_mysql_db/tasks/state_present_absent.yml b/tests/integration/targets/test_mysql_db/tasks/state_present_absent.yml index e5c5f33..5b6e871 100644 --- a/tests/integration/targets/test_mysql_db/tasks/state_present_absent.yml +++ b/tests/integration/targets/test_mysql_db/tasks/state_present_absent.yml @@ -95,7 +95,7 @@ - name: assert test mysql_db encoding param not valid - issue 8075 (failed=true) assert: that: - - "result.failed == true" + - result is failed - "'Traceback' not in result.msg" - "'Unknown character set' in result.msg" @@ -196,7 +196,7 @@ - name: assert output message that database was created assert: that: - - "result.changed == true" + - result is changed - name: run command to test database was created using user1 command: "{{ mysql_command }} -e \"show databases like '{{ db_user1 | regex_replace(\"([%_\\\\])\", \"\\\\\\1\") }}'\"" @@ -233,7 +233,7 @@ - name: assert output message that database was not created using dbuser2 assert: that: - - "result.failed == true" + - result is failed - "'Access denied' in result.msg" - name: run command to test that database was not created @@ -260,7 +260,7 @@ - name: assert output message that database was not deleted using dbuser2 assert: that: - - "result.failed == true" + - result is failed - "'Access denied' in result.msg" - name: run command to test database was not deleted diff --git a/tests/integration/targets/test_mysql_info/tasks/main.yml b/tests/integration/targets/test_mysql_info/tasks/main.yml index c3d601d..ec2bd9b 100644 --- a/tests/integration/targets/test_mysql_info/tasks/main.yml +++ b/tests/integration/targets/test_mysql_info/tasks/main.yml @@ -47,7 +47,7 @@ - assert: that: - - result.changed == false + - result is not changed - "mysql_version in result.version.full or mariadb_version in result.version.full" - result.settings != {} - result.global_status != {} @@ -66,7 +66,7 @@ - assert: that: - - result.changed == false + - result is not changed - result.version != {} # Remove cred files @@ -86,8 +86,8 @@ - assert: that: - - result.changed == false - - result.version != {} + - result is not changed + - result.version != {} # Test excluding - name: Collect all info except settings and users @@ -98,13 +98,13 @@ - assert: that: - - result.changed == false - - result.version != {} - - result.global_status != {} - - result.databases != {} - - result.engines != {} - - result.settings is not defined - - result.users is not defined + - result is not changed + - result.version != {} + - result.global_status != {} + - result.databases != {} + - result.engines != {} + - result.settings is not defined + - result.users is not defined # Test including - name: Collect info only about version and databases @@ -117,13 +117,13 @@ - assert: that: - - result.changed == false - - result.version != {} - - result.databases != {} - - result.engines is not defined - - result.settings is not defined - - result.global_status is not defined - - result.users is not defined + - result is not changed + - result.version != {} + - result.databases != {} + - result.engines is not defined + - result.settings is not defined + - result.global_status is not defined + - result.users is not defined # Test exclude_fields: db_size # 'unsupported' element is passed to check that an unsupported value @@ -140,9 +140,9 @@ - assert: that: - - result.changed == false - - result.databases != {} - - result.databases.mysql == {} + - result is not changed + - result.databases != {} + - result.databases.mysql == {} ######################################################## # Issue #65727, empty databases must be in returned dict @@ -163,9 +163,9 @@ # Check acme is in returned dict - assert: that: - - result.changed == false - - result.databases.acme.size == 0 - - result.databases.mysql != {} + - result is not changed + - result.databases.acme.size == 0 + - result.databases.mysql != {} - name: Collect info about databases excluding their sizes mysql_info: @@ -180,9 +180,9 @@ # Check acme is in returned dict - assert: that: - - result.changed == false - - result.databases.acme == {} - - result.databases.mysql == {} + - result is not changed + - result.databases.acme == {} + - result.databases.mysql == {} - name: Remove acme database mysql_db: @@ -212,4 +212,4 @@ - name: Check assert: that: - result.databases.allviews.size == 0 + - result.databases.allviews.size == 0 diff --git a/tests/integration/targets/test_mysql_query/tasks/mysql_query_initial.yml b/tests/integration/targets/test_mysql_query/tasks/mysql_query_initial.yml index 5bf379f..cbb7b53 100644 --- a/tests/integration/targets/test_mysql_query/tasks/mysql_query_initial.yml +++ b/tests/integration/targets/test_mysql_query/tasks/mysql_query_initial.yml @@ -18,8 +18,8 @@ - assert: that: - - result is changed - - result.executed_queries == ['CREATE DATABASE {{ test_db }}'] + - result is changed + - result.executed_queries == ['CREATE DATABASE {{ test_db }}'] - name: Create {{ test_table1 }} mysql_query: @@ -30,8 +30,8 @@ - assert: that: - - result is changed - - result.executed_queries == ['CREATE TABLE {{ test_table1 }} (id int)'] + - result is changed + - result.executed_queries == ['CREATE TABLE {{ test_table1 }} (id int)'] - name: Insert test data mysql_query: @@ -45,9 +45,9 @@ - assert: that: - - result is changed - - result.rowcount == [2, 1] - - result.executed_queries == ['INSERT INTO {{ test_table1 }} VALUES (1), (2)', 'INSERT INTO {{ test_table1 }} VALUES (3)'] + - result is changed + - result.rowcount == [2, 1] + - result.executed_queries == ['INSERT INTO {{ test_table1 }} VALUES (1), (2)', 'INSERT INTO {{ test_table1 }} VALUES (3)'] - name: Check data in {{ test_table1 }} mysql_query: @@ -58,12 +58,12 @@ - assert: that: - - result is not changed - - result.executed_queries == ['SELECT * FROM {{ test_table1 }}'] - - result.rowcount == [3] - - result.query_result[0][0].id == 1 - - result.query_result[0][1].id == 2 - - result.query_result[0][2].id == 3 + - result is not changed + - result.executed_queries == ['SELECT * FROM {{ test_table1 }}'] + - result.rowcount == [3] + - result.query_result[0][0].id == 1 + - result.query_result[0][1].id == 2 + - result.query_result[0][2].id == 3 - name: Check data in {{ test_table1 }} using positional args mysql_query: @@ -76,10 +76,10 @@ - assert: that: - - result is not changed - - result.executed_queries == ["SELECT * FROM {{ test_table1 }} WHERE id = 1"] - - result.rowcount == [1] - - result.query_result[0][0].id == 1 + - result is not changed + - result.executed_queries == ["SELECT * FROM {{ test_table1 }} WHERE id = 1"] + - result.rowcount == [1] + - result.query_result[0][0].id == 1 - name: Check data in {{ test_table1 }} using named args mysql_query: @@ -92,10 +92,10 @@ - assert: that: - - result is not changed - - result.executed_queries == ["SELECT * FROM {{ test_table1 }} WHERE id = 1"] - - result.rowcount == [1] - - result.query_result[0][0].id == 1 + - result is not changed + - result.executed_queries == ["SELECT * FROM {{ test_table1 }} WHERE id = 1"] + - result.rowcount == [1] + - result.query_result[0][0].id == 1 - name: Update data in {{ test_table1 }} mysql_query: @@ -109,9 +109,9 @@ - assert: that: - - result is changed - - result.executed_queries == ['UPDATE {{ test_table1 }} SET id = 0 WHERE id = 1'] - - result.rowcount == [1] + - result is changed + - result.executed_queries == ['UPDATE {{ test_table1 }} SET id = 0 WHERE id = 1'] + - result.rowcount == [1] - name: Check the prev update - row with value 1 does not exist anymore mysql_query: @@ -124,9 +124,9 @@ - assert: that: - - result is not changed - - result.executed_queries == ['SELECT * FROM {{ test_table1 }} WHERE id = 1'] - - result.rowcount == [0] + - result is not changed + - result.executed_queries == ['SELECT * FROM {{ test_table1 }} WHERE id = 1'] + - result.rowcount == [0] - name: Check the prev update - row with value - exist mysql_query: @@ -139,9 +139,9 @@ - assert: that: - - result is not changed - - result.executed_queries == ['SELECT * FROM {{ test_table1 }} WHERE id = 0'] - - result.rowcount == [1] + - result is not changed + - result.executed_queries == ['SELECT * FROM {{ test_table1 }} WHERE id = 0'] + - result.rowcount == [1] - name: Update data in {{ test_table1 }} again mysql_query: @@ -155,9 +155,9 @@ - assert: that: - - result is not changed - - result.executed_queries == ['UPDATE {{ test_table1 }} SET id = 0 WHERE id = 1'] - - result.rowcount == [0] + - result is not changed + - result.executed_queries == ['UPDATE {{ test_table1 }} SET id = 0 WHERE id = 1'] + - result.rowcount == [0] - name: Delete data from {{ test_table1 }} mysql_query: @@ -170,9 +170,9 @@ - assert: that: - - result is changed - - result.executed_queries == ['DELETE FROM {{ test_table1 }} WHERE id = 0', 'SELECT * FROM {{ test_table1 }} WHERE id = 0'] - - result.rowcount == [1, 0] + - result is changed + - result.executed_queries == ['DELETE FROM {{ test_table1 }} WHERE id = 0', 'SELECT * FROM {{ test_table1 }} WHERE id = 0'] + - result.rowcount == [1, 0] - name: Delete data from {{ test_table1 }} again mysql_query: @@ -183,9 +183,9 @@ - assert: that: - - result is not changed - - result.executed_queries == ['DELETE FROM {{ test_table1 }} WHERE id = 0'] - - result.rowcount == [0] + - result is not changed + - result.executed_queries == ['DELETE FROM {{ test_table1 }} WHERE id = 0'] + - result.rowcount == [0] - name: Truncate {{ test_table1 }} mysql_query: @@ -198,9 +198,9 @@ - assert: that: - - result is changed - - result.executed_queries == ['TRUNCATE {{ test_table1 }}', 'SELECT * FROM {{ test_table1 }}'] - - result.rowcount == [0, 0] + - result is changed + - result.executed_queries == ['TRUNCATE {{ test_table1 }}', 'SELECT * FROM {{ test_table1 }}'] + - result.rowcount == [0, 0] - name: Rename {{ test_table1 }} mysql_query: @@ -211,9 +211,9 @@ - assert: that: - - result is changed - - result.executed_queries == ['RENAME TABLE {{ test_table1 }} TO {{ test_table2 }}'] - - result.rowcount == [0] + - result is changed + - result.executed_queries == ['RENAME TABLE {{ test_table1 }} TO {{ test_table2 }}'] + - result.rowcount == [0] - name: Check the prev rename mysql_query: @@ -225,7 +225,7 @@ - assert: that: - - result.failed == true + - result is failed - name: Check the prev rename mysql_query: @@ -236,7 +236,7 @@ - assert: that: - - result.rowcount == [0] + - result.rowcount == [0] - name: Create {{ test_table3 }} mysql_query: @@ -259,7 +259,7 @@ - assert: that: - - result.rowcount == [2] + - result.rowcount == [2] - name: Pass wrong query type mysql_query: @@ -271,8 +271,8 @@ - assert: that: - - result is failed - - result.msg is search('the query option value must be a string or list') + - result is failed + - result.msg is search('the query option value must be a string or list') - name: Pass wrong query element mysql_query: @@ -286,8 +286,8 @@ - assert: that: - - result is failed - - result.msg is search('the elements in query list must be strings') + - result is failed + - result.msg is search('the elements in query list must be strings') - name: Create {{ test_table4 }} mysql_query: @@ -305,8 +305,8 @@ - assert: that: - - result is changed - - result.rowcount == [1] + - result is changed + - result.rowcount == [1] - name: Replace test data mysql_query: @@ -318,8 +318,8 @@ - assert: that: - - result is changed - - result.rowcount == [2] + - result is changed + - result.rowcount == [2] # Issue https://github.com/ansible-collections/community.mysql/issues/268 - name: Create table @@ -341,17 +341,17 @@ # Issue https://github.com/ansible-collections/community.mysql/issues/268 - assert: that: - # PyMySQL driver throws a warning, so the following is correct - - result is not changed + # PyMySQL driver throws a warning, so the following is correct + - result is not changed when: connector_name is search('pymysql') # Issue https://github.com/ansible-collections/community.mysql/issues/268 - assert: that: - # mysqlclient driver throws nothing, so it's impossible to figure out - # if the state was changed or not. - # We assume that it was for DDL queryes by default in the code - - result is changed + # mysqlclient driver throws nothing, so it's impossible to figure out + # if the state was changed or not. + # We assume that it was for DDL queryes by default in the code + - result is changed when: connector_name is search('mysqlclient') - name: Drop db {{ test_db }} @@ -362,5 +362,5 @@ - assert: that: - - result is changed - - result.executed_queries == ['DROP DATABASE {{ test_db }}'] + - result is changed + - result.executed_queries == ['DROP DATABASE {{ test_db }}'] diff --git a/tests/integration/targets/test_mysql_role/tasks/test_priv_subtract.yml b/tests/integration/targets/test_mysql_role/tasks/test_priv_subtract.yml index d5fe69c..95d2f1d 100644 --- a/tests/integration/targets/test_mysql_role/tasks/test_priv_subtract.yml +++ b/tests/integration/targets/test_mysql_role/tasks/test_priv_subtract.yml @@ -45,7 +45,7 @@ - name: Assert that there wasn't a change in permissions assert: that: - - "result.changed == false" + - result is not changed - name: Run command to show privileges for role (expect privileges in stdout) command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ role2 }}'\"" @@ -69,7 +69,7 @@ - name: Assert that there was a change because permissions were/would be revoked on data1.* assert: that: - - "result.changed == true" + - result is changed - name: Run command to show privileges for role (expect privileges in stdout) command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ role2 }}'\"" @@ -100,7 +100,7 @@ - name: Assert that there was no change because invalid permissions are ignored assert: that: - - "result.changed == false" + - result is not changed - name: Run command to show privileges for role (expect privileges in stdout) command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ role2 }}'\"" diff --git a/tests/integration/targets/test_mysql_user/tasks/assert_user_password.yml b/tests/integration/targets/test_mysql_user/tasks/assert_user_password.yml index fd7e281..ba045eb 100644 --- a/tests/integration/targets/test_mysql_user/tasks/assert_user_password.yml +++ b/tests/integration/targets/test_mysql_user/tasks/assert_user_password.yml @@ -13,7 +13,7 @@ - name: assert a change occurred assert: that: - - "result.changed == {{ expect_change }}" + - "result.changed | bool == {{ expect_change }} | bool" - "result.password_changed == {{ expect_password_change }}" - name: query the user command: "{{ mysql_command }} -BNe \"SELECT plugin, authentication_string FROM mysql.user where user='{{ username }}' and host='{{ host }}'\"" diff --git a/tests/integration/targets/test_mysql_user/tasks/create_user.yml b/tests/integration/targets/test_mysql_user/tasks/create_user.yml index 78c253d..9984ea9 100644 --- a/tests/integration/targets/test_mysql_user/tasks/create_user.yml +++ b/tests/integration/targets/test_mysql_user/tasks/create_user.yml @@ -43,4 +43,4 @@ - name: assert output message mysql user was created assert: that: - - "result.changed == true" + - result is changed diff --git a/tests/integration/targets/test_mysql_user/tasks/issue-265.yml b/tests/integration/targets/test_mysql_user/tasks/issue-265.yml index 6c91803..167b69b 100644 --- a/tests/integration/targets/test_mysql_user/tasks/issue-265.yml +++ b/tests/integration/targets/test_mysql_user/tasks/issue-265.yml @@ -28,7 +28,7 @@ - name: assert output message mysql user was created assert: that: - - "result.changed == true" + - result is changed - include: assert_user.yml user_name={{user_name_1}} @@ -45,7 +45,7 @@ - name: assert output message mysql user was removed assert: that: - - "result.changed == true" + - result is changed # Test blank user removal - name: create blank mysql user to be removed later @@ -68,7 +68,7 @@ - name: assert changed is true for removing all blank users assert: that: - - "result.changed == true" + - result is changed - name: remove blank mysql user with hosts=all (expect ok) mysql_user: @@ -82,7 +82,7 @@ - name: assert changed is true for removing all blank users assert: that: - - "result.changed == false" + - result is not changed - include: assert_no_user.yml user_name={{user_name_1}} @@ -109,7 +109,7 @@ - name: assert output message mysql user was created assert: that: - - "result.changed == true" + - result is changed - include: assert_user.yml user_name={{user_name_1}} @@ -126,7 +126,7 @@ - name: assert output message mysql user was removed assert: that: - - "result.changed == true" + - result is changed # Test blank user removal - name: create blank mysql user to be removed later @@ -149,7 +149,7 @@ - name: assert changed is true for removing all blank users assert: that: - - "result.changed == true" + - result is changed - name: remove blank mysql user with hosts=all (expect ok) mysql_user: @@ -163,6 +163,6 @@ - name: assert changed is true for removing all blank users assert: that: - - "result.changed == false" + - result is not changed - include: assert_no_user.yml user_name={{user_name_1}} diff --git a/tests/integration/targets/test_mysql_user/tasks/issue-64560.yaml b/tests/integration/targets/test_mysql_user/tasks/issue-64560.yaml index 46078b2..1c0af68 100644 --- a/tests/integration/targets/test_mysql_user/tasks/issue-64560.yaml +++ b/tests/integration/targets/test_mysql_user/tasks/issue-64560.yaml @@ -17,7 +17,9 @@ register: result - name: assert root password is changed - assert: { that: "result.changed == true" } + assert: + that: + - result is changed - name: Set root password again mysql_user: @@ -31,7 +33,9 @@ register: result - name: Assert root password is not changed - assert: { that: "result.changed == false" } + assert: + that: + - result is not changed - name: Set root password again mysql_user: diff --git a/tests/integration/targets/test_mysql_user/tasks/main.yml b/tests/integration/targets/test_mysql_user/tasks/main.yml index 1d36b40..db3304c 100644 --- a/tests/integration/targets/test_mysql_user/tasks/main.yml +++ b/tests/integration/targets/test_mysql_user/tasks/main.yml @@ -65,7 +65,9 @@ register: result - name: assert output message mysql user was not created - assert: { that: "result.changed == false" } + assert: + that: + - result is not changed # ============================================================ # remove mysql user and verify user is removed from mysql database @@ -81,7 +83,7 @@ - name: assert output message mysql user was removed assert: that: - - "result.changed == true" + - result is changed - include: assert_no_user.yml user_name={{user_name_1}} @@ -99,7 +101,7 @@ - name: assert output message mysql user that does not exist assert: that: - - "result.changed == false" + - result is not changed - include: assert_no_user.yml user_name={{user_name_1}} diff --git a/tests/integration/targets/test_mysql_user/tasks/remove_user.yml b/tests/integration/targets/test_mysql_user/tasks/remove_user.yml index 45a0ad4..7a2c9e9 100644 --- a/tests/integration/targets/test_mysql_user/tasks/remove_user.yml +++ b/tests/integration/targets/test_mysql_user/tasks/remove_user.yml @@ -37,7 +37,7 @@ - name: assert output message mysql user was removed assert: that: - - "result.changed == true" + - result is changed # ============================================================ - name: create blank mysql user to be removed later @@ -58,7 +58,7 @@ - name: assert changed is true for removing all blank users assert: that: - - "result.changed == true" + - result is changed - name: remove blank mysql user with hosts=all (expect ok) mysql_user: @@ -71,4 +71,4 @@ - name: assert changed is true for removing all blank users assert: that: - - "result.changed == false" + - result is not changed diff --git a/tests/integration/targets/test_mysql_user/tasks/test_priv_append.yml b/tests/integration/targets/test_mysql_user/tasks/test_priv_append.yml index cd10147..583f7c0 100644 --- a/tests/integration/targets/test_mysql_user/tasks/test_priv_append.yml +++ b/tests/integration/targets/test_mysql_user/tasks/test_priv_append.yml @@ -50,7 +50,7 @@ - name: Assert that there wasn't a change in permissions assert: that: - - "result.changed == false" + - result is not changed - name: Run command to show privileges for user (expect privileges in stdout) command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ user_name_4 }}'@'localhost'\"" @@ -76,7 +76,7 @@ - name: Assert that there was a change because permissions were added to data1.* assert: that: - - "result.changed == true" + - result is changed - name: Run command to show privileges for user (expect privileges in stdout) command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ user_name_4 }}'@'localhost'\"" diff --git a/tests/integration/targets/test_mysql_user/tasks/test_priv_subtract.yml b/tests/integration/targets/test_mysql_user/tasks/test_priv_subtract.yml index c8d08c7..7595243 100644 --- a/tests/integration/targets/test_mysql_user/tasks/test_priv_subtract.yml +++ b/tests/integration/targets/test_mysql_user/tasks/test_priv_subtract.yml @@ -47,7 +47,7 @@ - name: Assert that there wasn't a change in permissions assert: that: - - "result.changed == false" + - result is not changed - name: Run command to show privileges for user (expect privileges in stdout) command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ user_name_4 }}'@'localhost'\"" @@ -72,7 +72,7 @@ - name: Assert that there was a change because permissions were/would be revoked on data1.* assert: that: - - "result.changed == true" + - result is changed - name: Run command to show privileges for user (expect privileges in stdout) command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ user_name_4 }}'@'localhost'\"" @@ -104,7 +104,7 @@ - name: Assert that there was no change because invalid permissions are ignored assert: that: - - "result.changed == false" + - result is not changed - name: Run command to show privileges for user (expect privileges in stdout) command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ user_name_4 }}'@'localhost'\"" diff --git a/tests/integration/targets/test_mysql_user/tasks/test_privs.yml b/tests/integration/targets/test_mysql_user/tasks/test_privs.yml index 68025ac..d4798ff 100644 --- a/tests/integration/targets/test_mysql_user/tasks/test_privs.yml +++ b/tests/integration/targets/test_mysql_user/tasks/test_privs.yml @@ -51,7 +51,7 @@ - name: assert output message for current privileges assert: that: - - "result.changed == true" + - result is changed - name: run command to show privileges for user (expect privileges in stdout) command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{user_name_2}}'@'localhost'\"" @@ -101,7 +101,7 @@ - name: Assert that priv changed assert: that: - - "result.changed == true" + - result is changed - name: Add privs to a specific table (expect ok) mysql_user: @@ -115,7 +115,7 @@ - name: Assert that priv did not change assert: that: - - "result.changed == false" + - result is not changed # ============================================================ - name: update user with all privileges @@ -162,7 +162,7 @@ - name: Assert that priv changed assert: that: - - "result.changed == true" + - result is changed - name: Test idempotency (expect ok) mysql_user: @@ -177,7 +177,7 @@ - name: Assert that priv did not change assert: that: - - "result.changed == false" + - result is not changed when: install_type == 'mysql' or (install_type == 'mariadb' and mariadb_version is version('10.2', '==')) # ============================================================ diff --git a/tests/integration/targets/test_mysql_user/tasks/test_user_password.yml b/tests/integration/targets/test_mysql_user/tasks/test_user_password.yml index f3b0e06..57d8d29 100644 --- a/tests/integration/targets/test_mysql_user/tasks/test_user_password.yml +++ b/tests/integration/targets/test_mysql_user/tasks/test_user_password.yml @@ -32,7 +32,7 @@ - name: Assert that a change occurred because the user was added assert: that: - - "result.changed == true" + - result is changed - include: assert_user.yml user_name={{ test_user_name }} priv={{ test_default_priv_type }} @@ -49,7 +49,7 @@ - name: Assert that mysql_info was successful assert: that: - - "result.failed == false" + - result is succeeded - name: Run mysql_user again without any changes mysql_user: @@ -63,7 +63,7 @@ - name: Assert that there weren't any changes because username/password didn't change assert: that: - - "result.changed == false" + - result is not changed - include: assert_user.yml user_name={{ test_user_name }} priv={{ test_default_priv_type }} @@ -78,7 +78,7 @@ - name: Assert that a change occurred because the password was updated assert: that: - - "result.changed == true" + - result is changed - include: assert_user.yml user_name={{ test_user_name }} priv={{ test_default_priv_type }} @@ -95,7 +95,7 @@ - name: Assert that the mysql_info module failed because we used the old password assert: that: - - "result.failed == true" + - result is failed - name: Get the MySQL version data using the new password (should work) mysql_info: @@ -110,7 +110,7 @@ - name: Assert that the mysql_info module succeeded because we used the new password assert: that: - - "result.failed == false" + - result is succeeded # Cleanup - include: remove_user.yml user_name={{ test_user_name }} user_password={{ new_password }} @@ -131,7 +131,7 @@ - name: Assert that a change occurred because the user was added assert: that: - - "result.changed == true" + - result is changed - include: assert_user.yml user_name={{ test_user_name }} priv={{ test_default_priv_type }} @@ -148,7 +148,7 @@ - name: Assert that there weren't any changes because username/password didn't change assert: that: - - "result.changed == false" + - result is not changed # Cleanup - include: remove_user.yml user_name={{ test_user_name }} user_password={{ new_password }} @@ -170,7 +170,7 @@ - name: Assert that a change occurred because the user was added assert: that: - - "result.changed == true" + - result is changed - include: assert_user.yml user_name={{ test_user_name }} priv={{ test_default_priv_type }} @@ -187,7 +187,7 @@ - name: Assert that the mysql_info module succeeded because we used the new password assert: that: - - "result.failed == false" + - result is succeeded - name: Pass in the same password as before, but in the encrypted form (no change expected) mysql_user: @@ -200,7 +200,7 @@ - name: Assert that there weren't any changes because username/password didn't change assert: that: - - "result.changed == false" + - result is not changed # Cleanup - include: remove_user.yml user_name={{ test_user_name }} user_password={{ new_password }} @@ -220,7 +220,7 @@ - name: Assert that a change occurred because the user was added assert: that: - - "result.changed == true" + - result is changed - name: Get the MySQL version using an empty password for the newly created user mysql_info: @@ -235,7 +235,7 @@ - name: Assert that mysql_info was successful assert: that: - - "result.failed == false" + - result is succeeded - name: Get the MySQL version using an non-empty password (should fail) mysql_info: @@ -250,7 +250,7 @@ - name: Assert that mysql_info failed assert: that: - - "result.failed == true" + - result is failed - name: Update the user without changing the password mysql_user: @@ -263,7 +263,7 @@ - name: Assert that the user wasn't changed because the password is still empty assert: that: - - "result.changed == false" + - result is not changed # Cleanup - include: remove_user.yml user_name={{ test_user_name }} user_password='' diff --git a/tests/integration/targets/test_mysql_user/tasks/test_user_plugin_auth.yml b/tests/integration/targets/test_mysql_user/tasks/test_user_plugin_auth.yml index a4884d8..264d8bd 100644 --- a/tests/integration/targets/test_mysql_user/tasks/test_user_plugin_auth.yml +++ b/tests/integration/targets/test_mysql_user/tasks/test_user_plugin_auth.yml @@ -37,7 +37,7 @@ - name: Check that the module made a change assert: that: - - "result.changed == true" + - result is changed - name: Check that the expected plugin type is set assert: @@ -59,7 +59,7 @@ - name: Assert that mysql_info was successful assert: that: - - "result.failed == false" + - result is succeeded - name: Update the user with a different hash mysql_user: @@ -72,7 +72,7 @@ - name: Check that the module makes the change because the hash changed assert: that: - - "result.changed == true" + - result is changed - include: assert_user.yml user_name={{ test_user_name }} priv={{ test_default_priv_type }} @@ -88,7 +88,7 @@ - name: Assert that mysql_info was successful assert: that: - - "result.failed == false" + - result is succeeded # Cleanup - include: remove_user.yml user_name={{ test_user_name }} user_password={{ test_plugin_new_auth_string }} @@ -113,7 +113,7 @@ - name: Check that the module made a change assert: that: - - "result.changed == true" + - result is changed - name: Check that the expected plugin type is set assert: @@ -135,7 +135,7 @@ - name: Assert that mysql_info was successful assert: that: - - "result.failed == false" + - result is succeeded - name: Update the user with the same hash (no change expected) mysql_user: @@ -149,7 +149,7 @@ - name: Check that the module doesn't make a change when the same hash is passed in assert: that: - - "result.changed == false" + - result is not changed when: install_type == 'mysql' or (install_type == 'mariadb' and mariadb_version is version('10.3', '>=')) - include: assert_user.yml user_name={{ test_user_name }} priv={{ test_default_priv_type }} @@ -166,7 +166,7 @@ - name: Check that the module did not change the password assert: that: - - "result.changed == true" + - result is changed - name: Getting the MySQL info should still work mysql_info: @@ -180,7 +180,7 @@ - name: Assert that mysql_info was successful assert: that: - - "result.failed == false" + - result is succeeded # Cleanup - include: remove_user.yml user_name={{ test_user_name }} user_password={{ test_plugin_auth_string }} @@ -205,7 +205,7 @@ - name: Check that the module made a change assert: that: - - "result.changed == true" + - result is changed - name: Check that the expected plugin type is set assert: @@ -227,7 +227,7 @@ - name: Assert that mysql_info was successful assert: that: - - "result.failed == false" + - result is succeeded - name: Update the user with the same auth string mysql_user: @@ -242,7 +242,7 @@ - name: The module should detect a change even though the password is the same assert: that: - - "result.changed == true" + - result is changed - include: assert_user.yml user_name={{ test_user_name }} priv={{ test_default_priv_type }} @@ -257,7 +257,7 @@ - name: Check that the module did not change the password assert: that: - - "result.changed == false" + - result is not changed - name: Get the MySQL version using the newly created creds mysql_info: @@ -271,7 +271,7 @@ - name: Assert that mysql_info was successful assert: that: - - "result.failed == false" + - result is succeeded # Cleanup - include: remove_user.yml user_name={{ test_user_name }} user_password={{ test_plugin_auth_string }} @@ -295,7 +295,7 @@ - name: Check that the module made a change assert: that: - - "result.changed == true" + - result is changed - name: Check that the expected plugin type is set assert: @@ -318,7 +318,7 @@ - name: Assert that mysql_info was successful assert: that: - - "result.failed == false" + - result is succeeded - name: Get the MySQL version using an non-empty password (should fail) mysql_info: @@ -333,7 +333,7 @@ - name: Assert that mysql_info failed assert: that: - - "result.failed == true" + - result is failed - name: Update the user without changing the auth mechanism mysql_user: @@ -346,7 +346,7 @@ - name: Assert that the user wasn't changed because the auth string is still empty assert: that: - - "result.changed == false" + - result is not changed # Cleanup - include: remove_user.yml user_name={{ test_user_name }} user_password={{ test_plugin_auth_string }} @@ -381,7 +381,7 @@ - name: Check that the module made a change assert: that: - - "result.changed == true" + - result is changed - name: Check that the expected plugin type is set assert: @@ -406,7 +406,7 @@ - name: Check that the module made a change assert: that: - - "result.changed == true" + - result is changed - name: Check that the expected plugin type is set assert: diff --git a/tests/integration/targets/test_mysql_variables/tasks/assert_fail_msg.yml b/tests/integration/targets/test_mysql_variables/tasks/assert_fail_msg.yml index e7e0885..a09bcdb 100644 --- a/tests/integration/targets/test_mysql_variables/tasks/assert_fail_msg.yml +++ b/tests/integration/targets/test_mysql_variables/tasks/assert_fail_msg.yml @@ -22,4 +22,4 @@ - name: assert message failure (expect failed=true) assert: that: - - "output.failed | bool == true" + - output is failed diff --git a/tests/integration/targets/test_mysql_variables/tasks/assert_var.yml b/tests/integration/targets/test_mysql_variables/tasks/assert_var.yml index 704f069..96d196d 100644 --- a/tests/integration/targets/test_mysql_variables/tasks/assert_var.yml +++ b/tests/integration/targets/test_mysql_variables/tasks/assert_var.yml @@ -31,6 +31,6 @@ - name: assert output mysql variable name and value assert: that: - - "result.changed | bool == true" + - result is changed - "'{{ var_name }}' in result.stdout" - "'{{ var_value }}' in result.stdout" diff --git a/tests/integration/targets/test_mysql_variables/tasks/assert_var_output.yml b/tests/integration/targets/test_mysql_variables/tasks/assert_var_output.yml index 01362ef..6f26386 100644 --- a/tests/integration/targets/test_mysql_variables/tasks/assert_var_output.yml +++ b/tests/integration/targets/test_mysql_variables/tasks/assert_var_output.yml @@ -35,6 +35,6 @@ - name: assert output variable info match mysql variable info assert: that: - - "result.changed | bool == true" + - result is changed - "key_name in result.stdout" - "key_value in result.stdout" From f1d63e3fc8a9669b5c2d1a6c913c30414b1cb5cd Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Fri, 26 Aug 2022 11:38:41 +0200 Subject: [PATCH 106/217] Docs: add info how to cope with a protocol-related connection error using login_unix_socket argument (#420) --- plugins/doc_fragments/mysql.py | 3 +++ plugins/modules/mysql_db.py | 3 +++ plugins/modules/mysql_info.py | 3 +++ plugins/modules/mysql_query.py | 3 +++ plugins/modules/mysql_replication.py | 3 +++ plugins/modules/mysql_role.py | 3 +++ plugins/modules/mysql_user.py | 3 +++ plugins/modules/mysql_variables.py | 3 +++ 8 files changed, 24 insertions(+) diff --git a/plugins/doc_fragments/mysql.py b/plugins/doc_fragments/mysql.py index 66809c4..7d4ec96 100644 --- a/plugins/doc_fragments/mysql.py +++ b/plugins/doc_fragments/mysql.py @@ -35,6 +35,7 @@ options: login_unix_socket: description: - The path to a Unix domain socket for local connections. + - Use this parameter to avoid the C(Please explicitly state intended protocol) error. type: str connect_timeout: description: @@ -78,6 +79,8 @@ requirements: - PyMySQL (Python 2.7 and Python 3.x) or - MySQLdb (Python 2.x) notes: + - "To avoid the C(Please explicitly state intended protocol) error, use the I(login_unix_socket) argument, + for example, C(login_unix_socket: /run/mysqld/mysqld.sock)." - Requires the PyMySQL (Python 2.7 and Python 3.X) or MySQL-python (Python 2.X) package installed on the remote host. The Python package may be installed with apt-get install python-pymysql (Ubuntu; see M(ansible.builtin.apt)) or yum install python2-PyMySQL (RHEL/CentOS/Fedora; see M(ansible.builtin.yum)). You can also use dnf install python2-PyMySQL diff --git a/plugins/modules/mysql_db.py b/plugins/modules/mysql_db.py index bf681fe..83a935e 100644 --- a/plugins/modules/mysql_db.py +++ b/plugins/modules/mysql_db.py @@ -198,10 +198,13 @@ extends_documentation_fragment: ''' EXAMPLES = r''' +# If you encounter the "Please explicitly state intended protocol" error, +# use the login_unix_socket argument - name: Create a new database with name 'bobdata' community.mysql.mysql_db: name: bobdata state: present + login_unix_socket: /run/mysqld/mysqld.sock - name: Create new databases with names 'foo' and 'bar' community.mysql.mysql_db: diff --git a/plugins/modules/mysql_info.py b/plugins/modules/mysql_info.py index 6f57403..1daa9b9 100644 --- a/plugins/modules/mysql_info.py +++ b/plugins/modules/mysql_info.py @@ -76,9 +76,12 @@ EXAMPLES = r''' # Display all info from databases group except settings: # ansible databases -m mysql_info -a 'filter=!settings' +# If you encounter the "Please explicitly state intended protocol" error, +# use the login_unix_socket argument - name: Collect all possible information using passwordless root access community.mysql.mysql_info: login_user: root + login_unix_socket: /run/mysqld/mysqld.sock - name: Get MySQL version with non-default credentials community.mysql.mysql_info: diff --git a/plugins/modules/mysql_query.py b/plugins/modules/mysql_query.py index a91335b..04f6201 100644 --- a/plugins/modules/mysql_query.py +++ b/plugins/modules/mysql_query.py @@ -57,10 +57,13 @@ extends_documentation_fragment: ''' EXAMPLES = r''' +# If you encounter the "Please explicitly state intended protocol" error, +# use the login_unix_socket argument - name: Simple select query to acme db community.mysql.mysql_query: login_db: acme query: SELECT * FROM orders + login_unix_socket: /run/mysqld/mysqld.sock - name: Select query to db acme with positional arguments community.mysql.mysql_query: diff --git a/plugins/modules/mysql_replication.py b/plugins/modules/mysql_replication.py index f4c21b9..68f3f22 100644 --- a/plugins/modules/mysql_replication.py +++ b/plugins/modules/mysql_replication.py @@ -202,9 +202,12 @@ seealso: ''' EXAMPLES = r''' +# If you encounter the "Please explicitly state intended protocol" error, +# use the login_unix_socket argument - name: Stop mysql replica thread community.mysql.mysql_replication: mode: stopreplica + login_unix_socket: /run/mysqld/mysqld.sock - name: Get primary binlog file name and binlog position community.mysql.mysql_replication: diff --git a/plugins/modules/mysql_role.py b/plugins/modules/mysql_role.py index b37d70d..25b7e4c 100644 --- a/plugins/modules/mysql_role.py +++ b/plugins/modules/mysql_role.py @@ -142,6 +142,9 @@ extends_documentation_fragment: ''' EXAMPLES = r''' +# If you encounter the "Please explicitly state intended protocol" error, +# use the login_unix_socket argument, for example, login_unix_socket: /run/mysqld/mysqld.sock + # Example of a .my.cnf file content for setting a root password # [client] # user=root diff --git a/plugins/modules/mysql_user.py b/plugins/modules/mysql_user.py index c85a910..849aa8d 100644 --- a/plugins/modules/mysql_user.py +++ b/plugins/modules/mysql_user.py @@ -177,11 +177,14 @@ extends_documentation_fragment: ''' EXAMPLES = r''' +# If you encounter the "Please explicitly state intended protocol" error, +# use the login_unix_socket argument - name: Removes anonymous user account for localhost community.mysql.mysql_user: name: '' host: localhost state: absent + login_unix_socket: /run/mysqld/mysqld.sock - name: Removes all anonymous user accounts community.mysql.mysql_user: diff --git a/plugins/modules/mysql_variables.py b/plugins/modules/mysql_variables.py index 06beee3..2544e8d 100644 --- a/plugins/modules/mysql_variables.py +++ b/plugins/modules/mysql_variables.py @@ -58,9 +58,12 @@ extends_documentation_fragment: ''' EXAMPLES = r''' +# If you encounter the "Please explicitly state intended protocol" error, +# use the login_unix_socket argument - name: Check for sync_binlog setting community.mysql.mysql_variables: variable: sync_binlog + login_unix_socket: /run/mysqld/mysqld.sock - name: Set read_only variable to 1 persistently community.mysql.mysql_variables: From aef6a2040c7200197373fca28fcb953b54e05509 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D0=B4=D1=80=D0=B5=D0=B9=20=D0=9D=D0=B5=D1=83?= =?UTF-8?q?=D1=81=D1=82=D1=80=D0=BE=D0=B5=D0=B2?= <99169437+aneustroev@users.noreply.github.com> Date: Fri, 2 Sep 2022 13:59:51 +0500 Subject: [PATCH 107/217] Add SOURCE_SSL_VERIFY_SERVER_CERT parameter (#435) * Add SOURCE_SSL_VERIFY_SERVER_CERT parameter * Rewiev fixs and add changelog fragment * fix version * Update changelogs/fragments/435-mysql_replication_verify_server_cert.yml Co-authored-by: Andrew Klychkov Co-authored-by: Andrew Klychkov --- .../435-mysql_replication_verify_server_cert.yml | 3 +++ plugins/modules/mysql_replication.py | 10 ++++++++++ 2 files changed, 13 insertions(+) create mode 100644 changelogs/fragments/435-mysql_replication_verify_server_cert.yml diff --git a/changelogs/fragments/435-mysql_replication_verify_server_cert.yml b/changelogs/fragments/435-mysql_replication_verify_server_cert.yml new file mode 100644 index 0000000..8e5a2eb --- /dev/null +++ b/changelogs/fragments/435-mysql_replication_verify_server_cert.yml @@ -0,0 +1,3 @@ +--- +minor_changes: + - "mysql_replication - add a new option: ``primary_ssl_verify_server_cert`` (https://github.com//pull/435)." \ No newline at end of file diff --git a/plugins/modules/mysql_replication.py b/plugins/modules/mysql_replication.py index 68f3f22..d63905f 100644 --- a/plugins/modules/mysql_replication.py +++ b/plugins/modules/mysql_replication.py @@ -131,6 +131,12 @@ options: L(MySQL encrypted replication documentation,https://dev.mysql.com/doc/refman/8.0/en/replication-solutions-encrypted-connections.html). type: str aliases: [master_ssl_cipher] + primary_ssl_verify_server_cert: + description: + - Same as mysql variable. + type: bool + default: false + version_added: '3.5.0' primary_auto_position: description: - Whether the host uses GTID based replication or not. @@ -458,6 +464,7 @@ def main(): primary_ssl_cert=dict(type='str', aliases=['master_ssl_cert']), primary_ssl_key=dict(type='str', no_log=False, aliases=['master_ssl_key']), primary_ssl_cipher=dict(type='str', aliases=['master_ssl_cipher']), + primary_ssl_verify_server_cert=dict(type='bool', default=False), primary_use_gtid=dict(type='str', choices=[ 'current_pos', 'replica_pos', 'disabled'], aliases=['master_use_gtid']), primary_delay=dict(type='int', aliases=['master_delay']), @@ -487,6 +494,7 @@ def main(): primary_ssl_cert = module.params["primary_ssl_cert"] primary_ssl_key = module.params["primary_ssl_key"] primary_ssl_cipher = module.params["primary_ssl_cipher"] + primary_ssl_verify_server_cert = module.params["primary_ssl_verify_server_cert"] primary_auto_position = module.params["primary_auto_position"] ssl_cert = module.params["client_cert"] ssl_key = module.params["client_key"] @@ -595,6 +603,8 @@ def main(): chm.append("MASTER_SSL_KEY='%s'" % primary_ssl_key) if primary_ssl_cipher is not None: chm.append("MASTER_SSL_CIPHER='%s'" % primary_ssl_cipher) + if primary_ssl_verify_server_cert: + chm.append("SOURCE_SSL_VERIFY_SERVER_CERT=1") if primary_auto_position: chm.append("MASTER_AUTO_POSITION=1") if primary_use_gtid is not None: From cc5cf9836814df222cb85a7c798d37d530231b67 Mon Sep 17 00:00:00 2001 From: "R.Sicart" Date: Fri, 2 Sep 2022 13:40:06 +0200 Subject: [PATCH 108/217] Fix: grant revoked priv (#434) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix: exclude mysql 8 from test_mysql_user's 'Assert that priv did not change' test * Add tests to verify that GRANT permission is present after user modification * Fix: do not revoke GRANT permission when it's already allowed and present in priv parameter * Deduplicate tests name Easier to debug this way * Fix assertions named 'GRANT permission is present' * Only revoke grant option if it exists and absence is requested * Fix assertion comments * Fix: Only revoke grant option if it exists and absence is requested * Avoid pointless revocations when ALL are granted * Assert that priv did not change on mariadb also * Fix: sanity and unity tests * Format long lines * Add changelog fragment Co-authored-by: Laurent Indermühle --- ...434-do-not-revoke-grant-option-always.yaml | 5 ++ plugins/module_utils/user.py | 13 +++- .../test_mysql_user/tasks/test_privs.yml | 69 ++++++++++++++++++- 3 files changed, 83 insertions(+), 4 deletions(-) create mode 100644 changelogs/fragments/434-do-not-revoke-grant-option-always.yaml diff --git a/changelogs/fragments/434-do-not-revoke-grant-option-always.yaml b/changelogs/fragments/434-do-not-revoke-grant-option-always.yaml new file mode 100644 index 0000000..a6edb24 --- /dev/null +++ b/changelogs/fragments/434-do-not-revoke-grant-option-always.yaml @@ -0,0 +1,5 @@ +--- +bugfixes: + - mysql_user - grant option was revoked accidentally when modifying users. + This fix revokes grant option only when privs are setup to do that + (https://github.com/ansible-collections/community.mysql/issues/77#issuecomment-1209693807). diff --git a/plugins/module_utils/user.py b/plugins/module_utils/user.py index 7e27d13..bc874e1 100644 --- a/plugins/module_utils/user.py +++ b/plugins/module_utils/user.py @@ -359,9 +359,20 @@ def user_mod(cursor, user, host, host_all, password, encrypted, revoke_privs = list(set(new_priv[db_table]) & set(curr_priv[db_table])) else: # When replacing (neither append_privs nor subtract_privs), grant all missing privileges - # and revoke existing privileges that were not requested. + # and revoke existing privileges that were not requested... grant_privs = list(set(new_priv[db_table]) - set(curr_priv[db_table])) revoke_privs = list(set(curr_priv[db_table]) - set(new_priv[db_table])) + + # ... avoiding pointless revocations when ALL are granted + if 'ALL' in grant_privs or 'ALL PRIVILEGES' in grant_privs: + revoke_privs = list(set(['GRANT', 'PROXY']).intersection(set(revoke_privs))) + + # Only revoke grant option if it exists and absence is requested + # + # For more details + # https://github.com/ansible-collections/community.mysql/issues/77#issuecomment-1209693807 + grant_option = 'GRANT' in revoke_privs and 'GRANT' not in grant_privs + if grant_privs == ['GRANT']: # USAGE grants no privileges, it is only needed because 'WITH GRANT OPTION' cannot stand alone grant_privs.append('USAGE') diff --git a/tests/integration/targets/test_mysql_user/tasks/test_privs.yml b/tests/integration/targets/test_mysql_user/tasks/test_privs.yml index d4798ff..3c911a9 100644 --- a/tests/integration/targets/test_mysql_user/tasks/test_privs.yml +++ b/tests/integration/targets/test_mysql_user/tasks/test_privs.yml @@ -164,7 +164,7 @@ that: - result is changed - - name: Test idempotency (expect ok) + - name: Test idempotency with a long privileges list (expect ok) mysql_user: <<: *mysql_params name: '{{ user_name_2 }}' @@ -173,12 +173,75 @@ state: present register: result - # FIXME: on mariadb >=10.5.2 there's always a change because the REPLICATION CLIENT privilege was renamed to BINLOG MONITOR + # FIXME: on mysql >=8 and mariadb >=10.5.2 there's always a change because + # the REPLICATION CLIENT privilege was renamed to BINLOG MONITOR - name: Assert that priv did not change assert: that: - result is not changed - when: install_type == 'mysql' or (install_type == 'mariadb' and mariadb_version is version('10.2', '==')) + when: (install_type == 'mysql' and mysql_version is version('8', '<')) or + (install_type == 'mariadb' and mariadb_version is version('10.5', '<')) + + - name: remove username + mysql_user: + <<: *mysql_params + name: '{{ user_name_2 }}' + password: '{{ user_password_2 }}' + state: absent + + # ============================================================ + - name: grant all privileges with grant option + mysql_user: + <<: *mysql_params + name: '{{ user_name_2 }}' + password: '{{ user_password_2 }}' + priv: '*.*:ALL,GRANT' + state: present + register: result + + - name: Assert that priv changed + assert: + that: + - result is changed + + - name: Collect user info by host + community.mysql.mysql_info: + <<: *mysql_params + filter: "users" + register: mysql_info_about_users + + - name: Assert that 'GRANT' permission is present + assert: + that: + - mysql_info_about_users.users.localhost.{{ user_name_2 }}.Grant_priv == 'Y' + + - name: Test idempotency (expect ok) + mysql_user: + <<: *mysql_params + name: '{{ user_name_2 }}' + password: '{{ user_password_2 }}' + priv: '*.*:ALL,GRANT' + state: present + register: result + + # FIXME: on mysql >=8 there's always a change (ALL PRIVILEGES -> specific privileges) + - name: Assert that priv did not change + assert: + that: + - result is not changed + when: (install_type == 'mysql' and mysql_version is version('8', '<')) or + (install_type == 'mariadb') + + - name: Collect user info by host + community.mysql.mysql_info: + <<: *mysql_params + filter: "users" + register: mysql_info_about_users + + - name: Assert that 'GRANT' permission is present + assert: + that: + - mysql_info_about_users.users.localhost.{{ user_name_2 }}.Grant_priv == 'Y' # ============================================================ - name: update user with invalid privileges From 3670b2adc62c8f3e5ece99a045f41704aa2307b2 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Mon, 5 Sep 2022 09:06:41 +0200 Subject: [PATCH 109/217] Release 3.5.0 commit (#436) --- CHANGELOG.rst | 20 +++++++++++++++++++ changelogs/changelog.yaml | 18 +++++++++++++++++ ...434-do-not-revoke-grant-option-always.yaml | 5 ----- ...5-mysql_replication_verify_server_cert.yml | 3 --- galaxy.yml | 2 +- 5 files changed, 39 insertions(+), 9 deletions(-) delete mode 100644 changelogs/fragments/434-do-not-revoke-grant-option-always.yaml delete mode 100644 changelogs/fragments/435-mysql_replication_verify_server_cert.yml diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 31c62a2..0339b22 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,26 @@ Community MySQL Collection Release Notes This changelog describes changes after version 2.0.0. +v3.5.0 +====== + +Release Summary +--------------- + +This is the minor release of the ``community.mysql`` collection. +This changelog contains all changes to the modules in this collection +that have been added after the release of ``community.mysql`` 3.4.0. + +Minor Changes +------------- + +- mysql_replication - add a new option: ``primary_ssl_verify_server_cert`` (https://github.com//pull/435). + +Bugfixes +-------- + +- mysql_user - grant option was revoked accidentally when modifying users. This fix revokes grant option only when privs are setup to do that (https://github.com/ansible-collections/community.mysql/issues/77#issuecomment-1209693807). + v3.4.0 ====== diff --git a/changelogs/changelog.yaml b/changelogs/changelog.yaml index 99d7227..2413820 100644 --- a/changelogs/changelog.yaml +++ b/changelogs/changelog.yaml @@ -227,3 +227,21 @@ releases: - fix-256-mysql_dump-errors.yml - simplified-bsd-license.yml release_date: '2022-08-02' + 3.5.0: + changes: + bugfixes: + - mysql_user - grant option was revoked accidentally when modifying users. This + fix revokes grant option only when privs are setup to do that (https://github.com/ansible-collections/community.mysql/issues/77#issuecomment-1209693807). + minor_changes: + - 'mysql_replication - add a new option: ``primary_ssl_verify_server_cert`` + (https://github.com//pull/435).' + release_summary: 'This is the minor release of the ``community.mysql`` collection. + + This changelog contains all changes to the modules in this collection + + that have been added after the release of ``community.mysql`` 3.4.0.' + fragments: + - 3.5.0.yml + - 434-do-not-revoke-grant-option-always.yaml + - 435-mysql_replication_verify_server_cert.yml + release_date: '2022-09-05' diff --git a/changelogs/fragments/434-do-not-revoke-grant-option-always.yaml b/changelogs/fragments/434-do-not-revoke-grant-option-always.yaml deleted file mode 100644 index a6edb24..0000000 --- a/changelogs/fragments/434-do-not-revoke-grant-option-always.yaml +++ /dev/null @@ -1,5 +0,0 @@ ---- -bugfixes: - - mysql_user - grant option was revoked accidentally when modifying users. - This fix revokes grant option only when privs are setup to do that - (https://github.com/ansible-collections/community.mysql/issues/77#issuecomment-1209693807). diff --git a/changelogs/fragments/435-mysql_replication_verify_server_cert.yml b/changelogs/fragments/435-mysql_replication_verify_server_cert.yml deleted file mode 100644 index 8e5a2eb..0000000 --- a/changelogs/fragments/435-mysql_replication_verify_server_cert.yml +++ /dev/null @@ -1,3 +0,0 @@ ---- -minor_changes: - - "mysql_replication - add a new option: ``primary_ssl_verify_server_cert`` (https://github.com//pull/435)." \ No newline at end of file diff --git a/galaxy.yml b/galaxy.yml index b30a3f9..8794398 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -1,6 +1,6 @@ namespace: community name: mysql -version: 3.4.1 +version: 3.5.0 readme: README.md authors: - Ansible community From ea73d408c3e31982346add6965bd330173d8c64d Mon Sep 17 00:00:00 2001 From: Maxwell G <9920591+gotmax23@users.noreply.github.com> Date: Tue, 6 Sep 2022 02:00:41 -0500 Subject: [PATCH 110/217] Combine REVIEW_CHECKLIST.md and CONTRIBUTING.md and fix links (#432) --- CONTRIBUTING.md | 4 +++- REVIEW_CHECKLIST.md | 3 --- 2 files changed, 3 insertions(+), 4 deletions(-) delete mode 100644 REVIEW_CHECKLIST.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index edcfe55..70cd555 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,3 +1,5 @@ # Contributing -Refer to the [Ansible Contributing guidelines](https://github.com/ansible/community-docs/blob/main/contributing.rst) to learn how to contribute to this collection. +Refer to the [Ansible Contributing guidelines](https://docs.ansible.com/ansible/devel/community/index.html) to learn how to contribute to this collection. + +Refer to the [review checklist](https://docs.ansible.com/ansible/devel/community/collection_contributors/collection_reviewing.html) when triaging issues or reviewing PRs. diff --git a/REVIEW_CHECKLIST.md b/REVIEW_CHECKLIST.md deleted file mode 100644 index 9dccf7e..0000000 --- a/REVIEW_CHECKLIST.md +++ /dev/null @@ -1,3 +0,0 @@ -# Review Checklist - -Refer to the [Collection review checklist](https://github.com/ansible/community-docs/blob/main/review_checklist.rst). From 2d75bc19b8ca973c67521e74754b712c816fd2a3 Mon Sep 17 00:00:00 2001 From: "R.Sicart" Date: Thu, 8 Sep 2022 18:26:58 +0200 Subject: [PATCH 111/217] Fix privilege changing everytime (#438) * Compare privileges from before and after manipulation * Add unit tests * Fix FIXME integration tests related to this issue * Fix sanity check * Fix assertion when appending privs in mysql_role_initial integration tests * Fix pylint * [ci-skip] Add changelog fragment * Fix: missing fragment file extension * Replace privileges_equal() by a comparison * Fix: sanity pylint * Fix: forgot to remove privileges_equal import from unit tests --- .../fragments/438-fix-privilege-changing-everytime.yml | 7 +++++++ plugins/module_utils/user.py | 5 ++++- .../test_mysql_role/tasks/mysql_role_initial.yml | 10 ++-------- .../targets/test_mysql_user/tasks/test_privs.yml | 4 ---- 4 files changed, 13 insertions(+), 13 deletions(-) create mode 100644 changelogs/fragments/438-fix-privilege-changing-everytime.yml diff --git a/changelogs/fragments/438-fix-privilege-changing-everytime.yml b/changelogs/fragments/438-fix-privilege-changing-everytime.yml new file mode 100644 index 0000000..da7baa8 --- /dev/null +++ b/changelogs/fragments/438-fix-privilege-changing-everytime.yml @@ -0,0 +1,7 @@ +--- +bugfixes: + - mysql_user, mysql_role - mysql/mariadb recent versions translate 'ALL + PRIVILEGES' to a list of specific privileges. That caused a change every time + we modified user privileges. This fix compares privs before and after user + modification to avoid this infinite change + (https://github.com/ansible-collections/community.mysql/issues/77). diff --git a/plugins/module_utils/user.py b/plugins/module_utils/user.py index bc874e1..7def8c7 100644 --- a/plugins/module_utils/user.py +++ b/plugins/module_utils/user.py @@ -385,7 +385,10 @@ def user_mod(cursor, user, host, host_all, password, encrypted, privileges_revoke(cursor, user, host, db_table, revoke_privs, grant_option, maria_role) if len(grant_privs) > 0: privileges_grant(cursor, user, host, db_table, grant_privs, tls_requires, maria_role) - changed = True + + # after privilege manipulation, compare privileges from before and now + after_priv = privileges_get(cursor, user, host, maria_role) + changed = changed or (curr_priv != after_priv) if role: continue diff --git a/tests/integration/targets/test_mysql_role/tasks/mysql_role_initial.yml b/tests/integration/targets/test_mysql_role/tasks/mysql_role_initial.yml index 8c81a75..36f2418 100644 --- a/tests/integration/targets/test_mysql_role/tasks/mysql_role_initial.yml +++ b/tests/integration/targets/test_mysql_role/tasks/mysql_role_initial.yml @@ -1491,16 +1491,10 @@ priv: '{{ test_db1 }}.{{ test_table }}:SELECT,INSERT/{{ test_db2 }}.{{ test_table }}:DELETE' append_privs: yes - # TODO it must be changed. The module uses user_mod function - # taken from mysql_user module. It's a bug / expected behavior - # because I added a similar tasks to mysql_user tests - # https://github.com/ansible-collections/community.mysql/issues/50#issuecomment-871216825 - # and it's also failed. Create an issue after the module is merged to avoid conflicts. - # TODO Fix this after user_mod is fixed. - - name: Check + - name: Check that there's no change assert: that: - - result is changed + - result is not changed - name: Rewrite privs <<: *task_params diff --git a/tests/integration/targets/test_mysql_user/tasks/test_privs.yml b/tests/integration/targets/test_mysql_user/tasks/test_privs.yml index 3c911a9..b9581f7 100644 --- a/tests/integration/targets/test_mysql_user/tasks/test_privs.yml +++ b/tests/integration/targets/test_mysql_user/tasks/test_privs.yml @@ -179,8 +179,6 @@ assert: that: - result is not changed - when: (install_type == 'mysql' and mysql_version is version('8', '<')) or - (install_type == 'mariadb' and mariadb_version is version('10.5', '<')) - name: remove username mysql_user: @@ -229,8 +227,6 @@ assert: that: - result is not changed - when: (install_type == 'mysql' and mysql_version is version('8', '<')) or - (install_type == 'mariadb') - name: Collect user info by host community.mysql.mysql_info: From 7defd8e72832f268a38da35871352879979f1bfa Mon Sep 17 00:00:00 2001 From: "R.Sicart" Date: Fri, 9 Sep 2022 15:22:00 +0200 Subject: [PATCH 112/217] Release 3.5.1 commit (#443) --- CHANGELOG.rst | 15 +++++++++++++++ changelogs/changelog.yaml | 16 ++++++++++++++++ .../438-fix-privilege-changing-everytime.yml | 7 ------- galaxy.yml | 2 +- 4 files changed, 32 insertions(+), 8 deletions(-) delete mode 100644 changelogs/fragments/438-fix-privilege-changing-everytime.yml diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 0339b22..cb5e2cd 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,21 @@ Community MySQL Collection Release Notes This changelog describes changes after version 2.0.0. +v3.5.1 +====== + +Release Summary +--------------- + +This is the patch release of the ``community.mysql`` collection. +This changelog contains all changes to the modules and plugins in this collection +that have been made after the previous release. + +Bugfixes +-------- + +- mysql_user, mysql_role - mysql/mariadb recent versions translate 'ALL PRIVILEGES' to a list of specific privileges. That caused a change every time we modified user privileges. This fix compares privs before and after user modification to avoid this infinite change (https://github.com/ansible-collections/community.mysql/issues/77). + v3.5.0 ====== diff --git a/changelogs/changelog.yaml b/changelogs/changelog.yaml index 2413820..be7f028 100644 --- a/changelogs/changelog.yaml +++ b/changelogs/changelog.yaml @@ -245,3 +245,19 @@ releases: - 434-do-not-revoke-grant-option-always.yaml - 435-mysql_replication_verify_server_cert.yml release_date: '2022-09-05' + 3.5.1: + changes: + bugfixes: + - mysql_user, mysql_role - mysql/mariadb recent versions translate 'ALL PRIVILEGES' + to a list of specific privileges. That caused a change every time we modified + user privileges. This fix compares privs before and after user modification + to avoid this infinite change (https://github.com/ansible-collections/community.mysql/issues/77). + release_summary: 'This is the patch release of the ``community.mysql`` collection. + + This changelog contains all changes to the modules and plugins in this collection + + that have been made after the previous release.' + fragments: + - 3.5.1.yml + - 438-fix-privilege-changing-everytime.yml + release_date: '2022-09-09' diff --git a/changelogs/fragments/438-fix-privilege-changing-everytime.yml b/changelogs/fragments/438-fix-privilege-changing-everytime.yml deleted file mode 100644 index da7baa8..0000000 --- a/changelogs/fragments/438-fix-privilege-changing-everytime.yml +++ /dev/null @@ -1,7 +0,0 @@ ---- -bugfixes: - - mysql_user, mysql_role - mysql/mariadb recent versions translate 'ALL - PRIVILEGES' to a list of specific privileges. That caused a change every time - we modified user privileges. This fix compares privs before and after user - modification to avoid this infinite change - (https://github.com/ansible-collections/community.mysql/issues/77). diff --git a/galaxy.yml b/galaxy.yml index 8794398..733762d 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -1,6 +1,6 @@ namespace: community name: mysql -version: 3.5.0 +version: 3.5.1 readme: README.md authors: - Ansible community From ff9f58e8d1851339d77fcaa688881f647c45b53c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=5Fchocolat?= Date: Fri, 16 Sep 2022 09:15:29 +0200 Subject: [PATCH 113/217] changelog: fix broken link in ansible docs (#446) --- changelogs/changelog.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelogs/changelog.yaml b/changelogs/changelog.yaml index be7f028..ce080f8 100644 --- a/changelogs/changelog.yaml +++ b/changelogs/changelog.yaml @@ -4,7 +4,7 @@ releases: changes: breaking_changes: - mysql_replication - remove ``Is_Slave`` and ``Is_Master`` return values (were - replaced with ``Is_Primary`` and ``Is_Replica`` (https://github.com/ansible-collections /community.mysql/issues/145). + replaced with ``Is_Primary`` and ``Is_Replica`` (https://github.com/ansible-collections/community.mysql/issues/145). - mysql_replication - remove the mode options values containing ``master``/``slave`` and the master_use_gtid option ``slave_pos`` (were replaced with corresponding ``primary``/``replica`` values) (https://github.com/ansible-collections/community.mysql/issues/145). From 2cd29207f3e68e253e99774a05b25e10dddc9fc9 Mon Sep 17 00:00:00 2001 From: "R.Sicart" Date: Fri, 16 Sep 2022 13:38:22 +0200 Subject: [PATCH 114/217] Fix: devel requires python 3.9 in roles CI (#444) * Fix: devel requires python 3.9 Package 'ansible-core' requires a different Python: 3.8.13 not in '>=3.9' * Exclude older version of Ansible when testing Python 3.9 --- .github/workflows/ansible-test-roles.yml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ansible-test-roles.yml b/.github/workflows/ansible-test-roles.yml index bda6986..34bee52 100644 --- a/.github/workflows/ansible-test-roles.yml +++ b/.github/workflows/ansible-test-roles.yml @@ -30,15 +30,24 @@ jobs: python: - 3.6 - 3.8 + - 3.9 exclude: - - python: 3.8 - ansible: stable-2.11 - python: 3.6 ansible: stable-2.12 - python: 3.6 ansible: stable-2.13 - python: 3.6 ansible: devel + - python: 3.8 + ansible: stable-2.11 + - python: 3.8 + ansible: stable-2.13 + - python: 3.8 + ansible: devel + - python: 3.9 + ansible: stable-2.11 + - python: 3.9 + ansible: stable-2.12 steps: From b8e2c02e89524344f37e88d1c42e61bdd71796a4 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Thu, 22 Sep 2022 11:24:01 +0200 Subject: [PATCH 115/217] CI: add stable-2.14 to test matrix (#449) --- .github/workflows/ansible-test-plugins.yml | 11 +++++++++++ tests/sanity/ignore-2.15.txt | 8 ++++++++ 2 files changed, 19 insertions(+) create mode 100644 tests/sanity/ignore-2.15.txt diff --git a/.github/workflows/ansible-test-plugins.yml b/.github/workflows/ansible-test-plugins.yml index 3056760..7182116 100644 --- a/.github/workflows/ansible-test-plugins.yml +++ b/.github/workflows/ansible-test-plugins.yml @@ -28,6 +28,7 @@ jobs: - stable-2.11 - stable-2.12 - stable-2.13 + - stable-2.14 - devel python: - 3.8 @@ -35,6 +36,8 @@ jobs: exclude: - python: 3.8 ansible: stable-2.13 + - python: 3.8 + ansible: stable-2.14 - python: 3.8 ansible: devel - python: 3.9 @@ -77,6 +80,7 @@ jobs: - stable-2.11 - stable-2.12 - stable-2.13 + - stable-2.14 - devel python: - 3.6 @@ -95,12 +99,16 @@ jobs: ansible: stable-2.12 - python: 3.6 ansible: stable-2.13 + - python: 3.6 + ansible: stable-2.14 - python: 3.6 ansible: devel - python: 3.8 ansible: stable-2.11 - python: 3.8 ansible: stable-2.13 + - python: 3.8 + ansible: stable-2.14 - python: 3.8 ansible: devel - python: 3.9 @@ -167,6 +175,7 @@ jobs: - stable-2.11 - stable-2.12 - stable-2.13 + - stable-2.14 - devel python: - 3.8 @@ -174,6 +183,8 @@ jobs: exclude: - python: 3.8 ansible: stable-2.13 + - python: 3.8 + ansible: stable-2.14 - python: 3.8 ansible: devel - python: 3.9 diff --git a/tests/sanity/ignore-2.15.txt b/tests/sanity/ignore-2.15.txt new file mode 100644 index 0000000..c0323af --- /dev/null +++ b/tests/sanity/ignore-2.15.txt @@ -0,0 +1,8 @@ +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_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_variables.py validate-modules:doc-required-mismatch From 81075307442c943ba2661dcbf5cd59459f89083c Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Mon, 3 Oct 2022 14:27:55 +0200 Subject: [PATCH 116/217] Sync GHA workflow w/ the collection template (#452) * Sync GHA workflow w/ the collection template * Drop the trailing pre-cmd semicolon * Recover missing `-e` flag of `sed` * Use relative paths for version configs * Unquote `env.connector_version_file` * Use string formatting to fix the substitution problem --- .github/workflows/ansible-test-plugins.yml | 141 ++++++--------------- 1 file changed, 38 insertions(+), 103 deletions(-) diff --git a/.github/workflows/ansible-test-plugins.yml b/.github/workflows/ansible-test-plugins.yml index 7182116..2f247da 100644 --- a/.github/workflows/ansible-test-plugins.yml +++ b/.github/workflows/ansible-test-plugins.yml @@ -15,12 +15,12 @@ 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" + mysql_version_file: "tests/integration/targets/setup_mysql/defaults/main.yml" + connector_version_file: "tests/integration/targets/setup_mysql/vars/main.yml" jobs: sanity: - name: "Sanity (Python: ${{ matrix.python }}, Ansible: ${{ matrix.ansible }})" + name: "Sanity (Ansible: ${{ matrix.ansible }})" runs-on: ubuntu-latest strategy: matrix: @@ -30,38 +30,12 @@ jobs: - stable-2.13 - stable-2.14 - devel - python: - - 3.8 - - 3.9 - exclude: - - python: 3.8 - ansible: stable-2.13 - - python: 3.8 - ansible: stable-2.14 - - python: 3.8 - ansible: devel - - python: 3.9 - ansible: stable-2.11 - - python: 3.9 - ansible: stable-2.12 steps: - - - name: Check out code - uses: actions/checkout@v2 + - name: Perform sanity testing + uses: ansible-community/ansible-test-gh-action@release/v1 with: - path: ansible_collections/community/mysql - - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python }} - - - name: Install ansible-base (${{ matrix.ansible }}) - run: pip install https://github.com/ansible/ansible/archive/${{ matrix.ansible }}.tar.gz --disable-pip-version-check - - - name: Run sanity tests - run: ansible-test sanity --docker -v --color - working-directory: ./ansible_collections/community/mysql + ansible-core-version: ${{ matrix.ansible }} + testing-type: sanity integration: name: "Integration (Python: ${{ matrix.python }}, Ansible: ${{ matrix.ansible }}, MySQL: ${{ matrix.db_engine_version }}, Connector: ${{ matrix.connector }})" @@ -117,51 +91,31 @@ jobs: ansible: stable-2.12 steps: - - - name: Check out code - uses: actions/checkout@v2 + - name: >- + Perform integration testing against + Ansible version ${{ matrix.ansible }} + under Python ${{ matrix.python }} + uses: ansible-community/ansible-test-gh-action@release/v1 with: - path: ansible_collections/community/mysql - - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python }} - - - name: Install ansible-base (${{ matrix.ansible }}) - run: pip install https://github.com/ansible/ansible/archive/${{ matrix.ansible }}.tar.gz --disable-pip-version-check - - - name: Set MySQL version (${{ matrix.db_engine_version }}) - run: | - export DB_VERSION=$(echo "${{ matrix.db_engine_version }}" | awk -F_ '{print $2}') - sed -i "s/^mysql_version:.*/mysql_version: $DB_VERSION/g" ${{ env.mysql_version_file }} - if: ${{ startsWith(matrix.db_engine_version, 'mysql') }} - - - name: Set MariaDB version (${{ matrix.db_engine_version }}) - run: | - export DB_VERSION=$(echo "${{ matrix.db_engine_version }}" | awk -F_ '{print $2}') - sed -i -e "s/^mariadb_version:.*/mariadb_version: $DB_VERSION/g" -e 's/^mariadb_install: false/mariadb_install: true/g' ${{ env.mysql_version_file }} - if: ${{ startsWith(matrix.db_engine_version, 'mariadb') }} - - - name: Set MariaDB URL sub dir - run: | - sed -i -e "s/^mariadb_url_subdir:.*/mariadb_url_subdir: linux-systemd/g" ${{ env.connector_version_file }} - if: matrix.db_engine_version == 'mariadb_10.8.3' - - - 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 - - - name: Generate coverage report. - run: ansible-test coverage xml -v --requirements --group-by command --group-by version - working-directory: ./ansible_collections/community/mysql - - - uses: codecov/codecov-action@v1 - with: - fail_ci_if_error: false + ansible-core-version: ${{ matrix.ansible }} + pre-test-cmd: >- + DB_ENGINE=$(echo '${{ matrix.db_engine_version }}' | awk -F_ '{print $1}'); + DB_VERSION=$(echo '${{ matrix.db_engine_version }}' | awk -F_ '{print $2}'); + DB_ENGINE_PRETTY=$([[ "${DB_ENGINE}" == 'mysql' ]] && echo 'MySQL' || echo 'MariaDB'); + >&2 echo Matrix factor for the DB is ${{ matrix.db_engine_version }}...; + >&2 echo Setting ${DB_ENGINE_PRETTY} version to ${DB_VERSION}...; + sed -i -e "s/^${DB_ENGINE}_version:.*/${DB_ENGINE}_version: $DB_VERSION/g" -e 's/^mariadb_install: false/mariadb_install: true/g' '${{ env.mysql_version_file }}'; + ${{ + matrix.db_engine_version == 'mariadb_10.8.3' + && format( + '>&2 echo Set MariaDB v10.8.3 URL sub dir...; sed -i -e "s/^mariadb_url_subdir:.*/mariadb_url_subdir: linux-systemd/g" "{0}";', env.connector_version_file + ) + || '' + }} + >&2 echo Setting Connector version to ${{ matrix.connector }}...; + sed -i 's/^python_packages:.*/python_packages: [${{ matrix.connector }}]/' ${{ env.connector_version_file }} + target-python-version: ${{ matrix.python }} + testing-type: integration units: runs-on: ubuntu-latest @@ -193,30 +147,11 @@ jobs: ansible: stable-2.12 steps: - - name: Check out code - uses: actions/checkout@v2 + - name: >- + Perform unit testing against + Ansible version ${{ matrix.ansible }} + uses: ansible-community/ansible-test-gh-action@release/v1 with: - path: ./ansible_collections/community/mysql - - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python }} - - - name: Install ansible-base (${{matrix.ansible}}) - run: pip install https://github.com/ansible/ansible/archive/${{ matrix.ansible }}.tar.gz --disable-pip-version-check - - # Run the unit tests - - name: Run unit test - run: ansible-test units -v --color --docker --coverage - working-directory: ./ansible_collections/community/mysql - - # ansible-test support producing code coverage date - - name: Generate coverage report - run: ansible-test coverage xml -v --requirements --group-by command --group-by version - working-directory: ./ansible_collections/community/mysql - - # See the reports at https://codecov.io/gh/GITHUBORG/REPONAME - - uses: codecov/codecov-action@v1 - with: - fail_ci_if_error: false + ansible-core-version: ${{ matrix.ansible }} + target-python-version: ${{ matrix.python }} + testing-type: units From b9a6ec4f7d5c8e7293cb3f84e333d1f5fba20be8 Mon Sep 17 00:00:00 2001 From: Gabriel PREDA Date: Tue, 4 Oct 2022 12:08:59 +0300 Subject: [PATCH 117/217] * add `socket` option suggestion in documentation (#437) * * add `socket` option suggestion in documentation * white space fix * * move first two at the end --- .gitignore | 3 +++ plugins/doc_fragments/mysql.py | 7 +++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 6bbe85a..1922df0 100644 --- a/.gitignore +++ b/.gitignore @@ -134,3 +134,6 @@ dmypy.json # MacOS .DS_Store + +# IntelliJ IDEA or PyCharm +.idea/ diff --git a/plugins/doc_fragments/mysql.py b/plugins/doc_fragments/mysql.py index 7d4ec96..939126c 100644 --- a/plugins/doc_fragments/mysql.py +++ b/plugins/doc_fragments/mysql.py @@ -79,8 +79,6 @@ requirements: - PyMySQL (Python 2.7 and Python 3.x) or - MySQLdb (Python 2.x) notes: - - "To avoid the C(Please explicitly state intended protocol) error, use the I(login_unix_socket) argument, - for example, C(login_unix_socket: /run/mysqld/mysqld.sock)." - Requires the PyMySQL (Python 2.7 and Python 3.X) or MySQL-python (Python 2.X) package installed on the remote host. The Python package may be installed with apt-get install python-pymysql (Ubuntu; see M(ansible.builtin.apt)) or yum install python2-PyMySQL (RHEL/CentOS/Fedora; see M(ansible.builtin.yum)). You can also use dnf install python2-PyMySQL @@ -107,4 +105,9 @@ notes: - "If credentials from the config file (for example, C(/root/.my.cnf)) are not needed to connect to a database server, but the file exists and does not contain a C([client]) section, before any other valid directives, it will be read and this will cause the connection to fail, to prevent this set it to an empty string, (for example C(config_file: ''))." + - "To avoid the C(Please explicitly state intended protocol) error, use the I(login_unix_socket) argument, + for example, C(login_unix_socket: /run/mysqld/mysqld.sock)." + - Alternatively, to avoid using I(login_unix_socket) argument on each invocation you can specify the socket path + using the `socket` option in your MySQL config file (usually C(~/.my.cnf)) on the destination host, for + example C(socket=/var/lib/mysql/mysql.sock). ''' From 09e02320fd15c51a80183cde80b059e2b9e44dfd Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Tue, 1 Nov 2022 12:59:06 +0100 Subject: [PATCH 118/217] README: Add matrix room + badge (#459) * README: Add matrix room + badge * improve --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 266db1d..82c0c6d 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # MySQL collection for Ansible -[![Plugins CI](https://github.com/ansible-collections/community.mysql/workflows/Plugins%20CI/badge.svg?event=push)](https://github.com/ansible-collections/community.mysql/actions?query=workflow%3A"Plugins+CI") [![Roles CI](https://github.com/ansible-collections/community.mysql/workflows/Roles%20CI/badge.svg?event=push)](https://github.com/ansible-collections/community.mysql/actions?query=workflow%3A"Roles+CI") [![Codecov](https://img.shields.io/codecov/c/github/ansible-collections/community.mysql)](https://codecov.io/gh/ansible-collections/community.mysql) +[![Plugins CI](https://github.com/ansible-collections/community.mysql/workflows/Plugins%20CI/badge.svg?event=push)](https://github.com/ansible-collections/community.mysql/actions?query=workflow%3A"Plugins+CI") [![Roles CI](https://github.com/ansible-collections/community.mysql/workflows/Roles%20CI/badge.svg?event=push)](https://github.com/ansible-collections/community.mysql/actions?query=workflow%3A"Roles+CI") [![Codecov](https://img.shields.io/codecov/c/github/ansible-collections/community.mysql)](https://codecov.io/gh/ansible-collections/community.mysql) [![](https://img.shields.io/matrix/mysql:ansible.com.svg?server_fqdn=ansible-accounts.ems.host&label=Discuss%20at%20%23mysql:ansible.com&logo=matrix)] This collection is a part of the Ansible package. @@ -36,7 +36,7 @@ They also should be subscribed to Ansible's [The Bullhorn newsletter](https://do We announce releases and important changes through Ansible's [The Bullhorn newsletter](https://eepurl.com/gZmiEP). Be sure you are subscribed. -Join us in the `#ansible` (general use questions and support), `#ansible-community` (community and collection development questions), and other [IRC channels](https://docs.ansible.com/ansible/devel/community/communication.html#irc-channels) on [Libera.Chat](https://libera.chat). +Join us on Matrix in the `#mysql:ansible.com` [room](https://matrix.to/#/#mysql:ansible.com), the `#users:ansible.com` [room](https://matrix.to/#/#users:ansible.com) (general use questions and support), `#ansible-community:ansible.com` [room](https://matrix.to/#/#community:ansible.com) (community and collection development questions), and other Matrix rooms or corresponding bridged Libera.Chat channels. See the [Ansible Communication Guide](https://docs.ansible.com/ansible/devel/community/communication.html) for details. We take part in the global quarterly [Ansible Contributor Summit](https://github.com/ansible/community/wiki/Contributor-Summit) virtually or in-person. Track [The Bullhorn newsletter](https://eepurl.com/gZmiEP) and join us. From 4dac66382a4383b1f2113106e0a43a62946069e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20Inderm=C3=BChle?= Date: Tue, 6 Dec 2022 08:41:04 +0100 Subject: [PATCH 119/217] Add fixed version of Ubuntu (#470) This is because ubuntu-latest link to ubuntu-22.04 which includes cgroup-v2. I thinks our tests fails because of that. See https://github.com/ansible-collections/news-for-maintainers/issues/28 for more information. --- .github/workflows/ansible-test-plugins.yml | 6 +++--- .github/workflows/ansible-test-roles.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ansible-test-plugins.yml b/.github/workflows/ansible-test-plugins.yml index 2f247da..e1957cf 100644 --- a/.github/workflows/ansible-test-plugins.yml +++ b/.github/workflows/ansible-test-plugins.yml @@ -21,7 +21,7 @@ env: jobs: sanity: name: "Sanity (Ansible: ${{ matrix.ansible }})" - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 strategy: matrix: ansible: @@ -39,7 +39,7 @@ jobs: integration: name: "Integration (Python: ${{ matrix.python }}, Ansible: ${{ matrix.ansible }}, MySQL: ${{ matrix.db_engine_version }}, Connector: ${{ matrix.connector }})" - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 strategy: fail-fast: false matrix: @@ -118,7 +118,7 @@ jobs: testing-type: integration units: - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 name: Units (Ⓐ${{ matrix.ansible }}) strategy: # As soon as the first unit test fails, diff --git a/.github/workflows/ansible-test-roles.yml b/.github/workflows/ansible-test-roles.yml index 34bee52..4748b5a 100644 --- a/.github/workflows/ansible-test-roles.yml +++ b/.github/workflows/ansible-test-roles.yml @@ -14,7 +14,7 @@ on: jobs: molecule: name: "Molecule (Python: ${{ matrix.python }}, Ansible: ${{ matrix.ansible }}, MySQL: ${{ matrix.mysql }})" - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 env: PY_COLORS: 1 ANSIBLE_FORCE_COLOR: 1 From 6ac89ca1f608d3c798410dcadf39cdc9c9b19996 Mon Sep 17 00:00:00 2001 From: Diego Gullo Date: Tue, 6 Dec 2022 16:12:01 +0400 Subject: [PATCH 120/217] Display a more informative error when InvalidPrivsError is raised (#465) (#466) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Display a more informative error when InvalidPrivsError is raised (Issue #465) Co-authored-by: Laurent Indermühle --- ...re_informative_invalid_priv_exceptiion.yml | 5 +++ plugins/module_utils/user.py | 3 +- .../targets/setup_mysql/handlers/main.yml | 2 ++ .../targets/setup_mysql/tasks/main.yml | 10 ++++++ .../setup_remote_tmp_dir/handlers/main.yml | 4 +++ .../setup_remote_tmp_dir/tasks/main.yml | 4 +++ .../targets/test_mysql_user/tasks/main.yml | 4 +++ .../tasks/test_privs_issue_465.yml | 31 +++++++++++++++++++ 8 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 changelogs/fragments/465-display_more_informative_invalid_priv_exceptiion.yml create mode 100644 tests/integration/targets/test_mysql_user/tasks/test_privs_issue_465.yml diff --git a/changelogs/fragments/465-display_more_informative_invalid_priv_exceptiion.yml b/changelogs/fragments/465-display_more_informative_invalid_priv_exceptiion.yml new file mode 100644 index 0000000..fc47d37 --- /dev/null +++ b/changelogs/fragments/465-display_more_informative_invalid_priv_exceptiion.yml @@ -0,0 +1,5 @@ +--- +minor_changes: + - mysql_user - display a more informative invalid privilege exception. + Changes the exception handling of the granting permission logic to show the query executed , params + and the exception message granting privileges fails` (https://github.com/ansible-collections/community.mysql/issues/465). \ No newline at end of file diff --git a/plugins/module_utils/user.py b/plugins/module_utils/user.py index 7def8c7..e80bccf 100644 --- a/plugins/module_utils/user.py +++ b/plugins/module_utils/user.py @@ -725,7 +725,8 @@ def privileges_grant(cursor, user, host, db_table, priv, tls_requires, maria_rol try: cursor.execute(query, params) except (mysql_driver.ProgrammingError, mysql_driver.OperationalError, mysql_driver.InternalError) as e: - raise InvalidPrivsError("Error granting privileges, invalid priv string: %s" % priv_string) + raise InvalidPrivsError("Error granting privileges, invalid priv string: %s , params: %s, query: %s ," + " exception: %s." % (priv_string, str(params), query, str(e))) def convert_priv_dict_to_str(priv): diff --git a/tests/integration/targets/setup_mysql/handlers/main.yml b/tests/integration/targets/setup_mysql/handlers/main.yml index 090a5e7..8f751ee 100644 --- a/tests/integration/targets/setup_mysql/handlers/main.yml +++ b/tests/integration/targets/setup_mysql/handlers/main.yml @@ -4,3 +4,5 @@ src: installed_file.j2 dest: "{{ dbdeployer_installed_file }}" listen: create zookeeper installed file + tags: + - setup_mysql diff --git a/tests/integration/targets/setup_mysql/tasks/main.yml b/tests/integration/targets/setup_mysql/tasks/main.yml index c6a8348..47a5ee0 100644 --- a/tests/integration/targets/setup_mysql/tasks/main.yml +++ b/tests/integration/targets/setup_mysql/tasks/main.yml @@ -5,7 +5,17 @@ #################################################################### - import_tasks: setvars.yml + tags: + - setup_mysql - import_tasks: dir.yml + tags: + - setup_mysql - import_tasks: install.yml + tags: + - setup_mysql - import_tasks: config.yml + tags: + - setup_mysql - import_tasks: verify.yml + tags: + - setup_mysql diff --git a/tests/integration/targets/setup_remote_tmp_dir/handlers/main.yml b/tests/integration/targets/setup_remote_tmp_dir/handlers/main.yml index 229037c..39f3239 100644 --- a/tests/integration/targets/setup_remote_tmp_dir/handlers/main.yml +++ b/tests/integration/targets/setup_remote_tmp_dir/handlers/main.yml @@ -1,5 +1,9 @@ - name: delete temporary directory include_tasks: default-cleanup.yml + tags: + - setup_remote_tmp_dir - name: delete temporary directory (windows) include_tasks: windows-cleanup.yml + tags: + - setup_remote_tmp_dir diff --git a/tests/integration/targets/setup_remote_tmp_dir/tasks/main.yml b/tests/integration/targets/setup_remote_tmp_dir/tasks/main.yml index 93d786f..5d898ab 100644 --- a/tests/integration/targets/setup_remote_tmp_dir/tasks/main.yml +++ b/tests/integration/targets/setup_remote_tmp_dir/tasks/main.yml @@ -7,9 +7,13 @@ setup: gather_subset: distribution when: ansible_facts == {} + tags: + - setup_remote_tmp_dir - include_tasks: "{{ lookup('first_found', files)}}" vars: files: - "{{ ansible_os_family | lower }}.yml" - "default.yml" + tags: + - setup_remote_tmp_dir diff --git a/tests/integration/targets/test_mysql_user/tasks/main.yml b/tests/integration/targets/test_mysql_user/tasks/main.yml index db3304c..ef21c55 100644 --- a/tests/integration/targets/test_mysql_user/tasks/main.yml +++ b/tests/integration/targets/test_mysql_user/tasks/main.yml @@ -281,6 +281,10 @@ - include: test_priv_subtract.yml enable_check_mode=no - include: test_priv_subtract.yml enable_check_mode=yes + - import_tasks: test_privs_issue_465.yml + tags: + - issue_465 + # Tests for the TLS requires dictionary - include: tls_requirements.yml diff --git a/tests/integration/targets/test_mysql_user/tasks/test_privs_issue_465.yml b/tests/integration/targets/test_mysql_user/tasks/test_privs_issue_465.yml new file mode 100644 index 0000000..edf4a0f --- /dev/null +++ b/tests/integration/targets/test_mysql_user/tasks/test_privs_issue_465.yml @@ -0,0 +1,31 @@ +--- +# test code for privileges for mysql_user module - issue 465 + +- 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: create a user with parameters that will always cause an exception + mysql_user: + <<: *mysql_params + name: user_issue_465 + password: a_test_password_465 + priv: '*.{{ db_name }}:SELECT' + state: present + ignore_errors: true + register: result + + - name: assert output message for current privileges + assert: + that: + - result is failed + - result.msg is search('invalid priv string') + - result.msg is search('params') + - result.msg is search('query') + - result.msg is search('exception') From 015f58ea5a11ac46c81de8b2de8f9910efaf5e3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20Inderm=C3=BChle?= Date: Thu, 8 Dec 2022 19:32:22 +0100 Subject: [PATCH 121/217] Update CONTRIBUTORS --- CONTRIBUTORS | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index cacb4ff..3acc8f3 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -33,6 +33,7 @@ baldpale banyek BarbzYHOOL Berbe +bizmate bjne bmalynovytch bmildren From eade7ec1f0aad6de6a6a94e5acb5e9b213c54c2b Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Fri, 9 Dec 2022 14:50:37 +0100 Subject: [PATCH 122/217] CI: add PR change detection (#473) --- .github/workflows/ansible-test-plugins.yml | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ansible-test-plugins.yml b/.github/workflows/ansible-test-plugins.yml index e1957cf..27c657f 100644 --- a/.github/workflows/ansible-test-plugins.yml +++ b/.github/workflows/ansible-test-plugins.yml @@ -25,7 +25,6 @@ jobs: strategy: matrix: ansible: - - stable-2.11 - stable-2.12 - stable-2.13 - stable-2.14 @@ -36,6 +35,7 @@ jobs: with: ansible-core-version: ${{ matrix.ansible }} testing-type: sanity + pull-request-change-detection: true integration: name: "Integration (Python: ${{ matrix.python }}, Ansible: ${{ matrix.ansible }}, MySQL: ${{ matrix.db_engine_version }}, Connector: ${{ matrix.connector }})" @@ -51,7 +51,6 @@ jobs: # also change the "Set MariaDB URL sub dir" task - mariadb_10.8.3 ansible: - - stable-2.11 - stable-2.12 - stable-2.13 - stable-2.14 @@ -77,16 +76,12 @@ jobs: ansible: stable-2.14 - python: 3.6 ansible: devel - - python: 3.8 - ansible: stable-2.11 - python: 3.8 ansible: stable-2.13 - python: 3.8 ansible: stable-2.14 - python: 3.8 ansible: devel - - python: 3.9 - ansible: stable-2.11 - python: 3.9 ansible: stable-2.12 @@ -116,6 +111,7 @@ jobs: sed -i 's/^python_packages:.*/python_packages: [${{ matrix.connector }}]/' ${{ env.connector_version_file }} target-python-version: ${{ matrix.python }} testing-type: integration + pull-request-change-detection: true units: runs-on: ubuntu-20.04 @@ -126,7 +122,6 @@ jobs: fail-fast: true matrix: ansible: - - stable-2.11 - stable-2.12 - stable-2.13 - stable-2.14 @@ -141,8 +136,6 @@ jobs: ansible: stable-2.14 - python: 3.8 ansible: devel - - python: 3.9 - ansible: stable-2.11 - python: 3.9 ansible: stable-2.12 @@ -155,3 +148,4 @@ jobs: ansible-core-version: ${{ matrix.ansible }} target-python-version: ${{ matrix.python }} testing-type: units + pull-request-change-detection: true From 8a579b42e3491d826b0035514ef4ff392bc1e2d5 Mon Sep 17 00:00:00 2001 From: hubiongithub <79990207+hubiongithub@users.noreply.github.com> Date: Tue, 3 Jan 2023 09:47:11 +0100 Subject: [PATCH 123/217] add service name to plugin pam/auth_pam usage (#445) * add service name to plugin pam/auth_pam usage * typo fixed * MySLQ is using identified with auth_pam by ... instead of identified with pam using ... like mariadb does * a : in description lines breaks yaml syntax * clearify documentation and add changelog fragment * Update changelogs/fragments/445_add_service_name_to_plugin_pam_auth_pam_usage.yml Co-authored-by: Andrew Klychkov * Update plugins/module_utils/user.py Co-authored-by: Andrew Klychkov Co-authored-by: Andrew Klychkov --- ...add_service_name_to_plugin_pam_auth_pam_usage.yml | 3 +++ plugins/module_utils/user.py | 12 ++++++++++-- plugins/modules/mysql_user.py | 3 ++- 3 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 changelogs/fragments/445_add_service_name_to_plugin_pam_auth_pam_usage.yml diff --git a/changelogs/fragments/445_add_service_name_to_plugin_pam_auth_pam_usage.yml b/changelogs/fragments/445_add_service_name_to_plugin_pam_auth_pam_usage.yml new file mode 100644 index 0000000..2b9a523 --- /dev/null +++ b/changelogs/fragments/445_add_service_name_to_plugin_pam_auth_pam_usage.yml @@ -0,0 +1,3 @@ +--- +minor_changes: + - mysql_user - add plugin_auth_string as optional parameter to use a specific pam service if pam/auth_pam plugin is used (https://github.com/ansible-collections/community.mysql/pull/445). diff --git a/plugins/module_utils/user.py b/plugins/module_utils/user.py index e80bccf..e36aa57 100644 --- a/plugins/module_utils/user.py +++ b/plugins/module_utils/user.py @@ -170,7 +170,11 @@ def user_add(cursor, user, host, host_all, password, encrypted, elif plugin and plugin_hash_string: query_with_args = "CREATE USER %s@%s IDENTIFIED WITH %s AS %s", (user, host, plugin, plugin_hash_string) elif plugin and plugin_auth_string: - query_with_args = "CREATE USER %s@%s IDENTIFIED WITH %s BY %s", (user, host, plugin, plugin_auth_string) + # Mysql and MariaDB differ in naming pam plugin and Syntax to set it + if plugin == 'pam': # Used by MariaDB which requires the USING keyword, not BY + query_with_args = "CREATE USER %s@%s IDENTIFIED WITH %s USING %s", (user, host, plugin, plugin_auth_string) + else: + query_with_args = "CREATE USER %s@%s IDENTIFIED WITH %s BY %s", (user, host, plugin, plugin_auth_string) elif plugin: query_with_args = "CREATE USER %s@%s IDENTIFIED WITH %s", (user, host, plugin) else: @@ -305,7 +309,11 @@ def user_mod(cursor, user, host, host_all, password, encrypted, if plugin_hash_string: query_with_args = "ALTER USER %s@%s IDENTIFIED WITH %s AS %s", (user, host, plugin, plugin_hash_string) elif plugin_auth_string: - query_with_args = "ALTER USER %s@%s IDENTIFIED WITH %s BY %s", (user, host, plugin, plugin_auth_string) + # Mysql and MariaDB differ in naming pam plugin and syntax to set it + if plugin == 'pam': + query_with_args = "ALTER USER %s@%s IDENTIFIED WITH %s USING %s", (user, host, plugin, plugin_auth_string) + else: + query_with_args = "ALTER USER %s@%s IDENTIFIED WITH %s BY %s", (user, host, plugin, plugin_auth_string) else: query_with_args = "ALTER USER %s@%s IDENTIFIED WITH %s", (user, host, plugin) diff --git a/plugins/modules/mysql_user.py b/plugins/modules/mysql_user.py index 849aa8d..ed7dde0 100644 --- a/plugins/modules/mysql_user.py +++ b/plugins/modules/mysql_user.py @@ -117,7 +117,7 @@ options: update_password: description: - C(always) will update passwords if they differ. This affects I(password) and the combination of I(plugin), I(plugin_hash_string), I(plugin_auth_string). - - C(on_create) will only set the password or the combination of plugin, plugin_hash_string, plugin_auth_string for newly created users. + - C(on_create) will only set the password or the combination of I(plugin), I(plugin_hash_string), I(plugin_auth_string) for newly created users. - "C(on_new_username) works like C(on_create), but it tries to reuse an existing password: If one different user with the same username exists, or multiple different users with the same username and equal C(plugin) and C(authentication_string) attribute, the existing C(plugin) and C(authentication_string) are used for the @@ -138,6 +138,7 @@ options: plugin_auth_string: description: - User's plugin auth_string (``CREATE USER user IDENTIFIED WITH plugin BY plugin_auth_string``). + - If I(plugin) is ``pam`` (MariaDB) or ``auth_pam`` (MySQL) an optional I(plugin_auth_string) can be used to choose a specific PAM service. type: str version_added: '0.1.0' resource_limits: From 3ff1fad5f3e254b0bee18667a7162a1f9c32585c Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Tue, 3 Jan 2023 11:24:59 +0100 Subject: [PATCH 124/217] Docs: change yes/no to true/false (#480) --- plugins/modules/mysql_db.py | 26 +++++++++++----------- plugins/modules/mysql_info.py | 10 ++++----- plugins/modules/mysql_query.py | 6 +++--- plugins/modules/mysql_replication.py | 6 +++--- plugins/modules/mysql_role.py | 32 ++++++++++++++-------------- plugins/modules/mysql_user.py | 26 +++++++++++----------- plugins/modules/mysql_variables.py | 2 +- 7 files changed, 54 insertions(+), 54 deletions(-) diff --git a/plugins/modules/mysql_db.py b/plugins/modules/mysql_db.py index 83a935e..5a8fe3e 100644 --- a/plugins/modules/mysql_db.py +++ b/plugins/modules/mysql_db.py @@ -53,12 +53,12 @@ options: description: - Execute the dump in a single transaction. type: bool - default: no + default: false quick: description: - Option used for dumping large tables. type: bool - default: yes + default: true ignore_tables: description: - A list of table names that will be ignored in the dump @@ -70,14 +70,14 @@ options: description: - Dump binary columns using hexadecimal notation. type: bool - default: no + default: false version_added: '0.1.0' force: description: - Continue dump or import even if we get an SQL error. - Used only when I(state) is C(dump) or C(import). type: bool - default: no + default: false version_added: '0.1.0' master_data: description: @@ -96,7 +96,7 @@ options: description: - Skip locking tables for read. Used when I(state=dump), ignored otherwise. type: bool - default: no + default: false version_added: '0.1.0' dump_extra_args: description: @@ -110,7 +110,7 @@ options: - If C(yes), the module will internally execute commands via a shell. - Used when I(state=import), ignored otherwise. type: bool - default: no + default: false version_added: '0.1.0' unsafe_login_password: description: @@ -121,7 +121,7 @@ options: - Used only when I(state) is C(import) or C(dump) and I(login_password) is passed, ignored otherwise. type: bool - default: no + default: false version_added: '0.1.0' restrict_config_file: description: @@ -132,14 +132,14 @@ options: under the hood that read named option file in addition to usual option files. - If this behavior is undesirable, use C(yes) to read only named option file. type: bool - default: no + default: false version_added: '0.1.0' check_implicit_admin: description: - Check if mysql allows login as root/nopassword before trying supplied credentials. - If success, passed I(login_user)/I(login_password) will be ignored. type: bool - default: no + default: false version_added: '0.1.0' config_overrides_defaults: description: @@ -148,7 +148,7 @@ options: - 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 + default: false version_added: '0.1.0' chdir: description: @@ -163,7 +163,7 @@ options: - The default is C(no) to prevent issues on systems without bash as a default interpreter. - The default will change to C(yes) in community.mysql 4.0.0. type: bool - default: no + default: false version_added: '3.4.0' seealso: @@ -230,7 +230,7 @@ EXAMPLES = r''' name: my_db state: import target: /tmp/dump.sql.bz2 - force: yes + force: true - name: Dump multiple databases community.mysql.mysql_db: @@ -302,7 +302,7 @@ EXAMPLES = r''' - name: Try to create database as root/nopassword first. If not allowed, pass the credentials community.mysql.mysql_db: - check_implicit_admin: yes + check_implicit_admin: true login_user: bob login_password: 123456 name: bobdata diff --git a/plugins/modules/mysql_info.py b/plugins/modules/mysql_info.py index 1daa9b9..c7761a2 100644 --- a/plugins/modules/mysql_info.py +++ b/plugins/modules/mysql_info.py @@ -42,7 +42,7 @@ options: description: - Includes names of empty databases to returned dictionary. type: bool - default: no + default: false notes: - Calculating the size of a database might be slow, depending on the number and size of tables in it. @@ -96,14 +96,14 @@ EXAMPLES = r''' filter: "!settings,!users" - name: Collect info about databases and version using ~/.my.cnf as a credential file - become: yes + become: true community.mysql.mysql_info: filter: - databases - version - name: Collect info about databases and version using ~alice/.my.cnf as a credential file - become: yes + become: true community.mysql.mysql_info: config_file: /home/alice/.my.cnf filter: @@ -111,13 +111,13 @@ EXAMPLES = r''' - version - name: Collect info about databases including empty and excluding their sizes - become: yes + become: true community.mysql.mysql_info: config_file: /home/alice/.my.cnf filter: - databases exclude_fields: db_size - return_empty_dbs: yes + return_empty_dbs: true ''' RETURN = r''' diff --git a/plugins/modules/mysql_query.py b/plugins/modules/mysql_query.py index 04f6201..a3d7ce2 100644 --- a/plugins/modules/mysql_query.py +++ b/plugins/modules/mysql_query.py @@ -27,7 +27,7 @@ options: the state has been changed even if it has not. If it is important in your workflow, use the C(PyMySQL) connector instead. type: raw - required: yes + required: true positional_args: description: - List of values to be passed as positional arguments to the query. @@ -46,7 +46,7 @@ options: description: - Where passed queries run in a single transaction (C(yes)) or commit them one-by-one (C(no)). type: bool - default: no + default: false seealso: - module: community.mysql.mysql_db author: @@ -87,7 +87,7 @@ EXAMPLES = r''' query: - INSERT INTO articles (id, story) VALUES (2, 'my_long_story') - INSERT INTO prices (id, price) VALUES (123, '100.00') - single_transaction: yes + single_transaction: true ''' RETURN = r''' diff --git a/plugins/modules/mysql_replication.py b/plugins/modules/mysql_replication.py index d63905f..5d1a0e5 100644 --- a/plugins/modules/mysql_replication.py +++ b/plugins/modules/mysql_replication.py @@ -184,7 +184,7 @@ options: description: - Fails on error when calling mysql. type: bool - default: False + default: false version_added: '0.1.0' notes: @@ -263,12 +263,12 @@ EXAMPLES = r''' community.mysql.mysql_replication: mode: startreplica connection_name: primary-1 - fail_on_error: yes + fail_on_error: true - name: Change primary and fail on error (like when replica thread is running) community.mysql.mysql_replication: mode: changeprimary - fail_on_error: yes + fail_on_error: true ''' diff --git a/plugins/modules/mysql_role.py b/plugins/modules/mysql_role.py index 25b7e4c..01cb625 100644 --- a/plugins/modules/mysql_role.py +++ b/plugins/modules/mysql_role.py @@ -53,7 +53,7 @@ options: - Append the privileges defined by the I(priv) option to the existing ones for this role instead of overwriting them. Mutually exclusive with I(subtract_privs). type: bool - default: no + default: false subtract_privs: description: @@ -62,7 +62,7 @@ options: Mutually exclusive with I(append_privs). version_added: '3.2.0' type: bool - default: no + default: false members: description: @@ -80,7 +80,7 @@ options: for this role instead of overwriting them. - Mutually exclusive with the I(detach_members) and I(admin) option. type: bool - default: no + default: false detach_members: description: @@ -88,7 +88,7 @@ options: instead of overwriting all the current members. - Mutually exclusive with the I(append_members) and I(admin) option. type: bool - default: no + default: false set_default_role_all: description: @@ -96,7 +96,7 @@ options: - If C(yes), runs B(SET DEFAULT ROLE ALL TO) each of the I(members) when changed. - If you want to avoid this behavior, set this option to C(no) explicitly. type: bool - default: yes + default: true state: description: @@ -112,14 +112,14 @@ options: - Check if mysql allows login as root/nopassword before trying supplied credentials. - If success, passed I(login_user)/I(login_password) will be ignored. type: bool - default: no + default: false members_must_exist: description: - When C(yes), the module fails if any user in I(members) does not exist. - When C(no), users in I(members) which don't exist are simply skipped. type: bool - default: yes + default: true notes: - Pay attention that the module runs C(SET DEFAULT ROLE ALL TO) @@ -181,7 +181,7 @@ EXAMPLES = r''' members: - 'alice@%' - 'bob@%' - set_default_role_all: no + set_default_role_all: false # Assuming that the role developers exists, # add john to the current members @@ -189,7 +189,7 @@ EXAMPLES = r''' community.mysql.mysql_role: name: developers state: present - append_members: yes + append_members: true members: - 'joe@localhost' @@ -208,7 +208,7 @@ EXAMPLES = r''' name: readers state: present priv: 'fiction.*:UPDATE' - append_privs: yes + append_privs: true - name: Create role with the 'SELECT' and 'UPDATE' privileges in db1 and db2 community.mysql.mysql_role: @@ -224,7 +224,7 @@ EXAMPLES = r''' name: readers members: - 'joe@localhost' - detach_members: yes + detach_members: true - name: Remove the role readers if exists community.mysql.mysql_role: @@ -258,7 +258,7 @@ EXAMPLES = r''' community.mysql.mysql_role: state: present name: foo - subtract_privs: yes + subtract_privs: true priv: 'db1.*': DELETE @@ -266,8 +266,8 @@ EXAMPLES = r''' community.mysql.mysql_role: state: present name: foo - append_members: yes - members_must_exist: no + append_members: true + members_must_exist: false members: - 'existing_user@localhost' - 'not_existing_user@localhost' @@ -276,8 +276,8 @@ EXAMPLES = r''' community.mysql.mysql_role: state: present name: foo - detach_members: yes - members_must_exist: no + detach_members: true + members_must_exist: false members: - 'existing_user@localhost' - 'not_existing_user@localhost' diff --git a/plugins/modules/mysql_user.py b/plugins/modules/mysql_user.py index ed7dde0..8acb8a3 100644 --- a/plugins/modules/mysql_user.py +++ b/plugins/modules/mysql_user.py @@ -29,7 +29,7 @@ options: description: - Indicate that the 'password' field is a `mysql_native_password` hash. type: bool - default: no + default: false host: description: - The 'host' part of the MySQL username. @@ -41,7 +41,7 @@ options: to all hostnames for a given user. - This option cannot be used when creating users. type: bool - default: no + default: false priv: description: - "MySQL privileges string in the format: C(db.table:priv1,priv2)." @@ -66,7 +66,7 @@ options: - Append the privileges defined by priv to the existing ones for this user instead of overwriting existing ones. Mutually exclusive with I(subtract_privs). type: bool - default: no + default: false subtract_privs: description: - Revoke the privileges defined by the I(priv) option and keep other existing privileges. @@ -74,7 +74,7 @@ options: Mutually exclusive with I(append_privs). version_added: '3.2.0' type: bool - default: no + default: false tls_requires: description: - Set requirement for secure transport as a dictionary of requirements (see the examples). @@ -87,7 +87,7 @@ options: description: - Whether binary logging should be enabled or disabled for the connection. type: bool - default: yes + default: true force_context: description: - Sets the С(mysql) system database as context for the executed statements (it will be used @@ -99,7 +99,7 @@ options: - See U(https://dev.mysql.com/doc/refman/8.0/en/replication-options-replica.html#option_mysqld_replicate-ignore-db) for a description on how replication filters work (filtering on the replica). type: bool - default: no + default: false version_added: '3.1.0' state: description: @@ -113,7 +113,7 @@ options: - Check if mysql allows login as root/nopassword before trying supplied credentials. - If success, passed I(login_user)/I(login_password) will be ignored. type: bool - default: no + default: false update_password: description: - C(always) will update passwords if they differ. This affects I(password) and the combination of I(plugin), I(plugin_hash_string), I(plugin_auth_string). @@ -190,7 +190,7 @@ EXAMPLES = r''' - name: Removes all anonymous user accounts community.mysql.mysql_user: name: '' - host_all: yes + host_all: true state: absent - name: Create database user with name 'bob' and password '12345' with all database privileges @@ -204,7 +204,7 @@ EXAMPLES = r''' community.mysql.mysql_user: name: bob password: '*EE0D72C1085C46C5278932678FBE2C6A782821B4' - encrypted: yes + encrypted: true priv: '*.*:ALL' state: present @@ -265,7 +265,7 @@ EXAMPLES = r''' If mysql allows root/nopassword login, try it without the credentials first. If it's not allowed, pass the credentials community.mysql.mysql_user: - check_implicit_admin: yes + check_implicit_admin: true login_user: root login_password: 123456 name: sally @@ -274,7 +274,7 @@ EXAMPLES = r''' - name: Ensure no user named 'sally' exists at all community.mysql.mysql_user: name: sally - host_all: yes + host_all: true state: absent - name: Specify grants composed of more than one word @@ -306,7 +306,7 @@ EXAMPLES = r''' password: 12345 priv: "*.*:USAGE" state: present - sql_log_bin: no + sql_log_bin: false - name: Create user 'bob' authenticated with plugin 'AWSAuthenticationPlugin' community.mysql.mysql_user: @@ -326,7 +326,7 @@ EXAMPLES = r''' - name: Ensure bob does not have the DELETE privilege community.mysql.mysql_user: name: bob - subtract_privs: yes + subtract_privs: true priv: 'db1.*': DELETE diff --git a/plugins/modules/mysql_variables.py b/plugins/modules/mysql_variables.py index 2544e8d..dc54c82 100644 --- a/plugins/modules/mysql_variables.py +++ b/plugins/modules/mysql_variables.py @@ -22,7 +22,7 @@ options: description: - Variable name to operate. type: str - required: yes + required: true value: description: - If set, then sets variable value to this. From 4ad71775a6de0223d603d72041d38697afe9a074 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Mon, 16 Jan 2023 12:46:31 +0100 Subject: [PATCH 125/217] README: update Matrix badge (#485) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 82c0c6d..dc46a94 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # MySQL collection for Ansible -[![Plugins CI](https://github.com/ansible-collections/community.mysql/workflows/Plugins%20CI/badge.svg?event=push)](https://github.com/ansible-collections/community.mysql/actions?query=workflow%3A"Plugins+CI") [![Roles CI](https://github.com/ansible-collections/community.mysql/workflows/Roles%20CI/badge.svg?event=push)](https://github.com/ansible-collections/community.mysql/actions?query=workflow%3A"Roles+CI") [![Codecov](https://img.shields.io/codecov/c/github/ansible-collections/community.mysql)](https://codecov.io/gh/ansible-collections/community.mysql) [![](https://img.shields.io/matrix/mysql:ansible.com.svg?server_fqdn=ansible-accounts.ems.host&label=Discuss%20at%20%23mysql:ansible.com&logo=matrix)] +[![Plugins CI](https://github.com/ansible-collections/community.mysql/workflows/Plugins%20CI/badge.svg?event=push)](https://github.com/ansible-collections/community.mysql/actions?query=workflow%3A"Plugins+CI") [![Roles CI](https://github.com/ansible-collections/community.mysql/workflows/Roles%20CI/badge.svg?event=push)](https://github.com/ansible-collections/community.mysql/actions?query=workflow%3A"Roles+CI") [![Codecov](https://img.shields.io/codecov/c/github/ansible-collections/community.mysql)](https://codecov.io/gh/ansible-collections/community.mysql) ![](https://img.shields.io/matrix/mysql:ansible.com.svg?server_fqdn=ansible-accounts.ems.host&label=Discuss%20on%20Matrix%20%23mysql:ansible.com&logo=matrix) This collection is a part of the Ansible package. From c242584baeb322bba79b547f28b9403cc2ced2b4 Mon Sep 17 00:00:00 2001 From: Alexander Skiba Date: Tue, 17 Jan 2023 10:34:20 +0100 Subject: [PATCH 126/217] mysql_user: enabled autocommit to support MySQL 8 (#483) * mysql_user: enabled autocommit to support MySQL 8 * Add changelog fragment * Link to issue instead of pull request in changelog fragment Co-authored-by: Andrew Klychkov Co-authored-by: Andrew Klychkov --- changelogs/fragments/479_enable_auto_commit.yml | 3 +++ plugins/modules/mysql_user.py | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 changelogs/fragments/479_enable_auto_commit.yml diff --git a/changelogs/fragments/479_enable_auto_commit.yml b/changelogs/fragments/479_enable_auto_commit.yml new file mode 100644 index 0000000..5701f30 --- /dev/null +++ b/changelogs/fragments/479_enable_auto_commit.yml @@ -0,0 +1,3 @@ +--- +minor_changes: + - mysql_user - enable auto_commit to avoid MySQL metadata table lock (https://github.com/ansible-collections/community.mysql/issues/479). diff --git a/plugins/modules/mysql_user.py b/plugins/modules/mysql_user.py index 8acb8a3..bd488b0 100644 --- a/plugins/modules/mysql_user.py +++ b/plugins/modules/mysql_user.py @@ -433,13 +433,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, check_hostname=check_hostname) + connect_timeout=connect_timeout, check_hostname=check_hostname, autocommit=True) 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, check_hostname=check_hostname) + connect_timeout=connect_timeout, check_hostname=check_hostname, autocommit=True) 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))) From 930a5a5d4983137333698a86ba8f2b2e9cca1274 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Tue, 24 Jan 2023 14:53:29 +0100 Subject: [PATCH 127/217] mysql_user: add session_vars argument (#489) * mysql_user: add session_vars argument * Update tests/integration/targets/test_mysql_user/tasks/main.yml Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com> Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com> --- .../fragments/0_mysql_user_session_vars.yml | 2 ++ plugins/module_utils/mysql.py | 12 +++++++++++ plugins/modules/mysql_user.py | 21 ++++++++++++++++++- .../targets/test_mysql_user/tasks/main.yml | 20 ++++++++++++++++++ 4 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 changelogs/fragments/0_mysql_user_session_vars.yml diff --git a/changelogs/fragments/0_mysql_user_session_vars.yml b/changelogs/fragments/0_mysql_user_session_vars.yml new file mode 100644 index 0000000..55bcd6c --- /dev/null +++ b/changelogs/fragments/0_mysql_user_session_vars.yml @@ -0,0 +1,2 @@ +minor_changes: +- mysql_user - add the ``session_vars`` argument to set session variables at the beginning of module execution (https://github.com/ansible-collections/community.mysql/issues/478). diff --git a/plugins/module_utils/mysql.py b/plugins/module_utils/mysql.py index d256599..18e34e0 100644 --- a/plugins/module_utils/mysql.py +++ b/plugins/module_utils/mysql.py @@ -34,6 +34,8 @@ mysql_driver_fail_msg = ('A MySQL module is required: for Python 2.7 either PyMy 'Consider setting ansible_python_interpreter to use ' 'the intended Python version.') +from ansible_collections.community.mysql.plugins.module_utils.database import mysql_quote_identifier + def parse_from_mysql_config_file(cnf): # Default values of comment_prefix is '#' and ';'. @@ -149,3 +151,13 @@ def get_server_version(cursor): version_str = result[0] return version_str + + +def set_session_vars(module, cursor, session_vars): + """Set session vars.""" + for var, value in session_vars.items(): + query = "SET SESSION %s = " % mysql_quote_identifier(var, 'vars') + try: + cursor.execute(query + "%s", (value,)) + except Exception as e: + module.fail_json(msg='Failed to execute %s%s: %s' % (query, value, e)) diff --git a/plugins/modules/mysql_user.py b/plugins/modules/mysql_user.py index bd488b0..e1808c8 100644 --- a/plugins/modules/mysql_user.py +++ b/plugins/modules/mysql_user.py @@ -149,6 +149,12 @@ options: - Used when I(state=present), ignored otherwise. type: dict version_added: '0.1.0' + session_vars: + description: + - "Dictionary of session variables in form of C(variable: value) to set at the beginning of module execution." + - Cannot be used to set global variables, use the M(community.mysql.mysql_variables) module instead. + type: dict + version_added: '3.6.0' notes: - "MySQL server installs with default I(login_user) of C(root) and no password. @@ -208,12 +214,15 @@ EXAMPLES = r''' priv: '*.*:ALL' state: present +# Set session var wsrep_on=off before creating the user - name: Create database user with password and all database privileges and 'WITH GRANT OPTION' community.mysql.mysql_user: name: bob password: 12345 priv: '*.*:ALL,GRANT' state: present + session_vars: + wsrep_on: off - name: Create user with password, all database privileges and 'WITH GRANT OPTION' in db1 and db2 community.mysql.mysql_user: @@ -341,7 +350,11 @@ RETURN = '''#''' 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, mysql_common_argument_spec + mysql_connect, + mysql_driver, + mysql_driver_fail_msg, + mysql_common_argument_spec, + set_session_vars, ) from ansible_collections.community.mysql.plugins.module_utils.user import ( convert_priv_dict_to_str, @@ -385,6 +398,7 @@ def main(): plugin_auth_string=dict(default=None, type='str'), resource_limits=dict(type='dict'), force_context=dict(type='bool', default=False), + session_vars=dict(type='dict'), ) module = AnsibleModule( argument_spec=argument_spec, @@ -419,6 +433,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"] + session_vars = module.params["session_vars"] + 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)) @@ -447,6 +463,9 @@ def main(): if not sql_log_bin: cursor.execute("SET SQL_LOG_BIN=0;") + if session_vars: + set_session_vars(module, cursor, session_vars) + get_impl(cursor) if priv is not None: diff --git a/tests/integration/targets/test_mysql_user/tasks/main.yml b/tests/integration/targets/test_mysql_user/tasks/main.yml index ef21c55..d829322 100644 --- a/tests/integration/targets/test_mysql_user/tasks/main.yml +++ b/tests/integration/targets/test_mysql_user/tasks/main.yml @@ -62,6 +62,8 @@ name: '{{user_name_1}}' password: '{{user_password_1}}' state: present + session_vars: + sort_buffer_size: 1024 register: result - name: assert output message mysql user was not created @@ -69,6 +71,24 @@ that: - result is not changed + # Try to set wrong session variable, must fail + - name: create mysql user trying to set global variable which is forbidden + mysql_user: + <<: *mysql_params + name: '{{user_name_1}}' + password: '{{user_password_1}}' + state: present + session_vars: + max_connections: 1000 + register: result + ignore_errors: true + + - name: we cannot set a global variable + assert: + that: + - result is failed + - result.msg is search('is a GLOBAL variable') + # ============================================================ # remove mysql user and verify user is removed from mysql database # From 00fa058a18c82e395a1064e7fd7f41d0dc259fdd Mon Sep 17 00:00:00 2001 From: "Jorge Rodriguez (A.K.A. Tiriel)" Date: Tue, 24 Jan 2023 19:12:35 +0200 Subject: [PATCH 128/217] 491-CI-fix-tarball-download (#491) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix mariadb test setup * Update mysql src URL * Add changelog fragment * Update 491_fix_download_url.yaml Sanity test failed because minor_changes in not an element of a list. * Fix casing Co-authored-by: Laurent Indermühle --- .github/workflows/ansible-test-plugins.yml | 16 ++++++---------- changelogs/fragments/491_fix_download_url.yaml | 3 +++ .../targets/setup_mysql/vars/main.yml | 2 +- 3 files changed, 10 insertions(+), 11 deletions(-) create mode 100644 changelogs/fragments/491_fix_download_url.yaml diff --git a/.github/workflows/ansible-test-plugins.yml b/.github/workflows/ansible-test-plugins.yml index 27c657f..ea6ae8e 100644 --- a/.github/workflows/ansible-test-plugins.yml +++ b/.github/workflows/ansible-test-plugins.yml @@ -47,8 +47,6 @@ jobs: - mysql_5.7.31 - mysql_8.0.22 - mariadb_10.3.34 - # When adding later versions below, - # also change the "Set MariaDB URL sub dir" task - mariadb_10.8.3 ansible: - stable-2.12 @@ -99,14 +97,12 @@ jobs: DB_ENGINE_PRETTY=$([[ "${DB_ENGINE}" == 'mysql' ]] && echo 'MySQL' || echo 'MariaDB'); >&2 echo Matrix factor for the DB is ${{ matrix.db_engine_version }}...; >&2 echo Setting ${DB_ENGINE_PRETTY} version to ${DB_VERSION}...; - sed -i -e "s/^${DB_ENGINE}_version:.*/${DB_ENGINE}_version: $DB_VERSION/g" -e 's/^mariadb_install: false/mariadb_install: true/g' '${{ env.mysql_version_file }}'; - ${{ - matrix.db_engine_version == 'mariadb_10.8.3' - && format( - '>&2 echo Set MariaDB v10.8.3 URL sub dir...; sed -i -e "s/^mariadb_url_subdir:.*/mariadb_url_subdir: linux-systemd/g" "{0}";', env.connector_version_file - ) - || '' - }} + sed -i -e "s/^${DB_ENGINE}_version:.*/${DB_ENGINE}_version: $DB_VERSION/g" '${{ env.mysql_version_file }}'; + if [[ ${{ matrix.db_engine_version }} == mariadb* ]]; + then + echo Set MariaDB install flag...; sed -i -e "s/^mariadb_install: false/mariadb_install: true/g" '${{ env.mysql_version_file }}'; + echo Set MariaDB v10.8.3 URL sub dir...; sed -i -e "s/^mariadb_url_subdir:.*/mariadb_url_subdir: linux-systemd/g" '${{ env.connector_version_file }}'; + fi; >&2 echo Setting Connector version to ${{ matrix.connector }}...; sed -i 's/^python_packages:.*/python_packages: [${{ matrix.connector }}]/' ${{ env.connector_version_file }} target-python-version: ${{ matrix.python }} diff --git a/changelogs/fragments/491_fix_download_url.yaml b/changelogs/fragments/491_fix_download_url.yaml new file mode 100644 index 0000000..27628bb --- /dev/null +++ b/changelogs/fragments/491_fix_download_url.yaml @@ -0,0 +1,3 @@ +--- +minor_changes: + - setup_mysql - update MySQL tarball URL (https://github.com/ansible-collections/community.mysql/pull/491). diff --git a/tests/integration/targets/setup_mysql/vars/main.yml b/tests/integration/targets/setup_mysql/vars/main.yml index 4aa52a2..8fbcd90 100644 --- a/tests/integration/targets/setup_mysql/vars/main.yml +++ b/tests/integration/targets/setup_mysql/vars/main.yml @@ -24,7 +24,7 @@ install_python_prereqs: - 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 }}" +mysql_src: "https://cdn.mysql.com/archives/mysql-{{ mysql_major_version }}/{{ mysql_tarball }}" mariadb_url_subdir: "linux" mariadb_tarball: "mariadb-{{ mariadb_version }}-{{ mariadb_url_subdir }}-x86_64.tar.gz" mariadb_src: "https://downloads.mariadb.com/MariaDB/mariadb-{{ mariadb_version }}/bintar-{{ mariadb_url_subdir }}-x86_64/{{ mariadb_tarball }}" From 3229ce4e55623453983fa80f0a0ef3109b784543 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Thu, 26 Jan 2023 09:00:45 +0100 Subject: [PATCH 129/217] README: improve Matrix badge (#494) * README: improve Matrix badge * Add text --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dc46a94..5f95251 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # MySQL collection for Ansible -[![Plugins CI](https://github.com/ansible-collections/community.mysql/workflows/Plugins%20CI/badge.svg?event=push)](https://github.com/ansible-collections/community.mysql/actions?query=workflow%3A"Plugins+CI") [![Roles CI](https://github.com/ansible-collections/community.mysql/workflows/Roles%20CI/badge.svg?event=push)](https://github.com/ansible-collections/community.mysql/actions?query=workflow%3A"Roles+CI") [![Codecov](https://img.shields.io/codecov/c/github/ansible-collections/community.mysql)](https://codecov.io/gh/ansible-collections/community.mysql) ![](https://img.shields.io/matrix/mysql:ansible.com.svg?server_fqdn=ansible-accounts.ems.host&label=Discuss%20on%20Matrix%20%23mysql:ansible.com&logo=matrix) +[![Plugins CI](https://github.com/ansible-collections/community.mysql/workflows/Plugins%20CI/badge.svg?event=push)](https://github.com/ansible-collections/community.mysql/actions?query=workflow%3A"Plugins+CI") [![Roles CI](https://github.com/ansible-collections/community.mysql/workflows/Roles%20CI/badge.svg?event=push)](https://github.com/ansible-collections/community.mysql/actions?query=workflow%3A"Roles+CI") [![Codecov](https://img.shields.io/codecov/c/github/ansible-collections/community.mysql)](https://codecov.io/gh/ansible-collections/community.mysql) [![Discuss on Matrix at #mysql:ansible.com](https://img.shields.io/matrix/mysql:ansible.com.svg?server_fqdn=ansible-accounts.ems.host&label=Discuss%20on%20Matrix%20at%20%23mysql:ansible.com&logo=matrix)](https://matrix.to/#/#mysql:ansible.com) This collection is a part of the Ansible package. From a5f3296d731bef582199300d8d296a8f8476c4a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20Inderm=C3=BChle?= Date: Mon, 30 Jan 2023 13:35:24 +0100 Subject: [PATCH 130/217] mysql_info - Add connector_name and connector_version to returned value (#497) * Add methods to retrieve connector name and version * Document that mysqlclient is also named MySQLdb * Document version_added * Add connector name and version in the returned block * Cut condition to display any name that is return In case of MySQLdb is renamed in mysqlclient. In that case, the integration tests will catch this the day we update the connector version. Co-authored-by: Andrew Klychkov --- ...nfo_returns_connector_name_and_version.yml | 3 ++ plugins/module_utils/mysql.py | 38 +++++++++++++++++++ plugins/modules/mysql_info.py | 32 ++++++++++++++-- .../test_mysql_info/tasks/connector_info.yml | 32 ++++++++++++++++ .../targets/test_mysql_info/tasks/main.yml | 4 ++ 5 files changed, 106 insertions(+), 3 deletions(-) create mode 100644 changelogs/fragments/497_mysql_info_returns_connector_name_and_version.yml create mode 100644 tests/integration/targets/test_mysql_info/tasks/connector_info.yml diff --git a/changelogs/fragments/497_mysql_info_returns_connector_name_and_version.yml b/changelogs/fragments/497_mysql_info_returns_connector_name_and_version.yml new file mode 100644 index 0000000..11fc4f5 --- /dev/null +++ b/changelogs/fragments/497_mysql_info_returns_connector_name_and_version.yml @@ -0,0 +1,3 @@ +--- +minor_changes: + - mysql_info - add ``connector_name`` and ``connector_version`` to returned values (https://github.com/ansible-collections/community.mysql/pull/497). diff --git a/plugins/module_utils/mysql.py b/plugins/module_utils/mysql.py index 18e34e0..2cafcb6 100644 --- a/plugins/module_utils/mysql.py +++ b/plugins/module_utils/mysql.py @@ -23,6 +23,7 @@ try: _mysql_cursor_param = 'cursor' except ImportError: try: + # mysqlclient is called MySQLdb import MySQLdb as mysql_driver import MySQLdb.cursors _mysql_cursor_param = 'cursorclass' @@ -37,6 +38,43 @@ mysql_driver_fail_msg = ('A MySQL module is required: for Python 2.7 either PyMy from ansible_collections.community.mysql.plugins.module_utils.database import mysql_quote_identifier +def get_connector_name(connector): + """ (class) -> str + Return the name of the connector (pymysql or mysqlclient (MySQLdb)) + or 'Unknown' if not pymysql or MySQLdb. When adding a + connector here, also modify get_connector_version. + """ + if connector is None or not hasattr(connector, '__name__'): + return 'Unknown' + + return connector.__name__ + + +def get_connector_version(connector): + """ (class) -> str + Return the version of pymysql or mysqlclient (MySQLdb). + Return 'Unknown' if the connector name is unknown. + """ + + if connector is None: + return 'Unknown' + + connector_name = get_connector_name(connector) + + if connector_name == 'pymysql': + # pymysql has two methods: + # - __version__ that returns the string: 0.7.11.None + # - VERSION that returns the tuple (0, 7, 11, None) + v = connector.VERSION[:3] + return '.'.join(map(str, v)) + elif connector_name == 'MySQLdb': + # version_info returns the tuple (2, 1, 1, 'final', 0) + v = connector.version_info[:3] + return '.'.join(map(str, v)) + else: + return 'Unknown' + + def parse_from_mysql_config_file(cnf): # Default values of comment_prefix is '#' and ';'. # '!' added to prevent a parsing error diff --git a/plugins/modules/mysql_info.py b/plugins/modules/mysql_info.py index c7761a2..11b1a80 100644 --- a/plugins/modules/mysql_info.py +++ b/plugins/modules/mysql_info.py @@ -58,6 +58,7 @@ seealso: author: - Andrew Klychkov (@Andersson007) - Sebastian Gumprich (@rndmh3ro) +- Laurent Indermühle (@laurent-indermuehle) extends_documentation_fragment: - community.mysql.mysql @@ -206,6 +207,21 @@ slave_hosts: type: dict sample: - { "2": { "Host": "", "Master_id": 1, "Port": 3306 } } +connector_name: + description: Name of the python connector used by the module. When the connector is not identified, returns C(Unknown). + returned: always + type: str + sample: + - "pymysql" + - "MySQLdb" + version_added: '3.6.0' +connector_version: + description: Version of the python connector used by the module. When the connector is not identified, returns C(Unknown). + returned: always + type: str + sample: + - "1.0.2" + version_added: '3.6.0' ''' from decimal import Decimal @@ -216,6 +232,8 @@ from ansible_collections.community.mysql.plugins.module_utils.mysql import ( mysql_common_argument_spec, mysql_driver, mysql_driver_fail_msg, + get_connector_name, + get_connector_version, ) from ansible.module_utils.six import iteritems from ansible.module_utils._text import to_native @@ -558,21 +576,29 @@ def main(): if mysql_driver is None: module.fail_json(msg=mysql_driver_fail_msg) + connector_name = get_connector_name(mysql_driver) + connector_version = get_connector_version(mysql_driver) + 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. " - "Exception message: %s" % (config_file, to_native(e))) + msg = ('unable to connect to database using %s %s, check login_user ' + 'and login_password are correct or %s has the credentials. ' + 'Exception message: %s' % (connector_name, connector_version, config_file, to_native(e))) + module.fail_json(msg) ############################### # Create object and do main job mysql = MySQL_Info(module, cursor) - module.exit_json(changed=False, **mysql.get_info(filter_, exclude_fields, return_empty_dbs)) + module.exit_json(changed=False, + connector_name=connector_name, + connector_version=connector_version, + **mysql.get_info(filter_, exclude_fields, return_empty_dbs)) if __name__ == '__main__': diff --git a/tests/integration/targets/test_mysql_info/tasks/connector_info.yml b/tests/integration/targets/test_mysql_info/tasks/connector_info.yml new file mode 100644 index 0000000..ba76f59 --- /dev/null +++ b/tests/integration/targets/test_mysql_info/tasks/connector_info.yml @@ -0,0 +1,32 @@ +--- +# Added in 3.6.0 in +# https://github.com/ansible-collections/community.mysql/pull/497 + +# TODO: Refactor in PR490. +- name: Connector info | Assert connector_name exists and has expected values + ansible.builtin.assert: + that: + - result.connector_name is defined + - result.connector_name is in ['pymysql', 'MySQLdb'] + success_msg: >- + Assertions passed, result.connector_name is {{ result.connector_name }} + fail_msg: >- + Assertion failed, result.connector_name is + {{ result.connector_name | d('Unknown')}} which is different than expected + pymysql or MySQLdb + +# TODO: Refactor in PR490. +- name: Connector info | Assert connector_version exists and has expected values + ansible.builtin.assert: + that: + - result.connector_version is defined + - > + result.connector_version == 'Unknown' + or result.connector_version is version(connector_ver, '==') + success_msg: >- + Assertions passed, result.connector_version is + {{ result.connector_version }} + fail_msg: >- + Assertion failed, result.connector_version is + {{ result.connector_version }} which is different than expected + {{ connector_ver }} diff --git a/tests/integration/targets/test_mysql_info/tasks/main.yml b/tests/integration/targets/test_mysql_info/tasks/main.yml index ec2bd9b..a5428e3 100644 --- a/tests/integration/targets/test_mysql_info/tasks/main.yml +++ b/tests/integration/targets/test_mysql_info/tasks/main.yml @@ -55,6 +55,10 @@ - result.engines != {} - result.users != {} + - name: mysql_info - Test connector informations display + ansible.builtin.import_tasks: + file: connector_info.yml + # Access by non-default cred file - name: mysql_info - check non-default cred file mysql_info: From b8d647454040c56e8081433615288dc84f05ac7a Mon Sep 17 00:00:00 2001 From: Alexander Skiba Date: Wed, 1 Feb 2023 09:37:37 +0100 Subject: [PATCH 131/217] mysql_role: enable autocommit (#500) * mysql_role: enable autocommit * Add changelog fragment --- changelogs/fragments/479_enable_auto_commit_part2.yml | 3 +++ plugins/modules/mysql_role.py | 6 ++++-- 2 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 changelogs/fragments/479_enable_auto_commit_part2.yml diff --git a/changelogs/fragments/479_enable_auto_commit_part2.yml b/changelogs/fragments/479_enable_auto_commit_part2.yml new file mode 100644 index 0000000..a447acf --- /dev/null +++ b/changelogs/fragments/479_enable_auto_commit_part2.yml @@ -0,0 +1,3 @@ +--- + minor_changes: + - mysql_role - enable auto_commit to avoid MySQL metadata table lock (https://github.com/ansible-collections/community.mysql/issues/479). diff --git a/plugins/modules/mysql_role.py b/plugins/modules/mysql_role.py index 01cb625..070d793 100644 --- a/plugins/modules/mysql_role.py +++ b/plugins/modules/mysql_role.py @@ -1008,7 +1008,8 @@ def main(): cursor, db_conn = mysql_connect(module, 'root', '', config_file, ssl_cert, ssl_key, ssl_ca, db, connect_timeout=connect_timeout, - check_hostname=check_hostname) + check_hostname=check_hostname, + autocommit=True) except Exception: pass @@ -1016,7 +1017,8 @@ def main(): cursor, db_conn = mysql_connect(module, login_user, login_password, config_file, ssl_cert, ssl_key, ssl_ca, db, connect_timeout=connect_timeout, - check_hostname=check_hostname) + check_hostname=check_hostname, + autocommit=True) except Exception as e: module.fail_json(msg='unable to connect to database, ' From 521443a6714fa330637890436b4302b3ff8166cf Mon Sep 17 00:00:00 2001 From: Paul Campbell <118974000+pcampbell-payroc@users.noreply.github.com> Date: Sat, 4 Feb 2023 07:31:00 +0000 Subject: [PATCH 132/217] Allow uppercase in variable names for Galera wsrep variables (#501) * Allow uppercase in variable names for Galera wsrep variables * Changelog fragment for regex change * Corrected for excessive line lengths * Update changelogs/fragments/mysql_variables_allow_uppercase_identifiers.yml --------- Co-authored-by: Andrew Klychkov --- .../mysql_variables_allow_uppercase_identifiers.yml | 6 ++++++ plugins/modules/mysql_variables.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 changelogs/fragments/mysql_variables_allow_uppercase_identifiers.yml diff --git a/changelogs/fragments/mysql_variables_allow_uppercase_identifiers.yml b/changelogs/fragments/mysql_variables_allow_uppercase_identifiers.yml new file mode 100644 index 0000000..0d35467 --- /dev/null +++ b/changelogs/fragments/mysql_variables_allow_uppercase_identifiers.yml @@ -0,0 +1,6 @@ +--- +bugfixes: + - mysql_variables - add uppercase character pattern to regex to allow GLOBAL + variables containing uppercase characters. + This recognizes variable names used in Galera, for example, ``wsrep_OSU_method``, + which breaks the normal pattern of all lowercase characters (https://github.com/ansible-collections/community.mysql/pull/501). diff --git a/plugins/modules/mysql_variables.py b/plugins/modules/mysql_variables.py index dc54c82..f404d5a 100644 --- a/plugins/modules/mysql_variables.py +++ b/plugins/modules/mysql_variables.py @@ -199,7 +199,7 @@ def main(): if mysqlvar is None: module.fail_json(msg="Cannot run without variable to operate with") - if match('^[0-9a-z_.]+$', mysqlvar) is None: + if match('^[0-9A-Za-z_.]+$', mysqlvar) is None: module.fail_json(msg="invalid variable name \"%s\"" % mysqlvar) if mysql_driver is None: module.fail_json(msg=mysql_driver_fail_msg) From b34c23d07d1fd2097767a5e16e153cbf20ed8973 Mon Sep 17 00:00:00 2001 From: Markus Bergholz Date: Wed, 8 Feb 2023 09:24:35 +0100 Subject: [PATCH 133/217] Fix revoke only grant (#503) * fix * test * changelog --- .../fragments/503-fix-revoke-grant-only.yml | 2 + plugins/module_utils/user.py | 20 ++++--- .../targets/test_mysql_user/tasks/main.yml | 2 + .../tasks/revoke_only_grant.yml | 58 +++++++++++++++++++ 4 files changed, 73 insertions(+), 9 deletions(-) create mode 100644 changelogs/fragments/503-fix-revoke-grant-only.yml create mode 100644 tests/integration/targets/test_mysql_user/tasks/revoke_only_grant.yml diff --git a/changelogs/fragments/503-fix-revoke-grant-only.yml b/changelogs/fragments/503-fix-revoke-grant-only.yml new file mode 100644 index 0000000..5de4d4b --- /dev/null +++ b/changelogs/fragments/503-fix-revoke-grant-only.yml @@ -0,0 +1,2 @@ +bugfixes: + - mysql_user - when revoke privs consists only of ``GRANT``, a 2nd revoke query is executed with empty privs to revoke that ended in an SQL exception (https://github.com/ansible-collections/community.mysql/pull/503). \ No newline at end of file diff --git a/plugins/module_utils/user.py b/plugins/module_utils/user.py index e36aa57..fc4c40e 100644 --- a/plugins/module_utils/user.py +++ b/plugins/module_utils/user.py @@ -692,17 +692,19 @@ def privileges_revoke(cursor, user, host, db_table, priv, grant_option, maria_ro query = ' '.join(query) cursor.execute(query, (user, host)) priv_string = ",".join([p for p in priv if p not in ('GRANT', )]) - query = ["REVOKE %s ON %s" % (priv_string, db_table)] - if not maria_role: - query.append("FROM %s@%s") - params = (user, host) - else: - query.append("FROM %s") - params = (user,) + if priv_string != "": + query = ["REVOKE %s ON %s" % (priv_string, db_table)] - query = ' '.join(query) - cursor.execute(query, params) + if not maria_role: + query.append("FROM %s@%s") + params = (user, host) + else: + query.append("FROM %s") + params = (user,) + + query = ' '.join(query) + cursor.execute(query, params) cursor.execute("FLUSH PRIVILEGES") diff --git a/tests/integration/targets/test_mysql_user/tasks/main.yml b/tests/integration/targets/test_mysql_user/tasks/main.yml index d829322..5a029b8 100644 --- a/tests/integration/targets/test_mysql_user/tasks/main.yml +++ b/tests/integration/targets/test_mysql_user/tasks/main.yml @@ -322,3 +322,5 @@ # https://github.com/ansible-collections/community.mysql/issues/231 - include: test_user_grants_with_roles_applied.yml + + - include: revoke_only_grant.yml \ No newline at end of file diff --git a/tests/integration/targets/test_mysql_user/tasks/revoke_only_grant.yml b/tests/integration/targets/test_mysql_user/tasks/revoke_only_grant.yml new file mode 100644 index 0000000..19b9b6a --- /dev/null +++ b/tests/integration/targets/test_mysql_user/tasks/revoke_only_grant.yml @@ -0,0 +1,58 @@ +--- +- 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: Drop mysql user if exists + mysql_user: + <<: *mysql_params + name: '{{ user_name_1 }}' + state: absent + ignore_errors: true + + - name: create user with two grants + mysql_user: + <<: *mysql_params + name: "{{ user_name_1 }}" + password: "{{ user_password_1 }}" + update_password: on_create + priv: '*.*:SELECT,GRANT' + + - name: user must have only on priv, grant priv must be dropped + register: result + mysql_user: + <<: *mysql_params + name: "{{ user_name_1 }}" + password: "{{ user_password_1 }}" + update_password: on_create + priv: '*.*:SELECT' + + - assert: + that: + - result is not failed + - result is changed + + - name: immutable - user must have only on priv, grant priv must be dropped + register: result + mysql_user: + <<: *mysql_params + name: "{{ user_name_1 }}" + password: "{{ user_password_1 }}" + update_password: on_create + priv: '*.*:SELECT' + + - assert: + that: + - result is not failed + - result is not changed + + always: + - name: drop user + mysql_user: + <<: *mysql_params + name: '{{ user_name_1 }}' + state: absent + ignore_errors: true From 9acbd55e44962546238761bb848d12b2c28f8de0 Mon Sep 17 00:00:00 2001 From: Markus Bergholz Date: Wed, 8 Feb 2023 10:34:35 +0100 Subject: [PATCH 134/217] prepare community.mysql 3.6.0 (#507) --- CHANGELOG.rst | 29 +++++++++++++- changelogs/changelog.yaml | 39 +++++++++++++++++++ .../fragments/0_mysql_user_session_vars.yml | 2 - ...vice_name_to_plugin_pam_auth_pam_usage.yml | 3 -- ...re_informative_invalid_priv_exceptiion.yml | 5 --- .../fragments/479_enable_auto_commit.yml | 3 -- .../479_enable_auto_commit_part2.yml | 3 -- .../fragments/491_fix_download_url.yaml | 3 -- ...nfo_returns_connector_name_and_version.yml | 3 -- .../fragments/503-fix-revoke-grant-only.yml | 2 - ..._variables_allow_uppercase_identifiers.yml | 6 --- galaxy.yml | 2 +- 12 files changed, 68 insertions(+), 32 deletions(-) delete mode 100644 changelogs/fragments/0_mysql_user_session_vars.yml delete mode 100644 changelogs/fragments/445_add_service_name_to_plugin_pam_auth_pam_usage.yml delete mode 100644 changelogs/fragments/465-display_more_informative_invalid_priv_exceptiion.yml delete mode 100644 changelogs/fragments/479_enable_auto_commit.yml delete mode 100644 changelogs/fragments/479_enable_auto_commit_part2.yml delete mode 100644 changelogs/fragments/491_fix_download_url.yaml delete mode 100644 changelogs/fragments/497_mysql_info_returns_connector_name_and_version.yml delete mode 100644 changelogs/fragments/503-fix-revoke-grant-only.yml delete mode 100644 changelogs/fragments/mysql_variables_allow_uppercase_identifiers.yml diff --git a/CHANGELOG.rst b/CHANGELOG.rst index cb5e2cd..720ea41 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,33 @@ Community MySQL Collection Release Notes This changelog describes changes after version 2.0.0. +v3.6.0 +====== + +Release Summary +--------------- + +This is the minor release of the ``community.mysql`` collection. +This changelog contains all changes to the modules and plugins in this collection +that have been made after the previous release. + +Minor Changes +------------- + +- mysql_info - add ``connector_name`` and ``connector_version`` to returned values (https://github.com/ansible-collections/community.mysql/pull/497). +- mysql_role - enable auto_commit to avoid MySQL metadata table lock (https://github.com/ansible-collections/community.mysql/issues/479). +- mysql_user - add plugin_auth_string as optional parameter to use a specific pam service if pam/auth_pam plugin is used (https://github.com/ansible-collections/community.mysql/pull/445). +- mysql_user - add the ``session_vars`` argument to set session variables at the beginning of module execution (https://github.com/ansible-collections/community.mysql/issues/478). +- mysql_user - display a more informative invalid privilege exception. Changes the exception handling of the granting permission logic to show the query executed , params and the exception message granting privileges fails` (https://github.com/ansible-collections/community.mysql/issues/465). +- mysql_user - enable auto_commit to avoid MySQL metadata table lock (https://github.com/ansible-collections/community.mysql/issues/479). +- setup_mysql - update MySQL tarball URL (https://github.com/ansible-collections/community.mysql/pull/491). + +Bugfixes +-------- + +- mysql_user - when revoke privs consists only of ``GRANT``, a 2nd revoke query is executed with empty privs to revoke that ended in an SQL exception (https://github.com/ansible-collections/community.mysql/pull/503). +- mysql_variables - add uppercase character pattern to regex to allow GLOBAL variables containing uppercase characters. This recognizes variable names used in Galera, for example, ``wsrep_OSU_method``, which breaks the normal pattern of all lowercase characters (https://github.com/ansible-collections/community.mysql/pull/501). + v3.5.1 ====== @@ -216,7 +243,7 @@ that have been added after the release of ``community.mysql`` 2.3.2. Breaking Changes / Porting Guide -------------------------------- -- mysql_replication - remove ``Is_Slave`` and ``Is_Master`` return values (were replaced with ``Is_Primary`` and ``Is_Replica`` (https://github.com/ansible-collections /community.mysql/issues/145). +- mysql_replication - remove ``Is_Slave`` and ``Is_Master`` return values (were replaced with ``Is_Primary`` and ``Is_Replica`` (https://github.com/ansible-collections/community.mysql/issues/145). - mysql_replication - remove the mode options values containing ``master``/``slave`` and the master_use_gtid option ``slave_pos`` (were replaced with corresponding ``primary``/``replica`` values) (https://github.com/ansible-collections/community.mysql/issues/145). - mysql_user - remove support for the `REQUIRESSL` special privilege as it has ben superseded by the `tls_requires` option (https://github.com/ansible-collections/community.mysql/discussions/121). - mysql_user - validate privileges using database engine directly (https://github.com/ansible-collections/community.mysql/issues/234 https://github.com/ansible-collections/community.mysql/pull/243). Do not validate privileges in this module anymore. diff --git a/changelogs/changelog.yaml b/changelogs/changelog.yaml index ce080f8..e272941 100644 --- a/changelogs/changelog.yaml +++ b/changelogs/changelog.yaml @@ -261,3 +261,42 @@ releases: - 3.5.1.yml - 438-fix-privilege-changing-everytime.yml release_date: '2022-09-09' + 3.6.0: + changes: + bugfixes: + - mysql_user - when revoke privs consists only of ``GRANT``, a 2nd revoke query + is executed with empty privs to revoke that ended in an SQL exception (https://github.com/ansible-collections/community.mysql/pull/503). + - mysql_variables - add uppercase character pattern to regex to allow GLOBAL + variables containing uppercase characters. This recognizes variable names + used in Galera, for example, ``wsrep_OSU_method``, which breaks the normal + pattern of all lowercase characters (https://github.com/ansible-collections/community.mysql/pull/501). + minor_changes: + - mysql_info - add ``connector_name`` and ``connector_version`` to returned + values (https://github.com/ansible-collections/community.mysql/pull/497). + - mysql_role - enable auto_commit to avoid MySQL metadata table lock (https://github.com/ansible-collections/community.mysql/issues/479). + - mysql_user - add plugin_auth_string as optional parameter to use a specific + pam service if pam/auth_pam plugin is used (https://github.com/ansible-collections/community.mysql/pull/445). + - mysql_user - add the ``session_vars`` argument to set session variables at + the beginning of module execution (https://github.com/ansible-collections/community.mysql/issues/478). + - mysql_user - display a more informative invalid privilege exception. Changes + the exception handling of the granting permission logic to show the query + executed , params and the exception message granting privileges fails` (https://github.com/ansible-collections/community.mysql/issues/465). + - mysql_user - enable auto_commit to avoid MySQL metadata table lock (https://github.com/ansible-collections/community.mysql/issues/479). + - setup_mysql - update MySQL tarball URL (https://github.com/ansible-collections/community.mysql/pull/491). + release_summary: 'This is the minor release of the ``community.mysql`` collection. + + This changelog contains all changes to the modules and plugins in this collection + + that have been made after the previous release.' + fragments: + - 0_mysql_user_session_vars.yml + - 3.6.0.yml + - 445_add_service_name_to_plugin_pam_auth_pam_usage.yml + - 465-display_more_informative_invalid_priv_exceptiion.yml + - 479_enable_auto_commit.yml + - 479_enable_auto_commit_part2.yml + - 491_fix_download_url.yaml + - 497_mysql_info_returns_connector_name_and_version.yml + - 503-fix-revoke-grant-only.yml + - mysql_variables_allow_uppercase_identifiers.yml + release_date: '2023-02-08' diff --git a/changelogs/fragments/0_mysql_user_session_vars.yml b/changelogs/fragments/0_mysql_user_session_vars.yml deleted file mode 100644 index 55bcd6c..0000000 --- a/changelogs/fragments/0_mysql_user_session_vars.yml +++ /dev/null @@ -1,2 +0,0 @@ -minor_changes: -- mysql_user - add the ``session_vars`` argument to set session variables at the beginning of module execution (https://github.com/ansible-collections/community.mysql/issues/478). diff --git a/changelogs/fragments/445_add_service_name_to_plugin_pam_auth_pam_usage.yml b/changelogs/fragments/445_add_service_name_to_plugin_pam_auth_pam_usage.yml deleted file mode 100644 index 2b9a523..0000000 --- a/changelogs/fragments/445_add_service_name_to_plugin_pam_auth_pam_usage.yml +++ /dev/null @@ -1,3 +0,0 @@ ---- -minor_changes: - - mysql_user - add plugin_auth_string as optional parameter to use a specific pam service if pam/auth_pam plugin is used (https://github.com/ansible-collections/community.mysql/pull/445). diff --git a/changelogs/fragments/465-display_more_informative_invalid_priv_exceptiion.yml b/changelogs/fragments/465-display_more_informative_invalid_priv_exceptiion.yml deleted file mode 100644 index fc47d37..0000000 --- a/changelogs/fragments/465-display_more_informative_invalid_priv_exceptiion.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -minor_changes: - - mysql_user - display a more informative invalid privilege exception. - Changes the exception handling of the granting permission logic to show the query executed , params - and the exception message granting privileges fails` (https://github.com/ansible-collections/community.mysql/issues/465). \ No newline at end of file diff --git a/changelogs/fragments/479_enable_auto_commit.yml b/changelogs/fragments/479_enable_auto_commit.yml deleted file mode 100644 index 5701f30..0000000 --- a/changelogs/fragments/479_enable_auto_commit.yml +++ /dev/null @@ -1,3 +0,0 @@ ---- -minor_changes: - - mysql_user - enable auto_commit to avoid MySQL metadata table lock (https://github.com/ansible-collections/community.mysql/issues/479). diff --git a/changelogs/fragments/479_enable_auto_commit_part2.yml b/changelogs/fragments/479_enable_auto_commit_part2.yml deleted file mode 100644 index a447acf..0000000 --- a/changelogs/fragments/479_enable_auto_commit_part2.yml +++ /dev/null @@ -1,3 +0,0 @@ ---- - minor_changes: - - mysql_role - enable auto_commit to avoid MySQL metadata table lock (https://github.com/ansible-collections/community.mysql/issues/479). diff --git a/changelogs/fragments/491_fix_download_url.yaml b/changelogs/fragments/491_fix_download_url.yaml deleted file mode 100644 index 27628bb..0000000 --- a/changelogs/fragments/491_fix_download_url.yaml +++ /dev/null @@ -1,3 +0,0 @@ ---- -minor_changes: - - setup_mysql - update MySQL tarball URL (https://github.com/ansible-collections/community.mysql/pull/491). diff --git a/changelogs/fragments/497_mysql_info_returns_connector_name_and_version.yml b/changelogs/fragments/497_mysql_info_returns_connector_name_and_version.yml deleted file mode 100644 index 11fc4f5..0000000 --- a/changelogs/fragments/497_mysql_info_returns_connector_name_and_version.yml +++ /dev/null @@ -1,3 +0,0 @@ ---- -minor_changes: - - mysql_info - add ``connector_name`` and ``connector_version`` to returned values (https://github.com/ansible-collections/community.mysql/pull/497). diff --git a/changelogs/fragments/503-fix-revoke-grant-only.yml b/changelogs/fragments/503-fix-revoke-grant-only.yml deleted file mode 100644 index 5de4d4b..0000000 --- a/changelogs/fragments/503-fix-revoke-grant-only.yml +++ /dev/null @@ -1,2 +0,0 @@ -bugfixes: - - mysql_user - when revoke privs consists only of ``GRANT``, a 2nd revoke query is executed with empty privs to revoke that ended in an SQL exception (https://github.com/ansible-collections/community.mysql/pull/503). \ No newline at end of file diff --git a/changelogs/fragments/mysql_variables_allow_uppercase_identifiers.yml b/changelogs/fragments/mysql_variables_allow_uppercase_identifiers.yml deleted file mode 100644 index 0d35467..0000000 --- a/changelogs/fragments/mysql_variables_allow_uppercase_identifiers.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -bugfixes: - - mysql_variables - add uppercase character pattern to regex to allow GLOBAL - variables containing uppercase characters. - This recognizes variable names used in Galera, for example, ``wsrep_OSU_method``, - which breaks the normal pattern of all lowercase characters (https://github.com/ansible-collections/community.mysql/pull/501). diff --git a/galaxy.yml b/galaxy.yml index 733762d..bb7e2be 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -1,6 +1,6 @@ namespace: community name: mysql -version: 3.5.1 +version: 3.6.0 readme: README.md authors: - Ansible community From 2f151dc8f43f58e026849cdec7c28e84ff92b3d4 Mon Sep 17 00:00:00 2001 From: Daniel Ziegenberg Date: Fri, 10 Feb 2023 09:47:12 +0100 Subject: [PATCH 135/217] change deprecated parameter pw and db (#177) * change deprecated parameter pw to password * change deprecated parameter db to database * add changelog fragment * Old plugin versions are no longer supported * Use packaging version checking. * Use stdlib version comparison * Use parse_version from setuptools * Revert to tuple/reduce version check --------- Co-authored-by: Jorge-Rodriguez --- .../fragments/177-change_deprecated_connection_parameters.yml | 2 ++ plugins/module_utils/mysql.py | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 changelogs/fragments/177-change_deprecated_connection_parameters.yml diff --git a/changelogs/fragments/177-change_deprecated_connection_parameters.yml b/changelogs/fragments/177-change_deprecated_connection_parameters.yml new file mode 100644 index 0000000..3c9e088 --- /dev/null +++ b/changelogs/fragments/177-change_deprecated_connection_parameters.yml @@ -0,0 +1,2 @@ +minor_changes: +- mysql module utils - change deprecated connection parameters ``passwd`` and ``db`` to ``password`` and ``database`` (https://github.com/ansible-collections/community.mysql/pull/177). \ No newline at end of file diff --git a/plugins/module_utils/mysql.py b/plugins/module_utils/mysql.py index 2cafcb6..6aeebe5 100644 --- a/plugins/module_utils/mysql.py +++ b/plugins/module_utils/mysql.py @@ -122,7 +122,7 @@ def mysql_connect(module, login_user=None, login_password=None, config_file='', if login_user is not None: config['user'] = login_user if login_password is not None: - config['passwd'] = login_password + config['password'] = login_password if ssl_cert is not None: config['ssl']['cert'] = ssl_cert if ssl_key is not None: @@ -130,7 +130,7 @@ def mysql_connect(module, login_user=None, login_password=None, config_file='', if ssl_ca is not None: config['ssl']['ca'] = ssl_ca if db is not None: - config['db'] = db + config['database'] = db if connect_timeout is not None: config['connect_timeout'] = connect_timeout if check_hostname is not None: From 9b8455c2e66aebd2e0adeb544450749876d7537d Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Tue, 14 Feb 2023 09:57:44 +0100 Subject: [PATCH 136/217] Fix sanity (#508) * Fix sanity * Remove as unnecessary --- plugins/modules/mysql_replication.py | 1 - tests/sanity/ignore-2.10.txt | 8 -------- tests/sanity/ignore-2.11.txt | 8 -------- tests/sanity/ignore-2.15.txt | 2 ++ tests/sanity/ignore-2.9.txt | 3 --- tests/unit/plugins/module_utils/test_mysql_user.py | 5 ----- 6 files changed, 2 insertions(+), 25 deletions(-) delete mode 100644 tests/sanity/ignore-2.10.txt delete mode 100644 tests/sanity/ignore-2.11.txt delete mode 100644 tests/sanity/ignore-2.9.txt diff --git a/plugins/modules/mysql_replication.py b/plugins/modules/mysql_replication.py index 5d1a0e5..33e14bc 100644 --- a/plugins/modules/mysql_replication.py +++ b/plugins/modules/mysql_replication.py @@ -292,7 +292,6 @@ from ansible_collections.community.mysql.plugins.module_utils.mysql import ( mysql_common_argument_spec, ) from ansible.module_utils._text import to_native -from ansible_collections.community.mysql.plugins.module_utils.version import LooseVersion executed_queries = [] diff --git a/tests/sanity/ignore-2.10.txt b/tests/sanity/ignore-2.10.txt deleted file mode 100644 index c0323af..0000000 --- a/tests/sanity/ignore-2.10.txt +++ /dev/null @@ -1,8 +0,0 @@ -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_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_variables.py validate-modules:doc-required-mismatch diff --git a/tests/sanity/ignore-2.11.txt b/tests/sanity/ignore-2.11.txt deleted file mode 100644 index c0323af..0000000 --- a/tests/sanity/ignore-2.11.txt +++ /dev/null @@ -1,8 +0,0 @@ -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_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_variables.py validate-modules:doc-required-mismatch diff --git a/tests/sanity/ignore-2.15.txt b/tests/sanity/ignore-2.15.txt index c0323af..da0354c 100644 --- a/tests/sanity/ignore-2.15.txt +++ b/tests/sanity/ignore-2.15.txt @@ -6,3 +6,5 @@ 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_variables.py validate-modules:doc-required-mismatch +plugins/module_utils/mysql.py pylint:unused-import +plugins/module_utils/version.py pylint:unused-import diff --git a/tests/sanity/ignore-2.9.txt b/tests/sanity/ignore-2.9.txt deleted file mode 100644 index dabd55d..0000000 --- a/tests/sanity/ignore-2.9.txt +++ /dev/null @@ -1,3 +0,0 @@ -plugins/modules/mysql_db.py validate-modules:use-run-command-not-popen -plugins/modules/mysql_user.py validate-modules:parameter-type-not-in-doc -plugins/modules/mysql_user.py validate-modules:undocumented-parameter diff --git a/tests/unit/plugins/module_utils/test_mysql_user.py b/tests/unit/plugins/module_utils/test_mysql_user.py index f0a7b32..46b3b8e 100644 --- a/tests/unit/plugins/module_utils/test_mysql_user.py +++ b/tests/unit/plugins/module_utils/test_mysql_user.py @@ -4,10 +4,6 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type import pytest -try: - from unittest.mock import MagicMock -except ImportError: - from mock import MagicMock from ansible_collections.community.mysql.plugins.module_utils.user import ( handle_grant_on_col, @@ -15,7 +11,6 @@ from ansible_collections.community.mysql.plugins.module_utils.user import ( normalize_col_grants, sort_column_order ) -from ..utils import dummy_cursor_class @pytest.mark.parametrize( From 6970aef8f61373e9f85cb6f251b3b44decb7c496 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20Inderm=C3=BChle?= Date: Tue, 21 Mar 2023 08:16:09 +0100 Subject: [PATCH 137/217] Integrations tests : Use containers for more control and verify that versions match expectation (#490) * Draft: Add a mariadb container * Add playbook to test connection to the server * Add healthcheck to MariaDB before starting the tests This prevent the first test to fail because the db isn't ready yet. * Add default file for root necessary since using venv instead of docker * Add % instead of the default 'localhost' since we use remote connection Previously, everything was on localhost. Now ansible-test is in a venv and the db is in a container. The db see the IP address from the podman host (10.88.0.2) * Add ansible-test integration inventory to .gitignore * Revert to old workflow to use ansible-test --venv It seams that that ansible-test-gh-action doesn't handle this option: https://github.com/ansible-community/ansible-test-gh-action/blob/main/action.yml#L483-L497 * Cut target filtering * Fix comparison We are not logged in as 127.0.0.1 anymore, but 10.88... as I couldn't test this easily, I decided to simplify the test. * Add path to default-file /root doesn't exist with --venv * Fix workflow unknown option container_name * Attempt GHA communication between container using "docker host network" https://docs.github.com/en/actions/using-containerized-services/about-service-containers I re-revert the workflow to use the new custom action. But I'm not sure it will works because I don't know how the container for ansible-test is started and if it will have access to the services containers. * Cut anchors currently unsupported by GHA * Disable healthcheck I want to first prove that this setup is possible before adding safety * Disable sanity, units and matrix to speed up tests in GHA * Further disable tests to speed up * Add mysql_client to the controller * Install mysql_client the correct way * Fix package name and missing apt cache * Prepare controller with Podman/Docker Network We use the Podman/Docker network gateway address to communicate between container. I haven't tested Docker. I would have preferred to use a pod but only Podman support it and ansible-test only support the --docker-network option. * Swap MariaDB with MySQL * De-duplicate the mysql_command alias * Generalize mysql and mariadb version based on container name This way we can split db_engine and db_version and simplify tests. Also this is mandatory to use the matrix.db_engine_version as the image name for our services containers. * Cut docker healthcheck unsupported by GHA * Fix replication server_id already in use * Add static test with replication containers * Fix database not selected * Fix replication due to usage of gateway_addr instead of localhost * Simplify version computation * Linting * Refactor setup_mysql into setup_controller * Fix test_mysql_role * Fix server_id in GHA GHA lack a way to pass option to docker's command. Also server_id is not read as a environment variable. So I'm forced to use a config file. * Add back a package to connect to MySQL 8+ * Linting * Refactor test_mysql_user to work with other host than localhost * Refactor way tests info are passed from sed to file with lookup The idea is to avoid modifying test targets from the workflow to prevent ansible-test to think every tests needs to be run. * Fix missing var * Refactor test to use the db_version from setup_controller * Add temporary files to .gitignore * Fix volume path * Fix volume path by adding a final / * Fix volume path using $(pwd) * Fix volume path using github.workspace var * Cut files from gitignore because it prevents ansible-test to copy them * Fix pre-test-cmd missing separators * Cut the newline added by lookup 'file' * Fix tailing newline by not created it in the first place * Disable tests to concentrate on the \n and quote issue with my files * Fix trailing newline and quote in db_engine_version * Re-enable integration tests to validate db_engine_version is fixed * lint * Cut unused file * Fix pre-test-cmd paste in wrong context * Re-enable service containers * Add back docker healthcheck on services I saw in the GHA logs that it perform an healtcheck ! So I hope this will work. * Add tmate to debug the server_id in replicas * Attempt to fix "invalid syntax" * Enclose command in quotes * Refactor the way server_id is set for replicas The simple way is to add '--server-id 2' after the name of the image of the container. But GHA doesn't let us do that. The idea of mount a file from our repo doesn't work because the repo is check out later in the workflow and I failed to find a pre-job hook. Then I realized that this MySQL option is dynamic! So we will set that in the test target! * Re-activate all tests * Cut useless task * Use same variable as other target for consistency * Linting * Update version tested * Add options to the makefile * Add same variables as other target for consistency * Add IF NOT EXISTS to prevent misleading error on retry * Cut python 3.11 not supported by ansible-test yet * Attempt to set log-bin into docker * Reformat for readability * Document that full version is mandatory * Fix newline * Github complain it doesn't find python 3.1 !!! * Add option to run only a single target * Fix mysqlclient not supporting Python 3.9 * Enhance installation of mysql_client Initially I wanted to install mysql-client-5.7 to test mysql server 5.7 but this package is not available for Ubuntu 18+. I keep those changes because it allow us to specify the name of the package based on the Ubuntu version. * Linting * Add unique name to simplify debugging * Fix mysql_dump for MySQL 5.7 and MariaDB when using mysqldump 8 * Add unique name to simplify debugging * Deduplicate tasks * Lining * Add python script to recreate the test matrix from github workflow file * Fix dump with mysqldump 8 against mysql 5.7 * Disable test for replication with chanel for mysql 5.7 * Add better task name * Fix exclusion function * Disable replication with channel tests entirely for MySQL 5.7 * Activate Mysql 8 and Mariadb into GitHub Action Workflow * Cut Ansible since we can't change what the user have on his computer * Add running make command for all tests of the matrix * Add unique test names * Document run_all_tests.py * Add unique test names * Add tmate to experiment with docker healthcheck * Fix replication settings sh don't know 'echo -e', so we use bash instead. Also, we need to wait for the container to be healthy before trying to restart it. Otherwise that could corrupt it. * Add TODO verify that the version of mysql/mariadb is correct * Add more descriptive tests names * Use mysql_host var name instead of gateway_addr in tests * Refactor user@ into user@% * Fix healthcheck in GHA * Disable tests that fails only on MariaDB * Refactor to remove useless variables * Workaround for plugin role that fails with any MariaDB versions * Fix Python 3.10 beein run as 3.1 * Ensure replicas are healthy before rebooting them * Enable all tests * Add a virtualenv for ansible-test used locally * Simplify connector_name variables * Add PoC using custom ansible-test containers * Fix docker_container variable name * Cut forgotten comment * Fix error when using local registry by using quay.io * Change tag of test-containers to latest * Fix ansible-test unknown option I copied blindly https://github.com/ansible-collections/community.sops/blob/main/.github/workflows/ansible-test.yml#L195 and forgot what ansible-test was expecting * Cut column-statistics disabling Thanks to our test-container, we now use the correspond mysql-client. So to test mysql 5.7 we use mysql-client-5.7 and to test mysql 8 we use mysql-client-8. * Add manual test matrix (MariaDB 10.6, 10.7 and 10.8 missing) * Fix test matrix Python version should be quoted, otherwise 3.10 become 3.1 We can skip 2.14 and devel with Python3.8 We can skip devel with Python 3.9 We can skip MariaDB 10.4 with mysql-client-10.6 Add tests for MariaDB 10.6, 10.7 and 10.8 * Reduce number of tests and adapt containers images * Fix queries for roles * Add filter for issues resolved in newer version of mysqlclient * Add names to tests * Fix assertion for mariadb * Linting * Cut tests for incompatible MySQL 8 and pymysql 0.7.11 * Fix assertion for older mysqlclient than 2.0.1 with mysql (mariadb ok) * Cut playbook that are now handled by the test-containers * Change timeout from 10 to 30 seconds to let mysql/mariadb restart * Add connector information to the returned values I need to know what python library was used. I had a container with both mysqlclient and pymysql installed and tests used a different connector that what is advertised by the title of integration tests. We need to prevent that otherwise our tests are worth nothing. * Add a verify stage at setup of test to assert all version are correct * Attempt to build and publish an image on ghcr.io * Add latest release of actions and with a context * Add trigger on workflow file edit * Fix env not recognized in the 'on' clause * Add latest tag * Fix insufficient context * Add missing slash * Cut addition of tag 'latest' as GHA does it automatically * Add ghcr.io image for mariadb10.3 python3.8 mysqlclient2.0.1 * Change docker-image workflow to work on all images using matrix * Fix workflow title * Add support for version of mysqlclient * Fix context path * Workaround failed to push ghcr.io Error was: failed to copy: io: read/write on closed pipe * Add back all tests using ghcr.io images * Cut unused images * Fix verify database version Sometimes, version_full contains trailing information (-log). To prevent issues it's best to concatenate major and minor version. * Fix verify for mysqlclient second name MySQLdb * Rename variable for consistency * Fix container name * Add tag 'latest' to images * Cut filter for tests now that the right connector is used * Fix test of mysql/mariadb version in use * Fix python version lookup * Add clean up in "always" phase of the block Because our tests use --retry-on-error, and the first thing the test does is to try to create the database. We must cleanup otherwise if there is a retry, it will throw a misleading "database already exists" error. * Document TODO * Disable tests using pymysql 1.0.2 Many tests are failing but this must be fixed in the plugins in a future PR. * Cut test MySQL 8 with incompatible pymysql 0.7.11 It fails to connect with error about cryptography unsupported * Fix dict key lookup * Fix indentation * Cut tests that was excluded in previous matrix * Enable back sanity and unit tests * Refactor get_driver_version to display name while passing sanity tests * Fix variable name * Fix missing cffi package to connect to MySQL 8 using Python 3.9 * Fix image not found * Split Docker image workflow to rebuild only changed Dockerfile My goal is not to save the planet but to make it work. Currently docker/setup-buildx-action@v2 often fails. You have to rerun the workflow multiple times until it succeed. When you do that with the matrix with 15 containers, you never get to the point where they all built successfully. Having separate workflows makes rerun the failing build easier. * Fix verify ansible 'devel' for which the version is unknown Today 'devel' means 2.15, but in the future it will be something else. * Fix ansible version extraction for "devel" * Cut matrix from when build was done in a single workflow * Document fix container name * Add bold * Add option to let containers alive at end of testing * Enhance error handling and doc of get_driver_name and get_driver_version * Migrate tests documentations in their own file * Skip retry-on-error by default and add option to activate it on demand * Rename folder to better purpose * Enable back push and schedule workflow * Rename registry from fork to upstream * Cut Docker Image workflow's filter for branch from my fork * Add changelog fragment * Update supported versions * Rename file for clarity * Cut mariadb non long term releases * Add '-client' to the block title to better explain what it is * Update readme for tested versions of long term release of MariaDB * Attempt to add the workflow to the Action tab * Second attempt to add the workflow to the Action tab * Cut folder re-created by merge from main * Cut filter by branch GHA will build the image using the branch name as tag. So we can safely remove this filter. * Cut changelog item done in #497 * Attempt to fix upload of image under c.mysql instead of my fork * Add debug to buildkitd * Bump setup-buildy-action to latest * Cut dot in image name in attempt to fix buildx bad request 400 error * Sanitize the repository name using metadata-action https://github.com/docker/build-push-action/blob/master/TROUBLESHOOTING.md#repository-name-must-be-lowercase * Document why we use optional checkout action * Cut debugging from setup-buildx-action * Fix workflow to work both on fork and c.mysql repository * Use apt-get instead of apt that not have a stable CLI interface * Use apt-get instead of apt * update docker image path to my personal repo I'm unable to publish under community.mysql. Either it's the dot in the name or I do something wrong with the GITHUB_TOKEN, but we need to test my PR, so I'll use docker images from my fork for now. * Fix test after merge of PR497 * Enhance testing documentation header * Fix installation of ansible venv ansible-test is included in ansible package. Also, on Fedora 37 with python 3.11, pip is missing. By using ensurepip we solve that issue. * Document usage of continue_on_errors * Fix versions used in examples * Add support for systems with unsupported python set as default * Fix cleanup task * Fix variable assignation to the include task * Add forgotten variable to handle unsupported python version * Fix user site-packages not visible in virtualenv * Fix test connection to the database and tasks names * Add create podman network for system missing it. We saw that on a Fedora 33 with Podman 3.3.1, an old system. I didn't find in which release the default network changed and maybe it's defined in the Linux distribution. So in doubt I always attempt to create the network. * Add full path to image to prevent podman asking which registry to use * Add options to enforce recreate containers even if already exists * Reformat command multiline to oneline * Add deletion of anonymous volumes associated with the container * Comment unused variable * Change shebang from python to python3 to avoid confusion with python2 This script is a python3 script. * Add disk and RAM requirements * Cut the 3 from python command to follow shebang recommendations https://docs.ansible.com/ansible-core/devel/dev_guide/testing/sanity/shebang.html * Reformat spelling Co-authored-by: Jorge Rodriguez (A.K.A. Tiriel) * Reformat file path Co-authored-by: Jorge Rodriguez (A.K.A. Tiriel) * Fix link URI Co-authored-by: Jorge Rodriguez (A.K.A. Tiriel) * Fix link URI Co-authored-by: Jorge Rodriguez (A.K.A. Tiriel) * Lint Co-authored-by: Jorge Rodriguez (A.K.A. Tiriel) * Lint Co-authored-by: Jorge Rodriguez (A.K.A. Tiriel) * Add better task name Co-authored-by: Jorge Rodriguez (A.K.A. Tiriel) * Move utility task files in their own folder * Refactor using reusable GHA workflows * Fix path to called workflow file * Fix path to use local workflow * Fix cannot specify version when calling local workflows * Attempt to use a fixed repo name in the image name My last attempts produced duplicates images under my name + repo name: laurent-indermuehle/community.mysql. Previously I had only my name. And none of the above are what we want. We want only community.mysql in the image name... * Add called workflow file in the GHA hooks Without this, the containers are not rebuilt when you modify the file built-docker-image.yml. * Rollback to github.repository in container image name This time I think I understood. We publish in the github.repository_owner's namespace. In my case it's laurent-indermuehle and in case of upstream it's ansible-collection. A proof of that: https://github.com/orgs/ansible-collections/packages <- here there is one attempt I did in february to push my branch to the upstream. So, our tests containers will be visible to the whole community, not just community.mysql. --------- Co-authored-by: Jorge Rodriguez (A.K.A. Tiriel) --- .github/workflows/ansible-test-plugins.yml | 379 ++- .github/workflows/ansible-test-roles.yml | 1 + .github/workflows/build-docker-image.yml | 67 + ...r-image-mariadb103-py38-mysqlclient201.yml | 19 + ...ocker-image-mariadb103-py38-pymysql093.yml | 19 + ...r-image-mariadb103-py39-mysqlclient203.yml | 19 + ...ocker-image-mariadb103-py39-pymysql093.yml | 19 + ...-image-mariadb106-py310-mysqlclient211.yml | 19 + ...cker-image-mariadb106-py310-pymysql102.yml | 19 + .../docker-image-my57-py38-mysqlclient201.yml | 19 + .../docker-image-my57-py38-pymysql0711.yml | 19 + .../docker-image-my57-py38-pymysql093.yml | 19 + ...docker-image-my80-py310-mysqlclient211.yml | 19 + .../docker-image-my80-py310-pymysql102.yml | 19 + .../docker-image-my80-py38-mysqlclient201.yml | 19 + .../docker-image-my80-py38-pymysql093.yml | 19 + .../docker-image-my80-py39-mysqlclient203.yml | 19 + .../docker-image-my80-py39-pymysql093.yml | 19 + .gitignore | 1 + Makefile | 80 + README.md | 21 +- TESTING.md | 87 + .../490_refactor_integration_tests.yml | 6 + run_all_tests.py | 86 + .../mariadb103-py38-mysqlclient201/Dockerfile | 21 + .../mariadb103-py38-pymysql093/Dockerfile | 15 + .../mariadb103-py39-mysqlclient203/Dockerfile | 21 + .../mariadb103-py39-pymysql093/Dockerfile | 15 + .../Dockerfile | 21 + .../mariadb106-py310-pymysql102/Dockerfile | 15 + .../my57-py38-mysqlclient201/Dockerfile | 21 + .../my57-py38-pymysql0711/Dockerfile | 21 + .../my57-py38-pymysql093/Dockerfile | 15 + .../my80-py310-mysqlclient211/Dockerfile | 21 + .../my80-py310-pymysql102/Dockerfile | 15 + .../my80-py38-mysqlclient201/Dockerfile | 21 + .../my80-py38-pymysql093/Dockerfile | 15 + .../my80-py39-mysqlclient203/Dockerfile | 21 + .../my80-py39-pymysql093/Dockerfile | 16 + .../old_mariadb_replication/defaults/main.yml | 2 + .../tasks/mariadb_master_use_gtid.yml | 36 +- .../mariadb_replication_connection_name.yml | 22 +- .../tasks/mariadb_replication_initial.yml | 20 +- .../setup_controller/tasks/fake_root.yml | 11 + .../targets/setup_controller/tasks/main.yml | 18 + .../setup_controller/tasks/setvars.yml | 69 + .../targets/setup_controller/tasks/verify.yml | 59 + .../targets/setup_mysql/defaults/main.yml | 18 - .../targets/setup_mysql/handlers/main.yml | 8 - .../targets/setup_mysql/tasks/config.yml | 15 - .../targets/setup_mysql/tasks/dir.yml | 11 - .../targets/setup_mysql/tasks/install.yml | 90 - .../targets/setup_mysql/tasks/main.yml | 21 - .../targets/setup_mysql/tasks/setvars.yml | 33 - .../targets/setup_mysql/tasks/verify.yml | 27 - .../setup_mysql/templates/installed_file.j2 | 1 - .../targets/setup_mysql/vars/main.yml | 30 - .../targets/test_mysql_db/defaults/main.yml | 1 + .../targets/test_mysql_db/meta/main.yml | 2 +- .../tasks/config_overrides_defaults.yml | 73 +- .../tasks/encoding_dump_import.yml | 52 +- .../targets/test_mysql_db/tasks/issue-28.yml | 16 +- .../tasks/issue_256_mysqldump_errors.yml | 3 +- .../targets/test_mysql_db/tasks/main.yml | 5 +- .../tasks/multi_db_create_delete.yml | 45 +- .../test_mysql_db/tasks/state_dump_import.yml | 219 +- .../tasks/state_present_absent.yml | 108 +- .../targets/test_mysql_info/defaults/main.yml | 2 +- .../targets/test_mysql_info/meta/main.yml | 3 +- .../test_mysql_info/tasks/connector_info.yml | 6 +- .../test_mysql_info/tasks/issue-28.yml | 19 +- .../targets/test_mysql_info/tasks/main.yml | 28 +- .../test_mysql_query/defaults/main.yml | 1 + .../targets/test_mysql_query/meta/main.yml | 3 +- .../test_mysql_query/tasks/issue-28.yml | 19 +- .../tasks/mysql_query_initial.yml | 99 +- .../test_mysql_replication/defaults/main.yml | 2 +- .../test_mysql_replication/meta/main.yml | 2 +- .../tasks/issue-265.yml | 32 +- .../test_mysql_replication/tasks/issue-28.yml | 18 +- .../test_mysql_replication/tasks/main.yml | 3 +- .../tasks/mysql_replication_channel.yml | 3 +- .../tasks/mysql_replication_initial.yml | 175 +- .../tasks/mysql_replication_primary_delay.yml | 2 +- .../mysql_replication_resetprimary_mode.yml | 2 +- .../targets/test_mysql_role/defaults/main.yml | 17 +- .../targets/test_mysql_role/meta/main.yml | 3 +- .../targets/test_mysql_role/tasks/main.yml | 11 +- .../tasks/mysql_role_initial.yml | 2227 +++++++++-------- .../tasks/test_priv_subtract.yml | 32 +- .../targets/test_mysql_user/defaults/main.yml | 2 +- .../targets/test_mysql_user/meta/main.yml | 3 +- .../test_mysql_user/tasks/assert_no_user.yml | 25 - .../test_mysql_user/tasks/assert_user.yml | 38 - .../test_mysql_user/tasks/create_user.yml | 46 - .../test_mysql_user/tasks/issue-121.yml | 48 +- .../test_mysql_user/tasks/issue-265.yml | 71 +- .../test_mysql_user/tasks/issue-28.yml | 51 +- .../test_mysql_user/tasks/issue-29511.yaml | 30 +- .../test_mysql_user/tasks/issue-64560.yaml | 19 +- .../targets/test_mysql_user/tasks/main.yml | 152 +- .../test_mysql_user/tasks/remove_user.yml | 74 - .../tasks/test_idempotency.yml | 84 + .../tasks/test_priv_append.yml | 57 +- .../test_mysql_user/tasks/test_priv_dict.yml | 80 +- .../tasks/test_priv_subtract.yml | 76 +- .../test_mysql_user/tasks/test_privs.yml | 102 +- .../tasks/test_privs_issue_465.yml | 6 +- ...ce_limits.yml => test_resource_limits.yml} | 54 +- ...y_grant.yml => test_revoke_only_grant.yml} | 30 +- ...irements.yml => test_tls_requirements.yml} | 98 +- .../tasks/test_update_password.yml | 15 +- .../test_user_grants_with_roles_applied.yml | 53 +- .../tasks/test_user_password.yml | 110 +- .../tasks/test_user_plugin_auth.yml | 233 +- .../tasks/utils/assert_no_user.yml | 8 + .../tasks/utils/assert_user.yml | 21 + .../{ => utils}/assert_user_password.yml | 12 +- .../tasks/utils/create_user.yml | 12 + .../tasks/utils/remove_user.yml | 12 + .../test_mysql_variables/defaults/main.yml | 1 + .../test_mysql_variables/meta/main.yml | 3 +- .../test_mysql_variables/tasks/assert_var.yml | 7 +- .../test_mysql_variables/tasks/issue-28.yml | 16 +- .../tasks/mysql_variables.yml | 23 +- tests/integration/test_connection.yml | 81 + 126 files changed, 3942 insertions(+), 2822 deletions(-) create mode 100644 .github/workflows/build-docker-image.yml create mode 100644 .github/workflows/docker-image-mariadb103-py38-mysqlclient201.yml create mode 100644 .github/workflows/docker-image-mariadb103-py38-pymysql093.yml create mode 100644 .github/workflows/docker-image-mariadb103-py39-mysqlclient203.yml create mode 100644 .github/workflows/docker-image-mariadb103-py39-pymysql093.yml create mode 100644 .github/workflows/docker-image-mariadb106-py310-mysqlclient211.yml create mode 100644 .github/workflows/docker-image-mariadb106-py310-pymysql102.yml create mode 100644 .github/workflows/docker-image-my57-py38-mysqlclient201.yml create mode 100644 .github/workflows/docker-image-my57-py38-pymysql0711.yml create mode 100644 .github/workflows/docker-image-my57-py38-pymysql093.yml create mode 100644 .github/workflows/docker-image-my80-py310-mysqlclient211.yml create mode 100644 .github/workflows/docker-image-my80-py310-pymysql102.yml create mode 100644 .github/workflows/docker-image-my80-py38-mysqlclient201.yml create mode 100644 .github/workflows/docker-image-my80-py38-pymysql093.yml create mode 100644 .github/workflows/docker-image-my80-py39-mysqlclient203.yml create mode 100644 .github/workflows/docker-image-my80-py39-pymysql093.yml create mode 100644 Makefile create mode 100644 TESTING.md create mode 100644 changelogs/fragments/490_refactor_integration_tests.yml create mode 100755 run_all_tests.py create mode 100644 test-containers/mariadb103-py38-mysqlclient201/Dockerfile create mode 100644 test-containers/mariadb103-py38-pymysql093/Dockerfile create mode 100644 test-containers/mariadb103-py39-mysqlclient203/Dockerfile create mode 100644 test-containers/mariadb103-py39-pymysql093/Dockerfile create mode 100644 test-containers/mariadb106-py310-mysqlclient211/Dockerfile create mode 100644 test-containers/mariadb106-py310-pymysql102/Dockerfile create mode 100644 test-containers/my57-py38-mysqlclient201/Dockerfile create mode 100644 test-containers/my57-py38-pymysql0711/Dockerfile create mode 100644 test-containers/my57-py38-pymysql093/Dockerfile create mode 100644 test-containers/my80-py310-mysqlclient211/Dockerfile create mode 100644 test-containers/my80-py310-pymysql102/Dockerfile create mode 100644 test-containers/my80-py38-mysqlclient201/Dockerfile create mode 100644 test-containers/my80-py38-pymysql093/Dockerfile create mode 100644 test-containers/my80-py39-mysqlclient203/Dockerfile create mode 100644 test-containers/my80-py39-pymysql093/Dockerfile create mode 100644 tests/integration/targets/setup_controller/tasks/fake_root.yml create mode 100644 tests/integration/targets/setup_controller/tasks/main.yml create mode 100644 tests/integration/targets/setup_controller/tasks/setvars.yml create mode 100644 tests/integration/targets/setup_controller/tasks/verify.yml delete mode 100644 tests/integration/targets/setup_mysql/defaults/main.yml delete mode 100644 tests/integration/targets/setup_mysql/handlers/main.yml delete mode 100644 tests/integration/targets/setup_mysql/tasks/config.yml delete mode 100644 tests/integration/targets/setup_mysql/tasks/dir.yml delete mode 100644 tests/integration/targets/setup_mysql/tasks/install.yml delete mode 100644 tests/integration/targets/setup_mysql/tasks/main.yml delete mode 100644 tests/integration/targets/setup_mysql/tasks/setvars.yml delete mode 100644 tests/integration/targets/setup_mysql/tasks/verify.yml delete mode 100644 tests/integration/targets/setup_mysql/templates/installed_file.j2 delete mode 100644 tests/integration/targets/setup_mysql/vars/main.yml delete mode 100644 tests/integration/targets/test_mysql_user/tasks/assert_no_user.yml delete mode 100644 tests/integration/targets/test_mysql_user/tasks/assert_user.yml delete mode 100644 tests/integration/targets/test_mysql_user/tasks/create_user.yml delete mode 100644 tests/integration/targets/test_mysql_user/tasks/remove_user.yml create mode 100644 tests/integration/targets/test_mysql_user/tasks/test_idempotency.yml rename tests/integration/targets/test_mysql_user/tasks/{resource_limits.yml => test_resource_limits.yml} (60%) rename tests/integration/targets/test_mysql_user/tasks/{revoke_only_grant.yml => test_revoke_only_grant.yml} (61%) rename tests/integration/targets/test_mysql_user/tasks/{tls_requirements.yml => test_tls_requirements.yml} (57%) create mode 100644 tests/integration/targets/test_mysql_user/tasks/utils/assert_no_user.yml create mode 100644 tests/integration/targets/test_mysql_user/tasks/utils/assert_user.yml rename tests/integration/targets/test_mysql_user/tasks/{ => utils}/assert_user_password.yml (73%) create mode 100644 tests/integration/targets/test_mysql_user/tasks/utils/create_user.yml create mode 100644 tests/integration/targets/test_mysql_user/tasks/utils/remove_user.yml create mode 100644 tests/integration/test_connection.yml diff --git a/.github/workflows/ansible-test-plugins.yml b/.github/workflows/ansible-test-plugins.yml index ea6ae8e..5aeee56 100644 --- a/.github/workflows/ansible-test-plugins.yml +++ b/.github/workflows/ansible-test-plugins.yml @@ -1,3 +1,4 @@ +--- name: Plugins CI on: push: @@ -14,10 +15,6 @@ on: - cron: '0 6 * * *' -env: - mysql_version_file: "tests/integration/targets/setup_mysql/defaults/main.yml" - connector_version_file: "tests/integration/targets/setup_mysql/vars/main.yml" - jobs: sanity: name: "Sanity (Ansible: ${{ matrix.ansible }})" @@ -43,47 +40,312 @@ jobs: strategy: fail-fast: false matrix: - db_engine_version: - - mysql_5.7.31 - - mysql_8.0.22 - - mariadb_10.3.34 - - mariadb_10.8.3 - ansible: - - stable-2.12 - - stable-2.13 - - stable-2.14 - - devel - python: - - 3.6 - - 3.8 - - 3.9 - connector: - - pymysql==0.7.10 - - pymysql==0.9.3 - - mysqlclient==2.0.1 - exclude: - - db_engine_version: mysql_8.0.22 - connector: pymysql==0.7.10 - - db_engine_version: mariadb_10.8.3 - connector: pymysql==0.7.10 - - python: 3.6 - ansible: stable-2.12 - - python: 3.6 - ansible: stable-2.13 - - python: 3.6 - ansible: stable-2.14 - - python: 3.6 - ansible: devel - - python: 3.8 - ansible: stable-2.13 - - python: 3.8 - ansible: stable-2.14 - - python: 3.8 - ansible: devel - - python: 3.9 - ansible: stable-2.12 + include: + # Before we can activate test with pymysql 1.0.2 we should debug the + # following plugins: + # + # mysql_query: + # test "Assert that create table IF NOT EXISTS is not changed with pymysql" failed + # + # mysql_replication: + # test "Assert that startreplica is not changed" failed + + # ================================================================== + # mysql-client 5.7 + Python 3.8 + # ================================================================== + - ansible: stable-2.12 + db_engine_version: mysql:5.7.40 + python: '3.8' + connector: pymysql==0.7.11 + docker_image: ghcr.io/laurent-indermuehle/test-container-my57-py38-pymysql0711:latest + - ansible: stable-2.12 + db_engine_version: mysql:5.7.40 + python: '3.8' + connector: pymysql==0.9.3 + docker_image: ghcr.io/laurent-indermuehle/test-container-my57-py38-pymysql093:latest + - ansible: stable-2.12 + db_engine_version: mysql:5.7.40 + python: '3.8' + connector: mysqlclient==2.0.1 + docker_image: ghcr.io/laurent-indermuehle/test-container-my57-py38-mysqlclient201:latest + + + # ================================================================== + # mysql-client 8 + Python 3.8 + # ================================================================== + - ansible: stable-2.12 + db_engine_version: mysql:8.0.31 + python: '3.8' + connector: pymysql==0.9.3 + docker_image: ghcr.io/laurent-indermuehle/test-container-my80-py38-pymysql093:latest + - ansible: stable-2.12 + db_engine_version: mysql:8.0.31 + python: '3.8' + connector: mysqlclient==2.0.1 + docker_image: ghcr.io/laurent-indermuehle/test-container-my80-py38-mysqlclient201:latest + + + # ================================================================== + # mysql-client 8 + Python 3.9 + # ================================================================== + - ansible: stable-2.13 + db_engine_version: mysql:8.0.31 + python: '3.9' + connector: pymysql==0.9.3 + docker_image: ghcr.io/laurent-indermuehle/test-container-my80-py39-pymysql093:latest + - ansible: stable-2.13 + db_engine_version: mysql:8.0.31 + python: '3.9' + connector: mysqlclient==2.0.3 + docker_image: ghcr.io/laurent-indermuehle/test-container-my80-py39-mysqlclient203:latest + + - ansible: stable-2.14 + db_engine_version: mysql:8.0.31 + python: '3.9' + connector: pymysql==0.9.3 + docker_image: ghcr.io/laurent-indermuehle/test-container-my80-py39-pymysql093:latest + - ansible: stable-2.14 + db_engine_version: mysql:8.0.31 + python: '3.9' + connector: mysqlclient==2.0.3 + docker_image: ghcr.io/laurent-indermuehle/test-container-my80-py39-mysqlclient203:latest + + + # ================================================================== + # mysql-client 8 + Python 3.10 + # ================================================================== + # - ansible: stable-2.13 + # db_engine_version: mysql:8.0.31 + # python: '3.10' + # connector: pymysql==1.0.2 + # docker_image: ghcr.io/laurent-indermuehle/test-container-my80-py310-pymysql102:latest + - ansible: stable-2.13 + db_engine_version: mysql:8.0.31 + python: '3.10' + connector: mysqlclient==2.1.1 + docker_image: ghcr.io/laurent-indermuehle/test-container-my80-py310-mysqlclient211:latest + + # - ansible: stable-2.14 + # db_engine_version: mysql:8.0.31 + # python: '3.10' + # connector: pymysql==1.0.2 + # docker_image: ghcr.io/laurent-indermuehle/test-container-my80-py310-pymysql102:latest + - ansible: stable-2.14 + db_engine_version: mysql:8.0.31 + python: '3.10' + connector: mysqlclient==2.1.1 + docker_image: ghcr.io/laurent-indermuehle/test-container-my80-py310-mysqlclient211:latest + + # - ansible: devel + # db_engine_version: mysql:8.0.31 + # python: '3.10' + # connector: pymysql==1.0.2 + # docker_image: ghcr.io/laurent-indermuehle/test-container-my80-py310-pymysql102:latest + - ansible: devel + db_engine_version: mysql:8.0.31 + python: '3.10' + connector: mysqlclient==2.1.1 + docker_image: ghcr.io/laurent-indermuehle/test-container-my80-py310-mysqlclient211:latest + + # ================================================================== + # mariadb-client 10.3 + Python 3.8 + # ================================================================== + - ansible: stable-2.12 + db_engine_version: mariadb:10.4.27 + python: '3.8' + connector: pymysql==0.9.3 + docker_image: ghcr.io/laurent-indermuehle/test-container-mariadb103-py38-pymysql093:latest + - ansible: stable-2.12 + db_engine_version: mariadb:10.4.27 + python: '3.8' + connector: mysqlclient==2.0.1 + docker_image: ghcr.io/laurent-indermuehle/test-container-mariadb103-py38-mysqlclient201:latest + - ansible: stable-2.12 + db_engine_version: mariadb:10.5.18 + python: '3.8' + connector: pymysql==0.9.3 + docker_image: ghcr.io/laurent-indermuehle/test-container-mariadb103-py38-pymysql093:latest + - ansible: stable-2.12 + db_engine_version: mariadb:10.5.18 + python: '3.8' + connector: mysqlclient==2.0.1 + docker_image: ghcr.io/laurent-indermuehle/test-container-mariadb103-py38-mysqlclient201:latest + + + # ================================================================== + # mariadb-client 10.3 + Python 3.9 + # ================================================================== + - ansible: stable-2.13 + db_engine_version: mariadb:10.4.27 + python: '3.9' + connector: pymysql==0.9.3 + docker_image: ghcr.io/laurent-indermuehle/test-container-mariadb103-py39-pymysql093:latest + - ansible: stable-2.13 + db_engine_version: mariadb:10.4.27 + python: '3.9' + connector: mysqlclient==2.0.3 + docker_image: ghcr.io/laurent-indermuehle/test-container-mariadb103-py39-mysqlclient203:latest + - ansible: stable-2.13 + db_engine_version: mariadb:10.5.18 + python: '3.9' + connector: pymysql==0.9.3 + docker_image: ghcr.io/laurent-indermuehle/test-container-mariadb103-py39-pymysql093:latest + - ansible: stable-2.13 + db_engine_version: mariadb:10.5.18 + python: '3.9' + connector: mysqlclient==2.0.3 + docker_image: ghcr.io/laurent-indermuehle/test-container-mariadb103-py39-mysqlclient203:latest + + - ansible: stable-2.14 + db_engine_version: mariadb:10.4.27 + python: '3.9' + connector: pymysql==0.9.3 + docker_image: ghcr.io/laurent-indermuehle/test-container-mariadb103-py39-pymysql093:latest + - ansible: stable-2.14 + db_engine_version: mariadb:10.4.27 + python: '3.9' + connector: mysqlclient==2.0.3 + docker_image: ghcr.io/laurent-indermuehle/test-container-mariadb103-py39-mysqlclient203:latest + - ansible: stable-2.14 + db_engine_version: mariadb:10.5.18 + python: '3.9' + connector: pymysql==0.9.3 + docker_image: ghcr.io/laurent-indermuehle/test-container-mariadb103-py39-pymysql093:latest + - ansible: stable-2.14 + db_engine_version: mariadb:10.5.18 + python: '3.9' + connector: mysqlclient==2.0.3 + docker_image: ghcr.io/laurent-indermuehle/test-container-mariadb103-py39-mysqlclient203:latest + + + # ================================================================== + # mariadb-client 10.6 + Python 3.10 + # ================================================================== + # - ansible: stable-2.13 + # db_engine_version: mariadb:10.5.18 + # python: '3.10' + # connector: pymysql==1.0.2 + # docker_image: ghcr.io/laurent-indermuehle/test-container-mariadb106-py310-pymysql102:latest + - ansible: stable-2.13 + db_engine_version: mariadb:10.5.18 + python: '3.10' + connector: mysqlclient==2.1.1 + docker_image: ghcr.io/laurent-indermuehle/test-container-mariadb106-py310-mysqlclient211:latest + # - ansible: stable-2.13 + # db_engine_version: mariadb:10.6.11 + # python: '3.10' + # connector: pymysql==1.0.2 + # docker_image: ghcr.io/laurent-indermuehle/test-container-mariadb106-py310-pymysql102:latest + - ansible: stable-2.13 + db_engine_version: mariadb:10.6.11 + python: '3.10' + connector: mysqlclient==2.1.1 + docker_image: ghcr.io/laurent-indermuehle/test-container-mariadb106-py310-mysqlclient211:latest + + # - ansible: stable-2.14 + # db_engine_version: mariadb:10.5.18 + # python: '3.10' + # connector: pymysql==1.0.2 + # docker_image: ghcr.io/laurent-indermuehle/test-container-mariadb106-py310-pymysql102:latest + - ansible: stable-2.14 + db_engine_version: mariadb:10.5.18 + python: '3.10' + connector: mysqlclient==2.1.1 + docker_image: ghcr.io/laurent-indermuehle/test-container-mariadb106-py310-mysqlclient211:latest + # - ansible: stable-2.14 + # db_engine_version: mariadb:10.6.11 + # python: '3.10' + # connector: pymysql==1.0.2 + # docker_image: ghcr.io/laurent-indermuehle/test-container-mariadb106-py310-pymysql102:latest + - ansible: stable-2.14 + db_engine_version: mariadb:10.6.11 + python: '3.10' + connector: mysqlclient==2.1.1 + docker_image: ghcr.io/laurent-indermuehle/test-container-mariadb106-py310-mysqlclient211:latest + + # - ansible: devel + # db_engine_version: mariadb:10.5.18 + # python: '3.10' + # connector: pymysql==1.0.2 + # docker_image: ghcr.io/laurent-indermuehle/test-container-mariadb106-py310-pymysql102:latest + - ansible: devel + db_engine_version: mariadb:10.5.18 + python: '3.10' + connector: mysqlclient==2.1.1 + docker_image: ghcr.io/laurent-indermuehle/test-container-mariadb106-py310-mysqlclient211:latest + # - ansible: devel + # db_engine_version: mariadb:10.6.11 + # python: '3.10' + # connector: pymysql==1.0.2 + # docker_image: ghcr.io/laurent-indermuehle/test-container-mariadb106-py310-pymysql102:latest + - ansible: devel + db_engine_version: mariadb:10.6.11 + python: '3.10' + connector: mysqlclient==2.1.1 + docker_image: ghcr.io/laurent-indermuehle/test-container-mariadb106-py310-mysqlclient211:latest + + services: + db_primary: + image: docker.io/library/${{ matrix.db_engine_version }} + env: + MARIADB_ROOT_PASSWORD: msandbox + MYSQL_ROOT_PASSWORD: msandbox + ports: + - 3307:3306 + # We write our own health-cmd because the mariadb container does not + # provide a healthcheck + options: >- + --health-cmd "mysqladmin ping -P 3306 -pmsandbox |grep alive || exit 1" + --health-start-period 10s + --health-interval 10s + --health-timeout 5s + --health-retries 6 + + db_replica1: + image: docker.io/library/${{ matrix.db_engine_version }} + env: + MARIADB_ROOT_PASSWORD: msandbox + MYSQL_ROOT_PASSWORD: msandbox + ports: + - 3308:3306 + options: >- + --health-cmd "mysqladmin ping -P 3306 -pmsandbox |grep alive || exit 1" + --health-start-period 10s + --health-interval 10s + --health-timeout 5s + --health-retries 6 + + db_replica2: + image: docker.io/library/${{ matrix.db_engine_version }} + env: + MARIADB_ROOT_PASSWORD: msandbox + MYSQL_ROOT_PASSWORD: msandbox + ports: + - 3309:3306 + options: >- + --health-cmd "mysqladmin ping -P 3306 -pmsandbox |grep alive || exit 1" + --health-start-period 10s + --health-interval 10s + --health-timeout 5s + --health-retries 6 steps: + + # No need to check for service health. GitHub Action took care of it. + + - name: Restart MySQL server with settings for replication + run: | + docker exec ${{ job.services.db_primary.id }} bash -c 'echo -e [mysqld]\\nserver-id=1\\nlog-bin=/var/lib/mysql/primary-bin > /etc/mysql/conf.d/replication.cnf' + docker exec ${{ job.services.db_replica1.id }} bash -c 'echo -e [mysqld]\\nserver-id=2\\nlog-bin=/var/lib/mysql/replica1-bin > /etc/mysql/conf.d/replication.cnf' + docker exec ${{ job.services.db_replica2.id }} bash -c 'echo -e [mysqld]\\nserver-id=3\\nlog-bin=/var/lib/mysql/replica2-bin > /etc/mysql/conf.d/replication.cnf' + docker restart -t 30 ${{ job.services.db_primary.id }} + docker restart -t 30 ${{ job.services.db_replica1.id }} + docker restart -t 30 ${{ job.services.db_replica2.id }} + + - name: Wait for the primary to be healthy + run: | + while ! /usr/bin/docker inspect --format="{{if .Config.Healthcheck}}{{print .State.Health.Status}}{{end}}" ${{ job.services.db_primary.id }} | grep healthy && [[ "$SECONDS" -lt 120 ]]; do sleep 1; done + - name: >- Perform integration testing against Ansible version ${{ matrix.ansible }} @@ -92,22 +354,17 @@ jobs: with: ansible-core-version: ${{ matrix.ansible }} pre-test-cmd: >- - DB_ENGINE=$(echo '${{ matrix.db_engine_version }}' | awk -F_ '{print $1}'); - DB_VERSION=$(echo '${{ matrix.db_engine_version }}' | awk -F_ '{print $2}'); - DB_ENGINE_PRETTY=$([[ "${DB_ENGINE}" == 'mysql' ]] && echo 'MySQL' || echo 'MariaDB'); - >&2 echo Matrix factor for the DB is ${{ matrix.db_engine_version }}...; - >&2 echo Setting ${DB_ENGINE_PRETTY} version to ${DB_VERSION}...; - sed -i -e "s/^${DB_ENGINE}_version:.*/${DB_ENGINE}_version: $DB_VERSION/g" '${{ env.mysql_version_file }}'; - if [[ ${{ matrix.db_engine_version }} == mariadb* ]]; - then - echo Set MariaDB install flag...; sed -i -e "s/^mariadb_install: false/mariadb_install: true/g" '${{ env.mysql_version_file }}'; - echo Set MariaDB v10.8.3 URL sub dir...; sed -i -e "s/^mariadb_url_subdir:.*/mariadb_url_subdir: linux-systemd/g" '${{ env.connector_version_file }}'; - fi; - >&2 echo Setting Connector version to ${{ matrix.connector }}...; - sed -i 's/^python_packages:.*/python_packages: [${{ matrix.connector }}]/' ${{ env.connector_version_file }} + echo Setting db_engine_version to "${{ matrix.db_engine_version }}"...; + echo -n "${{ matrix.db_engine_version }}" > tests/integration/db_engine_version; + echo Setting Connector version to "${{ matrix.connector }}"...; + echo -n "${{ matrix.connector }}" > tests/integration/connector; + echo Setting Python version to "${{ matrix.python }}"...; + echo -n "${{ matrix.python }}" > tests/integration/python; + echo Setting Ansible version to "${{ matrix.ansible }}"...; + echo -n "${{ matrix.ansible }}" > tests/integration/ansible + docker-image: ${{ matrix.docker_image }} target-python-version: ${{ matrix.python }} testing-type: integration - pull-request-change-detection: true units: runs-on: ubuntu-20.04 @@ -126,13 +383,13 @@ jobs: - 3.8 - 3.9 exclude: - - python: 3.8 + - python: '3.8' ansible: stable-2.13 - - python: 3.8 + - python: '3.8' ansible: stable-2.14 - - python: 3.8 + - python: '3.8' ansible: devel - - python: 3.9 + - python: '3.9' ansible: stable-2.12 steps: diff --git a/.github/workflows/ansible-test-roles.yml b/.github/workflows/ansible-test-roles.yml index 4748b5a..13e7d41 100644 --- a/.github/workflows/ansible-test-roles.yml +++ b/.github/workflows/ansible-test-roles.yml @@ -1,3 +1,4 @@ +--- name: Roles CI on: push: diff --git a/.github/workflows/build-docker-image.yml b/.github/workflows/build-docker-image.yml new file mode 100644 index 0000000..fa10268 --- /dev/null +++ b/.github/workflows/build-docker-image.yml @@ -0,0 +1,67 @@ +--- +name: Build Docker Image for ansible-test + +on: + workflow_call: + inputs: + registry: + required: true + type: string + image_name: + required: true + type: string + context: + required: true + type: string + +jobs: + + build: + + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + # Requirement to use 'context' in docker/build-push-action@v3 + - name: Checkout repository + uses: actions/checkout@v3 + + # https://github.com/docker/login-action + - name: Log into registry ${{ inputs.registry }} + uses: docker/login-action@v2 + with: + registry: ${{ inputs.registry }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # https://github.com/docker/metadata-action + - name: Extract Docker metadata (tags, labels) + id: meta + uses: docker/metadata-action@v4 + with: + images: + "${{ inputs.registry }}\ + /${{ github.repository }}\ + /${{ inputs.image_name }}" + tags: latest + + # Setting up Docker Buildx with docker-container driver is required + # at the moment to be able to use a subdirectory with Git context + # + # https://github.com/docker/setup-buildx-action + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + # https://github.com/docker/build-push-action + - name: Build and push Docker image with Buildx + id: build-and-push + uses: docker/build-push-action@v3 + with: + context: ${{ inputs.context }} + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/docker-image-mariadb103-py38-mysqlclient201.yml b/.github/workflows/docker-image-mariadb103-py38-mysqlclient201.yml new file mode 100644 index 0000000..3d90270 --- /dev/null +++ b/.github/workflows/docker-image-mariadb103-py38-mysqlclient201.yml @@ -0,0 +1,19 @@ +--- +name: Docker Image CI mariadb103-py38-mysqlclient201 + +on: + push: + paths: + - 'test-containers/mariadb103-py38-mysqlclient201/**' + - '.github/workflows/docker-image-mariadb103-py38-mysqlclient201.yml' + - '.github/workflows/build-docker-image.yml' + +jobs: + + call-workflow-passing-data: + uses: ./.github/workflows/build-docker-image.yml + secrets: inherit + with: + registry: ghcr.io + image_name: test-container-mariadb103-py38-mysqlclient201 + context: test-containers/mariadb103-py38-mysqlclient201 diff --git a/.github/workflows/docker-image-mariadb103-py38-pymysql093.yml b/.github/workflows/docker-image-mariadb103-py38-pymysql093.yml new file mode 100644 index 0000000..1ca4600 --- /dev/null +++ b/.github/workflows/docker-image-mariadb103-py38-pymysql093.yml @@ -0,0 +1,19 @@ +--- +name: Docker Image CI mariadb103-py38-pymysql093 + +on: + push: + paths: + - 'test-containers/mariadb103-py38-pymysql093/**' + - '.github/workflows/docker-image-mariadb103-py38-pymysql093.yml' + - '.github/workflows/build-docker-image.yml' + +jobs: + + call-workflow-passing-data: + uses: ./.github/workflows/build-docker-image.yml + secrets: inherit + with: + registry: ghcr.io + image_name: test-container-mariadb103-py38-pymysql093 + context: test-containers/mariadb103-py38-pymysql093 diff --git a/.github/workflows/docker-image-mariadb103-py39-mysqlclient203.yml b/.github/workflows/docker-image-mariadb103-py39-mysqlclient203.yml new file mode 100644 index 0000000..37e91ee --- /dev/null +++ b/.github/workflows/docker-image-mariadb103-py39-mysqlclient203.yml @@ -0,0 +1,19 @@ +--- +name: Docker Image CI mariadb103-py39-mysqlclient203 + +on: + push: + paths: + - 'test-containers/mariadb103-py39-mysqlclient203/**' + - '.github/workflows/docker-image-mariadb103-py39-mysqlclient203.yml' + - '.github/workflows/build-docker-image.yml' + +jobs: + + call-workflow-passing-data: + uses: ./.github/workflows/build-docker-image.yml + secrets: inherit + with: + registry: ghcr.io + image_name: test-container-mariadb103-py39-mysqlclient203 + context: test-containers/mariadb103-py39-mysqlclient203 diff --git a/.github/workflows/docker-image-mariadb103-py39-pymysql093.yml b/.github/workflows/docker-image-mariadb103-py39-pymysql093.yml new file mode 100644 index 0000000..30acfc1 --- /dev/null +++ b/.github/workflows/docker-image-mariadb103-py39-pymysql093.yml @@ -0,0 +1,19 @@ +--- +name: Docker Image CI mariadb103-py39-pymysql093 + +on: + push: + paths: + - 'test-containers/mariadb103-py39-pymysql093/**' + - '.github/workflows/docker-image-mariadb103-py39-pymysql093.yml' + - '.github/workflows/build-docker-image.yml' + +jobs: + + call-workflow-passing-data: + uses: ./.github/workflows/build-docker-image.yml + secrets: inherit + with: + registry: ghcr.io + image_name: test-container-mariadb103-py39-pymysql093 + context: test-containers/mariadb103-py39-pymysql093 diff --git a/.github/workflows/docker-image-mariadb106-py310-mysqlclient211.yml b/.github/workflows/docker-image-mariadb106-py310-mysqlclient211.yml new file mode 100644 index 0000000..0fa7403 --- /dev/null +++ b/.github/workflows/docker-image-mariadb106-py310-mysqlclient211.yml @@ -0,0 +1,19 @@ +--- +name: Docker Image CI mariadb106-py310-mysqlclient211 + +on: + push: + paths: + - 'test-containers/mariadb106-py310-mysqlclient211/**' + - '.github/workflows/docker-image-mariadb106-py310-mysqlclient211.yml' + - '.github/workflows/build-docker-image.yml' + +jobs: + + call-workflow-passing-data: + uses: ./.github/workflows/build-docker-image.yml + secrets: inherit + with: + registry: ghcr.io + image_name: test-container-mariadb106-py310-mysqlclient211 + context: test-containers/mariadb106-py310-mysqlclient211 diff --git a/.github/workflows/docker-image-mariadb106-py310-pymysql102.yml b/.github/workflows/docker-image-mariadb106-py310-pymysql102.yml new file mode 100644 index 0000000..adfe9e3 --- /dev/null +++ b/.github/workflows/docker-image-mariadb106-py310-pymysql102.yml @@ -0,0 +1,19 @@ +--- +name: Docker Image CI mariadb106-py310-pymysql102 + +on: + push: + paths: + - 'test-containers/mariadb106-py310-pymysql102/**' + - '.github/workflows/docker-image-mariadb106-py310-pymysql102.yml' + - '.github/workflows/build-docker-image.yml' + +jobs: + + call-workflow-passing-data: + uses: ./.github/workflows/build-docker-image.yml + secrets: inherit + with: + registry: ghcr.io + image_name: test-container-mariadb106-py310-pymysql102 + context: test-containers/mariadb106-py310-pymysql102 diff --git a/.github/workflows/docker-image-my57-py38-mysqlclient201.yml b/.github/workflows/docker-image-my57-py38-mysqlclient201.yml new file mode 100644 index 0000000..2c18f63 --- /dev/null +++ b/.github/workflows/docker-image-my57-py38-mysqlclient201.yml @@ -0,0 +1,19 @@ +--- +name: Docker Image CI my57-py38-mysqlclient201 + +on: + push: + paths: + - 'test-containers/my57-py38-mysqlclient201/**' + - '.github/workflows/docker-image-my57-py38-mysqlclient201.yml' + - '.github/workflows/build-docker-image.yml' + +jobs: + + call-workflow-passing-data: + uses: ./.github/workflows/build-docker-image.yml + secrets: inherit + with: + registry: ghcr.io + image_name: test-container-my57-py38-mysqlclient201 + context: test-containers/my57-py38-mysqlclient201 diff --git a/.github/workflows/docker-image-my57-py38-pymysql0711.yml b/.github/workflows/docker-image-my57-py38-pymysql0711.yml new file mode 100644 index 0000000..1568d22 --- /dev/null +++ b/.github/workflows/docker-image-my57-py38-pymysql0711.yml @@ -0,0 +1,19 @@ +--- +name: Docker Image CI my57-py38-pymysql0711 + +on: + push: + paths: + - 'test-containers/my57-py38-pymysql0711/**' + - '.github/workflows/docker-image-my57-py38-pymysql0711.yml' + - '.github/workflows/build-docker-image.yml' + +jobs: + + call-workflow-passing-data: + uses: ./.github/workflows/build-docker-image.yml + secrets: inherit + with: + registry: ghcr.io + image_name: test-container-my57-py38-pymysql0711 + context: test-containers/my57-py38-pymysql0711 diff --git a/.github/workflows/docker-image-my57-py38-pymysql093.yml b/.github/workflows/docker-image-my57-py38-pymysql093.yml new file mode 100644 index 0000000..39bb583 --- /dev/null +++ b/.github/workflows/docker-image-my57-py38-pymysql093.yml @@ -0,0 +1,19 @@ +--- +name: Docker Image CI my57-py38-pymysql093 + +on: + push: + paths: + - 'test-containers/my57-py38-pymysql093/**' + - '.github/workflows/docker-image-my57-py38-pymysql093.yml' + - '.github/workflows/build-docker-image.yml' + +jobs: + + call-workflow-passing-data: + uses: ./.github/workflows/build-docker-image.yml + secrets: inherit + with: + registry: ghcr.io + image_name: test-container-my57-py38-pymysql093 + context: test-containers/my57-py38-pymysql093 diff --git a/.github/workflows/docker-image-my80-py310-mysqlclient211.yml b/.github/workflows/docker-image-my80-py310-mysqlclient211.yml new file mode 100644 index 0000000..824f77c --- /dev/null +++ b/.github/workflows/docker-image-my80-py310-mysqlclient211.yml @@ -0,0 +1,19 @@ +--- +name: Docker Image CI my80-py310-mysqlclient211 + +on: + push: + paths: + - 'test-containers/my80-py310-mysqlclient211/**' + - '.github/workflows/docker-image-my80-py310-mysqlclient211.yml' + - '.github/workflows/build-docker-image.yml' + +jobs: + + call-workflow-passing-data: + uses: ./.github/workflows/build-docker-image.yml + secrets: inherit + with: + registry: ghcr.io + image_name: test-container-my80-py310-mysqlclient211 + context: test-containers/my80-py310-mysqlclient211 diff --git a/.github/workflows/docker-image-my80-py310-pymysql102.yml b/.github/workflows/docker-image-my80-py310-pymysql102.yml new file mode 100644 index 0000000..0c54e12 --- /dev/null +++ b/.github/workflows/docker-image-my80-py310-pymysql102.yml @@ -0,0 +1,19 @@ +--- +name: Docker Image CI my80-py310-pymysql102 + +on: + push: + paths: + - 'test-containers/my80-py310-pymysql102/**' + - '.github/workflows/docker-image-my80-py310-pymysql102.yml' + - '.github/workflows/build-docker-image.yml' + +jobs: + + call-workflow-passing-data: + uses: ./.github/workflows/build-docker-image.yml + secrets: inherit + with: + registry: ghcr.io + image_name: test-container-my80-py310-pymysql102 + context: test-containers/my80-py310-pymysql102 diff --git a/.github/workflows/docker-image-my80-py38-mysqlclient201.yml b/.github/workflows/docker-image-my80-py38-mysqlclient201.yml new file mode 100644 index 0000000..0ac76b2 --- /dev/null +++ b/.github/workflows/docker-image-my80-py38-mysqlclient201.yml @@ -0,0 +1,19 @@ +--- +name: Docker Image CI my80-py38-mysqlclient201 + +on: + push: + paths: + - 'test-containers/my80-py38-mysqlclient201/**' + - '.github/workflows/docker-image-my80-py38-mysqlclient201.yml' + - '.github/workflows/build-docker-image.yml' + +jobs: + + call-workflow-passing-data: + uses: ./.github/workflows/build-docker-image.yml + secrets: inherit + with: + registry: ghcr.io + image_name: test-container-my80-py38-mysqlclient201 + context: test-containers/my80-py38-mysqlclient201 diff --git a/.github/workflows/docker-image-my80-py38-pymysql093.yml b/.github/workflows/docker-image-my80-py38-pymysql093.yml new file mode 100644 index 0000000..1677be6 --- /dev/null +++ b/.github/workflows/docker-image-my80-py38-pymysql093.yml @@ -0,0 +1,19 @@ +--- +name: Docker Image CI my80-py38-pymysql093 + +on: + push: + paths: + - 'test-containers/my80-py38-pymysql093/**' + - '.github/workflows/docker-image-my80-py38-pymysql093.yml' + - '.github/workflows/build-docker-image.yml' + +jobs: + + call-workflow-passing-data: + uses: ./.github/workflows/build-docker-image.yml + secrets: inherit + with: + registry: ghcr.io + image_name: test-container-my80-py38-pymysql093 + context: test-containers/my80-py38-pymysql093 diff --git a/.github/workflows/docker-image-my80-py39-mysqlclient203.yml b/.github/workflows/docker-image-my80-py39-mysqlclient203.yml new file mode 100644 index 0000000..e6b41db --- /dev/null +++ b/.github/workflows/docker-image-my80-py39-mysqlclient203.yml @@ -0,0 +1,19 @@ +--- +name: Docker Image CI my80-py39-mysqlclient203 + +on: + push: + paths: + - 'test-containers/my80-py39-mysqlclient203/**' + - '.github/workflows/docker-image-my80-py39-mysqlclient203.yml' + - '.github/workflows/build-docker-image.yml' + +jobs: + + call-workflow-passing-data: + uses: ./.github/workflows/build-docker-image.yml + secrets: inherit + with: + registry: ghcr.io + image_name: test-container-my80-py39-mysqlclient203 + context: test-containers/my80-py39-mysqlclient203 diff --git a/.github/workflows/docker-image-my80-py39-pymysql093.yml b/.github/workflows/docker-image-my80-py39-pymysql093.yml new file mode 100644 index 0000000..72ffd60 --- /dev/null +++ b/.github/workflows/docker-image-my80-py39-pymysql093.yml @@ -0,0 +1,19 @@ +--- +name: Docker Image CI my80-py39-pymysql093 + +on: + push: + paths: + - 'test-containers/my80-py39-pymysql093/*' + - '.github/workflows/docker-image-my80-py39-pymysql093.yml' + - '.github/workflows/build-docker-image.yml' + +jobs: + + call-workflow-passing-data: + uses: ./.github/workflows/build-docker-image.yml + secrets: inherit + with: + registry: ghcr.io + image_name: test-container-my80-py39-pymysql093 + context: test-containers/my80-py39-pymysql093 diff --git a/.gitignore b/.gitignore index 1922df0..9555f5e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /tests/output/ +/tests/integration/inventory /changelogs/.plugin-cache.yaml *.swp diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a94ffd8 --- /dev/null +++ b/Makefile @@ -0,0 +1,80 @@ +SHELL := /bin/bash + +# To tell ansible-test and Make to not kill the containers on failure or +# end of tests. Disabled by default. +ifdef keep_containers_alive + _keep_containers_alive = --docker-terminate never +endif + +# This match what GitHub Action will do. Disabled by default. +ifdef continue_on_errors + _continue_on_errors = --retry-on-error --continue-on-error +endif + +.PHONY: test-integration +test-integration: + echo -n $(db_engine_version) > tests/integration/db_engine_version + echo -n $(connector) > tests/integration/connector + echo -n $(python) > tests/integration/python + echo -n $(ansible) > tests/integration/ansible + # Create podman network for systems missing it. Error can be ignored + podman network create podman || true + podman run \ + --detach \ + --replace \ + --name primary \ + --env MARIADB_ROOT_PASSWORD=msandbox \ + --env MYSQL_ROOT_PASSWORD=msandbox \ + --network podman \ + --publish 3307:3306 \ + --health-cmd 'mysqladmin ping -P 3306 -pmsandbox | grep alive || exit 1' \ + docker.io/library/$(db_engine_version) \ + mysqld + podman run \ + --detach \ + --replace \ + --name replica1 \ + --env MARIADB_ROOT_PASSWORD=msandbox \ + --env MYSQL_ROOT_PASSWORD=msandbox \ + --network podman \ + --publish 3308:3306 \ + --health-cmd 'mysqladmin ping -P 3306 -pmsandbox | grep alive || exit 1' \ + docker.io/library/$(db_engine_version) \ + mysqld + podman run \ + --detach \ + --replace \ + --name replica2 \ + --env MARIADB_ROOT_PASSWORD=msandbox \ + --env MYSQL_ROOT_PASSWORD=msandbox \ + --network podman \ + --publish 3309:3306 \ + --health-cmd 'mysqladmin ping -P 3306 -pmsandbox | grep alive || exit 1' \ + docker.io/library/$(db_engine_version) \ + mysqld + # Setup replication and restart containers + podman exec primary bash -c 'echo -e [mysqld]\\nserver-id=1\\nlog-bin=/var/lib/mysql/primary-bin > /etc/mysql/conf.d/replication.cnf' + podman exec replica1 bash -c 'echo -e [mysqld]\\nserver-id=2\\nlog-bin=/var/lib/mysql/replica1-bin > /etc/mysql/conf.d/replication.cnf' + podman exec replica2 bash -c 'echo -e [mysqld]\\nserver-id=3\\nlog-bin=/var/lib/mysql/replica2-bin > /etc/mysql/conf.d/replication.cnf' + # Don't restart a container unless it is healthy + while ! podman healthcheck run primary && [[ "$$SECONDS" -lt 120 ]]; do sleep 1; done + podman restart -t 30 primary + while ! podman healthcheck run replica1 && [[ "$$SECONDS" -lt 120 ]]; do sleep 1; done + podman restart -t 30 replica1 + while ! podman healthcheck run replica2 && [[ "$$SECONDS" -lt 120 ]]; do sleep 1; done + podman restart -t 30 replica2 + while ! podman healthcheck run primary && [[ "$$SECONDS" -lt 120 ]]; do sleep 1; done + mkdir -p .venv/$(ansible) + python$(local_python_version) -m venv .venv/$(ansible) + source .venv/$(ansible)/bin/activate + python$(local_python_version) -m ensurepip + python$(local_python_version) -m pip install --disable-pip-version-check https://github.com/ansible/ansible/archive/$(ansible).tar.gz + -set -x; ansible-test integration $(target) -v --color --coverage --diff --docker $(docker_image) --docker-network podman $(_continue_on_errors) $(_keep_containers_alive) --python $(python); set +x + rm tests/integration/db_engine_version + rm tests/integration/connector + rm tests/integration/python + rm tests/integration/ansible +ifndef keep_containers_alive + podman stop --time 0 --ignore primary replica1 replica2 + podman rm --ignore --volumes primary replica1 replica2 +endif diff --git a/README.md b/README.md index 5f95251..07c3214 100644 --- a/README.md +++ b/README.md @@ -63,23 +63,32 @@ Every voice is important and every idea is valuable. If you have something on yo ### ansible-core -- 2.11 - 2.12 - 2.13 +- 2.14 - current development version ### Databases -- mysql 5.7.31 -- mysql 8.0.22 -- mariadb 10.3.34 (only collection version >= 3) -- mariadb 10.8.3 (only collection version >= 3) +For MariaDB, only Long Term releases are tested. + +- mysql 5.7.40 +- mysql 8.0.31 +- mariadb:10.3.34 (only collection version <= 3.5.1) +- mariadb:10.4.24 (only collection version >= 3.5.2) +- mariadb:10.5.18 (only collection version >= 3.5.2) +- mariadb:10.6.11 (only collection version >= 3.5.2) +- mariadb:10.11.?? (waiting for release) + ### Database connectors -- pymysql 0.7.10 +- pymysql 0.7.11 (Only tested with MySQL 5.7) - pymysql 0.9.3 +- pymysql 1.0.2 (only collection version >= ???) !!! Unsuported until future release !!! - mysqlclient 2.0.1 +- mysqlclient 2.0.3 (only collection version >= 3.5.2) +- mysqlclient 2.1.1 (only collection version >= 3.5.2) ## External requirements diff --git a/TESTING.md b/TESTING.md new file mode 100644 index 0000000..9aad0f5 --- /dev/null +++ b/TESTING.md @@ -0,0 +1,87 @@ +# Tests + +This collection uses GitHub Actions to run ansible-test to validate its content. Three type of tests are used: Sanity, Integration and Units. + +The tests covers the code for plugins and roles (no role available yet, but tests are ready) and can be found here: + +- Plugins: *.github/workflows/ansible-test-plugins.yml* +- Roles: *.github/workflows/ansible-test-roles.yml* (unused yet) + +Everytime you push on your fork or you create a pull request, both workflows runs. You can see the output on the "Actions" tab. + + +## Integration tests + +You can use GitHub to run ansible-test either on the community repo or your fork. But sometimes you want to quickly test a single version or a single target. To do that, you can use the Makefile present at the root of this repository. + +For now, the makefile only supports Podman. + +### Requirements + +- python >= 3.8 and <= 3.10 +- make +- Minimum 15GB of free space on the device storing containers images and volumes. You can use this command to check: `podman system info --format='{{.Store.GraphRoot}}'|xargs findmnt --noheadings --nofsroot --output SOURCE --target|xargs df -h --output=size,used,avail,pcent,target` +- Minimum 2GB of RAM + + +### Makefile options + +The Makefile accept the following options: + +- **local_python_version**: This option can be omitted if your system has a version supported by Ansible. You can check with `python -V`. +- **ansible**: Mandatory version of ansible to install in a venv to run ansible-test. +- **docker_image**: + The container image to use to run our tests. Those images Dockerfile are in https://github.com/community.mysql/test-containers and then pushed to quay.io: E.G.: + `quay.io/mws/community-mysql-test-containers-my57-py38-mysqlclient201-pymysql0711:latest`. Look in the link above for a complete list of available containers. You can also look into `.github/workflows/ansible-test-plugins.yml` + Unfortunatly you must provide the right container_image yourself. And you still need to provides db_engine_version, python, etc... because ansible-test won't do black magic to try to detect what we expect. Explicit is better than implicit anyway. + To minimise the amount of images, pymysql 0.7.11 and mysqlclient are shipped together. +- **db_engine_version**: The name of the container to use for the service containers that will host a primary database and two replicas. Either MYSQL or MariaDB. Use ':' as a separator. Do not use short version, like mysql:8 for instance. Our tests expect a full version to filter tests precisely. For instance: `when: db_version is version ('8.0.22', '>')`. +- **connector**: The name of the python package of the connector along with its version number. Use '==' as a separator. +- **python**: The python version to use in the controller. +- **target** : If omitted, all test targets will run. But you can limit the tests to a single target to speed up your tests. +- **keep_containers_alive**: This option keeps all tree databases containers and the ansible-test container alive at the end of tests or in case of failure. This is useful to enter one of the containers with `podman exec -it bash` for debugging. Rerunning the +test will recreate those containers. +- **continue_on_errors**: Tells ansible-test to retry on errors and also continue on errors. This is the way the GitHub Action's workflow runs the tests. If you develop a new target, this option can be used to validate that your tests cleanup everything so a new run can restart without errors like "Failed to create database x because it already exists". + +Examples: + +```sh +# Run all targets +make ansible="stable-2.12" db_engine_version="mysql:5.7.40" python="3.8" connector="pymysql==0.7.11" docker_image="ghcr.io/community.mysql/test-container-my57-py38-pymysql0711:latest" + +# A single target +make ansible="stable-2.14" db_engine_version="mysql:5.7.40" python="3.8" connector="pymysql==0.7.11" docker_image="ghcr.io/community.mysql/test-container-my57-py38-pymysql0711:latest" target="test_mysql_db" + +# Keep databases and ansible tests containers alives +# A single target and continue on errors +make ansible="stable-2.14" db_engine_version="mysql:8.0.31" python="3.9" connector="mysqlclient==2.0.3" docker_image="ghcr.io/community.mysql/test-container-my80-py39-mysqlclient203:latest" target="test_mysql_db" keep_containers_alive=1 continue_on_errors=1 + +# If your system has an usupported version of Python: +make local_python_version="3.8" ansible="stable-2.14" db_engine_version="mariadb:10.6.11" python="3.9" connector="pymysql==0.9.3" docker_image="ghcr.io/community.mysql/test-container-mariadb103-py39-pymysql093:latest" +``` + + +### Run all tests + +GitHub Action offer a test matrix that run every combination of Python, MySQL, MariaDB and Connector against each other. To reproduce this, this repo provides a script called *run_all_tests.py*. + +Examples: + +```sh +python run_all_tests.py +``` + + +### Add a new Python, Connector or Database version + +1. Add a workflow in [.github/workflows/](.github/workflows) +1. Add a new folder in [test-containers](test-containers) containing a new Dockerfile. Your container must contains 3 things: + - The python interpreter + - The python package to connect to the database (pymysql, mysqlclient, ...) + - A mysql client to query the database before to prepare tests before our tests starts. This client must provide both `mysql` and `mysqldump` commands. +1. Add your version in *.github/workflows/ansible-test-plugins.yml* + +After pushing the commit to the remote, the container will be build and published on ghcr.io. Have a look in the "Action" tab to see if it worked. In case of error `failed to copy: io: read/write on closed pipe` re-run the workflow, this append unfortunately a lot. + +To see the docker image produced, go to the main GitHub page of your fork or community.mysql (depending were you pushed) and look for the link "Packages" on the right hand side of the page. This page indicate a "Published x days ago" that is updated infrequently. To see the last time the container has been updated you must click on its title and look in the right hands side bellow the title "Last published". + diff --git a/changelogs/fragments/490_refactor_integration_tests.yml b/changelogs/fragments/490_refactor_integration_tests.yml new file mode 100644 index 0000000..0762adf --- /dev/null +++ b/changelogs/fragments/490_refactor_integration_tests.yml @@ -0,0 +1,6 @@ +--- +minor_changes: + - Integration tests - Add more versions of MariaDB + - Integration tests - Carefully verify every component of the tests in the new target 'setup_controller' to ensure expected versions are correct Python, Ansible, connector and MySQL/MariaDB. + - Integration tests - Add tools to test locally the same as on GHA by using same containers and virtualenv. Custom test containers are published in ghcr.io by this repo's workflows. MySQL/MariaDB are official Docker Hub images. + - Integration tests - New name for many tasks to makes it easier to find failing tests. Rename duplicates. Add name for tasks which doesn't had one, refactor some tests files to better group tests by subject, ... diff --git a/run_all_tests.py b/run_all_tests.py new file mode 100755 index 0000000..b7779a5 --- /dev/null +++ b/run_all_tests.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python + +import yaml +import os + +github_workflow_file = '.github/workflows/ansible-test-plugins.yml' + + +def read_github_workflow_file(): + with open(github_workflow_file, 'r') as gh_file: + try: + return yaml.safe_load(gh_file) + except yaml.YAMLError as exc: + print(exc) + + +def extract_value(target, dict_yaml): + for key, value in dict_yaml.items(): + if key == target: + return value + + +def extract_matrix(workflow_yaml): + jobs = extract_value('jobs', workflow_yaml) + integration = extract_value('integration', jobs) + strategy = extract_value('strategy', integration) + matrix = extract_value('matrix', strategy) + return matrix + + +# def is_exclude(exclude_list, test_suite): +# test_is_excluded = False +# for excl in exclude_list: +# match = 0 + +# if 'ansible' in excl: +# if excl.get('ansible') == test_suite[0]: +# match += 1 + +# if 'db_engine_version' in excl: +# if excl.get('db_engine_version') == test_suite[1]: +# match += 1 + +# if 'python' in excl: +# if excl.get('python') == test_suite[2]: +# match += 1 + +# if 'connector' in excl: +# if excl.get('connector') == test_suite[3]: +# match += 1 + +# if match > 1: +# test_is_excluded = True + +# return test_is_excluded + + +def main(): + workflow_yaml = read_github_workflow_file() + tests_matrix_yaml = extract_matrix(workflow_yaml) + + # matrix = [] + # exclude_list = tests_matrix_yaml.get('exclude') + # for ansible in tests_matrix_yaml.get('ansible'): + # for db_engine in tests_matrix_yaml.get('db_engine_version'): + # for python in tests_matrix_yaml.get('python'): + # for connector in tests_matrix_yaml.get('connector'): + # if not is_exclude(exclude_list, (ansible, db_engine, python, connector)): + # matrix.append((ansible, db_engine, python, connector)) + + for tests in tests_matrix_yaml.get('include'): + a = tests.get('ansible') + d = tests.get('db_engine_version') + p = tests.get('python') + c = tests.get('connector') + i = tests.get('docker_image') + make_cmd = f'make ansible="{a}" db_engine_version="{d}" python="{p}" connector="{c}" docker_image="{i}" test-integration' + print(f'Run tests for: Ansible: {a}, DB: {d}, Python: {p}, Connector: {c}, Docker image: {i}') + os.system(make_cmd) + # TODO, allow for CTRL+C to break the loop more easily + # TODO, store the failures from this iteration + # TODO, display a summary of failures from every iterations + + +if __name__ == '__main__': + main() diff --git a/test-containers/mariadb103-py38-mysqlclient201/Dockerfile b/test-containers/mariadb103-py38-mysqlclient201/Dockerfile new file mode 100644 index 0000000..68ea3f6 --- /dev/null +++ b/test-containers/mariadb103-py38-mysqlclient201/Dockerfile @@ -0,0 +1,21 @@ +FROM quay.io/ansible/ubuntu2004-test-container:main +# ubuntu2004 comes with mariadb-client-10.3 + +# iproute2 # To grab docker network gateway address +# python3.8-dev # Reqs for mysqlclient +# default-libmysqlclient-dev # Reqs for mysqlclient +# build-essential # Reqs for mysqlclient +RUN apt-get update -y && \ + DEBIAN_FRONTEND=noninteractive apt-get upgrade -y --no-install-recommends && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + python3.8 \ + python3.8-dev \ + mariadb-client \ + iproute2 \ + default-libmysqlclient-dev \ + build-essential + +RUN python3.8 -m pip install --disable-pip-version-check --no-cache-dir mysqlclient==2.0.1 + +ENV container=docker +CMD ["/sbin/init"] diff --git a/test-containers/mariadb103-py38-pymysql093/Dockerfile b/test-containers/mariadb103-py38-pymysql093/Dockerfile new file mode 100644 index 0000000..22c8c57 --- /dev/null +++ b/test-containers/mariadb103-py38-pymysql093/Dockerfile @@ -0,0 +1,15 @@ +FROM quay.io/ansible/ubuntu2004-test-container:main +# ubuntu2004 comes with mariadb-client-10.3 + +# iproute2 # To grab docker network gateway address +RUN apt-get update -y && \ + DEBIAN_FRONTEND=noninteractive apt-get upgrade -y --no-install-recommends && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + python3.8 \ + mariadb-client \ + iproute2 + +RUN python3.8 -m pip install --disable-pip-version-check --no-cache-dir pymysql==0.9.3 + +ENV container=docker +CMD ["/sbin/init"] diff --git a/test-containers/mariadb103-py39-mysqlclient203/Dockerfile b/test-containers/mariadb103-py39-mysqlclient203/Dockerfile new file mode 100644 index 0000000..b7837b2 --- /dev/null +++ b/test-containers/mariadb103-py39-mysqlclient203/Dockerfile @@ -0,0 +1,21 @@ +FROM quay.io/ansible/ubuntu2004-test-container:main +# ubuntu2004 comes with mariadb-client-10.3 + +# iproute2 # To grab docker network gateway address +# python3.9-dev # Reqs for mysqlclient +# default-libmysqlclient-dev # Reqs for mysqlclient +# build-essential # Reqs for mysqlclient +RUN apt-get update -y && \ + DEBIAN_FRONTEND=noninteractive apt-get upgrade -y --no-install-recommends && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + python3.9 \ + python3.9-dev \ + mariadb-client \ + iproute2 \ + default-libmysqlclient-dev \ + build-essential + +RUN python3.9 -m pip install --disable-pip-version-check --no-cache-dir mysqlclient==2.0.3 + +ENV container=docker +CMD ["/sbin/init"] diff --git a/test-containers/mariadb103-py39-pymysql093/Dockerfile b/test-containers/mariadb103-py39-pymysql093/Dockerfile new file mode 100644 index 0000000..a1451ff --- /dev/null +++ b/test-containers/mariadb103-py39-pymysql093/Dockerfile @@ -0,0 +1,15 @@ +FROM quay.io/ansible/ubuntu2004-test-container:main +# ubuntu2004 comes with mariadb-client-10.3 + +# iproute2 # To grab docker network gateway address +RUN apt-get update -y && \ + DEBIAN_FRONTEND=noninteractive apt-get upgrade -y --no-install-recommends && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + python3.9 \ + mariadb-client \ + iproute2 + +RUN python3.9 -m pip install --disable-pip-version-check --no-cache-dir pymysql==0.9.3 + +ENV container=docker +CMD ["/sbin/init"] diff --git a/test-containers/mariadb106-py310-mysqlclient211/Dockerfile b/test-containers/mariadb106-py310-mysqlclient211/Dockerfile new file mode 100644 index 0000000..f7e9eb1 --- /dev/null +++ b/test-containers/mariadb106-py310-mysqlclient211/Dockerfile @@ -0,0 +1,21 @@ +FROM quay.io/ansible/ubuntu2204-test-container:main +# ubuntu2204 comes with mariadb-client-10.6 + +# iproute2 # To grab docker network gateway address +# python3.10-dev # Reqs for mysqlclient +# default-libmysqlclient-dev # Reqs for mysqlclient +# build-essential # Reqs for mysqlclient +RUN apt-get update -y && \ + DEBIAN_FRONTEND=noninteractive apt-get upgrade -y --no-install-recommends && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + python3.10 \ + python3.10-dev \ + mariadb-client \ + iproute2 \ + default-libmysqlclient-dev \ + build-essential + +RUN python3.10 -m pip install --disable-pip-version-check --no-cache-dir mysqlclient==2.1.1 + +ENV container=docker +CMD ["/sbin/init"] diff --git a/test-containers/mariadb106-py310-pymysql102/Dockerfile b/test-containers/mariadb106-py310-pymysql102/Dockerfile new file mode 100644 index 0000000..afe6a77 --- /dev/null +++ b/test-containers/mariadb106-py310-pymysql102/Dockerfile @@ -0,0 +1,15 @@ +FROM quay.io/ansible/ubuntu2204-test-container:main +# ubuntu2204 comes with mariadb-client-10.6 + +# iproute2 # To grab docker network gateway address +RUN apt-get update -y && \ + DEBIAN_FRONTEND=noninteractive apt-get upgrade -y --no-install-recommends && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + python3.10 \ + mariadb-client \ + iproute2 + +RUN python3.10 -m pip install --disable-pip-version-check --no-cache-dir pymysql==1.0.2 + +ENV container=docker +CMD ["/sbin/init"] diff --git a/test-containers/my57-py38-mysqlclient201/Dockerfile b/test-containers/my57-py38-mysqlclient201/Dockerfile new file mode 100644 index 0000000..0eb1778 --- /dev/null +++ b/test-containers/my57-py38-mysqlclient201/Dockerfile @@ -0,0 +1,21 @@ +FROM quay.io/ansible/ubuntu1804-test-container:main +# ubuntu1804 comes with mysql-client-5.7 + +# iproute2 # To grab docker network gateway address +# python3.8-dev # Reqs for mysqlclient +# default-libmysqlclient-dev # Reqs for mysqlclient +# build-essential # Reqs for mysqlclient +RUN apt-get update -y && \ + DEBIAN_FRONTEND=noninteractive apt-get upgrade -y --no-install-recommends && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + python3.8 \ + python3.8-dev \ + mysql-client \ + iproute2 \ + default-libmysqlclient-dev \ + build-essential + +RUN python3.8 -m pip install --disable-pip-version-check --no-cache-dir mysqlclient==2.0.1 + +ENV container=docker +CMD ["/sbin/init"] diff --git a/test-containers/my57-py38-pymysql0711/Dockerfile b/test-containers/my57-py38-pymysql0711/Dockerfile new file mode 100644 index 0000000..9141709 --- /dev/null +++ b/test-containers/my57-py38-pymysql0711/Dockerfile @@ -0,0 +1,21 @@ +FROM quay.io/ansible/ubuntu1804-test-container:main +# ubuntu1804 comes with mysql-client-5.7 + +# iproute2 # To grab docker network gateway address +# python3.8-dev # Reqs for mysqlclient +# default-libmysqlclient-dev # Reqs for mysqlclient +# build-essential # Reqs for mysqlclient +RUN apt-get update -y && \ + DEBIAN_FRONTEND=noninteractive apt-get upgrade -y --no-install-recommends && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + python3.8 \ + python3.8-dev \ + mysql-client \ + iproute2 \ + default-libmysqlclient-dev \ + build-essential + +RUN python3.8 -m pip install --disable-pip-version-check --no-cache-dir pymysql==0.7.11 + +ENV container=docker +CMD ["/sbin/init"] diff --git a/test-containers/my57-py38-pymysql093/Dockerfile b/test-containers/my57-py38-pymysql093/Dockerfile new file mode 100644 index 0000000..6b0f519 --- /dev/null +++ b/test-containers/my57-py38-pymysql093/Dockerfile @@ -0,0 +1,15 @@ +FROM quay.io/ansible/ubuntu1804-test-container:main +# ubuntu1804 comes with mysql-client-5.7 + +# iproute2 # To grab docker network gateway address +RUN apt-get update -y && \ + DEBIAN_FRONTEND=noninteractive apt-get upgrade -y --no-install-recommends && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + python3.8 \ + mysql-client \ + iproute2 + +RUN python3.8 -m pip install --disable-pip-version-check --no-cache-dir pymysql==0.9.3 + +ENV container=docker +CMD ["/sbin/init"] diff --git a/test-containers/my80-py310-mysqlclient211/Dockerfile b/test-containers/my80-py310-mysqlclient211/Dockerfile new file mode 100644 index 0000000..1aea0cd --- /dev/null +++ b/test-containers/my80-py310-mysqlclient211/Dockerfile @@ -0,0 +1,21 @@ +FROM quay.io/ansible/ubuntu2204-test-container:main +# ubuntu2204 comes with mysql-client-8 + +# iproute2 # To grab docker network gateway address +# python3.10-dev # Reqs for mysqlclient +# default-libmysqlclient-dev # Reqs for mysqlclient +# build-essential # Reqs for mysqlclient +RUN apt-get update -y && \ + DEBIAN_FRONTEND=noninteractive apt-get upgrade -y --no-install-recommends && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + python3.10 \ + python3.10-dev \ + mysql-client \ + iproute2 \ + default-libmysqlclient-dev \ + build-essential + +RUN python3.10 -m pip install --disable-pip-version-check --no-cache-dir mysqlclient==2.1.1 + +ENV container=docker +CMD ["/sbin/init"] diff --git a/test-containers/my80-py310-pymysql102/Dockerfile b/test-containers/my80-py310-pymysql102/Dockerfile new file mode 100644 index 0000000..871a1e4 --- /dev/null +++ b/test-containers/my80-py310-pymysql102/Dockerfile @@ -0,0 +1,15 @@ +FROM quay.io/ansible/ubuntu2204-test-container:main +# ubuntu2204 comes with mysql-client-8 + +# iproute2 # To grab docker network gateway address +RUN apt-get update -y && \ + DEBIAN_FRONTEND=noninteractive apt-get upgrade -y --no-install-recommends && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + python3.10 \ + mysql-client \ + iproute2 + +RUN python3.10 -m pip install --disable-pip-version-check --no-cache-dir pymysql==1.0.2 + +ENV container=docker +CMD ["/sbin/init"] diff --git a/test-containers/my80-py38-mysqlclient201/Dockerfile b/test-containers/my80-py38-mysqlclient201/Dockerfile new file mode 100644 index 0000000..eb835c2 --- /dev/null +++ b/test-containers/my80-py38-mysqlclient201/Dockerfile @@ -0,0 +1,21 @@ +FROM quay.io/ansible/ubuntu2004-test-container:main +# ubuntu2004 comes with mysql-client-8 + +# iproute2 # To grab docker network gateway address +# python3.8-dev # Reqs for mysqlclient +# default-libmysqlclient-dev # Reqs for mysqlclient +# build-essential # Reqs for mysqlclient +RUN apt-get update -y && \ + DEBIAN_FRONTEND=noninteractive apt-get upgrade -y --no-install-recommends && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + python3.8 \ + python3.8-dev \ + mysql-client \ + iproute2 \ + default-libmysqlclient-dev \ + build-essential + +RUN python3.8 -m pip install --disable-pip-version-check --no-cache-dir mysqlclient==2.0.1 + +ENV container=docker +CMD ["/sbin/init"] diff --git a/test-containers/my80-py38-pymysql093/Dockerfile b/test-containers/my80-py38-pymysql093/Dockerfile new file mode 100644 index 0000000..e97e5e2 --- /dev/null +++ b/test-containers/my80-py38-pymysql093/Dockerfile @@ -0,0 +1,15 @@ +FROM quay.io/ansible/ubuntu2004-test-container:main +# ubuntu2004 comes with mysql-client-8 + +# iproute2 # To grab docker network gateway address +RUN apt-get update -y && \ + DEBIAN_FRONTEND=noninteractive apt-get upgrade -y --no-install-recommends && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + python3.8 \ + mysql-client \ + iproute2 + +RUN python3.8 -m pip install --disable-pip-version-check --no-cache-dir pymysql==0.9.3 + +ENV container=docker +CMD ["/sbin/init"] diff --git a/test-containers/my80-py39-mysqlclient203/Dockerfile b/test-containers/my80-py39-mysqlclient203/Dockerfile new file mode 100644 index 0000000..396d895 --- /dev/null +++ b/test-containers/my80-py39-mysqlclient203/Dockerfile @@ -0,0 +1,21 @@ +FROM quay.io/ansible/ubuntu2004-test-container:main +# ubuntu2004 comes with mysql-client-8 + +# iproute2 # To grab docker network gateway address +# python3.9-dev # Reqs for mysqlclient +# default-libmysqlclient-dev # Reqs for mysqlclient +# build-essential # Reqs for mysqlclient +RUN apt-get update -y && \ + DEBIAN_FRONTEND=noninteractive apt-get upgrade -y --no-install-recommends && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + python3.9 \ + python3.9-dev \ + mysql-client \ + iproute2 \ + default-libmysqlclient-dev \ + build-essential + +RUN python3.9 -m pip install --disable-pip-version-check --no-cache-dir mysqlclient==2.0.3 + +ENV container=docker +CMD ["/sbin/init"] diff --git a/test-containers/my80-py39-pymysql093/Dockerfile b/test-containers/my80-py39-pymysql093/Dockerfile new file mode 100644 index 0000000..57ef15e --- /dev/null +++ b/test-containers/my80-py39-pymysql093/Dockerfile @@ -0,0 +1,16 @@ +FROM quay.io/ansible/ubuntu2004-test-container:main +# ubuntu2004 comes with mysql-client-8 + +# iproute2 # To grab docker network gateway address +RUN apt-get update -y && \ + DEBIAN_FRONTEND=noninteractive apt-get upgrade -y --no-install-recommends && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + python3.9 \ + mysql-client \ + iproute2 + +# cffi # To connect to MySQL 8 with Python3.9 and PyMySQL +RUN python3.9 -m pip install --disable-pip-version-check --no-cache-dir cffi pymysql==0.9.3 + +ENV container=docker +CMD ["/sbin/init"] diff --git a/tests/integration/old_mariadb_replication/defaults/main.yml b/tests/integration/old_mariadb_replication/defaults/main.yml index 3751f4e..eb32dc1 100644 --- a/tests/integration/old_mariadb_replication/defaults/main.yml +++ b/tests/integration/old_mariadb_replication/defaults/main.yml @@ -1,3 +1,5 @@ +--- +mysql_host: "{{ gateway_addr }}" master_port: 3306 standby_port: 3307 test_db: test_db diff --git a/tests/integration/old_mariadb_replication/tasks/mariadb_master_use_gtid.yml b/tests/integration/old_mariadb_replication/tasks/mariadb_master_use_gtid.yml index e3e7605..699b61f 100644 --- a/tests/integration/old_mariadb_replication/tasks/mariadb_master_use_gtid.yml +++ b/tests/integration/old_mariadb_replication/tasks/mariadb_master_use_gtid.yml @@ -11,7 +11,7 @@ # Auxiliary step: - name: Get master status mysql_replication: - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: "{{ primary_db.port }}" mode: getmaster register: primary_status @@ -19,10 +19,10 @@ # Set master_use_gtid disabled: - name: Run replication mysql_replication: - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: "{{ replica_db.port }}" mode: changemaster - master_host: 127.0.0.1 + master_host: '{{ mysql_host }}' master_port: "{{ primary_db.port }}" master_user: "{{ replication_user }}" master_password: "{{ replication_pass }}" @@ -38,13 +38,13 @@ # Start standby for further tests: - name: Start standby mysql_replication: - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: "{{ primary_db.port }}" mode: startslave - name: Get standby status mysql_replication: - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: "{{ replica_db.port }}" mode: getslave register: slave_status @@ -56,7 +56,7 @@ # Stop standby for further tests: - name: Stop standby mysql_replication: - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: "{{ replica_db.port }}" mode: stopslave @@ -67,7 +67,7 @@ # Auxiliary step: - name: Get master status mysql_replication: - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: "{{ primary_db.port }}" mode: getmaster register: primary_status @@ -75,10 +75,10 @@ # Set master_use_gtid current_pos: - name: Run replication mysql_replication: - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: "{{ replica_db.port }}" mode: changemaster - master_host: 127.0.0.1 + master_host: '{{ mysql_host }}' master_port: "{{ primary_db.port }}" master_user: "{{ replication_user }}" master_password: "{{ replication_pass }}" @@ -94,13 +94,13 @@ # Start standby for further tests: - name: Start standby mysql_replication: - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: "{{ primary_db.port }}" mode: startslave - name: Get standby status mysql_replication: - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: "{{ replica_db.port }}" mode: getslave register: slave_status @@ -112,7 +112,7 @@ # Stop standby for further tests: - name: Stop standby mysql_replication: - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: "{{ replica_db.port }}" mode: stopslave @@ -123,7 +123,7 @@ # Auxiliary step: - name: Get master status mysql_replication: - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: "{{ primary_db.port }}" mode: getmaster register: primary_status @@ -131,10 +131,10 @@ # Set master_use_gtid slave_pos: - name: Run replication mysql_replication: - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: "{{ replica_db.port }}" mode: changemaster - master_host: 127.0.0.1 + master_host: '{{ mysql_host }}' master_port: "{{ primary_db.port }}" master_user: "{{ replication_user }}" master_password: "{{ replication_pass }}" @@ -150,13 +150,13 @@ # Start standby for further tests: - name: Start standby mysql_replication: - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: "{{ primary_db.port }}" mode: startslave - name: Get standby status mysql_replication: - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: "{{ replica_db.port }}" mode: getslave register: slave_status @@ -168,6 +168,6 @@ # Stop standby for further tests: - name: Stop standby mysql_replication: - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: "{{ replica_db.port }}" mode: stopslave diff --git a/tests/integration/old_mariadb_replication/tasks/mariadb_replication_connection_name.yml b/tests/integration/old_mariadb_replication/tasks/mariadb_replication_connection_name.yml index 98fa5fe..3928c78 100644 --- a/tests/integration/old_mariadb_replication/tasks/mariadb_replication_connection_name.yml +++ b/tests/integration/old_mariadb_replication/tasks/mariadb_replication_connection_name.yml @@ -4,20 +4,20 @@ # Needs for further tests: - name: Stop slave mysql_replication: - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: "{{ replica_db.port }}" mode: stopslave - name: Reset slave all mysql_replication: - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: "{{ replica_db.port }}" mode: resetslaveall # Get master log pos: - name: Get master status mysql_replication: - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: "{{ primary_db.port }}" mode: getmaster register: primary_status @@ -25,10 +25,10 @@ # Test changemaster mode: - name: Run replication with connection_name mysql_replication: - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: "{{ replica_db.port }}" mode: changemaster - master_host: 127.0.0.1 + master_host: '{{ mysql_host }}' master_port: "{{ primary_db.port }}" master_user: "{{ replication_user }}" master_password: "{{ replication_pass }}" @@ -45,7 +45,7 @@ # Test startslave mode: - name: Start slave with connection_name mysql_replication: - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: "{{ replica_db.port }}" mode: startslave connection_name: "{{ conn_name }}" @@ -59,7 +59,7 @@ # Test getslave mode: - name: Get standby statu with connection_name mysql_replication: - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: "{{ replica_db.port }}" mode: getslave connection_name: "{{ conn_name }}" @@ -68,7 +68,7 @@ - assert: that: - slave_status.Is_Slave == true - - slave_status.Master_Host == '127.0.0.1' + - slave_status.Master_Host == ''{{ mysql_host }}'' - slave_status.Exec_Master_Log_Pos == primary_status.Position - slave_status.Master_Port == {{ primary_db.port }} - slave_status.Last_IO_Errno == 0 @@ -78,7 +78,7 @@ # Test stopslave mode: - name: Stop slave with connection_name mysql_replication: - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: "{{ replica_db.port }}" mode: stopslave connection_name: "{{ conn_name }}" @@ -92,7 +92,7 @@ # Test reset - name: Reset slave with connection_name mysql_replication: - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: "{{ replica_db.port }}" mode: resetslave connection_name: "{{ conn_name }}" @@ -106,7 +106,7 @@ # Test reset all - name: Reset slave all with connection_name mysql_replication: - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: "{{ replica_db.port }}" mode: resetslaveall connection_name: "{{ conn_name }}" diff --git a/tests/integration/old_mariadb_replication/tasks/mariadb_replication_initial.yml b/tests/integration/old_mariadb_replication/tasks/mariadb_replication_initial.yml index 86a6760..f65d090 100644 --- a/tests/integration/old_mariadb_replication/tasks/mariadb_replication_initial.yml +++ b/tests/integration/old_mariadb_replication/tasks/mariadb_replication_initial.yml @@ -3,11 +3,11 @@ # Preparation: - name: Create user for replication - shell: "echo \"GRANT REPLICATION SLAVE ON *.* TO '{{ replication_user }}'@'localhost' IDENTIFIED BY '{{ replication_pass }}'; FLUSH PRIVILEGES;\" | mysql -P {{ primary_db.port }} -h 127.0.0.1" + shell: "echo \"GRANT REPLICATION SLAVE ON *.* TO '{{ replication_user }}'@'localhost' IDENTIFIED BY '{{ replication_pass }}'; FLUSH PRIVILEGES;\" | mysql -P {{ primary_db.port }} -h '{{ mysql_host }}'" - name: Create test database mysql_db: - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ primary_db.port }}' state: present name: '{{ test_db }}' @@ -16,12 +16,12 @@ shell: 'mysqldump -P {{ primary_db.port }} -h 127.0.01 --all-databases --master-data=2 > {{ dump_path }}' - name: Restore the dump to the replica - shell: 'mysql -P {{ replica_db.port }} -h 127.0.0.1 < {{ dump_path }}' + shell: "mysql -P {{ replica_db.port }} -h '{{ mysql_host }}' < {{ dump_path }}" # Test getmaster mode: - name: Get master status mysql_replication: - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: "{{ primary_db.port }}" mode: getmaster register: master_status @@ -35,10 +35,10 @@ # Test changemaster mode: - name: Run replication mysql_replication: - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: "{{ replica_db.port }}" mode: changemaster - master_host: 127.0.0.1 + master_host: '{{ mysql_host }}' master_port: "{{ primary_db.port }}" master_user: "{{ replication_user }}" master_password: "{{ replication_pass }}" @@ -54,7 +54,7 @@ # Test startslave mode: - name: Start slave mysql_replication: - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: "{{ replica_db.port }}" mode: startslave register: result @@ -67,7 +67,7 @@ # Test getslave mode: - name: Get replica status mysql_replication: - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: "{{ replica_db.port }}" mode: getslave register: slave_status @@ -75,7 +75,7 @@ - assert: that: - slave_status.Is_Slave == true - - slave_status.Master_Host == '127.0.0.1' + - slave_status.Master_Host == ''{{ mysql_host }}'' - slave_status.Exec_Master_Log_Pos == master_status.Position - slave_status.Master_Port == {{ primary_db.port }} - slave_status.Last_IO_Errno == 0 @@ -85,7 +85,7 @@ # Test stopslave mode: - name: Stop slave mysql_replication: - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: "{{ replica_db.port }}" mode: stopslave register: result diff --git a/tests/integration/targets/setup_controller/tasks/fake_root.yml b/tests/integration/targets/setup_controller/tasks/fake_root.yml new file mode 100644 index 0000000..49531b8 --- /dev/null +++ b/tests/integration/targets/setup_controller/tasks/fake_root.yml @@ -0,0 +1,11 @@ +--- + +- name: "{{ role_name }} | Fake root | Ensure folder" + ansible.builtin.file: + path: "{{ playbook_dir }}/root" + state: directory + +- name: "{{ role_name }} | Fake root | Ensure default file exists" + ansible.builtin.file: + path: "{{ playbook_dir }}/root/.my.cnf" + state: touch diff --git a/tests/integration/targets/setup_controller/tasks/main.yml b/tests/integration/targets/setup_controller/tasks/main.yml new file mode 100644 index 0000000..0d5e36b --- /dev/null +++ b/tests/integration/targets/setup_controller/tasks/main.yml @@ -0,0 +1,18 @@ +--- +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- name: Prepare the fake root folder + ansible.builtin.import_tasks: + file: fake_root.yml + +# setvars.yml requires the iproute2 package installed by install.yml +- name: Set variables + ansible.builtin.import_tasks: + file: setvars.yml + +- name: Verify all components version under test + ansible.builtin.import_tasks: + file: verify.yml diff --git a/tests/integration/targets/setup_controller/tasks/setvars.yml b/tests/integration/targets/setup_controller/tasks/setvars.yml new file mode 100644 index 0000000..d74136d --- /dev/null +++ b/tests/integration/targets/setup_controller/tasks/setvars.yml @@ -0,0 +1,69 @@ +--- + +- name: "{{ role_name }} | Setvars | Extract Podman/Docker Network Gateway" + ansible.builtin.shell: + cmd: ip route|grep default|awk '{print $3}' + register: ip_route_output + +- name: "{{ role_name }} | Setvars | Set Fact" + ansible.builtin.set_fact: + gateway_addr: "{{ ip_route_output.stdout }}" + connector_name_version: >- + {{ lookup( + 'file', + '/root/ansible_collections/community/mysql/tests/integration/connector' + ) }} + db_engine_version: >- + {{ lookup( + 'file', + '/root/ansible_collections/community/mysql/tests/integration/db_engine_version' + ) }} + python_version_lookup: >- + {{ lookup( + 'file', + '/root/ansible_collections/community/mysql/tests/integration/python' + ) }} + ansible_version_lookup: >- + {{ lookup( + 'file', + '/root/ansible_collections/community/mysql/tests/integration/ansible' + ) }} + +- name: "{{ role_name }} | Setvars | Set Fact using above facts" + ansible.builtin.set_fact: + connector_name: "{{ connector_name_version.split('=')[0].strip() }}" + connector_version: "{{ connector_name_version.split('=')[2].strip() }}" + db_engine: "{{ db_engine_version.split(':')[0].strip() }}" + db_version: "{{ db_engine_version.split(':')[1].strip() }}" + python_version: "{{ python_version_lookup.strip() }}" + test_ansible_version: >- + {%- if ansible_version_lookup == 'devel' -%} + {{ ansible_version_lookup }} + {%- else -%} + {{ ansible_version_lookup.split('-')[1].strip() }} + {%- endif -%} + mysql_command: >- + mysql + -h{{ gateway_addr }} + -P{{ mysql_primary_port }} + -u{{ mysql_user }} + -p{{ mysql_password }} + --protocol=tcp + mysql_command_wo_port: >- + mysql + -h{{ gateway_addr }} + -u{{ mysql_user }} + -p{{ mysql_password }} + --protocol=tcp + +- name: "{{ role_name }} | Setvars | Output test informations" + vars: + msg: |- + connector_name: {{ connector_name }} + connector_version: {{ connector_version }} + db_engine: {{ db_engine }} + db_version: {{ db_version }} + python_version: {{ python_version }} + test_ansible_version: {{ test_ansible_version }} + ansible.builtin.debug: + msg: "{{ msg.split('\n') }}" diff --git a/tests/integration/targets/setup_controller/tasks/verify.yml b/tests/integration/targets/setup_controller/tasks/verify.yml new file mode 100644 index 0000000..74aa0f2 --- /dev/null +++ b/tests/integration/targets/setup_controller/tasks/verify.yml @@ -0,0 +1,59 @@ +--- + +- vars: + mysql_parameters: &mysql_params + login_user: root + login_password: msandbox + login_host: "{{ gateway_addr }}" + login_port: 3307 + + block: + + - name: Query Primary container over TCP for MySQL/MariaDB version + mysql_info: + <<: *mysql_params + filter: + - version + register: primary_info + + - name: Assert that test container runs the expected MySQL/MariaDB version + assert: + that: + - "'{{ primary_info.version.major }}.{{ primary_info.version.minor }}\ + .{{ primary_info.version.release }}' == '{{ db_version }}'" + + - name: Assert that mysql_info module used the expected version of pymysql + assert: + that: + - primary_info.connector_name == connector_name + - primary_info.connector_version == connector_version + when: + - connector_name == 'pymysql' + + - name: Assert that mysql_info module used the expected version of mysqlclient + assert: + that: + - primary_info.connector_name == 'MySQLdb' + - primary_info.connector_version == connector_version + when: + - connector_name == 'mysqlclient' + + - name: Display the python version in use + command: + cmd: python{{ python_version }} -V + changed_when: false + register: python_in_use + + - name: Assert that expected Python is installed + assert: + that: + - python_in_use.stdout is search(python_version) + + - name: Assert that we run the expected ansible version + assert: + that: + - > + "{{ ansible_version.major }}.{{ ansible_version.minor }}" + is version(test_ansible_version, '==') + when: + - test_ansible_version != 'devel' # Devel will change overtime diff --git a/tests/integration/targets/setup_mysql/defaults/main.yml b/tests/integration/targets/setup_mysql/defaults/main.yml deleted file mode 100644 index cceb8f5..0000000 --- a/tests/integration/targets/setup_mysql/defaults/main.yml +++ /dev/null @@ -1,18 +0,0 @@ -dbdeployer_version: 1.64.0 -dbdeployer_home_dir: /opt/dbdeployer - -home_dir: /root - -mariadb_install: false - -mysql_version: 8.0.22 -mariadb_version: 10.5.4 - -mysql_base_port: 3306 - -percona_client_package: >- - {%- if mariadb_install -%} - mariadb-client - {%- else -%} - percona-server-client-5.7 - {%- endif -%} diff --git a/tests/integration/targets/setup_mysql/handlers/main.yml b/tests/integration/targets/setup_mysql/handlers/main.yml deleted file mode 100644 index 8f751ee..0000000 --- a/tests/integration/targets/setup_mysql/handlers/main.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -- name: "{{ role_name }} | handler | create dbdeployer installed file" - template: - src: installed_file.j2 - dest: "{{ dbdeployer_installed_file }}" - listen: create zookeeper installed file - tags: - - setup_mysql diff --git a/tests/integration/targets/setup_mysql/tasks/config.yml b/tests/integration/targets/setup_mysql/tasks/config.yml deleted file mode 100644 index 2b27e27..0000000 --- a/tests/integration/targets/setup_mysql/tasks/config.yml +++ /dev/null @@ -1,15 +0,0 @@ ---- -- name: "{{ role_name }} | config | download mysql tarball" - get_url: - url: "{{ install_src }}" - dest: "{{ dbdeployer_sandbox_download_dir }}/{{ install_tarball }}" - -- name: "{{ role_name }} | config | run unpack tarball" - shell: - cmd: "dbdeployer unpack {{ dbdeployer_sandbox_download_dir }}/{{ install_tarball }} --flavor {{ install_type }}" - creates: "{{ dbdeployer_sandbox_binary_dir }}/{{ install_version }}" - -- name: "{{ role_name }} | config | setup replication topology" - shell: - cmd: "dbdeployer deploy multiple {{ install_version }} --flavor {{ install_type }} --base-port {{ mysql_base_port }} --my-cnf-options=\"master_info_repository='TABLE'\" --my-cnf-options=\"relay_log_info_repository='TABLE'\"" - creates: "{{ dbdeployer_sandbox_home_dir }}/multi_msb_{{ install_version|replace('.','_') }}" diff --git a/tests/integration/targets/setup_mysql/tasks/dir.yml b/tests/integration/targets/setup_mysql/tasks/dir.yml deleted file mode 100644 index dc02879..0000000 --- a/tests/integration/targets/setup_mysql/tasks/dir.yml +++ /dev/null @@ -1,11 +0,0 @@ ---- -- name: "{{ role_name }} | dir | create dbdeployer directories" - file: - state: directory - path: "{{ item }}" - loop: - - "{{ dbdeployer_home_dir }}" - - "{{ dbdeployer_install_dir }}" - - "{{ dbdeployer_sandbox_download_dir }}" - - "{{ dbdeployer_sandbox_binary_dir }}" - - "{{ dbdeployer_sandbox_home_dir }}" diff --git a/tests/integration/targets/setup_mysql/tasks/install.yml b/tests/integration/targets/setup_mysql/tasks/install.yml deleted file mode 100644 index b64af25..0000000 --- a/tests/integration/targets/setup_mysql/tasks/install.yml +++ /dev/null @@ -1,90 +0,0 @@ ---- -- name: "{{ role_name }} | install | add apt signing key for percona" - apt_key: - keyserver: keyserver.ubuntu.com - id: 4D1BB29D63D98E422B2113B19334A25F8507EFA5 - state: present - when: install_type == 'mysql' - -- name: "{{ role_name }} | install | add percona repositories" - apt_repository: - repo: deb http://repo.percona.com/percona/apt {{ ansible_lsb.codename }} main - state: present - when: install_type == 'mysql' - -- name: "{{ role_name }} | install | add apt signing key for mariadb" - apt_key: - keyserver: keyserver.ubuntu.com - id: F1656F24C74CD1D8 - state: present - when: install_type == 'mariadb' - -- name: "{{ role_name }} | install | add mariadb repositories" - apt_repository: - repo: "deb [arch=amd64,arm64] https://downloads.mariadb.com/MariaDB/mariadb-{{ mysql_major_version }}/repo/ubuntu {{ ansible_lsb.codename }} main" - state: present - when: install_type == 'mariadb' - -- name: "{{ role_name }} | install | install packages required by percona" - apt: - name: "{{ percona_mysql_packages }}" - state: present - 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 }}" - register: connector - -- name: Extract connector.name.0 content - set_fact: - connector_name: "{{ connector.name.0 }}" - -- name: Debug connector_name content - debug: - msg: '{{ connector_name }}' - -- name: Extract connector version - set_fact: - connector_ver: "{{ connector_name.split('=')[2].strip() }}" - -- name: Debug connector_ver var content - debug: - msg: '{{ connector_ver }}' - -- name: "{{ role_name }} | install | install packages required by mysql" - apt: - name: "{{ install_prereqs }}" - state: present - environment: - DEBIAN_FRONTEND: noninteractive - -- name: "{{ role_name }} | install | download and unpack dbdeployer" - unarchive: - remote_src: true - src: "{{ dbdeployer_src }}" - dest: "{{ dbdeployer_install_dir }}" - creates: "{{ dbdeployer_installed_file }}" - register: dbdeployer_tarball_install - notify: - - create zookeeper installed file - until: dbdeployer_tarball_install is not failed - retries: 6 - delay: 5 - -- name: "{{ role_name }} | install | create symlink" - file: - src: "{{ dbdeployer_install_dir }}/dbdeployer-{{ dbdeployer_version }}.linux" - dest: /usr/local/bin/dbdeployer - follow: false - state: link - -- meta: flush_handlers diff --git a/tests/integration/targets/setup_mysql/tasks/main.yml b/tests/integration/targets/setup_mysql/tasks/main.yml deleted file mode 100644 index 47a5ee0..0000000 --- a/tests/integration/targets/setup_mysql/tasks/main.yml +++ /dev/null @@ -1,21 +0,0 @@ ---- -#################################################################### -# WARNING: These are designed specifically for Ansible tests # -# and should not be used as examples of how to write Ansible roles # -#################################################################### - -- import_tasks: setvars.yml - tags: - - setup_mysql -- import_tasks: dir.yml - tags: - - setup_mysql -- import_tasks: install.yml - tags: - - setup_mysql -- import_tasks: config.yml - tags: - - setup_mysql -- import_tasks: verify.yml - tags: - - setup_mysql diff --git a/tests/integration/targets/setup_mysql/tasks/setvars.yml b/tests/integration/targets/setup_mysql/tasks/setvars.yml deleted file mode 100644 index cfc90c1..0000000 --- a/tests/integration/targets/setup_mysql/tasks/setvars.yml +++ /dev/null @@ -1,33 +0,0 @@ ---- -- name: "{{ role_name }} | setvars | split mysql version in parts" - set_fact: - mysql_version_parts: >- - {%- if mariadb_install -%} - {{ mariadb_version.split('.') }} - {%- else -%} - {{ mysql_version.split('.') }} - {%- endif -%} - -- name: "{{ role_name }} | setvars | get mysql major version" - set_fact: - mysql_major_version: "{{ mysql_version_parts[0] + '.' + mysql_version_parts[1] }}" - -- name: "{{ role_name }} | setvars | set the appropriate extension dependent on the mysql version" - set_fact: - mysql_compression_extension: "{{ mysql_version is version('8.0.0', '<') | ternary('gz', 'xz') }}" - -- name: "{{ role_name }} | setvars | set the install type" - set_fact: - install_type: "{{ mariadb_install | ternary('mariadb', 'mysql') }}" - -- name: "{{ role_name }} | setvars | set install_version" - set_fact: - install_version: "{{ lookup('vars', install_type + '_version') }}" - -- name: "{{ role_name }} | setvars | set install_tarball" - set_fact: - install_tarball: "{{ lookup('vars', install_type + '_tarball') }}" - -- name: "{{ role_name }} | setvars | set install_src" - set_fact: - install_src: "{{ lookup('vars', install_type + '_src') }}" diff --git a/tests/integration/targets/setup_mysql/tasks/verify.yml b/tests/integration/targets/setup_mysql/tasks/verify.yml deleted file mode 100644 index ca383d9..0000000 --- a/tests/integration/targets/setup_mysql/tasks/verify.yml +++ /dev/null @@ -1,27 +0,0 @@ ---- -- name: "{{ role_name }} | verify | confirm primary is running and get the port" - shell: "{{ dbdeployer_sandbox_home_dir }}/multi_msb_{{ install_version|replace('.','_') }}/n1 -BNe'select @@port'" - register: primary_port - -- name: "{{ role_name }} | verify | confirm replica1 is running and get the port" - shell: "{{ dbdeployer_sandbox_home_dir }}/multi_msb_{{ install_version|replace('.','_') }}/n2 -BNe'select @@port'" - register: replica1_port - -- name: "{{ role_name }} | verify | confirm replica2 is running and get the port" - shell: "{{ dbdeployer_sandbox_home_dir }}/multi_msb_{{ install_version|replace('.','_') }}/n3 -BNe'select @@port'" - register: replica2_port - -- name: "{{ role_name }} | verify | confirm primary is running on expected port" - assert: - that: - - primary_port.stdout|int == 3307 - -- name: "{{ role_name }} | verify | confirm replica1 is running on expected port" - assert: - that: - - replica1_port.stdout|int == 3308 - -- name: "{{ role_name }} | verify | confirm replica2 is running on expected port" - assert: - that: - - replica2_port.stdout|int == 3309 diff --git a/tests/integration/targets/setup_mysql/templates/installed_file.j2 b/tests/integration/targets/setup_mysql/templates/installed_file.j2 deleted file mode 100644 index 862a357..0000000 --- a/tests/integration/targets/setup_mysql/templates/installed_file.j2 +++ /dev/null @@ -1 +0,0 @@ -{{ dbdeployer_version }} diff --git a/tests/integration/targets/setup_mysql/vars/main.yml b/tests/integration/targets/setup_mysql/vars/main.yml deleted file mode 100644 index 8fbcd90..0000000 --- a/tests/integration/targets/setup_mysql/vars/main.yml +++ /dev/null @@ -1,30 +0,0 @@ ---- -dbdeployer_install_dir: "{{ dbdeployer_home_dir }}/dbdeployer_{{ dbdeployer_version }}" -dbdeployer_src: "https://github.com/datacharmer/dbdeployer/releases/download/v{{ dbdeployer_version }}/dbdeployer-{{ dbdeployer_version }}.linux.tar.gz" -dbdeployer_installed_file: "{{ dbdeployer_home_dir }}/dbdeployer_installed" - -dbdeployer_sandbox_download_dir: "{{ home_dir }}/downloads" -dbdeployer_sandbox_binary_dir: "{{ home_dir }}/opt/mysql" -dbdeployer_sandbox_home_dir: "{{ home_dir }}/sandboxes" - -percona_mysql_packages: - - "{{ percona_client_package }}" - -python_packages: [pymysql == 0.9.3] - -install_prereqs: - - libaio1 - - libnuma1 - - libncurses5 - -install_python_prereqs: - - python3-dev - - python3-cryptography - - default-libmysqlclient-dev - - build-essential - -mysql_tarball: "mysql-{{ mysql_version }}-linux-glibc2.12-x86_64.tar.{{ mysql_compression_extension }}" -mysql_src: "https://cdn.mysql.com/archives/mysql-{{ mysql_major_version }}/{{ mysql_tarball }}" -mariadb_url_subdir: "linux" -mariadb_tarball: "mariadb-{{ mariadb_version }}-{{ mariadb_url_subdir }}-x86_64.tar.gz" -mariadb_src: "https://downloads.mariadb.com/MariaDB/mariadb-{{ mariadb_version }}/bintar-{{ mariadb_url_subdir }}-x86_64/{{ mariadb_tarball }}" diff --git a/tests/integration/targets/test_mysql_db/defaults/main.yml b/tests/integration/targets/test_mysql_db/defaults/main.yml index 6448e15..30ac858 100644 --- a/tests/integration/targets/test_mysql_db/defaults/main.yml +++ b/tests/integration/targets/test_mysql_db/defaults/main.yml @@ -2,6 +2,7 @@ # defaults file for test_mysql_db mysql_user: root mysql_password: msandbox +mysql_host: '{{ gateway_addr }}' mysql_primary_port: 3307 # Database names diff --git a/tests/integration/targets/test_mysql_db/meta/main.yml b/tests/integration/targets/test_mysql_db/meta/main.yml index f1174ff..aebda43 100644 --- a/tests/integration/targets/test_mysql_db/meta/main.yml +++ b/tests/integration/targets/test_mysql_db/meta/main.yml @@ -1,2 +1,2 @@ dependencies: - - setup_mysql + - setup_controller 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 c2fda2a..390c6ae 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 @@ -1,57 +1,59 @@ -- set_fact: +--- +- name: Config overrides | Set facts + set_fact: db_to_create: testdb1 - config_file: "/root/.my1.cnf" + config_file: "{{ playbook_dir }}/.my1.cnf" fake_port: 9999 fake_host: "blahblah.local" - include_dir: "/root/mycnf.d" + include_dir: "{{ playbook_dir }}/mycnf.d" -- name: Create custom config file +- name: Config overrides | Create custom config file shell: 'echo "[client]" > {{ config_file }}' -- name: Add fake port to config file +- name: Config overrides | Add fake port to config file shell: 'echo "port = {{ fake_port }}" >> {{ config_file }}' -- name: Add blank line +- name: Config overrides | Add blank line shell: 'echo "" >> {{ config_file }}' when: - > - connector_name is not search('pymysql') + connector_name != 'pymysql' or ( - connector_name is search('pymysql') - and connector_ver is version('0.9.3', '>=') + connector_name == 'pymysql' + and connector_version is version('0.9.3', '>=') ) -- name: Create include_dir +- name: Config overrides | Create include_dir file: path: '{{ include_dir }}' state: directory mode: '0777' when: - > - connector_name is not search('pymysql') + connector_name != 'pymysql' or ( - connector_name is search('pymysql') - and connector_ver is version('0.9.3', '>=') + connector_name == 'pymysql' + and connector_version is version('0.9.3', '>=') ) -- name: Add include_dir +- name: Config overrides | Add include_dir lineinfile: path: '{{ config_file }}' line: '!includedir {{ include_dir }}' insertafter: EOF when: - > - connector_name is not search('pymysql') + connector_name != 'pymysql' or ( - connector_name is search('pymysql') - and connector_ver is version('0.9.3', '>=') + connector_name == 'pymysql' + and connector_version is version('0.9.3', '>=') ) -- name: Create database using fake port to connect to, must fail +- name: Config overrides | Create database using fake port to connect to, must fail mysql_db: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: '{{ db_to_create }}' state: present @@ -61,17 +63,17 @@ ignore_errors: yes register: result -- name: Must fail because login_port default has beed overriden by wrong value from config file +- name: Config overrides | 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") + - result is failed + - result.msg is search("unable to connect to database") -- name: Create database using default port +- name: Config overrides | Create database using default port mysql_db: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: '{{ db_to_create }}' state: present @@ -80,22 +82,22 @@ config_overrides_defaults: no register: result -- name: Must not fail because of the default of login_port is correct +- name: Config overrides | Must not fail because of the default of login_port is correct assert: that: - - result is changed + - result is changed -- name: Reinit custom config file +- name: Config overrides | Reinit custom config file shell: 'echo "[client]" > {{ config_file }}' -- name: Add fake host to config file +- name: Config overrides | Add fake host to config file shell: 'echo "host = {{ fake_host }}" >> {{ config_file }}' -- name: Remove database using fake login_host +- name: Config overrides | Remove database using fake login_host mysql_db: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: '{{ db_to_create }}' state: absent @@ -104,18 +106,17 @@ register: result ignore_errors: yes -- name: Must fail because login_host default has beed overriden by wrong value from config file +- name: Config overrides | 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 }}'") or result.msg is search("Unknown MySQL server host '{{ fake_host }}'") + - result is failed + - 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 +- name: Config overrides | Clean up test database mysql_db: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: '{{ db_to_create }}' state: absent diff --git a/tests/integration/targets/test_mysql_db/tasks/encoding_dump_import.yml b/tests/integration/targets/test_mysql_db/tasks/encoding_dump_import.yml index 9ef3af5..02e5df2 100644 --- a/tests/integration/targets/test_mysql_db/tasks/encoding_dump_import.yml +++ b/tests/integration/targets/test_mysql_db/tasks/encoding_dump_import.yml @@ -1,45 +1,46 @@ --- -- set_fact: - latin1_file1: "{{tmp_dir}}/{{file}}" +- name: Encoding | Set fact + set_fact: + latin1_file1: "{{ tmp_dir }}/{{ file }}" - name: Deleting Latin1 encoded Database mysql_db: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: '{{ db_latin1_name }}' state: absent -- name: create Latin1 encoded database +- name: Encoding | Create Latin1 encoded database mysql_db: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: '{{ db_latin1_name }}' state: present encoding: latin1 -- name: create a table in Latin1 database +- name: Encoding | Create a table in Latin1 database command: "{{ mysql_command }} {{ db_latin1_name }} -e \"create table testlatin1(id int, name varchar(100))\"" # Inserting a string in latin1 into table, , this string be tested later, # so report any change of content in the test too -- name: inserting data into Latin1 database +- name: Encoding | Inserting data into Latin1 database command: "{{ mysql_command }} {{ db_latin1_name }} -e \"insert into testlatin1 value(47,'Amédée Bôlüt')\"" -- name: selecting table +- name: Encoding | Selecting table command: "{{ mysql_command }} {{ db_latin1_name }} -e \"select * from testlatin1\"" register: output -- name: Dumping a table in Latin1 database +- name: Encoding | Dumping a table in Latin1 database mysql_db: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: "{{ db_latin1_name }}" encoding: latin1 @@ -49,30 +50,30 @@ - assert: that: - - result is changed + - result is changed -- name: state dump - file name should exist +- name: Encoding | State dump - file name should exist (latin1_file1) file: name: '{{ latin1_file1 }}' state: file -- name: od the file and check of latin1 encoded string is present +- name: od the file and check of latin1 encoded string is present shell: grep -a 47 {{ latin1_file1 }} | od -c |grep "A m 351 d 351 e B 364\|A m 303 251 d 303 251 e B 303" -- name: Dropping {{ db_latin1_name }} database +- name: Encoding | Dropping {{ db_latin1_name }} database mysql_db: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: '{{ db_latin1_name }}' state: absent -- name: Importing the latin1 mysql script +- name: Encoding | Importing the latin1 mysql script mysql_db: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' state: import encoding: latin1 @@ -80,20 +81,25 @@ target: "{{ latin1_file1 }}" register: result -- assert: +- name: Encoding | Assert that importing latin1 is changed + assert: that: - - result is changed + - result is changed -- name: check encoding of table - shell: "{{ mysql_command }} {{ db_latin1_name }} -e \"SHOW FULL COLUMNS FROM testlatin1\"" +- name: Encoding | Check encoding of table + ansible.builtin.command: + cmd: > + {{ mysql_command }} + {{ db_latin1_name }} + -e "SHOW FULL COLUMNS FROM {{ db_latin1_name }}.testlatin1" register: output failed_when: '"latin1_swedish_ci" not in output.stdout' -- name: remove database +- name: Encoding | Clean up database mysql_db: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: '{{ db_latin1_name }}' state: absent diff --git a/tests/integration/targets/test_mysql_db/tasks/issue-28.yml b/tests/integration/targets/test_mysql_db/tasks/issue-28.yml index 64fe9d5..8cad28e 100644 --- a/tests/integration/targets/test_mysql_db/tasks/issue-28.yml +++ b/tests/integration/targets/test_mysql_db/tasks/issue-28.yml @@ -9,7 +9,7 @@ mysql_parameters: &mysql_params login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' when: tls_enabled block: @@ -25,6 +25,7 @@ mysql_user: <<: *mysql_params name: '{{ user_name_1 }}' + host_all: true state: absent ignore_errors: yes @@ -32,6 +33,7 @@ mysql_user: <<: *mysql_params name: "{{ user_name_1 }}" + host: "%" password: "{{ user_password_1 }}" priv: '*.*:ALL,GRANT' tls_requires: @@ -43,7 +45,7 @@ state: absent login_user: '{{ user_name_1 }}' login_password: '{{ user_password_1 }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' ca_cert: /tmp/cert.pem register: result @@ -52,12 +54,14 @@ - assert: that: - result is failed - when: connector_name is search('pymysql') + when: + - connector_name == 'pymysql' - assert: that: - result is succeeded - when: connector_name is not search('pymysql') + when: + - connector_name != 'pymysql' - name: attempt connection with newly created user ignoring hostname mysql_db: @@ -65,7 +69,7 @@ state: absent login_user: '{{ user_name_1 }}' login_password: '{{ user_password_1 }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' ca_cert: /tmp/cert.pem check_hostname: no @@ -80,5 +84,5 @@ mysql_user: <<: *mysql_params name: '{{ user_name_1 }}' - host: 127.0.0.1 + host_all: true state: absent diff --git a/tests/integration/targets/test_mysql_db/tasks/issue_256_mysqldump_errors.yml b/tests/integration/targets/test_mysql_db/tasks/issue_256_mysqldump_errors.yml index 58285b3..ea1768a 100644 --- a/tests/integration/targets/test_mysql_db/tasks/issue_256_mysqldump_errors.yml +++ b/tests/integration/targets/test_mysql_db/tasks/issue_256_mysqldump_errors.yml @@ -7,7 +7,7 @@ community.mysql.mysql_db: &mysql_defaults login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' community.mysql.mysql_query: *mysql_defaults @@ -73,6 +73,7 @@ name: all target: /tmp/full-dump-without-t1.sql pipefail: true # This should do nothing + register: full_dump_without_t1 ignore_errors: true diff --git a/tests/integration/targets/test_mysql_db/tasks/main.yml b/tests/integration/targets/test_mysql_db/tasks/main.yml index df6bb07..544ad4d 100644 --- a/tests/integration/targets/test_mysql_db/tasks/main.yml +++ b/tests/integration/targets/test_mysql_db/tasks/main.yml @@ -1,3 +1,4 @@ +--- #################################################################### # WARNING: These are designed specifically for Ansible tests # # and should not be used as examples of how to write Ansible roles # @@ -21,10 +22,6 @@ # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . -- name: alias mysql command to include default options - set_fact: - mysql_command: "mysql -u{{ mysql_user }} -p{{ mysql_password }} -P{{ mysql_primary_port }} --protocol=tcp" - - name: Check state present/absent include_tasks: state_present_absent.yml vars: diff --git a/tests/integration/targets/test_mysql_db/tasks/multi_db_create_delete.yml b/tests/integration/targets/test_mysql_db/tasks/multi_db_create_delete.yml index c2eb13c..0bd7d58 100644 --- a/tests/integration/targets/test_mysql_db/tasks/multi_db_create_delete.yml +++ b/tests/integration/targets/test_mysql_db/tasks/multi_db_create_delete.yml @@ -18,7 +18,7 @@ mysql_db: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: - '{{ db1_name }}' @@ -43,7 +43,7 @@ mysql_db: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: - '{{ db1_name }}' @@ -75,7 +75,7 @@ mysql_db: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: - '{{ db1_name }}' @@ -107,7 +107,7 @@ mysql_db: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: - '{{ db1_name }}' @@ -139,7 +139,7 @@ mysql_db: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: - '{{ db1_name }}' @@ -170,7 +170,7 @@ mysql_db: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: - '{{ db2_name }}' @@ -199,7 +199,7 @@ mysql_db: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: - '{{ db1_name }}' @@ -231,7 +231,7 @@ mysql_db: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: - '{{ db1_name }}' @@ -271,7 +271,7 @@ mysql_db: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: - '{{ db1_name }}' @@ -308,7 +308,7 @@ mysql_db: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: - "{{ db1_name }}" @@ -348,7 +348,7 @@ mysql_db: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: - "{{ db4_name }}" @@ -384,11 +384,12 @@ # ========================================================================== # Dump existing databases + - name: Dump existing databases mysql_db: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: - '{{ db1_name }}' @@ -398,13 +399,13 @@ target: '{{ dump1_file }}' register: dump_result -- name: assert successful completion of dump operation +- name: Assert successful completion of dump operation (existing database) assert: that: - dump_result is changed - dump_result.db_list == ['{{ db1_name }}', '{{ db2_name }}', '{{ db3_name }}'] -- name: run command to list databases like specified database name +- name: Run command to list databases like specified database name command: "{{ mysql_command }} \"-e show databases like 'database%'\"" register: mysql_result @@ -415,7 +416,7 @@ - "'{{ db2_name }}' in mysql_result.stdout" - "'{{ db3_name }}' in mysql_result.stdout" -- name: state dump - file name should exist +- name: State dump - file name should exist (dump1_file) file: name: '{{ dump1_file }}' state: file @@ -441,7 +442,7 @@ mysql_db: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: all state: dump @@ -466,7 +467,7 @@ - "'{{ db4_name }}' not in mysql_result.stdout" - "'{{ db5_name }}' not in mysql_result.stdout" -- name: state dump - file name should exist +- name: state dump - file name should exist (dump2_file) file: name: '{{ dump2_file }}' state: file @@ -479,7 +480,7 @@ mysql_db: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: - '{{ db2_name }}' @@ -509,7 +510,7 @@ mysql_db: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: - '{{ db2_name }}' @@ -539,7 +540,7 @@ mysql_db: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: - '{{ db2_name }}' @@ -569,7 +570,7 @@ mysql_db: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: - '{{ db2_name }}' @@ -598,7 +599,7 @@ mysql_db: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: - '{{ db1_name }}' diff --git a/tests/integration/targets/test_mysql_db/tasks/state_dump_import.yml b/tests/integration/targets/test_mysql_db/tasks/state_dump_import.yml index 724dd18..b4f9cda 100644 --- a/tests/integration/targets/test_mysql_db/tasks/state_dump_import.yml +++ b/tests/integration/targets/test_mysql_db/tasks/state_dump_import.yml @@ -17,113 +17,116 @@ # along with Ansible. If not, see . # ============================================================ -- set_fact: +- name: Dump and Import | Set facts + set_fact: db_file_name: "{{ tmp_dir }}/{{ file }}" wrong_sql_file: "{{ tmp_dir }}/wrong.sql" dump_file1: "{{ tmp_dir }}/{{ file2 }}" dump_file2: "{{ tmp_dir }}/{{ file3 }}" db_user: "test" db_user_unsafe_password: "pass!word" - config_file: "/root/.my.cnf" + config_file: "{{ playbook_dir }}/root/.my.cnf" -- name: create custom config file +- name: Dump and Import | Create custom config file shell: 'echo "[client]" > {{ config_file }}' -- name: create user for test unsafe_login_password parameter +- name: Dump and Import | Create user for test unsafe_login_password parameter mysql_user: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: '{{ db_user }}' + host: '%' password: '{{ db_user_unsafe_password }}' priv: '*.*:ALL' state: present -- name: state dump/import - create database +- name: Dump and Import | State dump/import - create database mysql_db: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: '{{ db_name }}' state: present check_implicit_admin: yes -- name: create database +- name: Dump and Import | Create database mysql_db: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: '{{ db_name2 }}' state: present check_implicit_admin: no -- name: state dump/import - create table department +- name: Dump and Import | State dump/import - create table department command: "{{ mysql_command }} {{ db_name }} \"-e create table department(id int, name varchar(100))\"" -- name: state dump/import - create table employee +- name: Dump and Import | State dump/import - create table employee command: "{{ mysql_command }} {{ db_name }} \"-e create table employee(id int, name varchar(100))\"" -- name: state dump/import - insert data into table employee +- name: Dump and Import | State dump/import - insert data into table employee command: "{{ mysql_command }} {{ db_name }} \"-e insert into employee value(47,'Joe Smith')\"" -- name: state dump/import - insert data into table department +- name: Dump and Import | State dump/import - insert data into table department command: "{{ mysql_command }} {{ db_name }} \"-e insert into department value(2,'Engineering')\"" -- name: state dump/import - file name should not exist +- name: Dump and Import | State dump/import - file name should not exist file: name: '{{ db_file_name }}' state: absent -- name: database dump file1 should not exist +- name: Dump and Import | Database dump file1 should not exist file: name: '{{ dump_file1 }}' state: absent -- name: database dump file2 should not exist +- name: Dump and Import | Database dump file2 should not exist file: name: '{{ dump_file2 }}' state: absent -- name: state dump without department table. +- name: Dump and Import | State dump without department table. mysql_db: login_user: '{{ db_user }}' login_password: '{{ db_user_unsafe_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' unsafe_login_password: yes name: '{{ db_name }}' state: dump target: '{{ db_file_name }}' ignore_tables: - - "{{ db_name }}.department" + - "{{ db_name }}.department" force: yes master_data: 1 skip_lock_tables: yes - dump_extra_args: --skip-triggers + dump_extra_args: >- + --skip-triggers config_file: '{{ config_file }}' restrict_config_file: yes check_implicit_admin: no register: result -- name: assert successful completion of dump operation +- name: Dump and Import | Assert successful completion of dump operation assert: that: - result is changed - - result.executed_commands[0] is search("mysqldump --defaults-file={{ config_file }} --user={{ db_user }} --password=\*\*\*\*\*\*\*\* --force --host=127.0.0.1 --port={{ mysql_primary_port }} {{ db_name }} --skip-lock-tables --quick --ignore-table={{ db_name }}.department --master-data=1 --skip-triggers") + - result.executed_commands[0] is search(".department --master-data=1 --skip-triggers") -- name: state dump/import - file name should exist +- name: Dump and Import | State dump/import - file name should exist (db_file_name) file: name: '{{ db_file_name }}' state: file -- name: state dump with multiple databases in comma separated form. +- name: Dump and Import | State dump with multiple databases in comma separated form for MySQL. mysql_db: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: "{{ db_name }},{{ db_name2 }}" state: dump @@ -131,22 +134,22 @@ check_implicit_admin: yes register: dump_result1 -- name: assert successful completion of dump operation (with multiple databases in comma separated form) +- name: Dump and Import | Assert successful completion of dump operation (with multiple databases in comma separated form) assert: that: - dump_result1 is changed - dump_result1.executed_commands[0] is search(" --user=root --password=\*\*\*\*\*\*\*\*") -- name: state dump - dump file1 should exist +- name: Dump and Import | State dump - dump file1 should exist file: name: '{{ dump_file1 }}' state: file -- name: state dump with multiple databases in list form via check_mode +- name: Dump and Import | State dump with multiple databases in list form via check_mode mysql_db: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: - '{{ db_name }}' @@ -156,26 +159,26 @@ register: dump_result check_mode: yes -- name: assert successful completion of dump operation (with multiple databases in list form) via check mode +- name: Dump and Import | Assert successful completion of dump operation (with multiple databases in list form) via check mode assert: that: - dump_result is changed -- name: database dump file2 should not exist +- name: Dump and Import | Database dump file2 should not exist stat: path: '{{ dump_file2 }}' register: stat_result -- name: assert that check_mode does not create dump file for databases +- name: Dump and Import | Assert that check_mode does not create dump file for databases assert: that: - stat_result.stat.exists is defined and not stat_result.stat.exists -- name: state dump with multiple databases in list form. +- name: Dump and Import | State dump with multiple databases in list form. mysql_db: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: - '{{ db_name }}' @@ -184,39 +187,39 @@ target: '{{ dump_file2 }}' register: dump_result2 -- name: assert successful completion of dump operation (with multiple databases in list form) +- name: Dump and Import | Assert successful completion of dump operation (with multiple databases in list form) assert: that: - dump_result2 is changed -- name: state dump - dump file2 should exist +- name: Dump and Import | State dump - dump file2 should exist file: name: '{{ dump_file2 }}' state: file -- name: state dump/import - remove database +- name: Dump and Import | State dump/import - remove database mysql_db: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: '{{ db_name }}' state: absent -- name: remove database +- name: Dump and Import | Remove database mysql_db: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: '{{ db_name2 }}' state: absent -- name: test state=import to restore the database of type {{ format_type }} (expect changed=true) +- name: Dump and Import | Test state=import to restore the database of type {{ format_type }} (expect changed=true) mysql_db: login_user: '{{ db_user }}' login_password: '{{ db_user_unsafe_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' unsafe_login_password: yes name: '{{ db_name }}' @@ -225,20 +228,20 @@ use_shell: yes register: result -- name: show the tables +- name: Dump and Import | Show the tables command: "{{ mysql_command }} {{ db_name }} \"-e show tables\"" register: result -- name: assert that the department table is absent. +- name: Dump and Import | Assert that the department table is absent. assert: that: - "'department' not in result.stdout" -- name: test state=import to restore a database from multiple database dumped file1 +- name: Dump and Import | Test state=import to restore a database from multiple database dumped file1 mysql_db: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: '{{ db_name2 }}' state: import @@ -246,34 +249,34 @@ use_shell: no register: import_result -- name: assert output message restored a database from dump file1 +- name: Dump and Import | Assert output message restored a database from dump file1 assert: that: - import_result is changed -- name: remove database +- name: Dump and Import | Remove database mysql_db: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: '{{ db_name2 }}' state: absent -- name: run command to list databases +- name: Dump and Import | Run command to list databases command: "{{ mysql_command }} \"-e show databases like 'data%'\"" register: mysql_result -- name: assert that db_name2 database does not exist +- name: Dump and Import | Assert that db_name2 database does not exist assert: that: - "'{{ db_name2 }}' not in mysql_result.stdout" -- name: test state=import to restore a database from dumped file2 (check mode) +- name: Dump and Import | Test state=import to restore a database from dumped file2 (check mode) mysql_db: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: '{{ db_name2 }}' state: import @@ -281,96 +284,96 @@ register: check_import_result check_mode: yes -- name: assert output message restored a database from dump file2 (check mode) +- name: Dump and Import | Assert output message restored a database from dump file2 (check mode) assert: that: - check_import_result is changed -- name: run command to list databases +- name: Dump and Import | Run command to list databases command: "{{ mysql_command }} \"-e show databases like 'data%'\"" register: mysql_result -- name: assert that db_name2 database does not exist (check mode) +- name: Dump and Import | Assert that db_name2 database does not exist (check mode) assert: that: - "'{{ db_name2 }}' not in mysql_result.stdout" -- name: test state=import to restore a database from multiple database dumped file2 +- name: Dump and Import | Test state=import to restore a database from multiple database dumped file2 mysql_db: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: '{{ db_name2 }}' state: import target: '{{ dump_file2 }}' register: import_result2 -- name: assert output message restored a database from dump file2 +- name: Dump and Import | Assert output message restored a database from dump file2 assert: that: - import_result2 is changed - import_result2.db_list == ['{{ db_name2 }}'] -- name: run command to list databases +- name: Dump and Import | Run command to list databases command: "{{ mysql_command }} \"-e show databases like 'data%'\"" register: mysql_result -- name: assert that db_name2 database does exist after import +- name: Dump and Import | Assert that db_name2 database does exist after import assert: that: - "'{{ db_name2 }}' in mysql_result.stdout" -- name: test state=dump to backup the database of type {{ format_type }} (expect changed=true) +- name: Dump and Import | Test state=dump to backup the database of type {{ format_type }} (expect changed=true) mysql_db: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: '{{ db_name }}' state: dump target: '{{ db_file_name }}' register: result -- name: assert output message backup the database +- name: Dump and Import | Assert output message backup the database assert: that: - result is changed - "result.db =='{{ db_name }}'" -# - name: assert database was backed up successfully +# - name: Dump and Import | Assert database was backed up successfully # command: "file {{ db_file_name }}" # register: result # -# - name: assert file format type +# - name: Dump and Import | Assert file format type # assert: # that: # - "'{{ format_msg_type }}' in result.stdout" -- name: update database table employee +- name: Dump and Import | Update database table employee command: "{{ mysql_command }} {{ db_name }} \"-e update employee set name='John Doe' where id=47\"" -- name: test state=import to restore the database of type {{ format_type }} (expect changed=true) +- name: Dump and Import | Test state=import to restore the database of type {{ format_type }} (expect changed=true) mysql_db: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: '{{ db_name }}' state: import target: '{{ db_file_name }}' register: result -- name: assert output message restore the database +- name: Dump and Import | Assert output message restore the database assert: that: - result is changed -- name: select data from table employee +- name: Dump and Import | Select data from table employee command: "{{ mysql_command }} {{ db_name }} \"-e select * from employee\"" register: result -- name: assert data in database is from the restore database +- name: Dump and Import | Assert data in database is from the restore database assert: that: - "'47' in result.stdout" @@ -380,14 +383,14 @@ # Test ``force`` parameter ########################## -- name: create wrong sql file +- name: Dump and Import | Create wrong sql file shell: echo 'CREATE TABLE hello (id int); CREATE ELBAT ehlo (int id);' >> '{{ wrong_sql_file }}' -- name: try to import without force parameter, must fail +- name: Dump and Import | Try to import without force parameter, must fail mysql_db: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: '{{ db_name }}' state: import @@ -400,11 +403,11 @@ that: - result is failed -- name: try to import with force parameter +- name: Dump and Import | Try to import with force parameter mysql_db: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: '{{ db_name }}' state: import @@ -419,22 +422,22 @@ ######################## # Test import with chdir -- name: Create dir +- name: Dump and Import | Create dir file: path: ~/subdir state: directory -- name: Create test dump +- name: Dump and Import | Create test dump shell: 'echo "SOURCE ./subdir_test.sql" > ~/original_test.sql' -- name: Create test source +- name: Dump and Import | Create test source shell: 'echo "SELECT 1" > ~/subdir/subdir_test.sql' -- name: Try to restore without chdir argument, must fail +- name: Dump and Import | Try to restore without chdir argument, must fail mysql_db: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: '{{ db_name }}' state: import @@ -443,14 +446,14 @@ register: result - assert: that: - - result is failed - - result.msg is search('Failed to open file') + - result is failed + - result.msg is search('Failed to open file') -- name: Restore with chdir argument, must pass +- name: Dump and Import | Restore with chdir argument, must pass mysql_db: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: '{{ db_name }}' state: import @@ -459,46 +462,30 @@ register: result - assert: that: - - result is succeeded + - result is succeeded ########## # Clean up ########## -- name: remove database name +- name: Dump and Import | Clean up databases mysql_db: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' - name: '{{ db_name }}' + name: '{{ item }}' state: absent + loop: + - '{{ db_name }}' + - '{{ db_name2 }}' -- name: remove database - mysql_db: - login_user: '{{ mysql_user }}' - login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 - login_port: '{{ mysql_primary_port }}' - name: '{{ db_name2 }}' - state: absent - -- name: remove file name +- name: Dump and Import | Clean up files file: - name: '{{ db_file_name }}' - state: absent - -- name: remove file name - file: - name: '{{ wrong_sql_file }}' - state: absent - -- name: remove dump file1 - file: - name: '{{ dump_file1 }}' - state: absent - -- name: remove dump file2 - file: - name: '{{ dump_file2 }}' + name: '{{ item }}' state: absent + loop: + - '{{ db_file_name }}' + - '{{ wrong_sql_file }}' + - '{{ dump_file1 }}' + - '{{ dump_file2 }}' diff --git a/tests/integration/targets/test_mysql_db/tasks/state_present_absent.yml b/tests/integration/targets/test_mysql_db/tasks/state_present_absent.yml index 5b6e871..12633f2 100644 --- a/tests/integration/targets/test_mysql_db/tasks/state_present_absent.yml +++ b/tests/integration/targets/test_mysql_db/tasks/state_present_absent.yml @@ -1,3 +1,4 @@ +--- # test code for mysql_db module with database name containing special chars # This file is part of Ansible @@ -16,75 +17,75 @@ # along with Ansible. If not, see . # ============================================================ -- name: remove database if it exists +- name: State Present Absent | Remove database if it exists command: > "{{ mysql_command }} -sse 'DROP DATABASE IF EXISTS {{ db_name }}'" ignore_errors: true -- name: make sure the test database is not there +- name: State Present Absent | Make sure the test database is not there command: "{{ mysql_command }} {{ db_name }}" register: mysql_db_check failed_when: "'1049' not in mysql_db_check.stderr" -- name: test state=present for a database name (expect changed=true) +- name: State Present Absent | Test state=present for a database name (expect changed=true) mysql_db: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: '{{ db_name }}' state: present register: result -- name: assert output message that database exist +- name: State Present Absent | Assert output message that database exist assert: that: - result is changed - result.db == '{{ db_name }}' - result.executed_commands == ["CREATE DATABASE `{{ db_name }}`"] -- name: run command to test state=present for a database name (expect db_name in stdout) +- name: State Present Absent | Run command to test state=present for a database name (expect db_name in stdout) command: "{{ mysql_command }} -e \"show databases like '{{ db_name | regex_replace(\"([%_\\\\])\", \"\\\\\\1\") }}'\"" register: result -- name: assert database exist +- name: State Present Absent | Assert database exist assert: that: - "'{{ db_name }}' in result.stdout" # ============================================================ -- name: test state=absent for a database name (expect changed=true) +- name: State Present Absent | Test state=absent for a database name (expect changed=true) mysql_db: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: '{{ db_name }}' state: absent register: result -- name: assert output message that database does not exist +- name: State Present Absent | Assert output message that database does not exist assert: that: - result is changed - result.db == '{{ db_name }}' - result.executed_commands == ["DROP DATABASE `{{ db_name }}`"] -- name: run command to test state=absent for a database name (expect db_name not in stdout) +- name: State Present Absent | Run command to test state=absent for a database name (expect db_name not in stdout) command: "{{ mysql_command }} -e \"show databases like '{{ db_name | regex_replace(\"([%_\\\\])\", \"\\\\\\1\") }}'\"" register: result -- name: assert database does not exist +- name: State Present Absent | Assert database does not exist assert: that: - "'{{ db_name }}' not in result.stdout" # ============================================================ -- name: test mysql_db encoding param not valid - issue 8075 +- name: State Present Absent | Test mysql_db encoding param not valid - issue 8075 mysql_db: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: datanotvalid state: present @@ -92,7 +93,7 @@ register: result ignore_errors: true -- name: assert test mysql_db encoding param not valid - issue 8075 (failed=true) +- name: State Present Absent | Assert test mysql_db encoding param not valid - issue 8075 (failed=true) assert: that: - result is failed @@ -100,201 +101,202 @@ - "'Unknown character set' in result.msg" # ============================================================ -- name: test mysql_db using a valid encoding utf8 (expect changed=true) +- name: State Present Absent | Test mysql_db using a valid encoding utf8 (expect changed=true) mysql_db: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: 'en{{ db_name }}' state: present encoding: utf8 register: result -- name: assert output message created a database +- name: State Present Absent | Assert output message created a database assert: that: - result is changed - result.executed_commands == ["CREATE DATABASE `en{{ db_name }}` CHARACTER SET 'utf8'"] -- name: test database was created +- name: State Present Absent | Test database was created command: "{{ mysql_command }} -e \"SHOW CREATE DATABASE `en{{ db_name }}`\"" register: result -- name: assert created database is of encoding utf8 +- name: State Present Absent | Assert created database is of encoding utf8 assert: that: - "'utf8' in result.stdout" -- name: remove database +- name: State Present Absent | Remove database mysql_db: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: 'en{{ db_name }}' state: absent # ============================================================ -- name: test mysql_db using valid encoding binary (expect changed=true) +- name: State Present Absent | Test mysql_db using valid encoding binary (expect changed=true) mysql_db: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: 'en{{ db_name }}' state: present encoding: binary register: result -- name: assert output message that database was created +- name: State Present Absent | Assert output message that database was created assert: that: - result is changed - result.executed_commands == ["CREATE DATABASE `en{{ db_name }}` CHARACTER SET 'binary'"] -- name: run command to test database was created +- name: State Present Absent | Run command to test database was created command: "{{ mysql_command }} -e \"SHOW CREATE DATABASE `en{{ db_name }}`\"" register: result -- name: assert created database is of encoding binary +- name: State Present Absent | Assert created database is of encoding binary assert: that: - "'binary' in result.stdout" -- name: remove database +- name: State Present Absent | Remove database mysql_db: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: 'en{{ db_name }}' state: absent # ============================================================ -- name: create user1 to access database dbuser1 +- name: State Present Absent | Create user1 to access database dbuser1 mysql_user: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: user1 + host: '%' password: 'Hfd6fds^dfA8Ga' priv: '*.*:ALL' state: present -- name: create database dbuser1 using user1 +- name: State Present Absent | Create database dbuser1 using user1 mysql_db: login_user: user1 login_password: 'Hfd6fds^dfA8Ga' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: '{{ db_user1 }}' state: present register: result -- name: assert output message that database was created +- name: State Present Absent | Assert output message that database was created assert: that: - result is changed -- name: run command to test database was created using user1 +- name: State Present Absent | Run command to test database was created using user1 command: "{{ mysql_command }} -e \"show databases like '{{ db_user1 | regex_replace(\"([%_\\\\])\", \"\\\\\\1\") }}'\"" register: result -- name: assert database exist +- name: State Present Absent | Assert database exist assert: that: - "'{{ db_user1 }}' in result.stdout" # ============================================================ -- name: create user2 to access database with privilege select only +- name: State Present Absent | Create user2 to access database with privilege select only mysql_user: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: user2 password: 'kjsfd&F7safjad' priv: '*.*:SELECT' state: present -- name: create database dbuser2 using user2 with no privilege to create (expect failed=true) +- name: State Present Absent | Create database dbuser2 using user2 with no privilege to create (expect failed=true) mysql_db: login_user: user2 login_password: 'kjsfd&F7safjad' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: '{{ db_user2 }}' state: present register: result ignore_errors: true -- name: assert output message that database was not created using dbuser2 +- name: State Present Absent | Assert output message that database was not created using dbuser2 assert: that: - result is failed - "'Access denied' in result.msg" -- name: run command to test that database was not created +- name: State Present Absent | Run command to test that database was not created command: "{{ mysql_command }} -e \"show databases like '{{ db_user2 | regex_replace(\"([%_\\\\])\", \"\\\\\\1\") }}'\"" register: result -- name: assert database does not exist +- name: State Present Absent | Assert database does not exist assert: that: - "'{{ db_user2 }}' not in result.stdout" # ============================================================ -- name: delete database using user2 with no privilege to delete (expect failed=true) +- name: State Present Absent | Delete database using user2 with no privilege to delete (expect failed=true) mysql_db: login_user: user2 login_password: 'kjsfd&F7safjad' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: '{{ db_user1 }}' state: absent register: result ignore_errors: true -- name: assert output message that database was not deleted using dbuser2 +- name: State Present Absent | Assert output message that database was not deleted using dbuser2 assert: that: - result is failed - "'Access denied' in result.msg" -- name: run command to test database was not deleted +- name: State Present Absent | Run command to test database was not deleted command: "{{ mysql_command }} -e \"show databases like '{{ db_user1 | regex_replace(\"([%_\\\\])\", \"\\\\\\1\") }}'\"" register: result -- name: assert database still exist +- name: State Present Absent | Assert database still exist assert: that: - "'{{ db_user1 }}' in result.stdout" # ============================================================ -- name: delete database using user1 with all privilege to delete a database (expect changed=true) +- name: State Present Absent | Delete database using user1 with all privilege to delete a database (expect changed=true) mysql_db: login_user: user1 login_password: 'Hfd6fds^dfA8Ga' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: '{{ db_user1 }}' state: absent register: result ignore_errors: true -- name: assert output message that database was deleted using user1 +- name: State Present Absent | Assert output message that database was deleted using user1 assert: that: - result is changed - result.executed_commands == ["DROP DATABASE `{{ db_user1 }}`"] -- name: run command to test database was deleted using user1 +- name: State Present Absent | Run command to test database was deleted using user1 command: "{{ mysql_command }} -e \"show databases like '{{ db_name | regex_replace(\"([%_\\\\])\", \"\\\\\\1\") }}'\"" register: result -- name: assert database does not exist +- name: State Present Absent | Assert database does not exist assert: that: - "'{{ db_user1 }}' not in result.stdout" diff --git a/tests/integration/targets/test_mysql_info/defaults/main.yml b/tests/integration/targets/test_mysql_info/defaults/main.yml index e1b932c..e1cd880 100644 --- a/tests/integration/targets/test_mysql_info/defaults/main.yml +++ b/tests/integration/targets/test_mysql_info/defaults/main.yml @@ -2,7 +2,7 @@ # defaults file for test_mysql_info mysql_user: root mysql_password: msandbox -mysql_host: 127.0.0.1 +mysql_host: '{{ gateway_addr }}' mysql_primary_port: 3307 db_name: data diff --git a/tests/integration/targets/test_mysql_info/meta/main.yml b/tests/integration/targets/test_mysql_info/meta/main.yml index a7ace5d..4be5f58 100644 --- a/tests/integration/targets/test_mysql_info/meta/main.yml +++ b/tests/integration/targets/test_mysql_info/meta/main.yml @@ -1,3 +1,4 @@ +--- dependencies: - - setup_mysql + - setup_controller - setup_remote_tmp_dir diff --git a/tests/integration/targets/test_mysql_info/tasks/connector_info.yml b/tests/integration/targets/test_mysql_info/tasks/connector_info.yml index ba76f59..d525e8e 100644 --- a/tests/integration/targets/test_mysql_info/tasks/connector_info.yml +++ b/tests/integration/targets/test_mysql_info/tasks/connector_info.yml @@ -2,7 +2,6 @@ # Added in 3.6.0 in # https://github.com/ansible-collections/community.mysql/pull/497 -# TODO: Refactor in PR490. - name: Connector info | Assert connector_name exists and has expected values ansible.builtin.assert: that: @@ -15,18 +14,17 @@ {{ result.connector_name | d('Unknown')}} which is different than expected pymysql or MySQLdb -# TODO: Refactor in PR490. - name: Connector info | Assert connector_version exists and has expected values ansible.builtin.assert: that: - result.connector_version is defined - > result.connector_version == 'Unknown' - or result.connector_version is version(connector_ver, '==') + or result.connector_version is version(connector_version, '==') success_msg: >- Assertions passed, result.connector_version is {{ result.connector_version }} fail_msg: >- Assertion failed, result.connector_version is {{ result.connector_version }} which is different than expected - {{ connector_ver }} + {{ connector_version }} diff --git a/tests/integration/targets/test_mysql_info/tasks/issue-28.yml b/tests/integration/targets/test_mysql_info/tasks/issue-28.yml index bf4576f..83e6883 100644 --- a/tests/integration/targets/test_mysql_info/tasks/issue-28.yml +++ b/tests/integration/targets/test_mysql_info/tasks/issue-28.yml @@ -1,7 +1,4 @@ --- -- name: alias mysql command to include default options - set_fact: - mysql_command: "mysql -u{{ mysql_user }} -p{{ mysql_password }} -P{{ mysql_primary_port }} --protocol=tcp" - name: set fact tls_enabled command: "{{ mysql_command }} \"-e SHOW VARIABLES LIKE 'have_ssl';\"" @@ -13,7 +10,7 @@ mysql_parameters: &mysql_params login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' when: tls_enabled block: @@ -29,6 +26,7 @@ mysql_user: <<: *mysql_params name: '{{ user_name_1 }}' + host_all: true state: absent ignore_errors: yes @@ -36,6 +34,7 @@ mysql_user: <<: *mysql_params name: "{{ user_name_1 }}" + host: "%" password: "{{ user_password_1 }}" tls_requires: SSL: @@ -45,7 +44,7 @@ filter: version login_user: '{{ user_name_1 }}' login_password: '{{ user_password_1 }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' ca_cert: /tmp/cert.pem register: result @@ -54,19 +53,21 @@ - assert: that: - result is failed - when: connector_name is search('pymysql') + when: + - connector_name == 'pymysql' - assert: that: - result is succeeded - when: connector_name is not search('pymysql') + when: + - connector_name != 'pymysql' - name: attempt connection with newly created user ignoring hostname mysql_info: filter: version login_user: '{{ user_name_1 }}' login_password: '{{ user_password_1 }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' ca_cert: /tmp/cert.pem check_hostname: no @@ -81,5 +82,5 @@ mysql_user: <<: *mysql_params name: '{{ user_name_1 }}' - host: 127.0.0.1 + host_all: true 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 a5428e3..a01f915 100644 --- a/tests/integration/targets/test_mysql_info/tasks/main.yml +++ b/tests/integration/targets/test_mysql_info/tasks/main.yml @@ -1,3 +1,4 @@ +--- #################################################################### # WARNING: These are designed specifically for Ansible tests # # and should not be used as examples of how to write Ansible roles # @@ -24,14 +25,14 @@ - name: mysql_info - create default config file template: src: my.cnf.j2 - dest: /root/.my.cnf + dest: "{{ playbook_dir }}/root/.my.cnf" mode: '0400' # Create non-default MySQL config file with credentials - name: mysql_info - create non-default config file template: src: my.cnf.j2 - dest: /root/non-default_my.cnf + dest: "{{ playbook_dir }}/root/non-default_my.cnf" mode: '0400' ############### @@ -43,17 +44,18 @@ login_user: '{{ mysql_user }}' login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' + config_file: "{{ playbook_dir }}/root/.my.cnf" register: result - assert: that: - - result is not changed - - "mysql_version in result.version.full or mariadb_version in result.version.full" - - result.settings != {} - - result.global_status != {} - - result.databases != {} - - result.engines != {} - - result.users != {} + - result is not changed + - db_version in result.version.full + - result.settings != {} + - result.global_status != {} + - result.databases != {} + - result.engines != {} + - result.users != {} - name: mysql_info - Test connector informations display ansible.builtin.import_tasks: @@ -65,7 +67,7 @@ login_user: '{{ mysql_user }}' login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' - config_file: /root/non-default_my.cnf + config_file: "{{ playbook_dir }}/root/non-default_my.cnf" register: result - assert: @@ -78,9 +80,9 @@ file: path: '{{ item }}' state: absent - with_items: - - /root/.my.cnf - - /root/non-default_my.cnf + loop: + - "{{ playbook_dir }}/.my.cnf" + - "{{ playbook_dir }}/non-default_my.cnf" # Access with password - name: mysql_info - check access with password diff --git a/tests/integration/targets/test_mysql_query/defaults/main.yml b/tests/integration/targets/test_mysql_query/defaults/main.yml index 4ee25ff..6befdcf 100644 --- a/tests/integration/targets/test_mysql_query/defaults/main.yml +++ b/tests/integration/targets/test_mysql_query/defaults/main.yml @@ -1,5 +1,6 @@ mysql_user: root mysql_password: msandbox +mysql_host: '{{ gateway_addr }}' mysql_primary_port: 3307 db_name: data diff --git a/tests/integration/targets/test_mysql_query/meta/main.yml b/tests/integration/targets/test_mysql_query/meta/main.yml index ce08dc4..01ee3db 100644 --- a/tests/integration/targets/test_mysql_query/meta/main.yml +++ b/tests/integration/targets/test_mysql_query/meta/main.yml @@ -1,2 +1,3 @@ +--- dependencies: -- setup_mysql + - setup_controller diff --git a/tests/integration/targets/test_mysql_query/tasks/issue-28.yml b/tests/integration/targets/test_mysql_query/tasks/issue-28.yml index a61e07f..e788fea 100644 --- a/tests/integration/targets/test_mysql_query/tasks/issue-28.yml +++ b/tests/integration/targets/test_mysql_query/tasks/issue-28.yml @@ -1,7 +1,4 @@ --- -- name: alias mysql command to include default options - set_fact: - mysql_command: "mysql -u{{ mysql_user }} -p{{ mysql_password }} -P{{ mysql_primary_port }} --protocol=tcp" - name: set fact tls_enabled command: "{{ mysql_command }} \"-e SHOW VARIABLES LIKE 'have_ssl';\"" @@ -13,7 +10,7 @@ mysql_parameters: &mysql_params login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' when: tls_enabled block: @@ -29,6 +26,7 @@ mysql_user: <<: *mysql_params name: '{{ user_name_1 }}' + host_all: true state: absent ignore_errors: yes @@ -36,6 +34,7 @@ mysql_user: <<: *mysql_params name: "{{ user_name_1 }}" + host: "%" password: "{{ user_password_1 }}" tls_requires: SSL: @@ -45,7 +44,7 @@ query: 'SHOW DATABASES' login_user: '{{ user_name_1 }}' login_password: '{{ user_password_1 }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' ca_cert: /tmp/cert.pem register: result @@ -54,19 +53,21 @@ - assert: that: - result is failed - when: connector_name is search('pymysql') + when: + - connector_name == 'pymysql' - assert: that: - result is succeeded - when: connector_name is not search('pymysql') + when: + - connector_name != 'pymysql' - name: attempt connection with newly created user ignoring hostname mysql_query: query: 'SHOW DATABASES' login_user: '{{ user_name_1 }}' login_password: '{{ user_password_1 }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' ca_cert: /tmp/cert.pem check_hostname: no @@ -81,5 +82,5 @@ mysql_user: <<: *mysql_params name: '{{ user_name_1 }}' - host: 127.0.0.1 + host: "%" state: absent diff --git a/tests/integration/targets/test_mysql_query/tasks/mysql_query_initial.yml b/tests/integration/targets/test_mysql_query/tasks/mysql_query_initial.yml index cbb7b53..d97c554 100644 --- a/tests/integration/targets/test_mysql_query/tasks/mysql_query_initial.yml +++ b/tests/integration/targets/test_mysql_query/tasks/mysql_query_initial.yml @@ -1,3 +1,4 @@ +--- # Test code for mysql_query module # Copyright: (c) 2020, Andrew Klychkov (@Andersson007) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) @@ -5,7 +6,7 @@ mysql_parameters: &mysql_params login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' block: @@ -16,7 +17,8 @@ query: 'CREATE DATABASE {{ test_db }}' register: result - - assert: + - name: Assert that create db test_db is changed and returns expected query + assert: that: - result is changed - result.executed_queries == ['CREATE DATABASE {{ test_db }}'] @@ -28,7 +30,8 @@ query: 'CREATE TABLE {{ test_table1 }} (id int)' register: result - - assert: + - name: Assert that create table test_table1 is changed and returns expected query + assert: that: - result is changed - result.executed_queries == ['CREATE TABLE {{ test_table1 }} (id int)'] @@ -38,12 +41,13 @@ <<: *mysql_params login_db: '{{ test_db }}' query: - - 'INSERT INTO {{ test_table1 }} VALUES (1), (2)' - - 'INSERT INTO {{ test_table1 }} VALUES (3)' + - 'INSERT INTO {{ test_table1 }} VALUES (1), (2)' + - 'INSERT INTO {{ test_table1 }} VALUES (3)' single_transaction: yes register: result - - assert: + - name: Assert that inserting test data is changed and returns expected query and results + assert: that: - result is changed - result.rowcount == [2, 1] @@ -56,7 +60,8 @@ query: 'SELECT * FROM {{ test_table1 }}' register: result - - assert: + - name: Assert that query data in test_table1 is not changed and returns expected query and results + assert: that: - result is not changed - result.executed_queries == ['SELECT * FROM {{ test_table1 }}'] @@ -74,7 +79,8 @@ - 1 register: result - - assert: + - name: Assert that query data in test_table1 using positional args is not changed and returns expected query and results + assert: that: - result is not changed - result.executed_queries == ["SELECT * FROM {{ test_table1 }} WHERE id = 1"] @@ -90,7 +96,8 @@ some_id: 1 register: result - - assert: + - name: Assert that query data in test_table1 using named args is not changed and returns expected query and results + assert: that: - result is not changed - result.executed_queries == ["SELECT * FROM {{ test_table1 }} WHERE id = 1"] @@ -107,7 +114,8 @@ new_id: 0 register: result - - assert: + - name: Assert that update data in test_table1 is changed and returns the expected query + assert: that: - result is changed - result.executed_queries == ['UPDATE {{ test_table1 }} SET id = 0 WHERE id = 1'] @@ -122,7 +130,8 @@ some_id: 1 register: result - - assert: + - name: Assert that query that check the prev update is not changed and returns the expected query with id = 1 + assert: that: - result is not changed - result.executed_queries == ['SELECT * FROM {{ test_table1 }} WHERE id = 1'] @@ -137,7 +146,8 @@ some_id: 0 register: result - - assert: + - name: Assert that query that check the prev update is not changed and returns the expected query with id = 0 + assert: that: - result is not changed - result.executed_queries == ['SELECT * FROM {{ test_table1 }} WHERE id = 0'] @@ -153,7 +163,8 @@ new_id: 0 register: result - - assert: + - name: Assert that update data in test_table1 again is not changed and returns expected query + assert: that: - result is not changed - result.executed_queries == ['UPDATE {{ test_table1 }} SET id = 0 WHERE id = 1'] @@ -168,7 +179,8 @@ - 'SELECT * FROM {{ test_table1 }} WHERE id = 0' register: result - - assert: + - name: Assert that delete data from test_table1 is changed an returns expected query + assert: that: - result is changed - result.executed_queries == ['DELETE FROM {{ test_table1 }} WHERE id = 0', 'SELECT * FROM {{ test_table1 }} WHERE id = 0'] @@ -181,7 +193,8 @@ query: 'DELETE FROM {{ test_table1 }} WHERE id = 0' register: result - - assert: + - name: Assert that delete data from test_table1 again is not changed and returns expected query + assert: that: - result is not changed - result.executed_queries == ['DELETE FROM {{ test_table1 }} WHERE id = 0'] @@ -192,11 +205,12 @@ <<: *mysql_params login_db: '{{ test_db }}' query: - - 'TRUNCATE {{ test_table1 }}' - - 'SELECT * FROM {{ test_table1 }}' + - 'TRUNCATE {{ test_table1 }}' + - 'SELECT * FROM {{ test_table1 }}' register: result - - assert: + - name: Assert that truncate test_table1 is changed and returns expected query + assert: that: - result is changed - result.executed_queries == ['TRUNCATE {{ test_table1 }}', 'SELECT * FROM {{ test_table1 }}'] @@ -209,7 +223,8 @@ query: 'RENAME TABLE {{ test_table1 }} TO {{ test_table2 }}' register: result - - assert: + - name: Assert that rename table test_table1 is changed and returns expected query + assert: that: - result is changed - result.executed_queries == ['RENAME TABLE {{ test_table1 }} TO {{ test_table2 }}'] @@ -223,7 +238,8 @@ register: result ignore_errors: yes - - assert: + - name: Assert that query old table is failed + assert: that: - result is failed @@ -234,7 +250,8 @@ query: 'SELECT * FROM {{ test_table2 }}' register: result - - assert: + - name: Assert that query new table succeed and returns 0 row + assert: that: - result.rowcount == [0] @@ -257,7 +274,8 @@ query: 'SELECT id, story FROM {{ test_table3 }}' register: result - - assert: + - name: Assert that select from test_table3 returns 2 rows + assert: that: - result.rowcount == [2] @@ -269,7 +287,8 @@ register: result ignore_errors: yes - - assert: + - name: Assert that pass wrong query type is failed + assert: that: - result is failed - result.msg is search('the query option value must be a string or list') @@ -284,7 +303,8 @@ register: result ignore_errors: yes - - assert: + - name: Assert that pass wrong query element is failed + assert: that: - result is failed - result.msg is search('the elements in query list must be strings') @@ -303,7 +323,8 @@ single_transaction: yes register: result - - assert: + - name: Assert that insert test data using replace statement is changed + assert: that: - result is changed - result.rowcount == [1] @@ -339,20 +360,24 @@ register: result # Issue https://github.com/ansible-collections/community.mysql/issues/268 - - assert: + - name: Assert that create table IF NOT EXISTS is not changed with pymysql + assert: that: # PyMySQL driver throws a warning, so the following is correct - result is not changed - when: connector_name is search('pymysql') + when: + - connector_name == 'pymysql' # Issue https://github.com/ansible-collections/community.mysql/issues/268 - - assert: + - name: Assert that create table IF NOT EXISTS is changed with mysqlclient + assert: that: - # mysqlclient driver throws nothing, so it's impossible to figure out - # if the state was changed or not. - # We assume that it was for DDL queryes by default in the code + # Mysqlclient 2.0.1, driver throws nothing with mysql, so it's + # impossible to figure out if the state was changed or not. + # We assume that it was for DDL queries by default in the code - result is changed - when: connector_name is search('mysqlclient') + when: + - connector_name == 'mysqlclient' - name: Drop db {{ test_db }} mysql_query: @@ -360,7 +385,15 @@ query: 'DROP DATABASE {{ test_db }}' register: result - - assert: + - name: Assert that drop database is changed and returns expected query + assert: that: - result is changed - result.executed_queries == ['DROP DATABASE {{ test_db }}'] + + always: + + - name: Clean up test_db + mysql_query: + <<: *mysql_params + query: 'DROP DATABASE IF EXISTS {{ test_db }}' diff --git a/tests/integration/targets/test_mysql_replication/defaults/main.yml b/tests/integration/targets/test_mysql_replication/defaults/main.yml index d2d2080..48fd560 100644 --- a/tests/integration/targets/test_mysql_replication/defaults/main.yml +++ b/tests/integration/targets/test_mysql_replication/defaults/main.yml @@ -1,6 +1,6 @@ mysql_user: root mysql_password: msandbox -mysql_host: 127.0.0.1 +mysql_host: '{{ gateway_addr }}' mysql_primary_port: 3307 mysql_replica1_port: 3308 mysql_replica2_port: 3309 diff --git a/tests/integration/targets/test_mysql_replication/meta/main.yml b/tests/integration/targets/test_mysql_replication/meta/main.yml index 36e111c..01ee3db 100644 --- a/tests/integration/targets/test_mysql_replication/meta/main.yml +++ b/tests/integration/targets/test_mysql_replication/meta/main.yml @@ -1,3 +1,3 @@ --- dependencies: -- setup_mysql + - setup_controller diff --git a/tests/integration/targets/test_mysql_replication/tasks/issue-265.yml b/tests/integration/targets/test_mysql_replication/tasks/issue-265.yml index 24232f3..1718b99 100644 --- a/tests/integration/targets/test_mysql_replication/tasks/issue-265.yml +++ b/tests/integration/targets/test_mysql_replication/tasks/issue-265.yml @@ -1,13 +1,10 @@ --- -- name: alias mysql command to include default options - set_fact: - mysql_command: "mysql -u{{ mysql_user }} -p{{ mysql_password }} --protocol=tcp" - vars: mysql_parameters: &mysql_params login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' block: @@ -29,6 +26,7 @@ mysql_user: <<: *mysql_params name: '{{ user_name_1 }}' + host: '{{ gateway_addr }}' state: absent ignore_errors: yes @@ -38,6 +36,7 @@ mysql_user: <<: *mysql_params name: "{{ user_name_1 }}" + host: '{{ gateway_addr }}' password: "{{ user_password_1 }}" priv: '*.*:ALL,GRANT' force_context: yes @@ -47,7 +46,7 @@ mode: getprimary login_user: '{{ user_name_1 }}' login_password: '{{ user_password_1 }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_replica1_port }}' register: result ignore_errors: yes @@ -60,6 +59,7 @@ mysql_user: <<: *mysql_params name: '{{ user_name_1 }}' + host: '{{ gateway_addr }}' state: absent force_context: yes @@ -68,7 +68,7 @@ mode: getprimary login_user: '{{ user_name_1 }}' login_password: '{{ user_password_1 }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_replica1_port }}' register: result ignore_errors: yes @@ -92,12 +92,12 @@ - result.queries == ["STOP SLAVE"] or result.queries == ["STOP REPLICA"] - name: Create replication filter MySQL - shell: "echo \"CHANGE REPLICATION FILTER REPLICATE_IGNORE_DB = (mysql);\" | {{ mysql_command }} -P{{ mysql_replica1_port }}" - when: install_type == 'mysql' + shell: "echo \"CHANGE REPLICATION FILTER REPLICATE_IGNORE_DB = (mysql);\" | {{ mysql_command_wo_port }} -P{{ mysql_replica1_port }}" + when: db_engine == 'mysql' - name: Create replication filter MariaDB - shell: "echo \"SET GLOBAL replicate_ignore_db = 'mysql';\" | {{ mysql_command }} -P{{ mysql_replica1_port }}" - when: install_type == 'mariadb' + shell: "echo \"SET GLOBAL replicate_ignore_db = 'mysql';\" | {{ mysql_command_wo_port }} -P{{ mysql_replica1_port }}" + when: db_engine == 'mariadb' - name: Start replica mysql_replication: @@ -117,6 +117,7 @@ mysql_user: <<: *mysql_params name: "{{ user_name_1 }}" + host: "{{ gateway_addr }}" password: "{{ user_password_1 }}" priv: '*.*:ALL,GRANT' force_context: yes @@ -126,7 +127,7 @@ mode: getprimary login_user: '{{ user_name_1 }}' login_password: '{{ user_password_1 }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_replica1_port }}' register: result ignore_errors: yes @@ -139,6 +140,7 @@ mysql_user: <<: *mysql_params name: '{{ user_name_1 }}' + host: "{{ gateway_addr }}" state: absent force_context: yes @@ -157,9 +159,9 @@ - result.queries == ["STOP SLAVE"] or result.queries == ["STOP REPLICA"] - name: Remove replication filter MySQL - shell: "echo \"CHANGE REPLICATION FILTER REPLICATE_IGNORE_DB = ();\" | {{ mysql_command }} -P{{ mysql_replica1_port }}" - when: install_type == 'mysql' + shell: "echo \"CHANGE REPLICATION FILTER REPLICATE_IGNORE_DB = ();\" | {{ mysql_command_wo_port }} -P{{ mysql_replica1_port }}" + when: db_engine == 'mysql' - name: Remove replication filter MariaDB - shell: "echo \"SET GLOBAL replicate_ignore_db = '';\" | {{ mysql_command }} -P{{ mysql_replica1_port }}" - when: install_type == 'mariadb' + shell: "echo \"SET GLOBAL replicate_ignore_db = '';\" | {{ mysql_command_wo_port }} -P{{ mysql_replica1_port }}" + when: db_engine == 'mariadb' diff --git a/tests/integration/targets/test_mysql_replication/tasks/issue-28.yml b/tests/integration/targets/test_mysql_replication/tasks/issue-28.yml index e6333f0..4225a07 100644 --- a/tests/integration/targets/test_mysql_replication/tasks/issue-28.yml +++ b/tests/integration/targets/test_mysql_replication/tasks/issue-28.yml @@ -1,7 +1,4 @@ --- -- name: alias mysql command to include default options - set_fact: - mysql_command: "mysql -u{{ mysql_user }} -p{{ mysql_password }} -P{{ mysql_primary_port }} --protocol=tcp" - name: set fact tls_enabled command: "{{ mysql_command }} \"-e SHOW VARIABLES LIKE 'have_ssl';\"" @@ -13,7 +10,7 @@ mysql_parameters: &mysql_params login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' when: tls_enabled block: @@ -29,6 +26,7 @@ mysql_user: <<: *mysql_params name: '{{ user_name_1 }}' + host_all: true state: absent ignore_errors: yes @@ -46,7 +44,7 @@ mode: getprimary login_user: '{{ user_name_1 }}' login_password: '{{ user_password_1 }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' ca_cert: /tmp/cert.pem register: result @@ -55,19 +53,21 @@ - assert: that: - result is failed - when: connector_name is search('pymysql') + when: + - connector_name == 'pymysql' - assert: that: - result is succeeded - when: connector_name is not search('pymysql') + when: + - connector_name != 'pymysql' - name: attempt connection with newly created user ignoring hostname mysql_replication: mode: getprimary login_user: '{{ user_name_1 }}' login_password: '{{ user_password_1 }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' ca_cert: /tmp/cert.pem check_hostname: no @@ -82,5 +82,5 @@ mysql_user: <<: *mysql_params name: '{{ user_name_1 }}' - host: 127.0.0.1 + host: '{{ gateway_addr }}' 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 044787a..1574921 100644 --- a/tests/integration/targets/test_mysql_replication/tasks/main.yml +++ b/tests/integration/targets/test_mysql_replication/tasks/main.yml @@ -18,7 +18,8 @@ # Tests of channel parameter: - import_tasks: mysql_replication_channel.yml when: - - install_type == 'mysql' # FIXME: mariadb introduces FOR CHANNEL in 10.7 + - db_engine == 'mysql' # FIXME: mariadb introduces FOR CHANNEL in 10.7 + - mysql8022_and_higher == true # FIXME: mysql 5.7 should work, but our tets fails, why? # Tests of resetprimary mode: - import_tasks: mysql_replication_resetprimary_mode.yml diff --git a/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_channel.yml b/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_channel.yml index e314aae..f438dbf 100644 --- a/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_channel.yml +++ b/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_channel.yml @@ -1,3 +1,4 @@ +--- # Copyright: (c) 2019, Andrew Klychkov (@Andersson007) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) @@ -5,7 +6,7 @@ mysql_params: &mysql_params login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' block: # Get primary log file and log pos: 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 78206fc..1dd4c88 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 @@ -1,3 +1,4 @@ +--- # Copyright: (c) 2019, Andrew Klychkov (@Andersson007) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) @@ -5,16 +6,9 @@ mysql_params: &mysql_params login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' block: - - name: find out the database version - mysql_info: - <<: *mysql_params - login_port: '{{ mysql_primary_port }}' - filter: version - register: db - - name: Set mysql8022_and_higher set_fact: mysql8022_and_higher: false @@ -23,21 +17,31 @@ set_fact: mysql8022_and_higher: true when: - - db.version.major > 8 or (db.version.major == 8 and db.version.minor > 0) or (db.version.major == 8 and db.version.minor == 0 and db.version.release >= 22) - - install_type == 'mysql' + - db_engine == 'mysql' + - db_version is version('8.0.22', '>=') - - name: alias mysql command to include default options - set_fact: - mysql_command: "mysql -u{{ mysql_user }} -p{{ mysql_password }} --protocol=tcp" - - # Preparation: + # We use iF NOT EXISTS because the GITHUB Action: + # "ansible-community/ansible-test-gh-action" uses "--retry-on-error". + # If test_mysql_replication fails, test will run again an without the IF + # NOT EXISTS, we see "Error 1396 (HY000): Operation CREATE USER failed..." + # which is misleading. - name: Create user for mysql replication - shell: "echo \"CREATE USER '{{ replication_user }}'@'localhost' IDENTIFIED WITH mysql_native_password BY '{{ replication_pass }}'; GRANT REPLICATION SLAVE ON *.* TO '{{ replication_user }}'@'localhost';\" | {{ mysql_command }} -P{{ mysql_primary_port }}" - when: install_type == 'mysql' + shell: + "echo \"CREATE USER IF NOT EXISTS \ + '{{ replication_user }}'@'{{ mysql_host }}' \ + IDENTIFIED WITH mysql_native_password BY '{{ replication_pass }}'; \ + GRANT REPLICATION SLAVE ON *.* TO \ + '{{ replication_user }}'@'{{ mysql_host }}';\" | {{ mysql_command }}" + when: db_engine == 'mysql' - name: Create user for mariadb replication - shell: "echo \"CREATE USER '{{ replication_user }}'@'localhost' IDENTIFIED BY '{{ replication_pass }}'; GRANT REPLICATION SLAVE ON *.* TO '{{ replication_user }}'@'localhost';\" | {{ mysql_command }} -P{{ mysql_primary_port }}" - when: install_type == 'mariadb' + shell: + "echo \"CREATE USER IF NOT EXISTS \ + '{{ replication_user }}'@'{{ mysql_host }}' \ + IDENTIFIED BY '{{ replication_pass }}'; \ + GRANT REPLICATION SLAVE ON *.* TO \ + '{{ replication_user }}'@'{{ mysql_host }}';\" | {{ mysql_command }}" + when: db_engine == 'mariadb' - name: Create test database mysql_db: @@ -47,13 +51,31 @@ name: '{{ test_db }}' - name: Dump all databases from the primary - shell: 'mysqldump -u{{ mysql_user }} -p{{ mysql_password }} -h{{ mysql_host }} --protocol=tcp -P{{ mysql_primary_port }} --all-databases --ignore-table=mysql.innodb_index_stats --ignore-table=mysql.innodb_table_stats --master-data=2 > {{ dump_path }}' + shell: + cmd: >- + mysqldump + -u{{ mysql_user }} + -p{{ mysql_password }} + -h{{ mysql_host }} + -P{{ mysql_primary_port }} + --protocol=tcp + --all-databases + --ignore-table=mysql.innodb_index_stats + --ignore-table=mysql.innodb_table_stats + --master-data=2 + > {{ dump_path }} - name: Restore the dump to replica1 - shell: '{{ mysql_command }} -P{{ mysql_replica1_port }} < {{ dump_path }}' + shell: + cmd: >- + {{ mysql_command_wo_port }} + -P{{ mysql_replica1_port }} < {{ dump_path }} - name: Restore the dump to replica2 - shell: '{{ mysql_command }} -P{{ mysql_replica2_port }} < {{ dump_path }}' + shell: + cmd: >- + {{ mysql_command_wo_port }} + -P{{ mysql_replica2_port }} < {{ dump_path }} # Test getprimary mode: - name: Get primary status @@ -63,11 +85,12 @@ mode: getprimary register: mysql_primary_status - - assert: + - name: Assert that primary is in expected state + assert: that: - - mysql_primary_status.Is_Primary == true - - mysql_primary_status.Position != 0 - - mysql_primary_status is not changed + - mysql_primary_status.Is_Primary == true + - mysql_primary_status.Position != 0 + - mysql_primary_status is not changed # Test startreplica fails without changeprimary first. This needs fail_on_error - name: Start replica and fail because primary is not specified; failing on error as requested @@ -80,9 +103,10 @@ register: result ignore_errors: yes - - assert: + - name: Assert that startreplica is failed + assert: that: - - result is failed + - result is failed # Test startreplica doesn't fail if fail_on_error: no - name: Start replica and fail without propagating it to ansible as we were asked not to @@ -93,9 +117,10 @@ fail_on_error: no register: result - - assert: + - name: Assert that startreplica succeeded + assert: that: - - result is not failed + - result is not failed # Test startreplica doesn't fail if there is no fail_on_error. # This is suboptimal because nothing happens, but it's the old behavior. @@ -106,9 +131,10 @@ mode: startreplica register: result - - assert: + - name: Assert that start replica succeeded again + assert: that: - - result is not failed + - result is not failed # Test changeprimary mode: # primary_ssl_ca will be set as '' to check the module's behaviour for #23976, @@ -128,10 +154,11 @@ primary_ssl: no register: result - - assert: + - name: Assert that changeprimmary is changed and return expected query + assert: that: - - result is changed - - result.queries == ["CHANGE MASTER TO MASTER_HOST='{{ mysql_host }}',MASTER_USER='{{ replication_user }}',MASTER_PASSWORD='********',MASTER_PORT={{ mysql_primary_port }},MASTER_LOG_FILE='{{ mysql_primary_status.File }}',MASTER_LOG_POS={{ mysql_primary_status.Position }},MASTER_SSL=0,MASTER_SSL_CA=''"] + - result is changed + - result.queries == ["CHANGE MASTER TO MASTER_HOST='{{ mysql_host }}',MASTER_USER='{{ replication_user }}',MASTER_PASSWORD='********',MASTER_PORT={{ mysql_primary_port }},MASTER_LOG_FILE='{{ mysql_primary_status.File }}',MASTER_LOG_POS={{ mysql_primary_status.Position }},MASTER_SSL=0,MASTER_SSL_CA=''"] # Test startreplica mode: - name: Start replica @@ -141,10 +168,11 @@ mode: startreplica register: result - - assert: + - name: Assert that startreplica is changed and returns expected query + assert: that: - - result is changed - - result.queries == ["START SLAVE"] or result.queries == ["START REPLICA"] + - result is changed + - result.queries == ["START SLAVE"] or result.queries == ["START REPLICA"] # Test getreplica mode: - name: Get replica status @@ -154,34 +182,36 @@ mode: getreplica register: replica_status - - assert: + - name: Assert that getreplica returns expected values for MySQL older than 8.0.22 and Mariadb + assert: that: - - replica_status.Is_Replica == true - - replica_status.Master_Host == '{{ mysql_host }}' - - replica_status.Exec_Master_Log_Pos == mysql_primary_status.Position - - replica_status.Master_Port == {{ mysql_primary_port }} - - replica_status.Last_IO_Errno == 0 - - replica_status.Last_IO_Error == '' - - replica_status is not changed + - replica_status.Is_Replica == true + - replica_status.Master_Host == '{{ mysql_host }}' + - replica_status.Exec_Master_Log_Pos == mysql_primary_status.Position + - replica_status.Master_Port == {{ mysql_primary_port }} + - replica_status.Last_IO_Errno == 0 + - replica_status.Last_IO_Error == '' + - replica_status is not changed when: mysql8022_and_higher == false - - assert: + - name: Assert that getreplica returns expected values for MySQL newer than 8.0.22 + assert: that: - - replica_status.Is_Replica == true - - replica_status.Source_Host == '{{ mysql_host }}' - - replica_status.Exec_Source_Log_Pos == mysql_primary_status.Position - - replica_status.Source_Port == {{ mysql_primary_port }} - - replica_status.Last_IO_Errno == 0 - - replica_status.Last_IO_Error == '' - - replica_status is not changed + - replica_status.Is_Replica == true + - replica_status.Source_Host == '{{ mysql_host }}' + - replica_status.Exec_Source_Log_Pos == mysql_primary_status.Position + - replica_status.Source_Port == {{ mysql_primary_port }} + - replica_status.Last_IO_Errno == 0 + - replica_status.Last_IO_Error == '' + - replica_status is not changed when: mysql8022_and_higher == true # Create test table and add data to it: - name: Create test table - shell: "echo \"CREATE TABLE {{ test_table }} (id int);\" | {{ mysql_command }} -P{{ mysql_primary_port }} {{ test_db }}" + shell: "echo \"CREATE TABLE {{ test_table }} (id int);\" | {{ mysql_command_wo_port }} -P{{ mysql_primary_port }} {{ test_db }}" - name: Insert data - shell: "echo \"INSERT INTO {{ test_table }} (id) VALUES (1), (2), (3); FLUSH LOGS;\" | {{ mysql_command }} -P{{ mysql_primary_port }} {{ test_db }}" + shell: "echo \"INSERT INTO {{ test_table }} (id) VALUES (1), (2), (3); FLUSH LOGS;\" | {{ mysql_command_wo_port }} -P{{ mysql_primary_port }} {{ test_db }}" - name: Small pause to be sure the bin log, which was flushed previously, reached the replica ansible.builtin.wait_for: @@ -197,19 +227,18 @@ # mysql_primary_status.Position is not actual and it has been changed by the prev step, # so replica_status.Exec_Master_Log_Pos must be different: - - assert: + - name: Assert that getreplica Log_Pos is different for MySQL older than 8.0.22 and MariaDB + assert: that: - - replica_status.Exec_Master_Log_Pos != mysql_primary_status.Position + - replica_status.Exec_Master_Log_Pos != mysql_primary_status.Position when: mysql8022_and_higher == false - - assert: + - name: Assert that getreplica Log_Pos is different for MySQL newer than 8.0.22 + assert: that: - - replica_status.Exec_Source_Log_Pos != mysql_primary_status.Position + - replica_status.Exec_Source_Log_Pos != mysql_primary_status.Position when: mysql8022_and_higher == true - - shell: pip show pymysql | awk '/Version/ {print $2}' - register: pymysql_version - - name: Start replica that is already running mysql_replication: <<: *mysql_params @@ -219,7 +248,8 @@ register: result # mysqlclient 2.0.1 always return "changed" - - assert: + - name: Assert that startreplica is not changed + assert: that: - result is not changed when: @@ -233,10 +263,11 @@ mode: stopreplica register: result - - assert: + - name: Assert that stopreplica is changed and returns expected query + assert: that: - - result is changed - - result.queries == ["STOP SLAVE"] or result.queries == ["STOP REPLICA"] + - result is changed + - result.queries == ["STOP SLAVE"] or result.queries == ["STOP REPLICA"] - name: Pause for 2 seconds to let the replication stop ansible.builtin.wait_for: @@ -252,7 +283,8 @@ fail_on_error: true register: result - - assert: + - name: Assert that stopreplica is not changed + assert: that: - result is not changed when: @@ -269,7 +301,8 @@ register: result ignore_errors: yes - - assert: + - name: Assert that stopslave returns expected error message + assert: that: - - result.msg == "value of mode must be one of{{ ":" }} getprimary, getreplica, changeprimary, stopreplica, startreplica, resetprimary, resetreplica, resetreplicaall, got{{ ":" }} stopslave" - - result is failed + - result.msg == "value of mode must be one of{{ ":" }} getprimary, getreplica, changeprimary, stopreplica, startreplica, resetprimary, resetreplica, resetreplicaall, got{{ ":" }} stopslave" + - result is failed diff --git a/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_primary_delay.yml b/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_primary_delay.yml index ecdcc81..5e967e8 100644 --- a/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_primary_delay.yml +++ b/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_primary_delay.yml @@ -5,7 +5,7 @@ mysql_params: &mysql_params login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' block: diff --git a/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_resetprimary_mode.yml b/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_resetprimary_mode.yml index a4ed75e..4bccc76 100644 --- a/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_resetprimary_mode.yml +++ b/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_resetprimary_mode.yml @@ -5,7 +5,7 @@ mysql_params: &mysql_params login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' block: diff --git a/tests/integration/targets/test_mysql_role/defaults/main.yml b/tests/integration/targets/test_mysql_role/defaults/main.yml index 544f098..62dc5f1 100644 --- a/tests/integration/targets/test_mysql_role/defaults/main.yml +++ b/tests/integration/targets/test_mysql_role/defaults/main.yml @@ -1,18 +1,5 @@ +--- mysql_user: root mysql_password: msandbox +mysql_host: '{{ gateway_addr }}' mysql_primary_port: 3307 - -test_db: test_db -test_table: test_table -test_db1: test_db1 -test_db2: test_db2 - -user0: user0 -user1: user1 -user2: user2 -nonexistent: user3 - -role0: role0 -role1: role1 -role2: role2 -role3: role3 \ No newline at end of file diff --git a/tests/integration/targets/test_mysql_role/meta/main.yml b/tests/integration/targets/test_mysql_role/meta/main.yml index ce08dc4..01ee3db 100644 --- a/tests/integration/targets/test_mysql_role/meta/main.yml +++ b/tests/integration/targets/test_mysql_role/meta/main.yml @@ -1,2 +1,3 @@ +--- dependencies: -- setup_mysql + - setup_controller diff --git a/tests/integration/targets/test_mysql_role/tasks/main.yml b/tests/integration/targets/test_mysql_role/tasks/main.yml index 952bf6f..c3c9bd3 100644 --- a/tests/integration/targets/test_mysql_role/tasks/main.yml +++ b/tests/integration/targets/test_mysql_role/tasks/main.yml @@ -3,13 +3,12 @@ # and should not be used as examples of how to write Ansible roles # #################################################################### -- name: alias mysql command to include default options - set_fact: - mysql_command: "mysql -u{{ mysql_user }} -p{{ mysql_password }} -P{{ mysql_primary_port }} --protocol=tcp" - - # mysql_role module initial CI tests -- import_tasks: mysql_role_initial.yml +# TODO, many tests fails with MariaDB, debug them then remove the +# when clause and swap include_tasks for import_tasks. +- include_tasks: mysql_role_initial.yml + when: + - db_engine == 'mysql' # Test that subtract_privs will only revoke the grants given by priv # (https://github.com/ansible-collections/community.mysql/issues/331) diff --git a/tests/integration/targets/test_mysql_role/tasks/mysql_role_initial.yml b/tests/integration/targets/test_mysql_role/tasks/mysql_role_initial.yml index 36f2418..3762df9 100644 --- a/tests/integration/targets/test_mysql_role/tasks/mysql_role_initial.yml +++ b/tests/integration/targets/test_mysql_role/tasks/mysql_role_initial.yml @@ -1,15 +1,13 @@ +--- # Test code for mysql_role module - vars: mysql_parameters: &mysql_params login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' - task_parameters: &task_params - register: result - block: - name: Get server version @@ -18,1323 +16,1338 @@ register: srv - name: When run with unsupported server versions, must fail - <<: *task_params mysql_role: <<: *mysql_params name: test + register: result ignore_errors: yes - name: Must fail when meet unsupported version assert: that: - - result is failed - - result is search('Roles are not supported by the server') + - result is failed + - result is search('Roles are not supported by the server') when: - - srv['version']['major'] < 8 + - srv['version']['major'] < 8 - # Skip unsupported versions - - meta: end_play + - name: Skip unsupported versions + meta: end_play when: srv['version']['major'] < 8 ######### # Prepare - - name: Create db {{ test_db }} - <<: *task_params + - name: Create db test_db mysql_db: <<: *mysql_params - name: '{{ test_db }}' + name: 'test_db' + register: result - - name: Create table {{ test_table }} - <<: *task_params + - name: Create table test_table mysql_query: <<: *mysql_params - login_db: '{{ test_db }}' - query: 'DROP TABLE IF EXISTS {{ test_table }}' + login_db: 'test_db' + query: 'DROP TABLE IF EXISTS test_table' + register: result - - name: Create table {{ test_table }} - <<: *task_params + - name: Create table test_table mysql_query: <<: *mysql_params - login_db: '{{ test_db }}' - query: 'CREATE TABLE IF NOT EXISTS {{ test_table }} (id int)' + login_db: 'test_db' + query: 'CREATE TABLE IF NOT EXISTS test_table (id int)' + register: result - name: Create users - <<: *task_params mysql_user: <<: *mysql_params name: '{{ item }}' + host: '%' password: '{{ mysql_password }}' loop: - - '{{ user0 }}' - - '{{ user1 }}' - - '{{ user2 }}' + - 'user0' + - 'user1' + - 'user2' ########### # Run tests - - name: Create role {{ role0 }} in check_mode - <<: *task_params + - name: Create role0 in check_mode mysql_role: <<: *mysql_params - name: '{{ role0 }}' + name: 'role0' state: present members: - - '{{ user0 }}@localhost' + - 'user0@%' + register: result check_mode: yes - - name: Check + - name: Assert that create role0 is changed assert: that: - - result is changed + - result is changed - name: Check in DB - <<: *task_params mysql_query: <<: *mysql_params - query: "SELECT 1 FROM mysql.user WHERE User = '{{ role0 }}'" + query: "SELECT 1 FROM mysql.user WHERE User = 'role0'" + register: result - - name: Check + - name: Assert that user is not in mysql.user assert: that: - - result.rowcount.0 == 0 + - result.rowcount.0 == 0 # It must fail because of check_mode - - name: Check in DB, if not granted, the query will fail - <<: *task_params + - name: Check in DB, if not granted, the query will fail (expect failure) mysql_query: <<: *mysql_params - query: "SHOW GRANTS FOR {{ user0 }}@localhost USING '{{ role0 }}'" + query: "SHOW GRANTS FOR user0@'%' USING 'role0'" + register: result ignore_errors: yes - when: install_type == 'mysql' + when: db_engine == 'mysql' - - name: Check + - name: Assert that show grants is failed assert: that: - - result is failed - when: install_type == 'mysql' + - result is failed + when: db_engine == 'mysql' - name: Check in DB (mariadb) - <<: *task_params mysql_query: <<: *mysql_params - query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = '{{ user0 }}' AND Host = 'localhost' AND Role = '{{ role0 }}'" - when: install_type == 'mariadb' + query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = 'user0' AND Host = '%' AND Role = 'role0'" + register: result + when: db_engine == 'mariadb' - - name: Check (mariadb) + - name: Assert that user is not in mysql.roles_mapping (mariadb) assert: that: - - result.query_result.0.0['user_roles'] == 0 - when: install_type == 'mariadb' + - result.query_result.0.0['user_roles'] == 0 + when: db_engine == 'mariadb' - #===================== + # ===================== - name: Check that the user have no active roles - <<: *task_params mysql_query: - login_user: '{{ user0 }}' + login_user: 'user0' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' query: 'SELECT COALESCE(current_role(), "NONE") as "current_role()"' + register: result - - name: Check + - name: Assert that the user have no active roles assert: that: - - result.query_result.0.0["current_role()"] == "NONE" + - result.query_result.0.0["current_role()"] == "NONE" - - name: Create role {{ role0 }} - <<: *task_params + - name: Create role role0 mysql_role: <<: *mysql_params - name: '{{ role0 }}' + name: 'role0' state: present members: - - '{{ user0 }}@localhost' + - 'user0@%' + register: result - - name: Check + - name: Assert that create role is changed assert: that: - - result is changed + - result is changed - name: Check in DB - <<: *task_params mysql_query: <<: *mysql_params - query: "SELECT 1 FROM mysql.user WHERE User = '{{ role0 }}'" + query: "SELECT 1 FROM mysql.user WHERE User = 'role0'" + register: result - - name: Check + - name: Assert that role0 is in mysql.user assert: that: - - result.rowcount.0 == 1 + - result.rowcount.0 == 1 - - name: Check in DB, if not granted, the query will fail - <<: *task_params + - name: Query role0, if not granted, the query will fail mysql_query: <<: *mysql_params - query: "SHOW GRANTS FOR {{ user0 }}@localhost USING '{{ role0 }}'" - when: install_type == 'mysql' + query: "SHOW GRANTS FOR user0@'%' USING 'role0'" + register: result + when: db_engine == 'mysql' - - name: Check + - name: Assert that show grants is succeeded (mysql) assert: that: - - result is succeeded - when: install_type == 'mysql' + - result is succeeded + when: db_engine == 'mysql' - name: Check in DB (mariadb) - <<: *task_params mysql_query: <<: *mysql_params - query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = '{{ user0 }}' AND Host = 'localhost' AND Role = '{{ role0 }}'" - when: install_type == 'mariadb' + query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = 'user0' AND Host = '%' AND Role = 'role0'" + register: result + when: db_engine == 'mariadb' - - name: Check (mariadb) + - name: Assert that role is in mysql.roles_mapping (mariadb) assert: that: - - result.query_result.0.0['user_roles'] == 1 - when: install_type == 'mariadb' + - result.query_result.0.0['user_roles'] == 1 + when: db_engine == 'mariadb' - name: Check that the role is active - <<: *task_params mysql_query: - login_user: '{{ user0 }}' + login_user: 'user0' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' query: 'SELECT current_role()' - when: install_type == 'mysql' + register: result + when: db_engine == 'mysql' - - name: Check + - name: Assert that current_role() returns role0 assert: that: - - "'{{ role0 }}' in result.query_result.0.0['current_role()']" - when: install_type == 'mysql' + - "'role0' in result.query_result.0.0['current_role()']" + when: db_engine == 'mysql' - name: Check that the role is active (mariadb) - <<: *task_params mysql_query: - login_user: '{{ user0 }}' + login_user: 'user0' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' query: - - 'SET ROLE {{ role0 }}' + - 'SET ROLE role0' - 'SELECT current_role()' - when: install_type == 'mariadb' + register: result + when: db_engine == 'mariadb' - - name: Check (mariadb) + - name: Assert that role is active (mariadb) assert: that: - - "'{{ role0 }}' in result.query_result.1.0['current_role()']" - when: install_type == 'mariadb' + - "'role0' in result.query_result.1.0['current_role()']" + when: db_engine == 'mariadb' - #======================== + # ======================== - - name: Create role {{ role0 }} again in check_mode - <<: *task_params + - name: Create role role0 again in check_mode mysql_role: <<: *mysql_params - name: '{{ role0 }}' + name: 'role0' state: present + register: result check_mode: yes - - name: Check + - name: Assert that create role role0 again is not changed assert: that: - - result is not changed + - result is not changed - name: Check in DB - <<: *task_params mysql_query: <<: *mysql_params - query: "SELECT 1 FROM mysql.user WHERE User = '{{ role0 }}'" + query: "SELECT 1 FROM mysql.user WHERE User = 'role0'" + register: result - - name: Check + - name: Assert that role role0 is present in the database assert: that: - - result.rowcount.0 == 1 + - result.rowcount.0 == 1 - - name: Check in DB, if not granted, the query will fail - <<: *task_params + - name: Query role0, if not granted, the query will fail (2) mysql_query: <<: *mysql_params - query: "SHOW GRANTS FOR {{ user0 }}@localhost USING '{{ role0 }}'" - when: install_type == 'mysql' + query: "SHOW GRANTS FOR user0@'%' USING 'role0'" + register: result + when: db_engine == 'mysql' - - name: Check + - name: Assert that query for the role0 is succeeded for mysql (2) assert: that: - - result is succeeded - when: install_type == 'mysql' + - result is succeeded + when: db_engine == 'mysql' - name: Check in DB (mariadb) - <<: *task_params mysql_query: <<: *mysql_params - query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = '{{ user0 }}' AND Host = 'localhost' AND Role = '{{ role0 }}'" - when: install_type == 'mariadb' + query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = 'user0' AND Host = '%' AND Role = 'role0'" + register: result + when: db_engine == 'mariadb' - - name: Check (mariadb) + - name: Assert that query for the role0 is succeeded for mariadb assert: that: - - result.query_result.0.0['user_roles'] == 1 - when: install_type == 'mariadb' + - result.query_result.0.0['user_roles'] == 1 + when: db_engine == 'mariadb' - #======================== + # ======================== - - name: Create role {{ role0 }} again - <<: *task_params + - name: Create role0 again mysql_role: <<: *mysql_params - name: '{{ role0 }}' + name: 'role0' state: present + register: result - - name: Check + - name: Assert that create role0 again is not changed assert: that: - - result is not changed + - result is not changed - - name: Check in DB - <<: *task_params + - name: Query role0 mysql_query: <<: *mysql_params - query: "SELECT 1 FROM mysql.user WHERE User = '{{ role0 }}'" + query: "SELECT 1 FROM mysql.user WHERE User = 'role0'" + register: result - - name: Check + - name: Assert that role0 is in DB assert: that: - - result.rowcount.0 == 1 + - result.rowcount.0 == 1 - #======================== + # ======================== - - name: Drop role {{ role0 }} in check_mode - <<: *task_params + - name: Drop role0 in check_mode mysql_role: <<: *mysql_params - name: '{{ role0 }}' + name: 'role0' state: absent + register: result check_mode: yes - - name: Check + - name: Assert that drop role0 in check_mode is changed assert: that: - - result is changed + - result is changed - - name: Check in DB - <<: *task_params + - name: Query role0 mysql_query: <<: *mysql_params - query: "SELECT 1 FROM mysql.user WHERE User = '{{ role0 }}'" + query: "SELECT 1 FROM mysql.user WHERE User = 'role0'" + register: result - - name: Check + - name: Assert that role0 is in DB assert: that: - - result.rowcount.0 == 1 + - result.rowcount.0 == 1 # Must pass because of check_mode - - name: Check in DB, if not granted, the query will fail - <<: *task_params + - name: Query role0, if not granted, the query will fail (3) mysql_query: <<: *mysql_params - query: "SHOW GRANTS FOR {{ user0 }}@localhost USING '{{ role0 }}'" - when: install_type == 'mysql' + query: "SHOW GRANTS FOR user0@'%' USING 'role0'" + register: result + when: db_engine == 'mysql' - - name: Check + - name: Assert that role0 is still in mysql after drop in check_mode (3) assert: that: - - result is succeeded - when: install_type == 'mysql' + - result is succeeded + when: db_engine == 'mysql' # Must pass because of check_mode - - name: Check in DB (mariadb) - <<: *task_params + - name: Query count for user0 and role0 (mariadb) mysql_query: <<: *mysql_params - query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = '{{ user0 }}' AND Host = 'localhost' AND Role = '{{ role0 }}'" - when: install_type == 'mariadb' + query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = 'user0' AND Host = '%' AND Role = 'role0'" + register: result + when: db_engine == 'mariadb' - - name: Check (mariadb) + - name: Assert that role0 is still in mariadb after drop in check_mode assert: that: - - result.query_result.0.0['user_roles'] == 1 - when: install_type == 'mariadb' + - result.query_result.0.0['user_roles'] == 1 + when: db_engine == 'mariadb' - #======================== + # ======================== - - name: Drop role {{ role0 }} - <<: *task_params + - name: Drop role0 mysql_role: <<: *mysql_params - name: '{{ role0 }}' + name: 'role0' state: absent + register: result - - name: Check + - name: Assert that drop role0 is changed assert: that: - - result is changed + - result is changed - - name: Check in DB - <<: *task_params + - name: Query role0 mysql_query: <<: *mysql_params - query: "SELECT 1 FROM mysql.user WHERE User = '{{ role0 }}'" + query: "SELECT 1 FROM mysql.user WHERE User = 'role0'" + register: result - - name: Check + - name: Assert that role0 is absent from db assert: that: - - result.rowcount.0 == 0 + - result.rowcount.0 == 0 - - name: Check in DB, if not granted, the query will fail - <<: *task_params + - name: Query grants for role0, if not granted, the query will fail mysql_query: <<: *mysql_params - query: "SHOW GRANTS FOR {{ user0 }}@localhost USING '{{ role0 }}'" + query: "SHOW GRANTS FOR user0@'%' USING 'role0'" + register: result ignore_errors: yes - when: install_type == 'mysql' + when: db_engine == 'mysql' - - name: Check + - name: Assert that query for role0 in mysql is failed assert: that: - - result is failed - when: install_type == 'mysql' + - result is failed + when: db_engine == 'mysql' - - name: Check in DB (mariadb) - <<: *task_params + - name: Query count for user0 and role0 in mariadb mysql_query: <<: *mysql_params - query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = '{{ user0 }}' AND Host = 'localhost' AND Role = '{{ role0 }}'" + query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = 'user0' AND Host = '%' AND Role = 'role0'" + register: result ignore_errors: yes - when: install_type == 'mariadb' + when: db_engine == 'mariadb' - - name: Check (mariadb) + - name: Assert that query count for user0 and role0 in mariadb returns 0 rows assert: that: - - result.query_result.0.0['user_roles'] == 0 - when: install_type == 'mariadb' + - result.query_result.0.0['user_roles'] == 0 + when: db_engine == 'mariadb' - #======================== + # ======================== - - name: Drop role {{ role0 }} again in check_mode - <<: *task_params + - name: Drop role0 again in check_mode mysql_role: <<: *mysql_params - name: '{{ role0 }}' + name: 'role0' state: absent + register: result check_mode: yes - - name: Check + - name: Assert that drop role0 again in check_mode is not changed assert: that: - - result is not changed + - result is not changed - - name: Drop role {{ role0 }} again - <<: *task_params + - name: Drop role0 again mysql_role: <<: *mysql_params - name: '{{ role0 }}' + name: 'role0' state: absent + register: result - - name: Check + - name: Assert that drop role0 again is not changed assert: that: - - result is not changed + - result is not changed # ================== - - name: Create role {{ role0 }} in check_mode - <<: *task_params + - name: Create role0 in check_mode mysql_role: <<: *mysql_params - name: '{{ role0 }}' + name: 'role0' state: present members: - - '{{ user0 }}@localhost' + - 'user0@%' priv: '*.*': 'SELECT,INSERT' 'mysql.*': 'UPDATE' + register: result check_mode: yes - - name: Check + - name: Assert that create role0 in check_mode is changed assert: that: - - result is changed + - result is changed - - name: Check in DB - <<: *task_params + - name: Query role0 mysql_query: <<: *mysql_params - query: "SELECT 1 FROM mysql.user WHERE User = '{{ role0 }}'" + query: "SELECT 1 FROM mysql.user WHERE User = 'role0'" + register: result - - name: Check + - name: Assert that role0 created in check_mode is not in the database assert: that: - - result.rowcount.0 == 0 + - result.rowcount.0 == 0 - #======================== + # ======================== - - name: Create role {{ role0 }} - <<: *task_params + - name: Create role0 mysql_role: <<: *mysql_params - name: '{{ role0 }}' + name: 'role0' state: present members: - - '{{ user0 }}@localhost' + - 'user0@%' priv: '*.*': 'SELECT,INSERT' 'mysql.*': 'UPDATE' + register: result - - name: Check + - name: Assert that create role0 is changed assert: that: - - result is changed + - result is changed - - name: Check in DB - <<: *task_params + - name: Query role0 mysql_query: <<: *mysql_params - query: "SELECT 1 FROM mysql.user WHERE User = '{{ role0 }}'" + query: "SELECT 1 FROM mysql.user WHERE User = 'role0'" + register: result - - name: Check + - name: Assert that role0 is in the database assert: that: - - result.rowcount.0 == 1 + - result.rowcount.0 == 1 - #======================== + # ======================== - - name: Create role {{ role0 }} in check_mode again - <<: *task_params + - name: Create role0 in check_mode again mysql_role: <<: *mysql_params - name: '{{ role0 }}' + name: 'role0' state: present members: - - '{{ user0 }}@localhost' + - 'user0@%' priv: '*.*': 'SELECT,INSERT' 'mysql.*': 'UPDATE' + register: result check_mode: yes - - name: Check + # TODO fix this with mariadb. I disable this test because I'm not an + # expert with roles and I don't know if it's a correct behavior of our module + # against MariaDB or if it is a bug. We never tested MariaDB properly... + - name: Assert that create role0 in check_mode again is not changed assert: that: - - result is not changed + - result is not changed + when: + - db_engine == 'mysql' - #======================== + # ======================== - - name: Create role {{ role0 }} again - <<: *task_params + - name: Create role0 again (2) mysql_role: <<: *mysql_params - name: '{{ role0 }}' + name: 'role0' state: present members: - - '{{ user0 }}@localhost' + - 'user0@%' priv: '*.*': 'SELECT,INSERT' 'mysql.*': 'UPDATE' + register: result - - name: Check + # TODO fix this with mariadb. I disable this test because I'm not an + # expert with roles and I don't know if it's a correct behavior of our module + # against MariaDB or if it is a bug. We never tested MariaDB properly... + - name: Assert that create role0 again is not changed (2) assert: that: - - result is not changed + - result is not changed + when: + - db_engine == 'mysql' + # ############################################## # Test rewriting / appending / detaching members # ############################################## - - name: Create role {{ role1 }} - <<: *task_params + - name: Create role1 mysql_role: <<: *mysql_params - name: '{{ role1 }}' + name: 'role1' state: present + register: result # Rewriting members - name: Rewrite members in check_mode - <<: *task_params mysql_role: <<: *mysql_params - name: '{{ role0 }}' + name: 'role0' state: present members: - - '{{ user1 }}@localhost' - - '{{ user2 }}@localhost' - - '{{ role1 }}' + - 'user1@%' + - 'user2@%' + - 'role1' + register: result check_mode: yes - - name: Check - assert: - that: - - result is changed - - # user0 is still a member because of check_mode - - name: Check in DB, if not granted, the query will fail - <<: *task_params - mysql_query: - <<: *mysql_params - query: "SHOW GRANTS FOR {{ user0 }}@localhost USING '{{ role0 }}'" - when: install_type == 'mysql' - - - name: Check - assert: - that: - - result is succeeded - when: install_type == 'mysql' - - # user0 is still a member because of check_mode - - name: Check in DB (mariadb) - <<: *task_params - mysql_query: - <<: *mysql_params - query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = '{{ user0 }}' AND Host = 'localhost' AND Role = '{{ role0 }}'" - when: install_type == 'mariadb' - - - name: Check (mariadb) - assert: - that: - - result.query_result.0.0['user_roles'] == 1 - when: install_type == 'mariadb' - - # user1, user2, and role1 are not members because of check_mode - - name: Check in DB, if not granted, the query will fail - <<: *task_params - mysql_query: - <<: *mysql_params - query: "SHOW GRANTS FOR {{ user1 }}@localhost USING '{{ role0 }}'" - ignore_errors: yes - when: install_type == 'mysql' - - - name: Check - assert: - that: - - result is failed - when: install_type == 'mysql' - - - name: Check in DB (mariadb) - <<: *task_params - mysql_query: - <<: *mysql_params - query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = '{{ user1 }}' AND Host = 'localhost' AND Role = '{{ role0 }}'" - when: install_type == 'mariadb' - - - name: Check (mariadb) - assert: - that: - - result.query_result.0.0['user_roles'] == 0 - when: install_type == 'mariadb' - - - name: Check in DB, if not granted, the query will fail - <<: *task_params - mysql_query: - <<: *mysql_params - query: "SHOW GRANTS FOR {{ user2 }}@localhost USING '{{ role0 }}'" - ignore_errors: yes - when: install_type == 'mysql' - - - name: Check - assert: - that: - - result is failed - when: install_type == 'mysql' - - - name: Check in DB (mariadb) - <<: *task_params - mysql_query: - <<: *mysql_params - query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = '{{ user2 }}' AND Host = 'localhost' AND Role = '{{ role0 }}'" - when: install_type == 'mariadb' - - - name: Check (mariadb) - assert: - that: - - result.query_result.0.0['user_roles'] == 0 - when: install_type == 'mariadb' - - - name: Check in DB, if not granted, the query will fail - <<: *task_params - mysql_query: - <<: *mysql_params - query: "SHOW GRANTS FOR {{ role1 }} USING '{{ role0 }}'" - ignore_errors: yes - when: install_type == 'mysql' - - - name: Check - assert: - that: - - result is failed - when: install_type == 'mysql' - - - name: Check in DB (mariadb) - <<: *task_params - mysql_query: - <<: *mysql_params - query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = '{{ role1 }}' AND Role = '{{ role0 }}'" - when: install_type == 'mariadb' - - - name: Check (mariadb) - assert: - that: - - result.query_result.0.0['user_roles'] == 0 - when: install_type == 'mariadb' - - #======================== - - - name: Rewrite members - <<: *task_params - mysql_role: - <<: *mysql_params - name: '{{ role0 }}' - state: present - members: - - '{{ user1 }}@localhost' - - '{{ user2 }}@localhost' - - '{{ role1 }}' - - - name: Check - assert: - that: - - result is changed - - # user0 is not a member any more - - name: Check in DB, if not granted, the query will fail - <<: *task_params - mysql_query: - <<: *mysql_params - query: "SHOW GRANTS FOR {{ user0 }}@localhost USING '{{ role0 }}'" - ignore_errors: yes - when: install_type == 'mysql' - - - name: Check - assert: - that: - - result is failed - when: install_type == 'mysql' - - # user0 is not a member any more - - name: Check in DB (mariadb) - <<: *task_params - mysql_query: - <<: *mysql_params - query: "SHOW GRANTS FOR {{ user0 }}@localhost" - when: install_type == 'mariadb' - - - name: Check (mariadb) - assert: - that: - - "'{{ role0 }}' not in result.query_result.0.0['Grants for user0@localhost']" - when: install_type == 'mariadb' - - - name: Check in DB, if not granted, the query will fail - <<: *task_params - mysql_query: - <<: *mysql_params - query: "SHOW GRANTS FOR {{ user1 }}@localhost USING '{{ role0 }}'" - when: install_type == 'mysql' - - - name: Check - assert: - that: - - result is succeeded - when: install_type == 'mysql' - - - name: Check in DB (mariadb) - <<: *task_params - mysql_query: - <<: *mysql_params - query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = '{{ user1 }}' AND Host = 'localhost' AND Role = '{{ role0 }}'" - when: install_type == 'mariadb' - - - name: Check (mariadb) - assert: - that: - - result.query_result.0.0['user_roles'] == 1 - when: install_type == 'mariadb' - - - name: Check in DB, if not granted, the query will fail - <<: *task_params - mysql_query: - <<: *mysql_params - query: "SHOW GRANTS FOR {{ user2 }}@localhost USING '{{ role0 }}'" - when: install_type == 'mysql' - - - name: Check - assert: - that: - - result is succeeded - when: install_type == 'mysql' - - - name: Check in DB (mariadb) - <<: *task_params - mysql_query: - <<: *mysql_params - query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = '{{ user2 }}' AND Host = 'localhost' AND Role = '{{ role0 }}'" - when: install_type == 'mariadb' - - - name: Check (mariadb) - assert: - that: - - result.query_result.0.0['user_roles'] == 1 - when: install_type == 'mariadb' - - - name: Check in DB, if not granted, the query will fail - <<: *task_params - mysql_query: - <<: *mysql_params - query: "SHOW GRANTS FOR {{ role1 }} USING '{{ role0 }}'" - ignore_errors: yes - when: install_type == 'mysql' - - - name: Check - assert: - that: - - result is succeeded - when: install_type == 'mysql' - - - name: Check in DB (mariadb) - <<: *task_params - mysql_query: - <<: *mysql_params - query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = '{{ role1 }}' AND Role = '{{ role0 }}'" - when: install_type == 'mariadb' - - - name: Check (mariadb) - assert: - that: - - result.query_result.0.0['user_roles'] == 1 - when: install_type == 'mariadb' - - - #========================== - - - name: Rewrite members again in check_mode - <<: *task_params - mysql_role: - <<: *mysql_params - name: '{{ role0 }}' - state: present - members: - - '{{ user1 }}@localhost' - - '{{ user2 }}@localhost' - - '{{ role1 }}' - check_mode: yes - - - name: Check - assert: - that: - - result is not changed - - #========================== - - - name: Rewrite members again - <<: *task_params - mysql_role: - <<: *mysql_params - name: '{{ role0 }}' - state: present - members: - - '{{ user1 }}@localhost' - - '{{ user2 }}@localhost' - - '{{ role1 }}' - - - name: Check - assert: - that: - - result is not changed - - #========================== - - # Append members - - name: Append a member in check_mode - <<: *task_params - mysql_role: - <<: *mysql_params - name: '{{ role0 }}' - state: present - append_members: yes - members: - - '{{ user0 }}@localhost' - check_mode: yes - - - name: Check - assert: - that: - - result is changed - - - name: Check in DB, if not granted, the query will fail - <<: *task_params - mysql_query: - <<: *mysql_params - query: "SHOW GRANTS FOR {{ user0 }}@localhost USING '{{ role0 }}'" - ignore_errors: yes - when: install_type == 'mysql' - - - name: Check - assert: - that: - - result is failed - when: install_type == 'mysql' - - - name: Check in DB (mariadb) - <<: *task_params - mysql_query: - <<: *mysql_params - query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = '{{ user0 }}' AND Host = 'localhost' AND Role = '{{ role0 }}'" - when: install_type == 'mariadb' - - - name: Check (mariadb) - assert: - that: - - result.query_result.0.0['user_roles'] == 0 - when: install_type == 'mariadb' - #===================== - - - name: Append a member - <<: *task_params - mysql_role: - <<: *mysql_params - name: '{{ role0 }}' - state: present - append_members: yes - members: - - '{{ user0 }}@localhost' - - - name: Check - assert: - that: - - result is changed - - - name: Check in DB, if not granted, the query will fail - <<: *task_params - mysql_query: - <<: *mysql_params - query: "SHOW GRANTS FOR {{ user0 }}@localhost USING '{{ role0 }}'" - when: install_type == 'mysql' - - - name: Check - assert: - that: - - result is succeeded - when: install_type == 'mysql' - - - name: Check in DB (mariadb) - <<: *task_params - mysql_query: - <<: *mysql_params - query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = '{{ user0 }}' AND Host = 'localhost' AND Role = '{{ role0 }}'" - when: install_type == 'mariadb' - - - name: Check (mariadb) - assert: - that: - - result.query_result.0.0['user_roles'] == 1 - when: install_type == 'mariadb' - - # user1 and user2 must still be in DB because we are appending - - name: Check in DB, if not granted, the query will fail - <<: *task_params - mysql_query: - <<: *mysql_params - query: "SHOW GRANTS FOR {{ user1 }}@localhost USING '{{ role0 }}'" - when: install_type == 'mysql' - - - name: Check - assert: - that: - - result is succeeded - when: install_type == 'mysql' - - - name: Check in DB (mariadb) - <<: *task_params - mysql_query: - <<: *mysql_params - query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = '{{ user1 }}' AND Host = 'localhost' AND Role = '{{ role0 }}'" - when: install_type == 'mariadb' - - - name: Check (mariadb) - assert: - that: - - result.query_result.0.0['user_roles'] == 1 - when: install_type == 'mariadb' - - - name: Check in DB, if not granted, the query will fail - <<: *task_params - mysql_query: - <<: *mysql_params - query: "SHOW GRANTS FOR {{ user2 }}@localhost USING '{{ role0 }}'" - when: install_type == 'mysql' - - - name: Check - assert: - that: - - result is succeeded - when: install_type == 'mysql' - - - name: Check in DB (mariadb) - <<: *task_params - mysql_query: - <<: *mysql_params - query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = '{{ user2 }}' AND Host = 'localhost' AND Role = '{{ role0 }}'" - when: install_type == 'mariadb' - - - name: Check (mariadb) - assert: - that: - - result.query_result.0.0['user_roles'] == 1 - when: install_type == 'mariadb' - - #======================== - - - name: Append a member again in check_mode - <<: *task_params - mysql_role: - <<: *mysql_params - name: '{{ role0 }}' - state: present - append_members: yes - members: - - '{{ user0 }}@localhost' - check_mode: yes - - - name: Check - assert: - that: - - result is not changed - - #======================== - - - name: Append a member again - <<: *task_params - mysql_role: - <<: *mysql_params - name: '{{ role0 }}' - state: present - append_members: yes - members: - - '{{ user0 }}@localhost' - - - name: Check - assert: - that: - - result is not changed - - ############## - # Detach users - - name: Detach users in check_mode - <<: *task_params - mysql_role: - <<: *mysql_params - name: '{{ role0 }}' - state: present - detach_members: yes - members: - - '{{ user1 }}@localhost' - - '{{ user2 }}@localhost' - check_mode: yes - - - name: Check - assert: - that: - - result is changed - - # They must be there because of check_mode - - name: Check in DB, if not granted, the query will fail - <<: *task_params - mysql_query: - <<: *mysql_params - query: "SHOW GRANTS FOR {{ user0 }}@localhost USING '{{ role0 }}'" - when: install_type == 'mysql' - - - name: Check - assert: - that: - - result is succeeded - when: install_type == 'mysql' - - - name: Check in DB (mariadb) - <<: *task_params - mysql_query: - <<: *mysql_params - query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = '{{ user0 }}' AND Host = 'localhost' AND Role = '{{ role0 }}'" - when: install_type == 'mariadb' - - - name: Check (mariadb) - assert: - that: - - result.query_result.0.0['user_roles'] == 1 - when: install_type == 'mariadb' - - - name: Check in DB, if not granted, the query will fail - <<: *task_params - mysql_query: - <<: *mysql_params - query: "SHOW GRANTS FOR {{ user1 }}@localhost USING '{{ role0 }}'" - when: install_type == 'mysql' - - - name: Check - assert: - that: - - result is succeeded - when: install_type == 'mysql' - - - name: Check in DB (mariadb) - <<: *task_params - mysql_query: - <<: *mysql_params - query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = '{{ user1 }}' AND Host = 'localhost' AND Role = '{{ role0 }}'" - when: install_type == 'mariadb' - - - name: Check (mariadb) - assert: - that: - - result.query_result.0.0['user_roles'] == 1 - when: install_type == 'mariadb' - - - name: Check in DB, if not granted, the query will fail - <<: *task_params - mysql_query: - <<: *mysql_params - query: "SHOW GRANTS FOR {{ user2 }}@localhost USING '{{ role0 }}'" - when: install_type == 'mysql' - - - name: Check - assert: - that: - - result is succeeded - when: install_type == 'mysql' - - - name: Check in DB (mariadb) - <<: *task_params - mysql_query: - <<: *mysql_params - query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = '{{ user2 }}' AND Host = 'localhost' AND Role = '{{ role0 }}'" - when: install_type == 'mariadb' - - - name: Check (mariadb) - assert: - that: - - result.query_result.0.0['user_roles'] == 1 - when: install_type == 'mariadb' - - #======================== - - - name: Detach users - <<: *task_params - mysql_role: - <<: *mysql_params - name: '{{ role0 }}' - state: present - detach_members: yes - members: - - '{{ user1 }}@localhost' - - '{{ user2 }}@localhost' - - - name: Check - assert: - that: - - result is changed - - - name: Check in DB, if not granted, the query will fail - <<: *task_params - mysql_query: - <<: *mysql_params - query: "SHOW GRANTS FOR {{ user0 }}@localhost USING '{{ role0 }}'" - when: install_type == 'mysql' - - - name: Check - assert: - that: - - result is succeeded - when: install_type == 'mysql' - - - name: Check in DB (mariadb) - <<: *task_params - mysql_query: - <<: *mysql_params - query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = '{{ user0 }}' AND Host = 'localhost' AND Role = '{{ role0 }}'" - when: install_type == 'mariadb' - - - name: Check (mariadb) - assert: - that: - - result.query_result.0.0['user_roles'] == 1 - when: install_type == 'mariadb' - - - name: Check in DB, if not granted, the query will fail - <<: *task_params - mysql_query: - <<: *mysql_params - query: "SHOW GRANTS FOR {{ user1 }}@localhost USING '{{ role0 }}'" - ignore_errors: yes - when: install_type == 'mysql' - - - name: Check - assert: - that: - - result is failed - when: install_type == 'mysql' - - - name: Check in DB (mariadb) - <<: *task_params - mysql_query: - <<: *mysql_params - query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = '{{ user1 }}' AND Host = 'localhost' AND Role = '{{ role0 }}'" - when: install_type == 'mariadb' - - - name: Check (mariadb) - assert: - that: - - result.query_result.0.0['user_roles'] == 0 - when: install_type == 'mariadb' - - - name: Check in DB, if not granted, the query will fail - <<: *task_params - mysql_query: - <<: *mysql_params - query: "SHOW GRANTS FOR {{ user2 }}@localhost USING '{{ role0 }}'" - ignore_errors: yes - when: install_type == 'mysql' - - - name: Check - assert: - that: - - result is failed - when: install_type == 'mysql' - - - name: Check in DB (mariadb) - <<: *task_params - mysql_query: - <<: *mysql_params - query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = '{{ user2 }}' AND Host = 'localhost' AND Role = '{{ role0 }}'" - when: install_type == 'mariadb' - - - name: Check (mariadb) - assert: - that: - - result.query_result.0.0['user_roles'] == 0 - when: install_type == 'mariadb' - - #===================== - - - name: Detach users in check_mode again - <<: *task_params - mysql_role: - <<: *mysql_params - name: '{{ role0 }}' - state: present - detach_members: yes - members: - - '{{ user1 }}@localhost' - - '{{ user2 }}@localhost' - check_mode: yes - - - name: Check - assert: - that: - - result is not changed - - - name: Detach users again - <<: *task_params - mysql_role: - <<: *mysql_params - name: '{{ role0 }}' - state: present - detach_members: yes - members: - - '{{ user1 }}@localhost' - - '{{ user2 }}@localhost' - - - name: Check - assert: - that: - - result is not changed - - - name: '"detach" users when creating a new role' - <<: *task_params - mysql_role: - <<: *mysql_params - name: '{{ role3 }}' - state: present - detach_members: yes - members: - - '{{ user1 }}@localhost' - - - name: Check the role was created + - name: Assert that rewrite members in check_mode is changed assert: that: - result is changed - - name: Check grants - <<: *task_params + # user0 is still a member because of check_mode + - name: Query user0, if not granted, the query will fail mysql_query: <<: *mysql_params - query: "SHOW GRANTS FOR {{ user1 }}@localhost" + query: "SHOW GRANTS FOR user0@'%' USING 'role0'" + register: result + when: db_engine == 'mysql' - - name: asssert detach_members did not add a user to the role + - name: Assert that show grants for user0 in mysql is succeeded assert: that: - - "'{{ role3 }}' not in result.query_result.0.0['Grants for {{ user1 }}@localhost']" + - result is succeeded + when: db_engine == 'mysql' - # test members_must_exist - - name: try failing on not-existing user in check-mode - <<: *task_params + # user0 is still a member because of check_mode + - name: Query user0 (mariadb) + mysql_query: + <<: *mysql_params + query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = 'user0' AND Host = '%' AND Role = 'role0'" + register: result + when: db_engine == 'mariadb' + + - name: Assert that show grants for user0 in mariadb returns 1 row + assert: + that: + - result.query_result.0.0['user_roles'] == 1 + when: db_engine == 'mariadb' + + # user1, user2, and role1 are not members because of check_mode + - name: Query user1, if not granted, the query will fail (expect failue) + mysql_query: + <<: *mysql_params + query: "SHOW GRANTS FOR user1@'%' USING 'role0'" + ignore_errors: yes + register: result + when: db_engine == 'mysql' + + - name: Assert that query for user1 in mysql is failed due to check_mode + assert: + that: + - result is failed + when: db_engine == 'mysql' + + - name: Query user1 (mariadb) + mysql_query: + <<: *mysql_params + query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = 'user1' AND Host = '%' AND Role = 'role0'" + register: result + when: db_engine == 'mariadb' + + - name: Assert that query for user1 in mariadb is failed due to check_mode + assert: + that: + - result.query_result.0.0['user_roles'] == 0 + when: db_engine == 'mariadb' + + - name: Query user2, if not granted, the query will fail + mysql_query: + <<: *mysql_params + query: "SHOW GRANTS FOR user2@'%' USING 'role0'" + register: result + ignore_errors: yes + when: db_engine == 'mysql' + + - name: Assert that query for user2 in mysql is failed + assert: + that: + - result is failed + when: db_engine == 'mysql' + + - name: Query user2 (mariadb) + mysql_query: + <<: *mysql_params + query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = 'user2' AND Host = '%' AND Role = 'role0'" + register: result + when: db_engine == 'mariadb' + + - name: Assert that query user2 in mariadb returns 0 row + assert: + that: + - result.query_result.0.0['user_roles'] == 0 + when: db_engine == 'mariadb' + + - name: Query role1, if not granted, the query will fail + mysql_query: + <<: *mysql_params + query: "SHOW GRANTS FOR role1 USING 'role0'" + register: result + ignore_errors: yes + when: db_engine == 'mysql' + + - name: Assert that query role1 in mysql is failed + assert: + that: + - result is failed + when: db_engine == 'mysql' + + - name: Query role1 (mariadb) + mysql_query: + <<: *mysql_params + query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = 'role1' AND Role = 'role0'" + register: result + when: db_engine == 'mariadb' + + - name: Assert that query role0 in mariadb returns 0 row + assert: + that: + - result.query_result.0.0['user_roles'] == 0 + when: db_engine == 'mariadb' + + # ======================== + + - name: Rewrite members mysql_role: <<: *mysql_params - name: '{{ role0 }}' + name: 'role0' + state: present + members: + - 'user1@%' + - 'user2@%' + - 'role1' + register: result + + - name: Assert that rewrite members is changed + assert: + that: + - result is changed + + # user0 is not a member any more + - name: Query user0, if not granted, the query will fail + mysql_query: + <<: *mysql_params + query: "SHOW GRANTS FOR user0@'%' USING 'role0'" + register: result + ignore_errors: yes + when: db_engine == 'mysql' + + - name: Assert that query user0 in mysql is failed + assert: + that: + - result is failed + when: db_engine == 'mysql' + + # user0 is not a member any more + - name: Query user0 (mariadb) + mysql_query: + <<: *mysql_params + query: "SHOW GRANTS FOR user0@'%'" + register: result + when: db_engine == 'mariadb' + + - name: Assert that query user0 in mariadb doesn't returns role0 + assert: + that: + - "'role0' not in result.query_result.0.0['Grants for user0@%']" + when: db_engine == 'mariadb' + + - name: Query user1, if not granted, the query will fail (expect success) + mysql_query: + <<: *mysql_params + query: "SHOW GRANTS FOR user1@'%' USING 'role0'" + register: result + when: db_engine == 'mysql' + + - name: Assert that query user1 in mysql is succeeded + assert: + that: + - result is succeeded + when: db_engine == 'mysql' + + - name: Query user1 (mariadb) + mysql_query: + <<: *mysql_params + query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = 'user1' AND Host = '%' AND Role = 'role0'" + register: result + when: db_engine == 'mariadb' + + - name: Assert that query user1 in mariadb returns 1 row + assert: + that: + - result.query_result.0.0['user_roles'] == 1 + when: db_engine == 'mariadb' + + - name: Query user2, if not granted, the query will fail + mysql_query: + <<: *mysql_params + query: "SHOW GRANTS FOR user2@'%' USING 'role0'" + register: result + when: db_engine == 'mysql' + + - name: Assert that query user2 in mysql is succeeded + assert: + that: + - result is succeeded + when: db_engine == 'mysql' + + - name: Query user2 (mariadb) + mysql_query: + <<: *mysql_params + query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = 'user2' AND Host = '%' AND Role = 'role0'" + register: result + when: db_engine == 'mariadb' + + - name: Assert that query user2 in mariadb returns 1 row + assert: + that: + - result.query_result.0.0['user_roles'] == 1 + when: db_engine == 'mariadb' + + - name: Query role0, if not granted, the query will fail + mysql_query: + <<: *mysql_params + query: "SHOW GRANTS FOR role1 USING 'role0'" + register: result + ignore_errors: yes + when: db_engine == 'mysql' + + - name: Assert that query role0 in mysql is succeeded + assert: + that: + - result is succeeded + when: db_engine == 'mysql' + + - name: Query count user is role1 and role is role0 (mariadb) + mysql_query: + <<: *mysql_params + query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = 'role1' AND Role = 'role0'" + register: result + when: db_engine == 'mariadb' + + - name: Assert that query count user is role1 and role is role0 returns 1 row + assert: + that: + - result.query_result.0.0['user_roles'] == 1 + when: db_engine == 'mariadb' + + + # ========================== + + - name: Rewrite members again in check_mode + mysql_role: + <<: *mysql_params + name: 'role0' + state: present + members: + - 'user1@%' + - 'user2@%' + - 'role1' + register: result + check_mode: yes + + - name: Assert that rewrite members again in check_mode is not changed + assert: + that: + - result is not changed + + # ========================== + + - name: Rewrite members again + mysql_role: + <<: *mysql_params + name: 'role0' + state: present + members: + - 'user1@%' + - 'user2@%' + - 'role1' + register: result + + - name: Assert that rewrite members again is not changed + assert: + that: + - result is not changed + + # ========================== + + # Append members + - name: Append a member in check_mode + mysql_role: + <<: *mysql_params + name: 'role0' + state: present + append_members: yes + members: + - 'user0@%' + register: result + check_mode: yes + + - name: Assert that append a member in check_mode is changed + assert: + that: + - result is changed + + - name: Query user0, if not granted, the query will fail (expect failure) + mysql_query: + <<: *mysql_params + query: "SHOW GRANTS FOR user0@'%' USING 'role0'" + ignore_errors: yes + register: result + when: db_engine == 'mysql' + + - name: Assert that query user0 is failed + assert: + that: + - result is failed + when: db_engine == 'mysql' + + - name: Query count for user0 and role0 (mariadb) + mysql_query: + <<: *mysql_params + query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = 'user0' AND Host = '%' AND Role = 'role0'" + register: result + when: db_engine == 'mariadb' + + - name: Assert that query count for user0 and role0 in mariadb resturns 0 row + assert: + that: + - result.query_result.0.0['user_roles'] == 0 + when: db_engine == 'mariadb' + # ===================== + + - name: Append a member + mysql_role: + <<: *mysql_params + name: 'role0' + state: present + append_members: yes + members: + - 'user0@%' + register: result + + - name: Assert that append a member is changed + assert: + that: + - result is changed + + - name: Query user0, if not granted, the query will fail + mysql_query: + <<: *mysql_params + query: "SHOW GRANTS FOR user0@'%' USING 'role0'" + register: result + when: db_engine == 'mysql' + + - name: Assert that query user0 in mysql is succeeded + assert: + that: + - result is succeeded + when: db_engine == 'mysql' + + - name: Query count for user0 and role0 (mariadb) + mysql_query: + <<: *mysql_params + query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = 'user0' AND Host = '%' AND Role = 'role0'" + register: result + when: db_engine == 'mariadb' + + - name: Assert that query count for user0 and role0 in mariadb resturns 1 row + assert: + that: + - result.query_result.0.0['user_roles'] == 1 + when: db_engine == 'mariadb' + + # user1 and user2 must still be in DB because we are appending + - name: Query user1 using role0 (expect success) + mysql_query: + <<: *mysql_params + query: "SHOW GRANTS FOR user1@'%' USING 'role0'" + register: result + when: db_engine == 'mysql' + + - name: Assert that query for user1 in mysql is succeeded + assert: + that: + - result is succeeded + when: db_engine == 'mysql' + + - name: Query count for user1 and role0 (mariadb) + mysql_query: + <<: *mysql_params + query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = 'user1' AND Host = '%' AND Role = 'role0'" + register: result + when: db_engine == 'mariadb' + + - name: Assert that query count for user1 and role0 in mariadb returns 1 row + assert: + that: + - result.query_result.0.0['user_roles'] == 1 + when: db_engine == 'mariadb' + + - name: Query user2, if not granted, the query will fail + mysql_query: + <<: *mysql_params + query: "SHOW GRANTS FOR user2@'%' USING 'role0'" + register: result + when: db_engine == 'mysql' + + - name: Assert that query user2 in mysql is succeeded + assert: + that: + - result is succeeded + when: db_engine == 'mysql' + + - name: Query count for user2 and role0 (mariadb) + mysql_query: + <<: *mysql_params + query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = 'user2' AND Host = '%' AND Role = 'role0'" + register: result + when: db_engine == 'mariadb' + + - name: Assert that query count for user2 and role0 in mariadb returns 1 row + assert: + that: + - result.query_result.0.0['user_roles'] == 1 + when: db_engine == 'mariadb' + + # ======================== + + - name: Append a member again in check_mode + mysql_role: + <<: *mysql_params + name: 'role0' + state: present + append_members: yes + members: + - 'user0@%' + register: result + check_mode: yes + + - name: Assert that append a member again in check_mode is not changed + assert: + that: + - result is not changed + + # ======================== + + - name: Append a member again + mysql_role: + <<: *mysql_params + name: 'role0' + state: present + append_members: yes + members: + - 'user0@%' + register: result + + - name: Assert that append a member again is not changed + assert: + that: + - result is not changed + + ############## + # Detach users + - name: Detach users in check_mode + mysql_role: + <<: *mysql_params + name: 'role0' + state: present + detach_members: yes + members: + - 'user1@%' + - 'user2@%' + register: result + check_mode: yes + + - name: Assert that detach users in check_mode is changed + assert: + that: + - result is changed + + # They must be there because of check_mode + - name: Query user0, if not granted, the query will fail + mysql_query: + <<: *mysql_params + query: "SHOW GRANTS FOR user0@'%' USING 'role0'" + register: result + when: db_engine == 'mysql' + + - name: Assert that query user0 is succeeded + assert: + that: + - result is succeeded + when: db_engine == 'mysql' + + - name: Query count for user0 and role0 (mariadb) + mysql_query: + <<: *mysql_params + query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = 'user0' AND Host = '%' AND Role = 'role0'" + register: result + when: db_engine == 'mariadb' + + - name: Assert that query count for user0 and role0 in mariadb resturns 1 row + assert: + that: + - result.query_result.0.0['user_roles'] == 1 + when: db_engine == 'mariadb' + + - name: Query user1 using role0 (expect success) + mysql_query: + <<: *mysql_params + query: "SHOW GRANTS FOR user1@'%' USING 'role0'" + register: result + when: db_engine == 'mysql' + + - name: Assert that query user1 in mysql is succeeded + assert: + that: + - result is succeeded + when: db_engine == 'mysql' + + - name: Query count for user1 and role0 (mariadb) + mysql_query: + <<: *mysql_params + query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = 'user1' AND Host = '%' AND Role = 'role0'" + register: result + when: db_engine == 'mariadb' + + - name: Assert that query count for user1 and role0 in mariadb returns 1 row + assert: + that: + - result.query_result.0.0['user_roles'] == 1 + when: db_engine == 'mariadb' + + - name: Query user2, if not granted, the query will fail + mysql_query: + <<: *mysql_params + query: "SHOW GRANTS FOR user2@'%' USING 'role0'" + register: result + when: db_engine == 'mysql' + + - name: Assert that query user2 in mysql is succeeded + assert: + that: + - result is succeeded + when: db_engine == 'mysql' + + - name: Query count user2 and role0 (mariadb) + mysql_query: + <<: *mysql_params + query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = 'user2' AND Host = '%' AND Role = 'role0'" + register: result + when: db_engine == 'mariadb' + + - name: Assert that query count user2 and role0 in mariadb returns 1 row + assert: + that: + - result.query_result.0.0['user_roles'] == 1 + when: db_engine == 'mariadb' + + # ======================== + + - name: Detach users + mysql_role: + <<: *mysql_params + name: 'role0' + state: present + detach_members: yes + members: + - 'user1@%' + - 'user2@%' + register: result + + - name: Assert that detach users is changed + assert: + that: + - result is changed + + - name: Query user0, if not granted, the query will fail + mysql_query: + <<: *mysql_params + query: "SHOW GRANTS FOR user0@'%' USING 'role0'" + register: result + when: db_engine == 'mysql' + + - name: Assert that query user0 in mysql is succeeded + assert: + that: + - result is succeeded + when: db_engine == 'mysql' + + - name: Query count for user0 and role0 (mariadb) + mysql_query: + <<: *mysql_params + query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = 'user0' AND Host = '%' AND Role = 'role0'" + register: result + when: db_engine == 'mariadb' + + - name: Assert that query count for user0 and role0 returns 1 row + assert: + that: + - result.query_result.0.0['user_roles'] == 1 + when: db_engine == 'mariadb' + + - name: Query user1, if not granted, the query will fail (expect failure) + mysql_query: + <<: *mysql_params + query: "SHOW GRANTS FOR user1@'%' USING 'role0'" + ignore_errors: yes + register: result + when: db_engine == 'mysql' + + - name: Assert that query user1 in mysql is failed + assert: + that: + - result is failed + when: db_engine == 'mysql' + + - name: Query count for user1 and role0 (mariadb) + mysql_query: + <<: *mysql_params + query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = 'user1' AND Host = '%' AND Role = 'role0'" + register: result + when: db_engine == 'mariadb' + + - name: Assert that query count for user1 and role0 in mariadb returns 0 row + assert: + that: + - result.query_result.0.0['user_roles'] == 0 + when: db_engine == 'mariadb' + + - name: Query user2, if not granted, the query will fail + mysql_query: + <<: *mysql_params + query: "SHOW GRANTS FOR user2@'%' USING 'role0'" + register: result + ignore_errors: yes + when: db_engine == 'mysql' + + - name: Assert that query user2 in mysql is failed + assert: + that: + - result is failed + when: db_engine == 'mysql' + + - name: Query count for user2 and role0 (mariadb) + mysql_query: + <<: *mysql_params + query: "SELECT count(User) as user_roles FROM mysql.roles_mapping WHERE User = 'user2' AND Host = '%' AND Role = 'role0'" + register: result + when: db_engine == 'mariadb' + + - name: Assert that query count for user2 and role0 returns 0 row + assert: + that: + - result.query_result.0.0['user_roles'] == 0 + when: db_engine == 'mariadb' + + # ===================== + + - name: Detach users in check_mode again + mysql_role: + <<: *mysql_params + name: 'role0' + state: present + detach_members: yes + members: + - 'user1@%' + - 'user2@%' + register: result + check_mode: yes + + - name: Assert that detach users in check_mode again is not changed + assert: + that: + - result is not changed + + - name: Detach users again + mysql_role: + <<: *mysql_params + name: 'role0' + state: present + detach_members: yes + members: + - 'user1@%' + - 'user2@%' + register: result + + - name: Assert that detach users again is not changed + assert: + that: + - result is not changed + + - name: '"detach" users when creating a new role' + mysql_role: + <<: *mysql_params + name: 'role3' + state: present + detach_members: yes + members: + - 'user1@%' + register: result + + - name: Assert that creating a role while detach users is changed + assert: + that: + - result is changed + + - name: Query grants for user1 + mysql_query: + <<: *mysql_params + query: "SHOW GRANTS FOR user1@'%'" + register: result + + - name: Assert detach_members did not add a user to the role + assert: + that: + - "'role3' not in result.query_result.0.0" + + # test members_must_exist + - name: Try failing on not-existing user in check-mode + mysql_role: + <<: *mysql_params + name: 'role0' state: present members_must_exist: yes append_members: yes members: - - 'not_existent@localhost' + - 'not_existent@%' + register: result ignore_errors: yes check_mode: yes - - name: assert failure + + - name: Assert nonexistent user in check-mode is failed assert: that: - result is failed - - name: try failing on not-existing user in check-mode - <<: *task_params + - name: Try failing on not-existing user in check-mode mysql_role: <<: *mysql_params - name: '{{ role0 }}' + name: 'role0' state: present members_must_exist: no append_members: yes members: - - 'not_existent@localhost' + - 'not_existent@%' + register: result check_mode: yes + - name: Check for lack of change assert: that: - result is not changed - - name: try failing on not-existing user - <<: *task_params + - name: Try failing on not-existing user mysql_role: <<: *mysql_params - name: '{{ role0 }}' + name: 'role0' state: present members_must_exist: yes append_members: yes members: - - 'not_existent@localhost' + - 'not_existent@%' + register: result ignore_errors: yes - - name: assert failure + + - name: Assert nonexistent user with members_must_exist is failed assert: that: - result is failed - - name: try failing on not-existing user - <<: *task_params + - name: Try failing on not-existing user mysql_role: <<: *mysql_params - name: '{{ role0 }}' + name: 'role0' state: present members_must_exist: no append_members: yes members: - - 'not_existent@localhost' - - name: Check for lack of change + - 'not_existent@%' + register: result + + - name: Assert nonexistent user with members_must_exist=no is not changed assert: that: - result is not changed @@ -1344,131 +1357,131 @@ # ########## - name: Create test DBs - <<: *task_params mysql_query: <<: *mysql_params query: 'CREATE DATABASE {{ item }}' loop: - - '{{ test_db1 }}' - - '{{ test_db2 }}' + - 'test_db1' + - 'test_db2' + register: result - - name: Create table {{ test_table }} - <<: *task_params + - name: Create table test_table mysql_query: <<: *mysql_params login_db: '{{ item }}' - query: 'CREATE TABLE {{ test_table }} (id int)' + query: 'CREATE TABLE test_table (id int)' loop: - - '{{ test_db1 }}' - - '{{ test_db2 }}' + - 'test_db1' + - 'test_db2' + register: result - - name: Check grants - <<: *task_params + - name: Query grants for role0 mysql_query: <<: *mysql_params - query: "SHOW GRANTS FOR {{ role0 }}" + query: "SHOW GRANTS FOR role0" + register: result - - name: Check + - name: Assert grants for role0 in mysql assert: that: - - result.query_result.0.0["Grants for role0@%"] == "GRANT SELECT, INSERT ON *.* TO `role0`@`%`" - - result.query_result.0.1["Grants for role0@%"] == "GRANT UPDATE ON `mysql`.* TO `role0`@`%`" - - result.rowcount.0 == 2 - when: install_type == 'mysql' + - result.query_result.0.0["Grants for role0@%"] == "GRANT SELECT, INSERT ON *.* TO `role0`@`%`" + - result.query_result.0.1["Grants for role0@%"] == "GRANT UPDATE ON `mysql`.* TO `role0`@`%`" + - result.rowcount.0 == 2 + when: db_engine == 'mysql' - - name: Check (mariadb) + - name: Assert grants for role0 in mariadb assert: that: - - result.query_result.0.0["Grants for role0"] == "GRANT SELECT, INSERT ON *.* TO `role0`" - - result.query_result.0.1["Grants for role0"] == "GRANT UPDATE ON `mysql`.* TO `role0`" - - result.rowcount.0 == 2 - when: install_type == 'mariadb' + - result.query_result.0.0["Grants for role0"] == "GRANT SELECT, INSERT ON *.* TO `role0`" + - result.query_result.0.1["Grants for role0"] == "GRANT UPDATE ON `mysql`.* TO `role0`" + - result.rowcount.0 == 2 + when: db_engine == 'mariadb' - name: Append privs in check_mode - <<: *task_params mysql_role: <<: *mysql_params - name: '{{ role0 }}' + name: 'role0' state: present - priv: '{{ test_db1 }}.{{ test_table }}:SELECT,INSERT/{{ test_db2 }}.{{ test_table }}:DELETE' + priv: 'test_db1.test_table:SELECT,INSERT/test_db2.test_table:DELETE' append_privs: yes + register: result check_mode: yes - - name: Check + - name: Assert append privs in check_mode is changed assert: that: - - result is changed + - result is changed - - name: Check grants - <<: *task_params + - name: Query grants for role0 mysql_query: <<: *mysql_params - query: "SHOW GRANTS FOR {{ role0 }}" + query: "SHOW GRANTS FOR role0" + register: result - - name: Check + - name: Assert grants for role0 in mysql assert: that: - - result.query_result.0.0["Grants for role0@%"] == "GRANT SELECT, INSERT ON *.* TO `role0`@`%`" - - result.query_result.0.1["Grants for role0@%"] == "GRANT UPDATE ON `mysql`.* TO `role0`@`%`" - - result.rowcount.0 == 2 - when: install_type == 'mysql' + - result.query_result.0.0["Grants for role0@%"] == "GRANT SELECT, INSERT ON *.* TO `role0`@`%`" + - result.query_result.0.1["Grants for role0@%"] == "GRANT UPDATE ON `mysql`.* TO `role0`@`%`" + - result.rowcount.0 == 2 + when: db_engine == 'mysql' - - name: Check (mariadb) + - name: Assert grants for role0 in mariadb assert: that: - - result.query_result.0.0["Grants for role0"] == "GRANT SELECT, INSERT ON *.* TO `role0`" - - result.query_result.0.1["Grants for role0"] == "GRANT UPDATE ON `mysql`.* TO `role0`" - - result.rowcount.0 == 2 - when: install_type == 'mariadb' + - result.query_result.0.0["Grants for role0"] == "GRANT SELECT, INSERT ON *.* TO `role0`" + - result.query_result.0.1["Grants for role0"] == "GRANT UPDATE ON `mysql`.* TO `role0`" + - result.rowcount.0 == 2 + when: db_engine == 'mariadb' - name: Append privs - <<: *task_params mysql_role: <<: *mysql_params - name: '{{ role0 }}' + name: 'role0' state: present - priv: '{{ test_db1 }}.{{ test_table }}:SELECT,INSERT/{{ test_db2 }}.{{ test_table }}:DELETE' + priv: 'test_db1.test_table:SELECT,INSERT/test_db2.test_table:DELETE' append_privs: yes + register: result - - name: Check + - name: Assert that append privs is changed assert: that: - - result is changed + - result is changed - - name: Check grants - <<: *task_params + - name: Query grants for role0 mysql_query: <<: *mysql_params - query: "SHOW GRANTS FOR {{ role0 }}" + query: "SHOW GRANTS FOR role0" + register: result - - name: Check + - name: Assert grants for role0 in mysql assert: that: - - result.query_result.0.0["Grants for role0@%"] == "GRANT SELECT, INSERT ON *.* TO `role0`@`%`" - - result.query_result.0.1["Grants for role0@%"] == "GRANT UPDATE ON `mysql`.* TO `role0`@`%`" - - result.query_result.0.2["Grants for role0@%"] == "GRANT SELECT, INSERT ON `test_db1`.`test_table` TO `role0`@`%`" - - result.query_result.0.3["Grants for role0@%"] == "GRANT DELETE ON `test_db2`.`test_table` TO `role0`@`%`" - - result.rowcount.0 == 4 - when: install_type == 'mysql' + - result.query_result.0.0["Grants for role0@%"] == "GRANT SELECT, INSERT ON *.* TO `role0`@`%`" + - result.query_result.0.1["Grants for role0@%"] == "GRANT UPDATE ON `mysql`.* TO `role0`@`%`" + - result.query_result.0.2["Grants for role0@%"] == "GRANT SELECT, INSERT ON `test_db1`.`test_table` TO `role0`@`%`" + - result.query_result.0.3["Grants for role0@%"] == "GRANT DELETE ON `test_db2`.`test_table` TO `role0`@`%`" + - result.rowcount.0 == 4 + when: db_engine == 'mysql' - - name: Check (mariadb) + - name: Assert grants for role0 in mariadb assert: that: - - result.query_result.0.0["Grants for role0"] == "GRANT SELECT, INSERT ON *.* TO `role0`" - - result.query_result.0.1["Grants for role0"] == "GRANT UPDATE ON `mysql`.* TO `role0`" - - result.query_result.0.2["Grants for role0"] == "GRANT SELECT, INSERT ON `test_db1`.`test_table` TO `role0`" - - result.query_result.0.3["Grants for role0"] == "GRANT DELETE ON `test_db2`.`test_table` TO `role0`" - - result.rowcount.0 == 4 - when: install_type == 'mariadb' + - result.query_result.0.0["Grants for role0"] == "GRANT SELECT, INSERT ON *.* TO `role0`" + - result.query_result.0.1["Grants for role0"] == "GRANT UPDATE ON `mysql`.* TO `role0`" + - result.query_result.0.2["Grants for role0"] == "GRANT SELECT, INSERT ON `test_db1`.`test_table` TO `role0`" + - result.query_result.0.3["Grants for role0"] == "GRANT DELETE ON `test_db2`.`test_table` TO `role0`" + - result.rowcount.0 == 4 + when: db_engine == 'mariadb' - name: Append privs again in check_mode - <<: *task_params mysql_role: <<: *mysql_params - name: '{{ role0 }}' + name: 'role0' state: present - priv: '{{ test_db1 }}.{{ test_table }}:SELECT,INSERT/{{ test_db2 }}.{{ test_table }}:DELETE' + priv: 'test_db1.test_table:SELECT,INSERT/test_db2.test_table:DELETE' append_privs: yes + register: result check_mode: yes # TODO it must be changed. The module uses user_mod function @@ -1477,173 +1490,171 @@ # https://github.com/ansible-collections/community.mysql/issues/50#issuecomment-871216825 # and it's also failed. Create an issue after the module is merged to avoid conflicts. # TODO Fix this after user_mod is fixed. - - name: Check + - name: Assert that append privs again in check_mode is changed assert: that: - - result is changed + - result is changed - name: Append privs again - <<: *task_params mysql_role: <<: *mysql_params - name: '{{ role0 }}' + name: 'role0' state: present - priv: '{{ test_db1 }}.{{ test_table }}:SELECT,INSERT/{{ test_db2 }}.{{ test_table }}:DELETE' + priv: 'test_db1.test_table:SELECT,INSERT/test_db2.test_table:DELETE' append_privs: yes + register: result - - name: Check that there's no change + - name: Assert that append privs again is not changed assert: that: - - result is not changed + - result is not changed - name: Rewrite privs - <<: *task_params mysql_role: <<: *mysql_params - name: '{{ role0 }}' + name: 'role0' state: present priv: '*.*': 'SELECT' + register: result - - name: Check + - name: Assert that rewrite privs is changed assert: that: - - result is changed + - result is changed - - name: Check grants - <<: *task_params + - name: Query grants for role0 mysql_query: <<: *mysql_params - query: "SHOW GRANTS FOR {{ role0 }}" + query: "SHOW GRANTS FOR role0" + register: result - - name: Check + - name: Assert grants for role0 in mysql assert: that: - - result.query_result.0.0["Grants for role0@%"] == "GRANT SELECT ON *.* TO `role0`@`%`" - - result.rowcount.0 == 1 - when: install_type == 'mysql' + - result.query_result.0.0["Grants for role0@%"] == "GRANT SELECT ON *.* TO `role0`@`%`" + - result.rowcount.0 == 1 + when: db_engine == 'mysql' - - name: Check (mariadb) + - name: Assert grants for role0 in mariadb assert: that: - - result.query_result.0.0["Grants for role0"] == "GRANT SELECT ON *.* TO `role0`" - - result.rowcount.0 == 1 - when: install_type == 'mariadb' + - result.query_result.0.0["Grants for role0"] == "GRANT SELECT ON *.* TO `role0`" + - result.rowcount.0 == 1 + when: db_engine == 'mariadb' # ################# # Test admin option # ################# - - name: Drop role - <<: *task_params + - name: Drop role0 mysql_role: <<: *mysql_params - name: '{{ role0 }}' + name: 'role0' state: absent + register: result - - name: Create role with admin - <<: *task_params + - name: Create role0 with admin mysql_role: <<: *mysql_params - name: '{{ role0 }}' + name: 'role0' state: present - admin: '{{ user0 }}@localhost' + admin: 'user0@%' + register: result ignore_errors: yes - - name: Check with MySQL + - name: Assert expected error message for mysql assert: that: - - result is failed - - result.msg is search('option can be used only with MariaDB') - when: install_type == 'mysql' + - result is failed + - result.msg is search('option can be used only with MariaDB') + when: db_engine == 'mysql' - - name: Check with MariaDB + - name: Assert create role0 in mariadb is changed assert: that: - - result is changed - when: install_type == 'mariadb' + - result is changed + when: db_engine == 'mariadb' - - name: Check in DB - <<: *task_params + - name: Query role0 in mariadb mysql_query: <<: *mysql_params - query: "SELECT 1 FROM mysql.user WHERE User = '{{ role0 }}' AND Host = ''" - when: install_type == 'mariadb' + query: "SELECT 1 FROM mysql.user WHERE User = 'role0' AND Host = ''" + register: result + when: db_engine == 'mariadb' - - name: Check + - name: Assert that query role0 in mariadb returns 1 row assert: that: - - result.rowcount.0 == 1 - when: install_type == 'mariadb' + - result.rowcount.0 == 1 + when: db_engine == 'mariadb' - - name: Create role with admin again - <<: *task_params + - name: Create role0 with admin again mysql_role: <<: *mysql_params - name: '{{ role0 }}' + name: 'role0' state: present - admin: '{{ user0 }}@localhost' + admin: 'user0@%' + register: result ignore_errors: yes - - name: Check with MySQL + - name: Assert expected error message in mysql again assert: that: - - result is failed - - result.msg is search('option can be used only with MariaDB') - when: install_type == 'mysql' + - result is failed + - result.msg is search('option can be used only with MariaDB') + when: db_engine == 'mysql' - - name: Check with MariaDB + - name: Assert create role0 in mariadb is not changed assert: that: - - result is not changed - when: install_type == 'mariadb' + - result is not changed + when: db_engine == 'mariadb' # Try to grant a role to a user who does not exist - - name: Create role with admin again - <<: *task_params + - name: Create role0 with admin again mysql_role: <<: *mysql_params - name: '{{ role0 }}' + name: 'role0' state: present members: - - '{{ nonexistent }}@localhost' + - 'nonexistent@%' + register: result ignore_errors: yes - - name: Check + - name: Assert that create role0 with admin again is failed assert: that: - - result is failed - - result.msg is search('does not exist') + - result is failed + - result.msg is search('does not exist') always: - # Clean up - - name: Drop DBs - mysql_query: - <<: *mysql_params - query: 'DROP DATABASE {{ item }}' - loop: - - '{{ test_db }}' - - '{{ test_db1 }}' - - '{{ test_db2 }}' - - name: Drop users - <<: *task_params - mysql_user: - <<: *mysql_params - name: '{{ item }}' - state: absent - loop: - - '{{ user0 }}' - - '{{ user1 }}' - - '{{ user2 }}' + - name: Clean up DBs + mysql_query: + <<: *mysql_params + query: 'DROP DATABASE IF EXISTS {{ item }}' + loop: + - 'test_db' + - 'test_db1' + - 'test_db2' - - name: Drop roles - <<: *task_params - mysql_role: - <<: *mysql_params - name: '{{ item }}' - state: absent - loop: - - '{{ role0 }}' - - test - - '{{ role3 }}' + - name: Clean up users + mysql_user: + <<: *mysql_params + name: '{{ item }}' + state: absent + loop: + - 'user0' + - 'user1' + - 'user2' + + - name: Clean up roles + mysql_role: + <<: *mysql_params + name: '{{ item }}' + state: absent + loop: + - 'role0' + - 'test' + - 'role3' diff --git a/tests/integration/targets/test_mysql_role/tasks/test_priv_subtract.yml b/tests/integration/targets/test_mysql_role/tasks/test_priv_subtract.yml index 95d2f1d..b79a1cb 100644 --- a/tests/integration/targets/test_mysql_role/tasks/test_priv_subtract.yml +++ b/tests/integration/targets/test_mysql_role/tasks/test_priv_subtract.yml @@ -3,7 +3,7 @@ mysql_parameters: &mysql_params login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' block: @@ -11,20 +11,18 @@ - name: Create test databases mysql_db: <<: *mysql_params - name: '{{ item }}' + name: data1 state: present - loop: - - data1 - name: Create a role with an initial set of privileges mysql_role: <<: *mysql_params - name: '{{ role2 }}' + name: 'role2' priv: 'data1.*:SELECT,INSERT' state: present - name: Run command to show privileges for role (expect privileges in stdout) - command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ role2 }}'\"" + command: "{{ mysql_command }} -e \"SHOW GRANTS FOR 'role2'\"" register: result - name: Assert that the initial set of privileges matches what is expected @@ -35,7 +33,7 @@ - name: Subtract privileges that are not in the current privileges, which should be a no-op mysql_role: <<: *mysql_params - name: '{{ role2 }}' + name: 'role2' priv: 'data1.*:DELETE' subtract_privs: yes state: present @@ -48,7 +46,7 @@ - result is not changed - name: Run command to show privileges for role (expect privileges in stdout) - command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ role2 }}'\"" + command: "{{ mysql_command }} -e \"SHOW GRANTS FOR 'role2'\"" register: result - name: Assert that the permissions still match what was originally granted @@ -59,7 +57,7 @@ - name: Subtract existing and not-existing privileges, but not all mysql_role: <<: *mysql_params - name: '{{ role2 }}' + name: 'role2' priv: 'data1.*:INSERT,DELETE' subtract_privs: yes state: present @@ -72,7 +70,7 @@ - result is changed - name: Run command to show privileges for role (expect privileges in stdout) - command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ role2 }}'\"" + command: "{{ mysql_command }} -e \"SHOW GRANTS FOR 'role2'\"" register: result - name: Assert that the permissions were not changed if check_mode is set to 'yes' @@ -90,7 +88,7 @@ - name: Try to subtract invalid privileges mysql_role: <<: *mysql_params - name: '{{ role2 }}' + name: 'role2' priv: 'data1.*:INVALID' subtract_privs: yes state: present @@ -103,7 +101,7 @@ - result is not changed - name: Run command to show privileges for role (expect privileges in stdout) - command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ role2 }}'\"" + command: "{{ mysql_command }} -e \"SHOW GRANTS FOR 'role2'\"" register: result - name: Assert that the permissions were not changed with check_mode=='yes' @@ -121,7 +119,7 @@ - name: trigger failure by trying to subtract and append privileges at the same time mysql_role: <<: *mysql_params - name: '{{ role2 }}' + name: 'role2' priv: 'data1.*:SELECT' subtract_privs: yes append_privs: yes @@ -136,7 +134,7 @@ - result is failed - name: Run command to show privileges for role (expect privileges in stdout) - command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ role2 }}'\"" + command: "{{ mysql_command }} -e \"SHOW GRANTS FOR 'role2'\"" register: result - name: Assert that the permissions stayed the same, with check_mode=='yes' @@ -156,13 +154,11 @@ - name: Drop test databases mysql_db: <<: *mysql_params - name: '{{ item }}' + name: 'data1' state: present - loop: - - data1 - name: Drop test role mysql_role: <<: *mysql_params - name: '{{ role2 }}' + name: 'role2' state: absent diff --git a/tests/integration/targets/test_mysql_user/defaults/main.yml b/tests/integration/targets/test_mysql_user/defaults/main.yml index 5cf9074..a87914c 100644 --- a/tests/integration/targets/test_mysql_user/defaults/main.yml +++ b/tests/integration/targets/test_mysql_user/defaults/main.yml @@ -2,7 +2,7 @@ # defaults file for test_mysql_user mysql_user: root mysql_password: msandbox -mysql_host: 127.0.0.1 +mysql_host: '{{ gateway_addr }}' mysql_primary_port: 3307 db_name: 'data' diff --git a/tests/integration/targets/test_mysql_user/meta/main.yml b/tests/integration/targets/test_mysql_user/meta/main.yml index a7ace5d..4be5f58 100644 --- a/tests/integration/targets/test_mysql_user/meta/main.yml +++ b/tests/integration/targets/test_mysql_user/meta/main.yml @@ -1,3 +1,4 @@ +--- dependencies: - - setup_mysql + - setup_controller - setup_remote_tmp_dir diff --git a/tests/integration/targets/test_mysql_user/tasks/assert_no_user.yml b/tests/integration/targets/test_mysql_user/tasks/assert_no_user.yml deleted file mode 100644 index 9861084..0000000 --- a/tests/integration/targets/test_mysql_user/tasks/assert_no_user.yml +++ /dev/null @@ -1,25 +0,0 @@ -# test code to assert no mysql user -# (c) 2014, Wayne Rosario - -# This file is part of Ansible -# -# Ansible is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Ansible is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Ansible. If not, see . - -# ============================================================ -- name: run command to query for mysql user - command: "{{ mysql_command }} -e \"SELECT User FROM mysql.user where user='{{ user_name }}'\"" - register: result - -- name: assert mysql user is not present - assert: { that: "'{{ user_name }}' not in result.stdout" } diff --git a/tests/integration/targets/test_mysql_user/tasks/assert_user.yml b/tests/integration/targets/test_mysql_user/tasks/assert_user.yml deleted file mode 100644 index d95d9d2..0000000 --- a/tests/integration/targets/test_mysql_user/tasks/assert_user.yml +++ /dev/null @@ -1,38 +0,0 @@ -# test code to assert mysql user -# (c) 2014, Wayne Rosario - -# This file is part of Ansible -# -# Ansible is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Ansible is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Ansible. If not, see . - -# ============================================================ -- name: run command to query for mysql user - command: "{{ mysql_command }} -e \"SELECT User FROM mysql.user where user='{{ user_name }}'\"" - register: result - -- name: assert mysql user is present - assert: - that: - - "'{{ user_name }}' in result.stdout" - -- name: run command to show privileges for user (expect privileges in stdout) - command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ user_name }}'@'localhost'\"" - register: result - when: priv is defined - -- name: assert user has giving privileges - assert: - that: - - "'GRANT {{priv}} ON *.*' in result.stdout" - when: priv is defined diff --git a/tests/integration/targets/test_mysql_user/tasks/create_user.yml b/tests/integration/targets/test_mysql_user/tasks/create_user.yml deleted file mode 100644 index 9984ea9..0000000 --- a/tests/integration/targets/test_mysql_user/tasks/create_user.yml +++ /dev/null @@ -1,46 +0,0 @@ -# test code to create mysql user -# (c) 2014, Wayne Rosario - -# This file is part of Ansible -# -# Ansible is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Ansible is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Ansible. If not, see . - -- 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: Drop mysql user if exists - mysql_user: - <<: *mysql_params - name: '{{ user_name_1 }}' - state: absent - ignore_errors: yes - - # ============================================================ - - name: create mysql user {{user_name}} - mysql_user: - <<: *mysql_params - name: '{{ user_name }}' - password: '{{ user_password }}' - state: present - register: result - - - name: assert output message mysql user was created - assert: - that: - - result is changed diff --git a/tests/integration/targets/test_mysql_user/tasks/issue-121.yml b/tests/integration/targets/test_mysql_user/tasks/issue-121.yml index 7d789ef..7f5934f 100644 --- a/tests/integration/targets/test_mysql_user/tasks/issue-121.yml +++ b/tests/integration/targets/test_mysql_user/tasks/issue-121.yml @@ -1,75 +1,73 @@ --- + - vars: mysql_parameters: &mysql_params login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' block: - # ============================================================ - - - name: get server certificate + - name: Issue-121 | Setup | Get server certificate copy: - content: "{{ lookup('pipe', \"openssl s_client -starttls mysql -connect localhost:3307 -showcerts 2>/dev/null /dev/null - + Issue-121 | Create user with both REQUIRESSL privilege and an incompatible + tls_requires option mysql_user: <<: *mysql_params name: "{{ user_name_1 }}" + host: '{{ gateway_addr }}' password: "{{ user_password_1 }}" priv: '*.*:SELECT,CREATE USER,REQUIRESSL,GRANT' tls_requires: X509: register: result - ignore_errors: yes + ignore_errors: true - - assert: + - name: >- + Issue-121 | Assert error granting privileges with incompatible tls_requires + option + assert: that: - result is failed - result.msg is search('Error granting privileges') - - name: Drop mysql user + - name: Issue-121 | Teardown | Drop mysql user mysql_user: <<: *mysql_params name: '{{ item }}' - host: 127.0.0.1 + host_all: true state: absent with_items: - "{{ user_name_1 }}" diff --git a/tests/integration/targets/test_mysql_user/tasks/issue-265.yml b/tests/integration/targets/test_mysql_user/tasks/issue-265.yml index 167b69b..bea41a8 100644 --- a/tests/integration/targets/test_mysql_user/tasks/issue-265.yml +++ b/tests/integration/targets/test_mysql_user/tasks/issue-265.yml @@ -3,52 +3,54 @@ mysql_parameters: &mysql_params login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' block: - - name: Drop mysql user if exists + - name: Issue-265 | Drop mysql user if exists mysql_user: <<: *mysql_params name: '{{ user_name_1 }}' + host_all: true state: absent ignore_errors: yes # Tests with force_context: yes # Test user creation - - name: create mysql user {{user_name_1}} + - name: Issue-265 | Create mysql user {{ user_name_1 }} mysql_user: <<: *mysql_params - name: '{{ user_name_1 }}' - password: '{{ user_password_1 }}' + name: "{{ user_name_1 }}" + password: "{{ user_password_1 }}" state: present force_context: yes register: result - - name: assert output message mysql user was created + - name: Issue-265 | Assert user was created assert: that: - result is changed - - include: assert_user.yml user_name={{user_name_1}} + - include: utils/assert_user.yml user_name={{ user_name_1 }} user_host=localhost # Test user removal - - name: remove mysql user {{user_name_1}} + - name: Issue-265 | remove mysql user {{ user_name_1 }} mysql_user: <<: *mysql_params - name: '{{user_name_1}}' - password: '{{user_password_1}}' + name: "{{ user_name_1 }}" + host_all: true + password: "{{ user_password_1 }}" state: absent force_context: yes register: result - - name: assert output message mysql user was removed + - name: Issue-265 | Assert user was removed assert: that: - result is changed # Test blank user removal - - name: create blank mysql user to be removed later + - name: Issue-265 | Create blank mysql user to be removed later mysql_user: <<: *mysql_params name: "" @@ -56,7 +58,7 @@ force_context: yes password: 'KJFDY&D*Sfuydsgf' - - name: remove blank mysql user with hosts=all (expect changed) + - name: Issue-265 | Remove blank mysql user with hosts=all (expect changed) mysql_user: <<: *mysql_params user: "" @@ -65,12 +67,12 @@ force_context: yes register: result - - name: assert changed is true for removing all blank users + - name: Issue-265 | Assert changed is true for removing all blank users assert: that: - result is changed - - name: remove blank mysql user with hosts=all (expect ok) + - name: Issue-265 | Remove blank mysql user with hosts=all (expect ok) mysql_user: <<: *mysql_params user: "" @@ -79,57 +81,58 @@ state: absent register: result - - name: assert changed is true for removing all blank users + - name: Issue-265 | Assert changed is true for removing all blank users assert: that: - result is not changed - - include: assert_no_user.yml user_name={{user_name_1}} + - include: utils/assert_no_user.yml user_name={{user_name_1}} # Tests with force_context: no # Test user creation - - name: Drop mysql user if exists + - name: Issue-265 | Drop mysql user if exists mysql_user: <<: *mysql_params - name: '{{ user_name_1 }}' + name: "{{ user_name_1 }}" + host_all: true state: absent ignore_errors: yes # Tests with force_context: yes # Test user creation - - name: create mysql user {{user_name_1}} + - name: Issue-265 | Create mysql user {{user_name_1}} mysql_user: <<: *mysql_params - name: '{{ user_name_1 }}' - password: '{{ user_password_1 }}' + name: "{{ user_name_1 }}" + password: "{{ user_password_1 }}" state: present force_context: yes register: result - - name: assert output message mysql user was created + - name: Issue-265 | Assert output message mysql user was created assert: that: - result is changed - - include: assert_user.yml user_name={{user_name_1}} + - include: utils/assert_user.yml user_name={{ user_name_1 }} user_host=localhost # Test user removal - - name: remove mysql user {{user_name_1}} + - name: Issue-265 | Remove mysql user {{ user_name_1 }} mysql_user: <<: *mysql_params - name: '{{user_name_1}}' - password: '{{user_password_1}}' + name: "{{ user_name_1 }}" + password: "{{ user_password_1 }}" state: absent force_context: no register: result - - name: assert output message mysql user was removed + - name: Issue-265 | Assert output message mysql user was removed assert: that: - result is changed # Test blank user removal - - name: create blank mysql user to be removed later + - name: Issue-265 | Create blank mysql user to be removed later mysql_user: <<: *mysql_params name: "" @@ -137,7 +140,7 @@ force_context: no password: 'KJFDY&D*Sfuydsgf' - - name: remove blank mysql user with hosts=all (expect changed) + - name: Issue-265 | Remove blank mysql user with hosts=all (expect changed) mysql_user: <<: *mysql_params user: "" @@ -146,12 +149,12 @@ force_context: no register: result - - name: assert changed is true for removing all blank users + - name: Issue-265 | Assert changed is true for removing all blank users assert: that: - result is changed - - name: remove blank mysql user with hosts=all (expect ok) + - name: Issue-265 | Remove blank mysql user with hosts=all (expect ok) mysql_user: <<: *mysql_params user: "" @@ -160,9 +163,9 @@ state: absent register: result - - name: assert changed is true for removing all blank users + - name: Issue-265 | Assert changed is true for removing all blank users assert: that: - result is not changed - - include: assert_no_user.yml user_name={{user_name_1}} + - include: utils/assert_no_user.yml user_name={{ user_name_1 }} diff --git a/tests/integration/targets/test_mysql_user/tasks/issue-28.yml b/tests/integration/targets/test_mysql_user/tasks/issue-28.yml index d56965a..51a2091 100644 --- a/tests/integration/targets/test_mysql_user/tasks/issue-28.yml +++ b/tests/integration/targets/test_mysql_user/tasks/issue-28.yml @@ -9,80 +9,87 @@ mysql_parameters: &mysql_params login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' when: tls_enabled block: # ============================================================ - - name: get server certificate + - name: Issue-28 | Setup | Get server certificate copy: - content: "{{ lookup('pipe', \"openssl s_client -starttls mysql -connect localhost:3307 -showcerts 2>/dev/null /dev/null = 0.7.11 is required' in result.msg - - name: Drop mysql user + - name: Issue-28 | Drop mysql user mysql_user: <<: *mysql_params name: '{{ item }}' - host: 127.0.0.1 + host: '{{ gateway_addr }}' state: absent with_items: - "{{ user_name_1 }}" diff --git a/tests/integration/targets/test_mysql_user/tasks/issue-29511.yaml b/tests/integration/targets/test_mysql_user/tasks/issue-29511.yaml index 31e6edf..17eb200 100644 --- a/tests/integration/targets/test_mysql_user/tasks/issue-29511.yaml +++ b/tests/integration/targets/test_mysql_user/tasks/issue-29511.yaml @@ -3,12 +3,12 @@ mysql_parameters: &mysql_params login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' block: - - name: Issue test setup - drop database + - name: Issue-29511 | test setup | drop database mysql_db: <<: *mysql_params name: "{{ item }}" @@ -17,7 +17,7 @@ - foo - bar - - name: Issue test setup - create database + - name: Issue-29511 | test setup | create database mysql_db: <<: *mysql_params name: "{{ item }}" @@ -26,7 +26,7 @@ - foo - bar - - name: Copy SQL scripts to remote + - name: Issue-29511 | Copy SQL scripts to remote copy: src: "{{ item }}" dest: "{{ remote_tmp_dir }}/{{ item | basename }}" @@ -34,13 +34,13 @@ - create-function.sql - create-procedure.sql - - name: Create function for test + - name: Issue-29511 | Create function for test shell: "{{ mysql_command }} < {{ remote_tmp_dir }}/create-function.sql" - - name: Create procedure for test + - name: Issue-29511 | Create procedure for test shell: "{{ mysql_command }} < {{ remote_tmp_dir }}/create-procedure.sql" - - name: Create user with FUNCTION and PROCEDURE privileges + - name: Issue-29511 | Create user with FUNCTION and PROCEDURE privileges mysql_user: <<: *mysql_params name: '{{ user_name_2 }}' @@ -49,13 +49,13 @@ priv: 'FUNCTION foo.function:EXECUTE/foo.*:SELECT/PROCEDURE bar.procedure:EXECUTE' register: result - - name: Assert Create user with FUNCTION and PROCEDURE privileges + - name: Issue-29511 | Assert Create user with FUNCTION and PROCEDURE privileges assert: that: - result is success - result is changed - - name: Create user with FUNCTION and PROCEDURE privileges - Idempotent check + - name: Issue-29511 | Create user with FUNCTION and PROCEDURE privileges - Idempotent check mysql_user: <<: *mysql_params name: '{{ user_name_2 }}' @@ -64,19 +64,13 @@ priv: 'FUNCTION foo.function:EXECUTE/foo.*:SELECT/PROCEDURE bar.procedure:EXECUTE' register: result - - name: Assert Create user with FUNCTION and PROCEDURE privileges + - name: Issue-29511 | Assert Create user with FUNCTION and PROCEDURE privileges assert: that: - result is success - result is not changed - - name: Remove user - mysql_user: - <<: *mysql_params - name: '{{ user_name_2 }}' - state: absent - - - name: Issue test teardown - cleanup databases + - name: Issue-29511 | Test teardown | cleanup databases mysql_db: <<: *mysql_params name: "{{ item }}" @@ -84,3 +78,5 @@ loop: - foo - bar + + - include: utils/remove_user.yml user_name="{{ user_name_2 }}" diff --git a/tests/integration/targets/test_mysql_user/tasks/issue-64560.yaml b/tests/integration/targets/test_mysql_user/tasks/issue-64560.yaml index 1c0af68..a7657f8 100644 --- a/tests/integration/targets/test_mysql_user/tasks/issue-64560.yaml +++ b/tests/integration/targets/test_mysql_user/tasks/issue-64560.yaml @@ -3,47 +3,50 @@ mysql_parameters: &mysql_params login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' block: - - name: Set root password + - name: Issue-64560 | Set root password mysql_user: <<: *mysql_params name: root + host: '%' password: '{{ root_password }}' check_implicit_admin: yes register: result - - name: assert root password is changed + - name: Issue-64560 | Assert root password is changed assert: that: - result is changed - - name: Set root password again + - name: Issue-64560 | Set root password again mysql_user: login_user: '{{ mysql_user }}' login_password: '{{ root_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: root + host: '%' password: '{{ root_password }}' check_implicit_admin: yes register: result - - name: Assert root password is not changed + - name: Issue-64560 | Assert root password is not changed assert: that: - result is not changed - - name: Set root password again + - name: Issue-64560 | Set root password again mysql_user: login_user: '{{ mysql_user }}' login_password: '{{ root_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' name: root + host: '%' password: '{{ mysql_password }}' check_implicit_admin: yes register: result diff --git a/tests/integration/targets/test_mysql_user/tasks/main.yml b/tests/integration/targets/test_mysql_user/tasks/main.yml index 5a029b8..188628f 100644 --- a/tests/integration/targets/test_mysql_user/tasks/main.yml +++ b/tests/integration/targets/test_mysql_user/tasks/main.yml @@ -1,3 +1,4 @@ +--- #################################################################### # WARNING: These are designed specifically for Ansible tests # # and should not be used as examples of how to write Ansible roles # @@ -24,15 +25,12 @@ # ============================================================ # create mysql user and verify user is added to mysql database # -- name: alias mysql command to include default options - set_fact: - mysql_command: "mysql -u{{ mysql_user }} -p{{ mysql_password }} -P{{ mysql_primary_port }} --protocol=tcp" - vars: mysql_parameters: &mysql_params login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' block: @@ -41,129 +39,49 @@ - include: issue-28.yml - - include: create_user.yml user_name={{user_name_1}} user_password={{ user_password_1 }} + - include: test_resource_limits.yml - - include: resource_limits.yml - - - include: assert_user.yml user_name={{user_name_1}} - - - include: remove_user.yml user_name={{user_name_1}} user_password={{ user_password_1 }} - - - include: assert_no_user.yml user_name={{user_name_1}} - - # ============================================================ - # Create mysql user that already exist on mysql database - # - - include: create_user.yml user_name={{user_name_1}} user_password={{ user_password_1 }} - - - name: create mysql user that already exist (expect changed=false) - mysql_user: - <<: *mysql_params - name: '{{user_name_1}}' - password: '{{user_password_1}}' - state: present - session_vars: - sort_buffer_size: 1024 - register: result - - - name: assert output message mysql user was not created - assert: - that: - - result is not changed - - # Try to set wrong session variable, must fail - - name: create mysql user trying to set global variable which is forbidden - mysql_user: - <<: *mysql_params - name: '{{user_name_1}}' - password: '{{user_password_1}}' - state: present - session_vars: - max_connections: 1000 - register: result - ignore_errors: true - - - name: we cannot set a global variable - assert: - that: - - result is failed - - result.msg is search('is a GLOBAL variable') - - # ============================================================ - # remove mysql user and verify user is removed from mysql database - # - - name: remove mysql user state=absent (expect changed=true) - mysql_user: - <<: *mysql_params - name: '{{ user_name_1 }}' - password: '{{ user_password_1 }}' - state: absent - register: result - - - name: assert output message mysql user was removed - assert: - that: - - result is changed - - - include: assert_no_user.yml user_name={{user_name_1}} - - # ============================================================ - # remove mysql user that does not exist on mysql database - # - - name: remove mysql user that does not exist state=absent (expect changed=false) - mysql_user: - <<: *mysql_params - name: '{{ user_name_1 }}' - password: '{{ user_password_1 }}' - state: absent - register: result - - - name: assert output message mysql user that does not exist - assert: - that: - - result is not changed - - - include: assert_no_user.yml user_name={{user_name_1}} + - include: test_idempotency.yml # ============================================================ # Create user with no privileges and verify default privileges are assign # - - name: create user with select privilege state=present (expect changed=true) + - name: create user with DEFAULT privilege state=present (expect changed=true) mysql_user: <<: *mysql_params - name: '{{ user_name_1 }}' - password: '{{ user_password_1 }}' + name: "{{ user_name_1 }}" + password: "{{ user_password_1 }}" state: present register: result - - include: assert_user.yml user_name={{user_name_1}} priv=USAGE + - include: utils/assert_user.yml user_name={{ user_name_1 }} user_host=localhost priv=USAGE - - include: remove_user.yml user_name={{user_name_1}} user_password={{ user_password_1 }} + - include: utils/remove_user.yml user_name={{ user_name_1 }} - - include: assert_no_user.yml user_name={{user_name_1}} + - include: utils/assert_no_user.yml user_name={{ user_name_1 }} # ============================================================ # Create user with select privileges and verify select privileges are assign # - - name: create user with select privilege state=present (expect changed=true) + - name: Create user with SELECT privilege state=present (expect changed=true) mysql_user: <<: *mysql_params - name: '{{ user_name_2 }}' - password: '{{ user_password_2 }}' + name: "{{ user_name_2 }}" + password: "{{ user_password_2 }}" state: present priv: '*.*:SELECT' register: result - - include: assert_user.yml user_name={{user_name_2}} priv=SELECT + - include: utils/assert_user.yml user_name={{ user_name_2 }} user_host=localhost priv=SELECT - - include: remove_user.yml user_name={{user_name_2}} user_password={{ user_password_2 }} + - include: utils/remove_user.yml user_name={{ user_name_2 }} - - include: assert_no_user.yml user_name={{user_name_2}} + - include: utils/assert_no_user.yml user_name={{ user_name_2 }} # ============================================================ # Assert user has access to multiple databases # - - name: give users access to multiple databases + - name: Give users access to multiple databases mysql_user: <<: *mysql_params name: '{{ item[0] }}' @@ -171,34 +89,34 @@ append_privs: yes password: '{{ user_password_1 }}' with_nested: - - [ '{{ user_name_1 }}', '{{ user_name_2 }}'] + - ['{{ user_name_1 }}', '{{ user_name_2 }}'] - "{{db_names}}" - - name: show grants access for user1 on multiple database + - name: Show grants access for user1 on multiple database command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ user_name_1 }}'@'localhost'\"" register: result - - name: assert grant access for user1 on multiple database + - name: Assert grant access for user1 on multiple database assert: that: - "'{{ item }}' in result.stdout" - with_items: "{{db_names}}" + with_items: "{{ db_names }}" - - name: show grants access for user2 on multiple database + - name: Show grants access for user2 on multiple database command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ user_name_2 }}'@'localhost'\"" register: result - - name: assert grant access for user2 on multiple database + - name: Assert grant access for user2 on multiple database assert: that: - "'{{ item }}' in result.stdout" with_items: "{{db_names}}" - - include: remove_user.yml user_name={{user_name_1}} user_password={{ user_password_1 }} + - include: utils/remove_user.yml user_name={{ user_name_1 }} - - include: remove_user.yml user_name={{user_name_2}} user_password={{ user_password_1 }} + - include: utils/remove_user.yml user_name={{ user_name_2 }} - - name: give user access to database via wildcard + - name: Give user SELECT access to database via wildcard mysql_user: <<: *mysql_params name: '{{ user_name_1 }}' @@ -206,7 +124,7 @@ append_privs: yes password: '{{ user_password_1 }}' - - name: show grants access for user1 on multiple database + - name: Show grants access for user1 on database via wildcard command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ user_name_1 }}'@'localhost'\"" register: result @@ -221,8 +139,8 @@ <<: *mysql_params name: '{{ user_name_1 }}' priv: - - unsuitable - - type + - unsuitable + - type append_privs: yes host_all: yes password: '{{ user_password_1 }}' @@ -235,7 +153,7 @@ - result is failed - result.msg is search('priv parameter must be str or dict') - - name: change user access to database via wildcard + - name: Change SELECT to INSERT for user access to database via wildcard mysql_user: <<: *mysql_params name: '{{ user_name_1 }}' @@ -244,7 +162,7 @@ host_all: yes password: '{{ user_password_1 }}' - - name: show grants access for user1 on multiple database + - name: Show grants access for user1 on database via wildcard command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ user_name_1 }}'@'localhost'\"" register: result @@ -254,7 +172,7 @@ - "'%db' in result.stdout" - "'INSERT' in result.stdout" - - include: remove_user.yml user_name={{user_name_1}} user_password={{ user_password_1 }} + - include: utils/remove_user.yml user_name={{user_name_1}} # ============================================================ # Test plaintext and encrypted password scenarios. @@ -266,7 +184,7 @@ # # FIXME: mariadb sql syntax for create/update user is not compatible - include: test_user_plugin_auth.yml - when: install_type == 'mysql' + when: db_engine == 'mysql' # ============================================================ # Assert create user with SELECT privileges, attempt to create database and update privileges to create database @@ -306,7 +224,7 @@ - issue_465 # Tests for the TLS requires dictionary - - include: tls_requirements.yml + - include: test_tls_requirements.yml - import_tasks: issue-29511.yaml tags: @@ -323,4 +241,4 @@ # https://github.com/ansible-collections/community.mysql/issues/231 - include: test_user_grants_with_roles_applied.yml - - include: revoke_only_grant.yml \ No newline at end of file + - include: test_revoke_only_grant.yml diff --git a/tests/integration/targets/test_mysql_user/tasks/remove_user.yml b/tests/integration/targets/test_mysql_user/tasks/remove_user.yml deleted file mode 100644 index 7a2c9e9..0000000 --- a/tests/integration/targets/test_mysql_user/tasks/remove_user.yml +++ /dev/null @@ -1,74 +0,0 @@ -# test code to remove mysql user -# (c) 2014, Wayne Rosario - -# This file is part of Ansible -# -# Ansible is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Ansible is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Ansible. If not, see . - -- 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: remove mysql user {{user_name}} - mysql_user: - <<: *mysql_params - name: '{{user_name}}' - password: '{{user_password}}' - state: absent - register: result - - - name: assert output message mysql user was removed - assert: - that: - - result is changed - - # ============================================================ - - name: create blank mysql user to be removed later - mysql_user: - <<: *mysql_params - name: "" - state: present - password: 'KJFDY&D*Sfuydsgf' - - - name: remove blank mysql user with hosts=all (expect changed) - mysql_user: - <<: *mysql_params - user: "" - host_all: true - state: absent - register: result - - - name: assert changed is true for removing all blank users - assert: - that: - - result is changed - - - name: remove blank mysql user with hosts=all (expect ok) - mysql_user: - <<: *mysql_params - user: "" - host_all: true - state: absent - register: result - - - name: assert changed is true for removing all blank users - assert: - that: - - result is not changed diff --git a/tests/integration/targets/test_mysql_user/tasks/test_idempotency.yml b/tests/integration/targets/test_mysql_user/tasks/test_idempotency.yml new file mode 100644 index 0000000..cc6850c --- /dev/null +++ b/tests/integration/targets/test_mysql_user/tasks/test_idempotency.yml @@ -0,0 +1,84 @@ +--- +- vars: + mysql_parameters: &mysql_params + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: '{{ mysql_host }}' + login_port: '{{ mysql_primary_port }}' + + block: + # ======================================================================== + # Creation + # ======================================================================== + - include: utils/create_user.yml user_name={{ user_name_1 }} user_password={{ user_password_1 }} + + - name: Idempotency | Create user that already exist (expect changed=false) + mysql_user: + <<: *mysql_params + name: "{{ user_name_1 }}" + password: "{{ user_password_1 }}" + state: present + register: result + + - name: Idempotency | Assert create user task is not changed + assert: {that: [result is not changed]} + + # ======================================================================== + # Removal + # ======================================================================== + - name: Idempotency | Remove user (expect changed=true) + mysql_user: + <<: *mysql_params + name: "{{ user_name_1 }}" + state: absent + register: result + + - name: Idempotency | Assert remove user task is changed + ansible.builtin.assert: + that: + - result is changed + + - name: Idempotency | Remove user that doesn't exists (expect changed=false) + mysql_user: + <<: *mysql_params + name: "{{ user_name_1 }}" + state: absent + register: result + + - name: Idempotency | Assert remove user task is not changed + ansible.builtin.assert: + that: + - result is not changed + + # ======================================================================== + # Removal with host_all + # ======================================================================== + + # Create blank user to be removed later + - include: utils/create_user.yml user_name="" user_password='KJFDY&D*Sfuysf' + + - name: Idempotency | Remove blank user with hosts=all (expect changed) + mysql_user: + <<: *mysql_params + user: "" + host_all: true + state: absent + register: result + + - name: Idempotency | Assert removing all blank users is changed + ansible.builtin.assert: + that: + - result is changed + + - name: Idempotency | Remove blank user with hosts=all (expect ok) + mysql_user: + <<: *mysql_params + user: "" + host_all: true + state: absent + register: result + + - name: Idempotency | Assert removing all blank users is not changed + ansible.builtin.assert: + that: + - result is not changed diff --git a/tests/integration/targets/test_mysql_user/tasks/test_priv_append.yml b/tests/integration/targets/test_mysql_user/tasks/test_priv_append.yml index 583f7c0..51d4a29 100644 --- a/tests/integration/targets/test_mysql_user/tasks/test_priv_append.yml +++ b/tests/integration/targets/test_mysql_user/tasks/test_priv_append.yml @@ -1,45 +1,48 @@ +--- # Test code to ensure that appending privileges will not result in unnecessary changes when the current privileges # are a superset of the new privileges that have been defined. - vars: mysql_parameters: &mysql_params login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' block: - - name: Create test databases + - name: Priv append | Create test databases mysql_db: <<: *mysql_params name: '{{ item }}' state: present loop: - - data1 - - data2 + - data1 + - data2 - - name: Create a user with an initial set of privileges + - name: Priv append | Create a user with an initial set of privileges mysql_user: <<: *mysql_params name: '{{ user_name_4 }}' + host: '%' password: '{{ user_password_4 }}' priv: 'data1.*:SELECT,INSERT/data2.*:SELECT,DELETE' state: present - - name: Run command to show privileges for user (expect privileges in stdout) - command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ user_name_4 }}'@'localhost'\"" + - name: Priv append | Run command to show privileges for user (expect privileges in stdout) + command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ user_name_4 }}'@'%'\"" register: result - - name: Assert that the initial set of privileges matches what is expected + - name: Priv append | Assert that the initial set of privileges matches what is expected assert: that: - "'GRANT SELECT, INSERT ON `data1`.*' in result.stdout" - "'GRANT SELECT, DELETE ON `data2`.*' in result.stdout" - - name: Append privileges that are a subset of the current privileges, which should be a no-op + - name: Priv append | Append privileges that are a subset of the current privileges, which should be a no-op mysql_user: <<: *mysql_params name: '{{ user_name_4 }}' + host: '%' password: '{{ user_password_4 }}' priv: 'data1.*:SELECT/data2.*:SELECT' append_privs: yes @@ -47,25 +50,26 @@ check_mode: '{{ enable_check_mode }}' register: result - - name: Assert that there wasn't a change in permissions + - name: Priv append | Assert that there wasn't a change in permissions assert: that: - result is not changed - - name: Run command to show privileges for user (expect privileges in stdout) - command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ user_name_4 }}'@'localhost'\"" + - name: Priv append | Run command to show privileges for user (expect privileges in stdout) + command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ user_name_4 }}'@'%'\"" register: result - - name: Assert that the permissions still match what was originally granted + - name: Priv append | Assert that the permissions still match what was originally granted assert: that: - "'GRANT SELECT, INSERT ON `data1`.*' in result.stdout" - "'GRANT SELECT, DELETE ON `data2`.*' in result.stdout" - - name: Append privileges that are not included in the current set of privileges to test that privileges are updated + - name: Priv append | Append privileges that are not included in the current set of privileges to test that privileges are updated mysql_user: <<: *mysql_params name: '{{ user_name_4 }}' + host: '%' password: '{{ user_password_4 }}' priv: 'data1.*:DELETE/data2.*:SELECT' append_privs: yes @@ -73,33 +77,34 @@ check_mode: '{{ enable_check_mode }}' register: result - - name: Assert that there was a change because permissions were added to data1.* + - name: Priv append | Assert that there was a change because permissions were added to data1.* assert: that: - result is changed - - name: Run command to show privileges for user (expect privileges in stdout) - command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ user_name_4 }}'@'localhost'\"" + - name: Priv append | Run command to show privileges for user (expect privileges in stdout) + command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ user_name_4 }}'@'%'\"" register: result - - name: Assert that the permissions were changed as expected if check_mode is set to 'no' + - name: Priv append | Assert that the permissions were changed as expected if check_mode is set to 'no' assert: that: - "'GRANT SELECT, INSERT, DELETE ON `data1`.*' in result.stdout" - "'GRANT SELECT, DELETE ON `data2`.*' in result.stdout" when: enable_check_mode == 'no' - - name: Assert that the permissions were not actually changed if check_mode is set to 'yes' + - name: Priv append | Assert that the permissions were not actually changed if check_mode is set to 'yes' assert: that: - "'GRANT SELECT, INSERT ON `data1`.*' in result.stdout" - "'GRANT SELECT, DELETE ON `data2`.*' in result.stdout" when: enable_check_mode == 'yes' - - name: Try to append invalid privileges + - name: Priv append | Try to append invalid privileges mysql_user: <<: *mysql_params name: '{{ user_name_4 }}' + host: '%' password: '{{ user_password_4 }}' priv: 'data1.*:INVALID/data2.*:SELECT' append_privs: yes @@ -108,7 +113,7 @@ register: result ignore_errors: true - - name: Assert that there wasn't a change in privileges if check_mode is set to 'no' + - name: Priv append | Assert that there wasn't a change in privileges if check_mode is set to 'no' assert: that: - result is failed @@ -123,11 +128,7 @@ name: '{{ item }}' state: present loop: - - data1 - - data2 + - data1 + - data2 - - name: Drop test user - mysql_user: - <<: *mysql_params - name: '{{ user_name_4 }}' - state: absent + - include: utils/remove_user.yml user_name={{ user_name_4 }} diff --git a/tests/integration/targets/test_mysql_user/tasks/test_priv_dict.yml b/tests/integration/targets/test_mysql_user/tasks/test_priv_dict.yml index d54c946..82385e1 100644 --- a/tests/integration/targets/test_mysql_user/tasks/test_priv_dict.yml +++ b/tests/integration/targets/test_mysql_user/tasks/test_priv_dict.yml @@ -1,24 +1,25 @@ +--- - vars: mysql_parameters: &mysql_params login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' block: # Tests for priv parameter value passed as a dict - - name: Create test databases + - name: Priv dict | Create test databases mysql_db: <<: *mysql_params name: '{{ item }}' state: present loop: - - data1 - - data2 - - data3 + - data1 + - data2 + - data3 - - name: Create user with privileges + - name: Priv dict | Create user with privileges mysql_user: <<: *mysql_params name: '{{ user_name_3 }}' @@ -28,7 +29,7 @@ "data2.*": "SELECT" state: present - - name: Run command to show privileges for user (expect privileges in stdout) + - name: Priv dict | Run command to show privileges for user (expect privileges in stdout) command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ user_name_3 }}'@'localhost'\"" register: result @@ -39,12 +40,12 @@ - "'GRANT SELECT ON `data2`.*' in result.stdout" # Issue https://github.com/ansible-collections/community.mysql/issues/99 - - name: Create test table test_table_issue99 + - name: Priv dict | Create test table test_table_issue99 mysql_query: <<: *mysql_params query: "CREATE TABLE IF NOT EXISTS data3.test_table_issue99 (a INT, b INT, c INT)" - - name: Grant select on a column + - name: Priv dict | Grant select on a column mysql_user: <<: *mysql_params name: '{{ user_name_3 }}' @@ -52,11 +53,12 @@ 'data3.test_table_issue99': 'SELECT (a)' register: result - - assert: + - name: Priv dict | Assert that select on a column is changed + assert: that: - - result is changed + - result is changed - - name: Grant select on the column again + - name: Priv dict | Grant select on the column again mysql_user: <<: *mysql_params name: '{{ user_name_3 }}' @@ -64,12 +66,12 @@ 'data3.test_table_issue99': 'SELECT (a)' register: result - - assert: + - name: Priv dict | Assert that select on the column is not changed + assert: that: - - result is not changed + - result is not changed - - - name: Grant select on columns + - name: Priv dict | Grant select on columns mysql_user: <<: *mysql_params name: '{{ user_name_3 }}' @@ -77,11 +79,12 @@ 'data3.test_table_issue99': 'SELECT (a, b),INSERT' register: result - - assert: + - name: Priv dict | Assert select on columns is changed + assert: that: - - result is changed + - result is changed - - name: Grant select on columns again + - name: Priv dict | Grant select on columns again mysql_user: <<: *mysql_params name: '{{ user_name_3 }}' @@ -89,11 +92,12 @@ 'data3.test_table_issue99': 'SELECT (a, b),INSERT' register: result - - assert: + - name: Priv dict | Assert that select on columns again is not changed + assert: that: - - result is not changed + - result is not changed - - name: Grant privs on columns + - name: Priv dict | Grant privs on columns mysql_user: <<: *mysql_params name: '{{ user_name_3 }}' @@ -101,11 +105,12 @@ 'data3.test_table_issue99': 'SELECT (a, b), INSERT (a, b), UPDATE' register: result - - assert: + - name: Priv dict | Assert that grant privs on columns is changed + assert: that: - - result is changed + - result is changed - - name: Grant same privs on columns again, note that the column order is different + - name: Priv dict | Grant same privs on columns again, note that the column order is different mysql_user: <<: *mysql_params name: '{{ user_name_3 }}' @@ -113,21 +118,22 @@ 'data3.test_table_issue99': 'SELECT (a, b), UPDATE, INSERT (b, a)' register: result - - assert: + - name: Priv dict | Assert that grants same privs with different order is not changed + assert: that: - - result is not changed + - result is not changed - - name: Run command to show privileges for user (expect privileges in stdout) + - name: Priv dict | Run command to show privileges for user (expect privileges in stdout) command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ user_name_3 }}'@'localhost'\"" register: result - - name: Assert user has giving privileges + - name: Priv dict | Assert user has giving privileges assert: that: - "'GRANT SELECT (`A`, `B`), INSERT (`A`, `B`), UPDATE' in result.stdout" when: "'(`A`, `B`)' in result.stdout" - - name: Assert user has giving privileges + - name: Priv dict | Assert user has giving privileges assert: that: - "'GRANT SELECT (A, B), INSERT (A, B), UPDATE' in result.stdout" @@ -135,18 +141,14 @@ ########## # Clean up - - name: Drop test databases + - name: Priv dict | Drop test databases mysql_db: <<: *mysql_params name: '{{ item }}' state: present loop: - - data1 - - data2 - - data3 + - data1 + - data2 + - data3 - - name: Drop test user - mysql_user: - <<: *mysql_params - name: '{{ user_name_3 }}' - state: absent + - include: utils/remove_user.yml user_name="{{ user_name_3 }}" diff --git a/tests/integration/targets/test_mysql_user/tasks/test_priv_subtract.yml b/tests/integration/targets/test_mysql_user/tasks/test_priv_subtract.yml index 7595243..b63f664 100644 --- a/tests/integration/targets/test_mysql_user/tasks/test_priv_subtract.yml +++ b/tests/integration/targets/test_mysql_user/tasks/test_priv_subtract.yml @@ -1,42 +1,45 @@ +--- # Test code to ensure that subtracting privileges will not result in unnecessary changes. - vars: mysql_parameters: &mysql_params login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' block: - - name: Create test databases + - name: Priv substract | Create test databases mysql_db: <<: *mysql_params name: '{{ item }}' state: present loop: - - data1 + - data1 - - name: Create a user with an initial set of privileges + - name: Priv substract | Create a user with an initial set of privileges mysql_user: <<: *mysql_params name: '{{ user_name_4 }}' + host: '%' password: '{{ user_password_4 }}' priv: 'data1.*:SELECT,INSERT' state: present - - name: Run command to show privileges for user (expect privileges in stdout) - command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ user_name_4 }}'@'localhost'\"" + - name: Priv substract | Run command to show privileges for user (expect privileges in stdout) + command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ user_name_4 }}'@'%'\"" register: result - - name: Assert that the initial set of privileges matches what is expected + - name: Priv substract | Assert that the initial set of privileges matches what is expected assert: that: - "'GRANT SELECT, INSERT ON `data1`.*' in result.stdout" - - name: Subtract privileges that are not in the current privileges, which should be a no-op + - name: Priv substract | Subtract privileges that are not in the current privileges, which should be a no-op mysql_user: <<: *mysql_params name: '{{ user_name_4 }}' + host: '%' password: '{{ user_password_4 }}' priv: 'data1.*:DELETE' subtract_privs: yes @@ -44,24 +47,25 @@ check_mode: '{{ enable_check_mode }}' register: result - - name: Assert that there wasn't a change in permissions + - name: Priv substract | Assert that there wasn't a change in permissions assert: that: - result is not changed - - name: Run command to show privileges for user (expect privileges in stdout) - command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ user_name_4 }}'@'localhost'\"" + - name: Priv substract | Run command to show privileges for user (expect privileges in stdout) + command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ user_name_4 }}'@'%'\"" register: result - - name: Assert that the permissions still match what was originally granted + - name: Priv substract | Assert that the permissions still match what was originally granted assert: that: - "'GRANT SELECT, INSERT ON `data1`.*' in result.stdout" - - name: Subtract existing and not-existing privileges, but not all + - name: Priv substract | Subtract existing and not-existing privileges, but not all mysql_user: <<: *mysql_params name: '{{ user_name_4 }}' + host: '%' password: '{{ user_password_4 }}' priv: 'data1.*:INSERT,DELETE' subtract_privs: yes @@ -69,31 +73,32 @@ check_mode: '{{ enable_check_mode }}' register: result - - name: Assert that there was a change because permissions were/would be revoked on data1.* + - name: Priv substract | Assert that there was a change because permissions were/would be revoked on data1.* assert: that: - result is changed - - name: Run command to show privileges for user (expect privileges in stdout) - command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ user_name_4 }}'@'localhost'\"" + - name: Priv substract | Run command to show privileges for user (expect privileges in stdout) + command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ user_name_4 }}'@'%'\"" register: result - - name: Assert that the permissions were not changed if check_mode is set to 'yes' + - name: Priv substract | Assert that the permissions were not changed if check_mode is set to 'yes' assert: that: - "'GRANT SELECT, INSERT ON `data1`.*' in result.stdout" when: enable_check_mode == 'yes' - - name: Assert that only DELETE was revoked if check_mode is set to 'no' + - name: Priv substract | Assert that only DELETE was revoked if check_mode is set to 'no' assert: that: - "'GRANT SELECT ON `data1`.*' in result.stdout" when: enable_check_mode == 'no' - - name: Try to subtract invalid privileges + - name: Priv substract | Try to subtract invalid privileges mysql_user: <<: *mysql_params name: '{{ user_name_4 }}' + host: '%' password: '{{ user_password_4 }}' priv: 'data1.*:INVALID' subtract_privs: yes @@ -101,31 +106,32 @@ check_mode: '{{ enable_check_mode }}' register: result - - name: Assert that there was no change because invalid permissions are ignored + - name: Priv substract | Assert that there was no change because invalid permissions are ignored assert: that: - result is not changed - - name: Run command to show privileges for user (expect privileges in stdout) - command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ user_name_4 }}'@'localhost'\"" + - name: Priv substract | Run command to show privileges for user (expect privileges in stdout) + command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ user_name_4 }}'@'%'\"" register: result - - name: Assert that the permissions were not changed with check_mode=='yes' + - name: Priv substract | Assert that the permissions were not changed with check_mode=='yes' assert: that: - "'GRANT SELECT, INSERT ON `data1`.*' in result.stdout" when: enable_check_mode == 'yes' - - name: Assert that the permissions were not changed with check_mode=='no' + - name: Priv substract | Assert that the permissions were not changed with check_mode=='no' assert: that: - "'GRANT SELECT ON `data1`.*' in result.stdout" when: enable_check_mode == 'no' - - name: trigger failure by trying to subtract and append privileges at the same time + - name: Priv substract | Trigger failure by trying to subtract and append privileges at the same time mysql_user: <<: *mysql_params name: '{{ user_name_4 }}' + host: '%' password: '{{ user_password_4 }}' priv: 'data1.*:SELECT' subtract_privs: yes @@ -135,22 +141,22 @@ register: result ignore_errors: true - - name: Assert the previous execution failed + - name: Priv substract | Assert the previous execution failed assert: that: - result is failed - - name: Run command to show privileges for user (expect privileges in stdout) - command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ user_name_4 }}'@'localhost'\"" + - name: Priv substract | Run command to show privileges for user (expect privileges in stdout) + command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ user_name_4 }}'@'%'\"" register: result - - name: Assert that the permissions stayed the same, with check_mode=='yes' + - name: Priv substract | Assert that the permissions stayed the same, with check_mode=='yes' assert: that: - "'GRANT SELECT, INSERT ON `data1`.*' in result.stdout" when: enable_check_mode == 'yes' - - name: Assert that the permissions stayed the same, with check_mode=='no' + - name: Priv substract | Assert that the permissions stayed the same, with check_mode=='no' assert: that: - "'GRANT SELECT ON `data1`.*' in result.stdout" @@ -158,16 +164,12 @@ ########## # Clean up - - name: Drop test databases + - name: Priv substract | Drop test databases mysql_db: <<: *mysql_params name: '{{ item }}' state: present loop: - - data1 + - data1 - - name: Drop test user - mysql_user: - <<: *mysql_params - name: '{{ user_name_4 }}' - state: absent + - include: utils/remove_user.yml user_name="{{ user_name_4 }}" diff --git a/tests/integration/targets/test_mysql_user/tasks/test_privs.yml b/tests/integration/targets/test_mysql_user/tasks/test_privs.yml index b9581f7..9801e19 100644 --- a/tests/integration/targets/test_mysql_user/tasks/test_privs.yml +++ b/tests/integration/targets/test_mysql_user/tasks/test_privs.yml @@ -1,3 +1,4 @@ +--- # test code for privileges for mysql_user module # (c) 2014, Wayne Rosario @@ -20,56 +21,58 @@ mysql_parameters: &mysql_params login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' block: # ============================================================ - - name: create user with basic select privileges + - name: Privs | Create user with basic select privileges mysql_user: <<: *mysql_params name: '{{ user_name_2 }}' + host: '%' password: '{{ user_password_2 }}' priv: '*.*:SELECT' state: present when: current_append_privs == "yes" - - include: assert_user.yml user_name={{user_name_2}} priv='SELECT' + - include: utils/assert_user.yml user_name={{ user_name_2 }} user_host=% priv='SELECT' when: current_append_privs == "yes" - - name: create user with current privileges (expect changed=true) + - name: Privs | Create user with current privileges (expect changed=true) mysql_user: <<: *mysql_params name: '{{ user_name_2 }}' + host: '%' password: '{{ user_password_2 }}' - priv: '*.*:{{current_privilege}}' - append_privs: '{{current_append_privs}}' + priv: '*.*:{{ current_privilege }}' + append_privs: '{{ current_append_privs }}' state: present register: result - - name: assert output message for current privileges + - name: Privs | Assert output message for current privileges assert: that: - result is changed - - name: run command to show privileges for user (expect privileges in stdout) - command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{user_name_2}}'@'localhost'\"" + - name: Privs | Run command to show privileges for user (expect privileges in stdout) + command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{user_name_2}}'@'%'\"" register: result - - name: assert user has correct privileges + - name: Privs | Assert user has correct privileges assert: that: - - "'GRANT {{current_privilege | replace(',', ', ')}} ON *.*' in result.stdout" + - "'GRANT {{ current_privilege | replace(',', ', ') }} ON *.*' in result.stdout" when: current_append_privs == "no" - - name: assert user has correct privileges + - name: Privs | Assert user has correct privileges assert: that: - - "'GRANT SELECT, {{current_privilege | replace(',', ', ')}} ON *.*' in result.stdout" + - "'GRANT SELECT, {{ current_privilege | replace(',', ', ') }} ON *.*' in result.stdout" when: current_append_privs == "yes" - - name: create database using user current privileges + - name: Privs | Create database using user current privileges mysql_db: login_user: '{{ user_name_2 }}' login_password: '{{ user_password_2 }}' @@ -79,56 +82,59 @@ state: present ignore_errors: true - - name: run command to test that database was not created + - name: Privs | Run command to test that database was not created command: "{{ mysql_command }} -e \"show databases like '{{ db_name }}'\"" register: result - - name: assert database was not created + - name: Privs | Assert database was not created assert: that: - - "'{{ db_name }}' not in result.stdout" + - db_name not in result.stdout # ============================================================ - - name: Add privs to a specific table (expect changed) + - name: Privs | Add privs to a specific table (expect changed) mysql_user: <<: *mysql_params name: '{{ user_name_2 }}' + host: '%' password: '{{ user_password_2 }}' priv: 'jmainguy.jmainguy:ALL' state: present register: result - - name: Assert that priv changed + - name: Privs | Assert that priv changed assert: that: - result is changed - - name: Add privs to a specific table (expect ok) + - name: Privs | Add privs to a specific table (expect ok) mysql_user: <<: *mysql_params name: '{{ user_name_2 }}' + host: '%' password: '{{ user_password_2 }}' priv: 'jmainguy.jmainguy:ALL' state: present register: result - - name: Assert that priv did not change + - name: Privs | Assert that priv did not change assert: that: - result is not changed # ============================================================ - - name: update user with all privileges + - name: Privs | Grant ALL to user {{ user_name_2 }} mysql_user: <<: *mysql_params name: '{{ user_name_2 }}' + host: '%' password: '{{ user_password_2 }}' priv: '*.*:ALL' state: present - # - include: assert_user.yml user_name={{user_name_2}} priv='ALL PRIVILEGES' + # - include: utils/assert_user.yml user_name={{user_name_2}} user_host=% priv='ALL PRIVILEGES' - - name: create database using user + - name: Privs | Create database using user {{ user_name_2 }} mysql_db: login_user: '{{ user_name_2 }}' login_password: '{{ user_password_2 }}' @@ -137,10 +143,10 @@ name: '{{ db_name }}' state: present - - name: run command to test database was created using user new privileges + - name: Privs | Run command to test database was created using user new privileges command: "{{ mysql_command }} -e \"SHOW CREATE DATABASE {{ db_name }}\"" - - name: drop database using user + - name: Privs | Drop database using user {{ user_name_2 }} mysql_db: login_user: '{{ user_name_2 }}' login_password: '{{ user_password_2 }}' @@ -150,24 +156,26 @@ state: absent # ============================================================ - - name: update user with a long privileges list (mysql has a special multiline grant output) + - name: Privs | Update user with a long privileges list (mysql has a special multiline grant output) mysql_user: <<: *mysql_params name: '{{ user_name_2 }}' + host: '%' password: '{{ user_password_2 }}' priv: '*.*:CREATE USER,FILE,PROCESS,RELOAD,REPLICATION CLIENT,REPLICATION SLAVE,SHOW DATABASES,SHUTDOWN,SUPER,CREATE,DROP,EVENT,LOCK TABLES,INSERT,UPDATE,DELETE,SELECT,SHOW VIEW,GRANT' state: present register: result - - name: Assert that priv changed + - name: Privs | Assert that priv changed assert: that: - result is changed - - name: Test idempotency with a long privileges list (expect ok) + - name: Privs | Test idempotency with a long privileges list (expect ok) mysql_user: <<: *mysql_params name: '{{ user_name_2 }}' + host: '%' password: '{{ user_password_2 }}' priv: '*.*:CREATE USER,FILE,PROCESS,RELOAD,REPLICATION CLIENT,REPLICATION SLAVE,SHOW DATABASES,SHUTDOWN,SUPER,CREATE,DROP,EVENT,LOCK TABLES,INSERT,UPDATE,DELETE,SELECT,SHOW VIEW,GRANT' state: present @@ -175,20 +183,15 @@ # FIXME: on mysql >=8 and mariadb >=10.5.2 there's always a change because # the REPLICATION CLIENT privilege was renamed to BINLOG MONITOR - - name: Assert that priv did not change + - name: Privs | Assert that priv did not change assert: that: - result is not changed - - name: remove username - mysql_user: - <<: *mysql_params - name: '{{ user_name_2 }}' - password: '{{ user_password_2 }}' - state: absent + - include: utils/remove_user.yml user_name="{{ user_name_2 }}" # ============================================================ - - name: grant all privileges with grant option + - name: Privs | Grant all privileges with grant option mysql_user: <<: *mysql_params name: '{{ user_name_2 }}' @@ -197,23 +200,23 @@ state: present register: result - - name: Assert that priv changed + - name: Privs | Assert that priv changed assert: that: - result is changed - - name: Collect user info by host + - name: Privs | Collect user info by host community.mysql.mysql_info: <<: *mysql_params filter: "users" register: mysql_info_about_users - - name: Assert that 'GRANT' permission is present + - name: Privs | Assert that 'GRANT' permission is present assert: that: - mysql_info_about_users.users.localhost.{{ user_name_2 }}.Grant_priv == 'Y' - - name: Test idempotency (expect ok) + - name: Privs | Test idempotency (expect ok) mysql_user: <<: *mysql_params name: '{{ user_name_2 }}' @@ -223,24 +226,24 @@ register: result # FIXME: on mysql >=8 there's always a change (ALL PRIVILEGES -> specific privileges) - - name: Assert that priv did not change + - name: Privs | Assert that priv did not change assert: that: - result is not changed - - name: Collect user info by host + - name: Privs | Collect user info by host community.mysql.mysql_info: <<: *mysql_params filter: "users" register: mysql_info_about_users - - name: Assert that 'GRANT' permission is present + - name: Privs | Assert that 'GRANT' permission is present (by host) assert: that: - mysql_info_about_users.users.localhost.{{ user_name_2 }}.Grant_priv == 'Y' # ============================================================ - - name: update user with invalid privileges + - name: Privs | Update user with invalid privileges mysql_user: <<: *mysql_params name: '{{ user_name_2 }}' @@ -250,15 +253,10 @@ register: result ignore_errors: yes - - name: Assert that priv did not change + - name: Privs | Assert that priv did not change assert: that: - result is failed - "'Error granting privileges' in result.msg" - - name: remove username - mysql_user: - <<: *mysql_params - name: '{{ user_name_2 }}' - password: '{{ user_password_2 }}' - state: absent + - include: utils/remove_user.yml user_name="{{ user_name_2 }}" diff --git a/tests/integration/targets/test_mysql_user/tasks/test_privs_issue_465.yml b/tests/integration/targets/test_mysql_user/tasks/test_privs_issue_465.yml index edf4a0f..2e6a41e 100644 --- a/tests/integration/targets/test_mysql_user/tasks/test_privs_issue_465.yml +++ b/tests/integration/targets/test_mysql_user/tasks/test_privs_issue_465.yml @@ -5,13 +5,13 @@ mysql_parameters: &mysql_params login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' block: # ============================================================ - - name: create a user with parameters that will always cause an exception + - name: Privs issue 465 | Create a user with parameters that will always cause an exception mysql_user: <<: *mysql_params name: user_issue_465 @@ -21,7 +21,7 @@ ignore_errors: true register: result - - name: assert output message for current privileges + - name: Privs issue 465 | Assert output message for current privileges assert: that: - result is failed diff --git a/tests/integration/targets/test_mysql_user/tasks/resource_limits.yml b/tests/integration/targets/test_mysql_user/tasks/test_resource_limits.yml similarity index 60% rename from tests/integration/targets/test_mysql_user/tasks/resource_limits.yml rename to tests/integration/targets/test_mysql_user/tasks/test_resource_limits.yml index 736adb3..7c2b97b 100644 --- a/tests/integration/targets/test_mysql_user/tasks/resource_limits.yml +++ b/tests/integration/targets/test_mysql_user/tasks/test_resource_limits.yml @@ -1,20 +1,22 @@ +--- # test code for resource_limits parameter - vars: mysql_parameters: &mysql_params login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' block: - - name: Drop mysql user {{ user_name_1 }} if exists + - name: Resource limits | Drop mysql user {{ user_name_1 }} if exists mysql_user: <<: *mysql_params name: '{{ user_name_1 }}' + host_all: true state: absent - - name: Create mysql user {{ user_name_1 }} with resource limits in check_mode + - name: Resource limits | Create mysql user {{ user_name_1 }} with resource limits in check_mode mysql_user: <<: *mysql_params name: '{{ user_name_1 }}' @@ -26,11 +28,12 @@ check_mode: yes register: result - - assert: + - name: Resource limits | Assert that create user with resource limits is changed + assert: that: - result is changed - - name: Create mysql user {{ user_name_1 }} with resource limits in actual mode + - name: Resource limits | Create mysql user {{ user_name_1 }} with resource limits in actual mode mysql_user: <<: *mysql_params name: '{{ user_name_1 }}' @@ -45,19 +48,23 @@ that: - result is changed - - name: Check + - name: Resource limits | Retrieve user mysql_query: <<: *mysql_params query: > - SELECT User FROM mysql.user WHERE User = '{{ user_name_1 }}' AND Host = 'localhost' - AND max_questions = 10 AND max_connections = 5 + SELECT User FROM mysql.user + WHERE User = '{{ user_name_1 }}' + AND Host = 'localhost' + AND max_questions = 10 + AND max_connections = 5 register: result - - assert: + - name: Resource limits | Assert that rowcount is 1 + assert: that: - - result.rowcount[0] == 1 + - result.rowcount[0] == 1 - - name: Try to set the same limits again in check mode + - name: Resource limits | Try to set the same limits again in check mode mysql_user: <<: *mysql_params name: '{{ user_name_1 }}' @@ -69,11 +76,12 @@ check_mode: yes register: result - - assert: + - name: Resource limits | Assert that set same limits again is not changed + assert: that: - result is not changed - - name: Try to set the same limits again in actual mode + - name: Resource limits | Try to set the same limits again in actual mode mysql_user: <<: *mysql_params name: '{{ user_name_1 }}' @@ -84,11 +92,12 @@ MAX_CONNECTIONS_PER_HOUR: 5 register: result - - assert: + - name: Resource limits | Assert that set same limits again in actual mode is not changed + assert: that: - result is not changed - - name: Change limits + - name: Resource limits | Change limits mysql_user: <<: *mysql_params name: '{{ user_name_1 }}' @@ -99,19 +108,24 @@ MAX_CONNECTIONS_PER_HOUR: 5 register: result - - assert: + - name: Resource limits | Assert limits changed + assert: that: - result is changed - - name: Check + - name: Resource limits | Get user limits mysql_query: <<: *mysql_params query: > - SELECT User FROM mysql.user WHERE User = '{{ user_name_1 }}' AND Host = 'localhost' - AND max_questions = 5 AND max_connections = 5 + SELECT User FROM mysql.user + WHERE User = '{{ user_name_1 }}' + AND Host = 'localhost' + AND max_questions = 5 + AND max_connections = 5 register: result - - assert: + - name: Resource limits | Assert limit row count + assert: that: - result.rowcount[0] == 1 diff --git a/tests/integration/targets/test_mysql_user/tasks/revoke_only_grant.yml b/tests/integration/targets/test_mysql_user/tasks/test_revoke_only_grant.yml similarity index 61% rename from tests/integration/targets/test_mysql_user/tasks/revoke_only_grant.yml rename to tests/integration/targets/test_mysql_user/tasks/test_revoke_only_grant.yml index 19b9b6a..de0fc62 100644 --- a/tests/integration/targets/test_mysql_user/tasks/revoke_only_grant.yml +++ b/tests/integration/targets/test_mysql_user/tasks/test_revoke_only_grant.yml @@ -3,17 +3,12 @@ mysql_parameters: &mysql_params login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' block: - - name: Drop mysql user if exists - mysql_user: - <<: *mysql_params - name: '{{ user_name_1 }}' - state: absent - ignore_errors: true + - include: utils/remove_user.yml user_name={{ user_name_1 }} - - name: create user with two grants + - name: Revoke only grants | Create user with two grants mysql_user: <<: *mysql_params name: "{{ user_name_1 }}" @@ -21,7 +16,7 @@ update_password: on_create priv: '*.*:SELECT,GRANT' - - name: user must have only on priv, grant priv must be dropped + - name: Revoke only grants | Revoke grant priv from db_user1 register: result mysql_user: <<: *mysql_params @@ -30,12 +25,13 @@ update_password: on_create priv: '*.*:SELECT' - - assert: + - name: Revoke only grants | Assert that db_user1 only have one priv left + assert: that: - result is not failed - result is changed - - name: immutable - user must have only on priv, grant priv must be dropped + - name: Revoke only grants | Update db_user1 again to test idempotence register: result mysql_user: <<: *mysql_params @@ -44,15 +40,11 @@ update_password: on_create priv: '*.*:SELECT' - - assert: + - name: Revoke only grants | Assert that task is idempotent + assert: that: - - result is not failed + - result is succeeded - result is not changed always: - - name: drop user - mysql_user: - <<: *mysql_params - name: '{{ user_name_1 }}' - state: absent - ignore_errors: true + - include: utils/remove_user.yml user_name={{ user_name_1 }} diff --git a/tests/integration/targets/test_mysql_user/tasks/tls_requirements.yml b/tests/integration/targets/test_mysql_user/tasks/test_tls_requirements.yml similarity index 57% rename from tests/integration/targets/test_mysql_user/tasks/tls_requirements.yml rename to tests/integration/targets/test_mysql_user/tasks/test_tls_requirements.yml index 7bf142e..f85ae3b 100644 --- a/tests/integration/targets/test_mysql_user/tasks/tls_requirements.yml +++ b/tests/integration/targets/test_mysql_user/tasks/test_tls_requirements.yml @@ -3,26 +3,12 @@ mysql_parameters: &mysql_params login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' block: - # ============================================================ - - name: find out the database version - mysql_info: - <<: *mysql_params - filter: version - register: db_version - - - name: Drop mysql user {{ item }} if exists - mysql_user: - <<: *mysql_params - name: '{{ item }}' - state: absent - with_items: ['{{ user_name_1 }}', '{{ user_name_2 }}', '{{ user_name_3 }}'] - - - name: create user with TLS requirements in check mode (expect changed=true) + - name: Tls reqs | Create user with TLS requirements in check mode (expect changed=true) mysql_user: <<: *mysql_params name: "{{ user_name_1 }}" @@ -32,14 +18,14 @@ check_mode: yes register: result - - name: Assert check mode user create reports changed state + - name: Tls reqs | Assert check mode user create reports changed state assert: that: - result is changed - - include: assert_no_user.yml user_name={{user_name_1}} + - include: utils/assert_no_user.yml user_name={{user_name_1}} - - name: create user with TLS requirements state=present (expect changed=true) + - name: Tls reqs | Create user with TLS requirements state=present (expect changed=true) mysql_user: <<: *mysql_params name: '{{ item[0] }}' @@ -55,45 +41,49 @@ issuer: '/CN=org/O=MyDom, Inc./C=US/ST=Oregon/L=Portland' - block: - - name: retrieve TLS requirements for users in old database version + - name: Tls reqs | Retrieve TLS requirements for users in old database version command: "{{ mysql_command }} -L -N -s -e \"SHOW GRANTS for '{{ item }}'@'localhost'\"" register: old_result with_items: ['{{ user_name_1 }}', '{{ user_name_2 }}', '{{ user_name_3 }}'] - - name: set old database separator + - name: Tls reqs | Set old database separator set_fact: separator: '\n' # Semantically: when mysql version <= 5.6 or MariaDB version <= 10.1 - when: db_version.version.major <= 5 and db_version.version.minor <= 6 or db_version.version.major == 10 and db_version.version.minor < 2 + when: + - (db_engine == 'mysql' and db_version is version('5.6', '<=')) + or (db_engine == 'mariadb' and db_version is version('10.1', '<=')) - block: - - name: retrieve TLS requirements for users in new database version + - name: Tls reqs | Retrieve TLS requirements for users in new database version command: "{{ mysql_command }} -L -N -s -e \"SHOW CREATE USER '{{ item }}'@'localhost'\"" register: new_result with_items: ['{{ user_name_1 }}', '{{ user_name_2 }}', '{{ user_name_3 }}'] - - name: set new database separator + - name: Tls reqs | Set new database separator set_fact: separator: 'PASSWORD' # Semantically: when mysql version >= 5.7 or MariaDB version >= 10.2 - when: db_version.version.major == 5 and db_version.version.minor >= 7 or db_version.version.major > 5 and db_version.version.major < 10 or db_version.version.major == 10 and db_version.version.minor >= 2 + when: + - (db_engine == 'mysql' and db_version is version('5.7', '>=')) + or (db_engine == 'mariadb' and db_version is version('10.2', '>=')) - block: - - name: assert user1 TLS requirements + - name: Tls reqs | Assert user1 TLS requirements assert: that: - "'SSL' in reqs" vars: - reqs: "{{((old_result.results[0] is skipped | ternary(new_result, old_result)).results | selectattr('item', 'contains', user_name_1) | first).stdout.split('REQUIRE')[1].split(separator)[0].strip()}}" - - name: assert user2 TLS requirements + - name: Tls reqs | Assert user2 TLS requirements assert: that: - "'X509' in reqs" vars: - reqs: "{{((old_result.results[0] is skipped | ternary(new_result, old_result)).results | selectattr('item', 'contains', user_name_2) | first).stdout.split('REQUIRE')[1].split(separator)[0].strip()}}" - - name: assert user3 TLS requirements + - name: Tls reqs | Assert user3 TLS requirements assert: that: - "'/CN=alice/O=MyDom, Inc./C=US/ST=Oregon/L=Portland' in (reqs | select('contains', 'SUBJECT') | first)" @@ -104,7 +94,7 @@ # CentOS 6 uses an older version of jinja that does not provide the selectattr filter. when: ansible_distribution != 'CentOS' or ansible_distribution_major_version != '6' - - name: modify user with TLS requirements state=present in check mode (expect changed=true) + - name: Tls reqs | Modify user with TLS requirements state=present in check mode (expect changed=true) mysql_user: <<: *mysql_params name: '{{ user_name_1 }}' @@ -114,28 +104,32 @@ check_mode: yes register: result - - name: Assert check mode user update reports changed state + - name: Tls reqs | Assert check mode user update reports changed state assert: that: - result is changed - - name: retrieve TLS requirements for users in old database version + - name: Tls reqs | Retrieve TLS requirements for users in old database version command: "{{ mysql_command }} -L -N -s -e \"SHOW GRANTS for '{{ user_name_1 }}'@'localhost'\"" register: old_result - when: db_version.version.major <= 5 and db_version.version.minor <= 6 or db_version.version.major == 10 and db_version.version.minor < 2 + when: + - (db_engine == 'mysql' and db_version is version('5.6', '<=')) + or (db_engine == 'mariadb' and db_version is version('10.2', '<')) - - name: retrieve TLS requirements for users in new database version + - name: Tls reqs | Retrieve TLS requirements for users in new database version command: "{{ mysql_command }} -L -N -s -e \"SHOW CREATE USER '{{ user_name_1 }}'@'localhost'\"" register: new_result - when: db_version.version.major == 5 and db_version.version.minor >= 7 or db_version.version.major > 5 and db_version.version.major < 10 or db_version.version.major == 10 and db_version.version.minor >= 2 + when: + - (db_engine == 'mysql' and db_version is version('5.7', '>=')) + or (db_engine == 'mariadb' and db_version is version('10.2', '>=')) - - name: assert user1 TLS requirements was not changed + - name: Tls reqs | Assert user1 TLS requirements was not changed assert: that: "'SSL' in reqs" vars: - reqs: "{{(old_result is skipped | ternary(new_result, old_result)).stdout.split('REQUIRE')[1].split(separator)[0].strip()}}" - - name: modify user with TLS requirements state=present (expect changed=true) + - name: Tls reqs | Modify user with TLS requirements state=present (expect changed=true) mysql_user: <<: *mysql_params name: '{{ user_name_1 }}' @@ -143,45 +137,49 @@ tls_requires: X509: - - name: retrieve TLS requirements for users in old database version + - name: Tls reqs | Retrieve TLS requirements for users in old database version command: "{{ mysql_command }} -L -N -s -e \"SHOW GRANTS for '{{ user_name_1 }}'@'localhost'\"" register: old_result - when: db_version.version.major <= 5 and db_version.version.minor <= 6 or db_version.version.major == 10 and db_version.version.minor < 2 + when: + - (db_engine == 'mysql' and db_version is version('5.6', '<=')) + or (db_engine == 'mariadb' and db_version is version('10.2', '<')) - - name: retrieve TLS requirements for users in new database version + - name: Tls reqs | Retrieve TLS requirements for users in new database version command: "{{ mysql_command }} -L -N -s -e \"SHOW CREATE USER '{{ user_name_1 }}'@'localhost'\"" register: new_result - when: db_version.version.major == 5 and db_version.version.minor >= 7 or db_version.version.major > 5 and db_version.version.major < 10 or db_version.version.major == 10 and db_version.version.minor >= 2 + when: + - (db_engine == 'mysql' and db_version is version('5.7', '>=')) + or (db_engine == 'mariadb' and db_version is version('10.2', '>=')) - - name: assert user1 TLS requirements + - name: Tls reqs | Assert user1 TLS requirements assert: that: "'X509' in reqs" vars: - reqs: "{{(old_result is skipped | ternary(new_result, old_result)).stdout.split('REQUIRE')[1].split(separator)[0].strip()}}" - - name: remove TLS requirements from user (expect changed=true) + - name: Tls reqs | Remove TLS requirements from user (expect changed=true) mysql_user: <<: *mysql_params name: '{{ user_name_1 }}' password: '{{ user_password_1 }}' tls_requires: - - name: retrieve TLS requirements for users + - name: Tls reqs | Retrieve TLS requirements for users command: "{{ mysql_command }} -L -N -s -e \"SHOW CREATE USER '{{ user_name_1 }}'@'localhost'\"" register: result - - name: assert user1 TLS requirements + - name: Tls reqs | Assert user1 TLS requirements assert: that: "'REQUIRE ' not in result.stdout or 'REQUIRE NONE' in result.stdout" - - include: remove_user.yml user_name={{user_name_1}} user_password={{ user_password_1 }} + - include: utils/remove_user.yml user_name={{user_name_1}} - - include: remove_user.yml user_name={{user_name_2}} user_password={{ user_password_1 }} + - include: utils/remove_user.yml user_name={{user_name_2}} - - include: remove_user.yml user_name={{user_name_3}} user_password={{ user_password_1 }} + - include: utils/remove_user.yml user_name={{user_name_3}} - - include: assert_no_user.yml user_name={{user_name_1}} + - include: utils/assert_no_user.yml user_name={{user_name_1}} - - include: assert_no_user.yml user_name={{user_name_2}} + - include: utils/assert_no_user.yml user_name={{user_name_2}} - - include: assert_no_user.yml user_name={{user_name_3}} + - include: utils/assert_no_user.yml user_name={{user_name_3}} diff --git a/tests/integration/targets/test_mysql_user/tasks/test_update_password.yml b/tests/integration/targets/test_mysql_user/tasks/test_update_password.yml index c9b74bb..428c1ef 100644 --- a/tests/integration/targets/test_mysql_user/tasks/test_update_password.yml +++ b/tests/integration/targets/test_mysql_user/tasks/test_update_password.yml @@ -1,10 +1,11 @@ +--- # Tests scenarios for both plaintext and encrypted user passwords. - vars: mysql_parameters: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' test_password1: kbB9tcx5WOGVGfzV test_password1_hash: '*AF6A7F9D038475C17EE46564F154104877EE5037' @@ -15,10 +16,10 @@ block: - - include_tasks: assert_user_password.yml + - include_tasks: utils/assert_user_password.yml vars: username: "{{ item.username }}" - host: '127.0.0.1' + host: "%" update_password: "{{ item.update_password }}" password: "{{ test_password1 }}" expect_change: "{{ item.expect_change }}" @@ -48,10 +49,10 @@ expect_change: false # same user, new password - - include_tasks: assert_user_password.yml + - include_tasks: utils/assert_user_password.yml vars: username: "{{ item.username }}" - host: '127.0.0.1' + host: "%" update_password: "{{ item.update_password }}" password: "{{ test_password2 }}" expect_change: "{{ item.expect_change }}" @@ -72,7 +73,7 @@ expect_password_hash: "{{ test_password1_hash }}" # new user, new password - - include_tasks: assert_user_password.yml + - include_tasks: utils/assert_user_password.yml vars: username: "{{ item.username }}" host: '::1' @@ -110,7 +111,7 @@ expect_password_hash: "{{ test_password2_hash }}" # another new user, another new password and multiple existing users with varying passwords - - include_tasks: assert_user_password.yml + - include_tasks: utils/assert_user_password.yml vars: username: "{{ item.username }}" host: '2001:db8::1' diff --git a/tests/integration/targets/test_mysql_user/tasks/test_user_grants_with_roles_applied.yml b/tests/integration/targets/test_mysql_user/tasks/test_user_grants_with_roles_applied.yml index 8ee738e..c6a1327 100644 --- a/tests/integration/targets/test_mysql_user/tasks/test_user_grants_with_roles_applied.yml +++ b/tests/integration/targets/test_mysql_user/tasks/test_user_grants_with_roles_applied.yml @@ -1,31 +1,30 @@ +--- # https://github.com/ansible-collections/community.mysql/issues/231 - vars: mysql_parameters: &mysql_params login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' block: - - name: Get server version - mysql_info: - <<: *mysql_params - register: srv - # Skip unsupported versions - - meta: end_play - when: srv['version']['major'] < 8 + - name: User grants with roles applied | Skip unsupported versions + meta: end_play + when: + - db_engine == 'mysql' + - db_version is version('8.0.0', '<') - - name: Create test databases + - name: User grants with roles applied | Create test databases mysql_db: <<: *mysql_params name: '{{ item }}' state: present loop: - - data1 - - data2 + - data1 + - data2 - - name: Create user with privileges + - name: User grants with roles applied | Create user with privileges mysql_user: <<: *mysql_params name: '{{ user_name_3 }}' @@ -35,7 +34,7 @@ "data2.*": "SELECT" state: present - - name: Run command to show privileges for user (expect privileges in stdout) + - name: User grants with roles applied | Run command to show privileges for user (expect privileges in stdout) command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ user_name_3 }}'@'localhost'\"" register: result @@ -45,14 +44,14 @@ - "'GRANT SELECT ON `data1`.*' in result.stdout" - "'GRANT SELECT ON `data2`.*' in result.stdout" - - name: Create role + - name: User grants with roles applied | Create role mysql_role: <<: *mysql_params name: test231 members: - - '{{ user_name_3 }}@localhost' + - '{{ user_name_3 }}@localhost' - - name: Try to change privs + - name: User grants with roles applied | Try to change privs mysql_user: <<: *mysql_params name: '{{ user_name_3 }}' @@ -61,11 +60,11 @@ "data2.*": "INSERT" state: present - - name: Run command to show privileges for user (expect privileges in stdout) + - name: User grants with roles applied | Run command to show privileges for user (expect privileges in stdout) command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ user_name_3 }}'@'localhost'\"" register: result - - name: Assert user has giving privileges + - name: User grants with roles applied | Assert user has giving privileges assert: that: - "'GRANT INSERT ON `data1`.*' in result.stdout" @@ -73,22 +72,18 @@ ########## # Clean up - - name: Drop test databases + - name: User grants with roles applied | Drop test databases mysql_db: <<: *mysql_params name: '{{ item }}' - state: present - loop: - - data1 - - data2 - - - name: Drop test user - mysql_user: - <<: *mysql_params - name: '{{ user_name_3 }}' state: absent + loop: + - data1 + - data2 - - name: Drop test role + - include: utils/remove_user.yml user_name={{ user_name_3 }} + + - name: User grants with roles applied | Drop test role mysql_role: <<: *mysql_params name: test231 diff --git a/tests/integration/targets/test_mysql_user/tasks/test_user_password.yml b/tests/integration/targets/test_mysql_user/tasks/test_user_password.yml index 57d8d29..d98c92c 100644 --- a/tests/integration/targets/test_mysql_user/tasks/test_user_password.yml +++ b/tests/integration/targets/test_mysql_user/tasks/test_user_password.yml @@ -1,10 +1,11 @@ +--- # Tests scenarios for both plaintext and encrypted user passwords. - vars: mysql_parameters: &mysql_params login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' test_user_name: 'test_user_password' initial_password: 'a5C8SN*DBa0%a75sGz' @@ -20,23 +21,24 @@ # Test setting plaintext password and changing it. # - - name: Create user with initial password + - name: Password | Create user with initial password mysql_user: <<: *mysql_params name: '{{ test_user_name }}' + host: '%' password: '{{ initial_password }}' priv: '{{ test_default_priv }}' state: present register: result - - name: Assert that a change occurred because the user was added + - name: Password | Assert that a change occurred because the user was added assert: that: - result is changed - - include: assert_user.yml user_name={{ test_user_name }} priv={{ test_default_priv_type }} + - include: utils/assert_user.yml user_name={{ test_user_name }} user_host=% priv={{ test_default_priv_type }} - - name: Get the MySQL version using the newly created used creds + - name: Password | Get the MySQL version using the newly created used creds mysql_info: login_user: '{{ test_user_name }}' login_password: '{{ initial_password }}' @@ -46,43 +48,45 @@ register: result ignore_errors: true - - name: Assert that mysql_info was successful + - name: Password | Assert that mysql_info was successful assert: that: - result is succeeded - - name: Run mysql_user again without any changes + - name: Password | Run mysql_user again without any changes mysql_user: <<: *mysql_params - name: '{{ test_user_name }}' - password: '{{ initial_password }}' - priv: '{{ test_default_priv }}' + name: "{{ test_user_name }}" + host: "%" + password: "{{ initial_password }}" + priv: "{{ test_default_priv }}" state: present register: result - - name: Assert that there weren't any changes because username/password didn't change + - name: Password | Assert that there weren't any changes because username/password didn't change assert: that: - result is not changed - - include: assert_user.yml user_name={{ test_user_name }} priv={{ test_default_priv_type }} + - include: utils/assert_user.yml user_name={{ test_user_name }} user_host=% priv={{ test_default_priv_type }} - - name: Update the user password + - name: Password | Update the user password mysql_user: <<: *mysql_params - name: '{{ test_user_name }}' - password: '{{ new_password }}' + name: "{{ test_user_name }}" + host: "%" + password: "{{ new_password }}" state: present register: result - - name: Assert that a change occurred because the password was updated + - name: Password | Assert that a change occurred because the password was updated assert: that: - result is changed - - include: assert_user.yml user_name={{ test_user_name }} priv={{ test_default_priv_type }} + - include: utils/assert_user.yml user_name={{ test_user_name }} user_host=% priv={{ test_default_priv_type }} - - name: Get the MySQL version data using the original password (should fail) + - name: Password | Get the MySQL version data using the original password (should fail) mysql_info: login_user: '{{ test_user_name }}' login_password: '{{ initial_password }}' @@ -92,12 +96,12 @@ register: result ignore_errors: true - - name: Assert that the mysql_info module failed because we used the old password + - name: Password | Assert that the mysql_info module failed because we used the old password assert: that: - result is failed - - name: Get the MySQL version data using the new password (should work) + - name: Password | Get the MySQL version data using the new password (should work) mysql_info: login_user: '{{ test_user_name }}' login_password: '{{ new_password }}' @@ -107,19 +111,19 @@ register: result ignore_errors: true - - name: Assert that the mysql_info module succeeded because we used the new password + - name: Password | Assert that the mysql_info module succeeded because we used the new password assert: that: - result is succeeded # Cleanup - - include: remove_user.yml user_name={{ test_user_name }} user_password={{ new_password }} + - include: utils/remove_user.yml user_name={{ test_user_name }} # ============================================================ # Test setting a plaintext password and then the same password encrypted to ensure there isn't a change detected. # - - name: Create user with initial password + - name: Password | Create user with initial password mysql_user: <<: *mysql_params name: '{{ test_user_name }}' @@ -128,14 +132,14 @@ state: present register: result - - name: Assert that a change occurred because the user was added + - name: Password | Assert that a change occurred because the user was added assert: that: - result is changed - - include: assert_user.yml user_name={{ test_user_name }} priv={{ test_default_priv_type }} + - include: utils/assert_user.yml user_name={{ test_user_name }} user_host=localhost priv={{ test_default_priv_type }} - - name: Pass in the same password as before, but in the encrypted form (no change expected) + - name: Password | Pass in the same password as before, but in the encrypted form (no change expected) mysql_user: <<: *mysql_params name: '{{ test_user_name }}' @@ -145,36 +149,37 @@ state: present register: result - - name: Assert that there weren't any changes because username/password didn't change + - name: Password | Assert that there weren't any changes because username/password didn't change assert: that: - result is not changed # Cleanup - - include: remove_user.yml user_name={{ test_user_name }} user_password={{ new_password }} + - include: utils/remove_user.yml user_name={{ test_user_name }} # ============================================================ # Test setting an encrypted password and then the same password in plaintext to ensure there isn't a change. # - - name: Create user with initial password + - name: Password | Create user with initial password mysql_user: <<: *mysql_params name: '{{ test_user_name }}' + host: "%" password: '{{ initial_password_encrypted }}' encrypted: yes priv: '{{ test_default_priv }}' state: present register: result - - name: Assert that a change occurred because the user was added + - name: Password | Assert that a change occurred because the user was added assert: that: - result is changed - - include: assert_user.yml user_name={{ test_user_name }} priv={{ test_default_priv_type }} + - include: utils/assert_user.yml user_name={{ test_user_name }} user_host=% priv={{ test_default_priv_type }} - - name: Get the MySQL version data using the new creds + - name: Password | Get the MySQL version data using the new creds mysql_info: login_user: '{{ test_user_name }}' login_password: '{{ initial_password }}' @@ -184,60 +189,62 @@ register: result ignore_errors: true - - name: Assert that the mysql_info module succeeded because we used the new password + - name: Password | Assert that the mysql_info module succeeded because we used the new password assert: that: - result is succeeded - - name: Pass in the same password as before, but in the encrypted form (no change expected) + - name: Password | Pass in the same password as before, but in the encrypted form (no change expected) mysql_user: <<: *mysql_params name: '{{ test_user_name }}' + host: "%" password: '{{ initial_password }}' state: present register: result - - name: Assert that there weren't any changes because username/password didn't change + - name: Password | Assert that there weren't any changes because username/password didn't change assert: that: - result is not changed # Cleanup - - include: remove_user.yml user_name={{ test_user_name }} user_password={{ new_password }} + - include: utils/remove_user.yml user_name={{ test_user_name }} # ============================================================ # Test setting an empty password. # - - name: Create user with empty password + - name: Password | Create user with empty password mysql_user: <<: *mysql_params - name: '{{ test_user_name }}' - priv: '{{ test_default_priv }}' + name: "{{ test_user_name }}" + host: "%" + priv: "{{ test_default_priv }}" state: present register: result - - name: Assert that a change occurred because the user was added + - name: Password | Assert that a change occurred because the user was added assert: that: - result is changed - - name: Get the MySQL version using an empty password for the newly created user + - name: Password | Get the MySQL version using an empty password for the newly created user mysql_info: - login_user: '{{ test_user_name }}' - login_password: '' - login_host: '{{ mysql_host }}' - login_port: '{{ mysql_primary_port }}' + login_user: "{{ test_user_name }}" + login_password: "" + login_host: "{{ mysql_host }}" + login_port: "{{ mysql_primary_port }}" filter: version register: result ignore_errors: true - - name: Assert that mysql_info was successful + - name: Password | Assert that mysql_info was successful assert: that: - result is succeeded - - name: Get the MySQL version using an non-empty password (should fail) + - name: Password | Get the MySQL version using an non-empty password (should fail) mysql_info: login_user: '{{ test_user_name }}' login_password: 'some_password' @@ -247,23 +254,24 @@ register: result ignore_errors: true - - name: Assert that mysql_info failed + - name: Password | Assert that mysql_info failed assert: that: - result is failed - - name: Update the user without changing the password + - name: Password | Update the user without changing the password mysql_user: <<: *mysql_params name: '{{ test_user_name }}' + host: "%" priv: '{{ test_default_priv }}' state: present register: result - - name: Assert that the user wasn't changed because the password is still empty + - name: Password | Assert that the user wasn't changed because the password is still empty assert: that: - result is not changed # Cleanup - - include: remove_user.yml user_name={{ test_user_name }} user_password='' + - include: utils/remove_user.yml user_name={{ test_user_name }} diff --git a/tests/integration/targets/test_mysql_user/tasks/test_user_plugin_auth.yml b/tests/integration/targets/test_mysql_user/tasks/test_user_plugin_auth.yml index 264d8bd..8d7740b 100644 --- a/tests/integration/targets/test_mysql_user/tasks/test_user_plugin_auth.yml +++ b/tests/integration/targets/test_mysql_user/tasks/test_user_plugin_auth.yml @@ -1,10 +1,11 @@ +--- # Test user plugin auth scenarios. - vars: mysql_parameters: &mysql_params login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' test_user_name: 'test_user_plugin_auth' test_plugin_type: 'mysql_native_password' @@ -21,33 +22,34 @@ # Test plugin auth initially setting a hash and then changing to a different hash. # - - name: Create user with plugin auth (with hash string) + - name: Plugin auth | Create user with plugin auth (with hash string) mysql_user: <<: *mysql_params name: '{{ test_user_name }}' + host: '%' plugin: '{{ test_plugin_type }}' plugin_hash_string: '{{ test_plugin_hash }}' priv: '{{ test_default_priv }}' register: result - - name: Get user information - command: "{{ mysql_command }} -e \"SELECT user, host, plugin FROM mysql.user WHERE user = '{{ test_user_name }}' and host = 'localhost'\"" + - name: Plugin auth | Get user information (with hash string) + command: "{{ mysql_command }} -e \"SELECT user, host, plugin FROM mysql.user WHERE user = '{{ test_user_name }}' and host = '%'\"" register: show_create_user - - name: Check that the module made a change + - name: Plugin auth | Check that the module made a change (with hash string) assert: that: - result is changed - - name: Check that the expected plugin type is set + - name: Plugin auth | Check that the expected plugin type is set (with hash string) assert: that: - "'{{ test_plugin_type }}' in show_create_user.stdout" - when: install_type == 'mysql' or (install_type == 'mariadb' and mariadb_version is version('10.3', '>=')) + when: db_engine == 'mysql' or (db_engine == 'mariadb' and db_version is version('10.3', '>=')) - - include: assert_user.yml user_name={{ test_user_name }} priv={{ test_default_priv_type }} + - include: utils/assert_user.yml user_name={{ test_user_name }} user_host=% priv={{ test_default_priv_type }} - - name: Get the MySQL version using the newly created creds + - name: Plugin auth | Get the MySQL version using the newly created creds mysql_info: login_user: '{{ test_user_name }}' login_password: '{{ test_plugin_auth_string }}' @@ -56,27 +58,28 @@ filter: version register: result - - name: Assert that mysql_info was successful + - name: Plugin auth | Assert that mysql_info was successful assert: that: - result is succeeded - - name: Update the user with a different hash + - name: Plugin auth | Update the user with a different hash mysql_user: <<: *mysql_params name: '{{ test_user_name }}' + host: '%' plugin: '{{ test_plugin_type }}' plugin_hash_string: '{{ test_plugin_new_hash }}' register: result - - name: Check that the module makes the change because the hash changed + - name: Plugin auth | Check that the module makes the change because the hash changed assert: that: - result is changed - - include: assert_user.yml user_name={{ test_user_name }} priv={{ test_default_priv_type }} + - include: utils/assert_user.yml user_name={{ test_user_name }} user_host=% priv={{ test_default_priv_type }} - - name: Getting the MySQL info with the new password should work + - name: Plugin auth | Getting the MySQL info with the new password should work mysql_info: login_user: '{{ test_user_name }}' login_password: '{{ test_plugin_new_auth_string }}' @@ -85,45 +88,46 @@ filter: version register: result - - name: Assert that mysql_info was successful + - name: Plugin auth | Assert that mysql_info was successful assert: that: - result is succeeded # Cleanup - - include: remove_user.yml user_name={{ test_user_name }} user_password={{ test_plugin_new_auth_string }} + - include: utils/remove_user.yml user_name={{ test_user_name }} # ============================================================ # Test plugin auth initially setting a hash and then switching to a plaintext auth string. # - - name: Create user with plugin auth (with hash string) + - name: Plugin auth | Create user with plugin auth (with hash string) mysql_user: <<: *mysql_params name: '{{ test_user_name }}' + host: '%' plugin: '{{ test_plugin_type }}' plugin_hash_string: '{{ test_plugin_hash }}' priv: '{{ test_default_priv }}' register: result - - name: Get user information - command: "{{ mysql_command }} -e \"SELECT user, host, plugin FROM mysql.user WHERE user = '{{ test_user_name }}' and host = 'localhost'\"" + - name: Plugin auth | Get user information + command: "{{ mysql_command }} -e \"SELECT user, host, plugin FROM mysql.user WHERE user = '{{ test_user_name }}' and host = '%'\"" register: show_create_user - - name: Check that the module made a change + - name: Plugin auth | Check that the module made a change (with hash string) assert: that: - result is changed - - name: Check that the expected plugin type is set + - name: Plugin auth | Check that the expected plugin type is set (with hash string) assert: that: - "'{{ test_plugin_type }}' in show_create_user.stdout" - when: install_type == 'mysql' or (install_type == 'mariadb' and mariadb_version is version('10.3', '>=')) + when: db_engine == 'mysql' or (db_engine == 'mariadb' and db_version is version('10.3', '>=')) - - include: assert_user.yml user_name={{ test_user_name }} priv={{ test_default_priv_type }} + - include: utils/assert_user.yml user_name={{ test_user_name }} user_host=% priv={{ test_default_priv_type }} - - name: Get the MySQL version using the newly created creds + - name: Plugin auth | Get the MySQL version using the newly created creds mysql_info: login_user: '{{ test_user_name }}' login_password: '{{ test_plugin_auth_string }}' @@ -132,43 +136,45 @@ filter: version register: result - - name: Assert that mysql_info was successful + - name: Plugin auth | Assert that mysql_info was successful assert: that: - result is succeeded - - name: Update the user with the same hash (no change expected) + - name: Plugin auth | Update the user with the same hash (no change expected) mysql_user: <<: *mysql_params name: '{{ test_user_name }}' + host: '%' plugin: '{{ test_plugin_type }}' plugin_hash_string: '{{ test_plugin_hash }}' register: result # FIXME: on mariadb 10.2 there's always a change - - name: Check that the module doesn't make a change when the same hash is passed in + - name: Plugin auth | Check that the module doesn't make a change when the same hash is passed in assert: that: - result is not changed - when: install_type == 'mysql' or (install_type == 'mariadb' and mariadb_version is version('10.3', '>=')) + when: db_engine == 'mysql' or (db_engine == 'mariadb' and db_version is version('10.3', '>=')) - - include: assert_user.yml user_name={{ test_user_name }} priv={{ test_default_priv_type }} + - include: utils/assert_user.yml user_name={{ test_user_name }} user_host=% priv={{ test_default_priv_type }} - - name: Change the user using the same plugin, but switch to the same auth string in plaintext form + - name: Plugin auth | Change the user using the same plugin, but switch to the same auth string in plaintext form mysql_user: <<: *mysql_params name: '{{ test_user_name }}' + host: '%' plugin: '{{ test_plugin_type }}' plugin_auth_string: '{{ test_plugin_auth_string }}' register: result # Expecting a change is currently by design (see comment in source). - - name: Check that the module did not change the password + - name: Plugin auth | Check that the module did not change the password assert: that: - result is changed - - name: Getting the MySQL info should still work + - name: Plugin auth | Getting the MySQL info should still work mysql_info: login_user: '{{ test_user_name }}' login_password: '{{ test_plugin_auth_string }}' @@ -177,45 +183,46 @@ filter: version register: result - - name: Assert that mysql_info was successful + - name: Plugin auth | Assert that mysql_info was successful assert: that: - result is succeeded # Cleanup - - include: remove_user.yml user_name={{ test_user_name }} user_password={{ test_plugin_auth_string }} + - include: utils/remove_user.yml user_name={{ test_user_name }} # ============================================================ # Test plugin auth initially setting a plaintext auth string and then switching to a hash. # - - name: Create user with plugin auth (with auth string) + - name: Plugin auth | Create user with plugin auth (with auth string) mysql_user: <<: *mysql_params name: '{{ test_user_name }}' + host: '%' plugin: '{{ test_plugin_type }}' plugin_auth_string: '{{ test_plugin_auth_string }}' priv: '{{ test_default_priv }}' register: result - - name: Get user information - command: "{{ mysql_command }} -e \"SHOW CREATE USER '{{ test_user_name }}'@'localhost'\"" + - name: Plugin auth | Get user information(with auth string) + command: "{{ mysql_command }} -e \"SHOW CREATE USER '{{ test_user_name }}'@'%'\"" register: show_create_user - - name: Check that the module made a change + - name: Plugin auth | Check that the module made a change (with auth string) assert: that: - result is changed - - name: Check that the expected plugin type is set + - name: Plugin auth | Check that the expected plugin type is set (with auth string) assert: that: - - "'{{ test_plugin_type }}' in show_create_user.stdout" - when: install_type == 'mysql' or (install_type == 'mariadb' and mariadb_version is version('10.3', '>=')) + - test_plugin_type in show_create_user.stdout + when: db_engine == 'mysql' or (db_engine == 'mariadb' and db_version is version('10.3', '>=')) - - include: assert_user.yml user_name={{ test_user_name }} priv={{ test_default_priv_type }} + - include: utils/assert_user.yml user_name={{ test_user_name }} user_host=% priv={{ test_default_priv_type }} - - name: Get the MySQL version using the newly created creds + - name: Plugin auth | Get the MySQL version using the newly created creds mysql_info: login_user: '{{ test_user_name }}' login_password: '{{ test_plugin_auth_string }}' @@ -224,42 +231,44 @@ filter: version register: result - - name: Assert that mysql_info was successful + - name: Plugin auth | Assert that mysql_info was successful assert: that: - result is succeeded - - name: Update the user with the same auth string + - name: Plugin auth | Update the user with the same auth string mysql_user: <<: *mysql_params name: '{{ test_user_name }}' + host: '%' plugin: '{{ test_plugin_type }}' plugin_auth_string: '{{ test_plugin_auth_string }}' register: result # This is the current expected behavior because there isn't a reliable way to hash the password in the mysql_user # module in order to be able to compare this password with the stored hash. See the source for more info. - - name: The module should detect a change even though the password is the same + - name: Plugin auth | The module should detect a change even though the password is the same assert: that: - result is changed - - include: assert_user.yml user_name={{ test_user_name }} priv={{ test_default_priv_type }} + - include: utils/assert_user.yml user_name={{ test_user_name }} user_host=% priv={{ test_default_priv_type }} - - name: Change the user using the same plugin, but switch to the same auth string in hash form + - name: Plugin auth | Change the user using the same plugin, but switch to the same auth string in hash form mysql_user: <<: *mysql_params name: '{{ test_user_name }}' + host: '%' plugin: '{{ test_plugin_type }}' plugin_hash_string: '{{ test_plugin_hash }}' register: result - - name: Check that the module did not change the password + - name: Plugin auth | Check that the module did not change the password assert: that: - result is not changed - - name: Get the MySQL version using the newly created creds + - name: Plugin auth | Get the MySQL version using the newly created creds mysql_info: login_user: '{{ test_user_name }}' login_password: '{{ test_plugin_auth_string }}' @@ -268,44 +277,45 @@ filter: version register: result - - name: Assert that mysql_info was successful + - name: Plugin auth | Assert that mysql_info was successful assert: that: - result is succeeded # Cleanup - - include: remove_user.yml user_name={{ test_user_name }} user_password={{ test_plugin_auth_string }} + - include: utils/remove_user.yml user_name={{ test_user_name }} # ============================================================ # Test plugin auth with an empty auth string. # - - name: Create user with plugin auth (empty auth string) + - name: Plugin auth | Create user with plugin auth (empty auth string) mysql_user: <<: *mysql_params name: '{{ test_user_name }}' + host: '%' plugin: '{{ test_plugin_type }}' priv: '{{ test_default_priv }}' register: result - - name: Get user information - command: "{{ mysql_command }} -e \"SHOW CREATE USER '{{ test_user_name }}'@'localhost'\"" + - name: Plugin auth | Get user information (empty auth string) + command: "{{ mysql_command }} -e \"SHOW CREATE USER '{{ test_user_name }}'@'%'\"" register: show_create_user - - name: Check that the module made a change + - name: Plugin auth | Check that the module made a change (empty auth string) assert: that: - result is changed - - name: Check that the expected plugin type is set + - name: Plugin auth | Check that the expected plugin type is set (empty auth string) assert: that: - "'{{ test_plugin_type }}' in show_create_user.stdout" - when: install_type == 'mysql' or (install_type == 'mariadb' and mariadb_version is version('10.3', '>=')) + when: db_engine == 'mysql' or (db_engine == 'mariadb' and db_version is version('10.3', '>=')) - - include: assert_user.yml user_name={{ test_user_name }} priv={{ test_default_priv_type }} + - include: utils/assert_user.yml user_name={{ test_user_name }} user_host=% priv={{ test_default_priv_type }} - - name: Get the MySQL version using an empty password for the newly created user + - name: Plugin auth | Get the MySQL version using an empty password for the newly created user mysql_info: login_user: '{{ test_user_name }}' login_password: '' @@ -315,12 +325,12 @@ register: result ignore_errors: true - - name: Assert that mysql_info was successful + - name: Plugin auth | Assert that mysql_info was successful assert: that: - result is succeeded - - name: Get the MySQL version using an non-empty password (should fail) + - name: Plugin auth | Get the MySQL version using an non-empty password (should fail) mysql_info: login_user: '{{ test_user_name }}' login_password: 'some_password' @@ -330,91 +340,92 @@ register: result ignore_errors: true - - name: Assert that mysql_info failed + - name: Plugin auth | Assert that mysql_info failed assert: that: - result is failed - - name: Update the user without changing the auth mechanism + - name: Plugin auth | Update the user without changing the auth mechanism mysql_user: <<: *mysql_params name: '{{ test_user_name }}' + host: '%' plugin: '{{ test_plugin_type }}' state: present register: result - - name: Assert that the user wasn't changed because the auth string is still empty + - name: Plugin auth | Assert that the user wasn't changed because the auth string is still empty assert: that: - result is not changed # Cleanup - - include: remove_user.yml user_name={{ test_user_name }} user_password={{ test_plugin_auth_string }} + - include: utils/remove_user.yml user_name={{ test_user_name }} # ============================================================ # Test plugin auth switching from one type of plugin to another without an auth string or hash. The only other # plugins that are loaded by default are sha2*, but these aren't compatible with pymysql < 0.9, so skip these tests # for those versions. # - - name: Test plugin auth switching which doesn't work on pymysql < 0.9 + - name: Plugin auth | Test plugin auth switching which doesn't work on pymysql < 0.9 when: - > - connector_name is not search('pymysql') + connector_name != 'pymysql' or ( - connector_name is search('pymysql') - and connector_ver is version('0.9', '>=') + connector_name == 'pymysql' + and connector_version is version('0.9', '>=') ) block: - - name: Create user with plugin auth (empty auth string) - mysql_user: - <<: *mysql_params - name: '{{ test_user_name }}' - plugin: '{{ test_plugin_type }}' - priv: '{{ test_default_priv }}' - register: result + - name: Plugin auth | Create user with plugin auth (empty auth string) + mysql_user: + <<: *mysql_params + name: '{{ test_user_name }}' + plugin: '{{ test_plugin_type }}' + priv: '{{ test_default_priv }}' + register: result - - name: Get user information - command: "{{ mysql_command }} -e \"SHOW CREATE USER '{{ test_user_name }}'@'localhost'\"" - register: show_create_user + - name: Plugin auth | Get user information (empty auth string) + command: "{{ mysql_command }} -e \"SHOW CREATE USER '{{ test_user_name }}'@'localhost'\"" + register: show_create_user - - name: Check that the module made a change - assert: - that: - - result is changed + - name: Plugin auth | Check that the module made a change (empty auth string) + assert: + that: + - result is changed - - name: Check that the expected plugin type is set - assert: - that: - - "'{{ test_plugin_type }}' in show_create_user.stdout" - when: install_type == 'mysql' or (install_type == 'mariadb' and mariadb_version is version('10.3', '>=')) + - name: Plugin auth | Check that the expected plugin type is set (empty auth string) + assert: + that: + - test_plugin_type in show_create_user.stdout + when: db_engine == 'mysql' or (db_engine == 'mariadb' and db_version is version('10.3', '>=')) - - include: assert_user.yml user_name={{ test_user_name }} priv={{ test_default_priv_type }} + - include: utils/assert_user.yml user_name={{ test_user_name }} user_host=localhost priv={{ test_default_priv_type }} - - name: Switch user to sha256_password auth plugin - mysql_user: - <<: *mysql_params - name: '{{ test_user_name }}' - plugin: sha256_password - priv: '{{ test_default_priv }}' - register: result + - name: Plugin auth | Switch user to sha256_password auth plugin + mysql_user: + <<: *mysql_params + name: '{{ test_user_name }}' + plugin: sha256_password + priv: '{{ test_default_priv }}' + register: result - - name: Get user information - command: "{{ mysql_command }} -e \"SHOW CREATE USER '{{ test_user_name }}'@'localhost'\"" - register: show_create_user + - name: Plugin auth | Get user information (sha256_password) + command: "{{ mysql_command }} -e \"SHOW CREATE USER '{{ test_user_name }}'@'localhost'\"" + register: show_create_user - - name: Check that the module made a change - assert: - that: - - result is changed + - name: Plugin auth | Check that the module made a change (sha256_password) + assert: + that: + - result is changed - - name: Check that the expected plugin type is set - assert: - that: + - name: Plugin auth | Check that the expected plugin type is set (sha256_password) + assert: + that: - "'sha256_password' in show_create_user.stdout" - when: install_type == 'mysql' or (install_type == 'mariadb' and mariadb_version is version('10.3', '>=')) + when: db_engine == 'mysql' or (db_engine == 'mariadb' and db_version is version('10.3', '>=')) - - include: assert_user.yml user_name={{ test_user_name }} priv={{ test_default_priv_type }} + - include: utils/assert_user.yml user_name={{ test_user_name }} user_host=localhost priv={{ test_default_priv_type }} - # Cleanup - - include: remove_user.yml user_name={{ test_user_name }} user_password={{ test_plugin_auth_string }} + # Cleanup + - include: utils/remove_user.yml user_name={{ test_user_name }} diff --git a/tests/integration/targets/test_mysql_user/tasks/utils/assert_no_user.yml b/tests/integration/targets/test_mysql_user/tasks/utils/assert_no_user.yml new file mode 100644 index 0000000..6fc4fbc --- /dev/null +++ b/tests/integration/targets/test_mysql_user/tasks/utils/assert_no_user.yml @@ -0,0 +1,8 @@ +--- +- name: Utils | Assert no user | Query for user {{ user_name }} + command: "{{ mysql_command }} -e \"SELECT User FROM mysql.user where user='{{ user_name }}'\"" + register: result + +- name: Utils | Assert no user | Assert mysql user is not present + assert: + that: user_name not in result.stdout diff --git a/tests/integration/targets/test_mysql_user/tasks/utils/assert_user.yml b/tests/integration/targets/test_mysql_user/tasks/utils/assert_user.yml new file mode 100644 index 0000000..e6bd23f --- /dev/null +++ b/tests/integration/targets/test_mysql_user/tasks/utils/assert_user.yml @@ -0,0 +1,21 @@ +--- + +- name: Utils | Assert user | Query for user {{ user_name }} + command: "{{ mysql_command }} -e \"SELECT user FROM mysql.user where user='{{ user_name }}'\"" + register: result + +- name: Utils | Assert user | Assert user is present + assert: + that: + - user_name in result.stdout + +- name: Utils | Assert user | Query for privileges of user {{ user_name }} + command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ user_name }}'@'{{ user_host }}'\"" + register: result + when: priv is defined + +- name: Utils | Assert user | Assert user has given privileges + ansible.builtin.assert: + that: + - "'GRANT {{ priv }} ON *.*' in result.stdout" + when: priv is defined diff --git a/tests/integration/targets/test_mysql_user/tasks/assert_user_password.yml b/tests/integration/targets/test_mysql_user/tasks/utils/assert_user_password.yml similarity index 73% rename from tests/integration/targets/test_mysql_user/tasks/assert_user_password.yml rename to tests/integration/targets/test_mysql_user/tasks/utils/assert_user_password.yml index ba045eb..d95e53b 100644 --- a/tests/integration/targets/test_mysql_user/tasks/assert_user_password.yml +++ b/tests/integration/targets/test_mysql_user/tasks/utils/assert_user_password.yml @@ -1,4 +1,5 @@ -- name: "applying user {{ username }}@{{ host }} with update_password={{ update_password }}" +--- +- name: Utils | Assert user password | Apply update_password to {{ username }} mysql_user: login_user: '{{ mysql_parameters.login_user }}' login_password: '{{ mysql_parameters.login_password }}' @@ -10,15 +11,18 @@ password: "{{ password }}" update_password: "{{ update_password }}" register: result -- name: assert a change occurred + +- name: Utils | Assert user password | Assert a change occurred assert: that: - "result.changed | bool == {{ expect_change }} | bool" - "result.password_changed == {{ expect_password_change }}" -- name: query the user + +- name: Utils | Assert user password | Query user {{ username }} command: "{{ mysql_command }} -BNe \"SELECT plugin, authentication_string FROM mysql.user where user='{{ username }}' and host='{{ host }}'\"" register: existing_user -- name: assert the password is as set to expect_hash + +- name: Utils | Assert user password | Assert expect_hash is in user stdout assert: that: - "'mysql_native_password\t{{ expect_password_hash }}' in existing_user.stdout_lines" diff --git a/tests/integration/targets/test_mysql_user/tasks/utils/create_user.yml b/tests/integration/targets/test_mysql_user/tasks/utils/create_user.yml new file mode 100644 index 0000000..b255ec4 --- /dev/null +++ b/tests/integration/targets/test_mysql_user/tasks/utils/create_user.yml @@ -0,0 +1,12 @@ +--- + +- name: Utils | Create user {{ user_name }} + mysql_user: + login_user: "{{ mysql_user }}" + login_password: "{{ mysql_password }}" + login_host: "{{ mysql_host }}" + login_port: "{{ mysql_primary_port }}" + name: "{{ user_name }}" + host: "{{ user_host | default(omit) }}" + password: "{{ user_password }}" + state: present diff --git a/tests/integration/targets/test_mysql_user/tasks/utils/remove_user.yml b/tests/integration/targets/test_mysql_user/tasks/utils/remove_user.yml new file mode 100644 index 0000000..473cece --- /dev/null +++ b/tests/integration/targets/test_mysql_user/tasks/utils/remove_user.yml @@ -0,0 +1,12 @@ +--- + +- name: Utils | Remove user {{ user_name }} + mysql_user: + login_user: "{{ mysql_user }}" + login_password: "{{ mysql_password }}" + login_host: "{{ mysql_host }}" + login_port: "{{ mysql_primary_port }}" + name: "{{ user_name }}" + host_all: true + state: absent + ignore_errors: true diff --git a/tests/integration/targets/test_mysql_variables/defaults/main.yml b/tests/integration/targets/test_mysql_variables/defaults/main.yml index 6d0e2ec..779eead 100644 --- a/tests/integration/targets/test_mysql_variables/defaults/main.yml +++ b/tests/integration/targets/test_mysql_variables/defaults/main.yml @@ -2,6 +2,7 @@ # defaults file for test_mysql_variables mysql_user: root mysql_password: msandbox +mysql_host: '{{ gateway_addr }}' mysql_primary_port: 3307 user_name_1: 'db_user1' diff --git a/tests/integration/targets/test_mysql_variables/meta/main.yml b/tests/integration/targets/test_mysql_variables/meta/main.yml index f1174ff..01ee3db 100644 --- a/tests/integration/targets/test_mysql_variables/meta/main.yml +++ b/tests/integration/targets/test_mysql_variables/meta/main.yml @@ -1,2 +1,3 @@ +--- dependencies: - - setup_mysql + - setup_controller diff --git a/tests/integration/targets/test_mysql_variables/tasks/assert_var.yml b/tests/integration/targets/test_mysql_variables/tasks/assert_var.yml index 96d196d..e64c5a7 100644 --- a/tests/integration/targets/test_mysql_variables/tasks/assert_var.yml +++ b/tests/integration/targets/test_mysql_variables/tasks/assert_var.yml @@ -1,3 +1,4 @@ +--- # test code to assert variables in mysql_variables module # (c) 2014, Wayne Rosario @@ -19,16 +20,16 @@ # ============================================================ # Assert mysql variable name and value from mysql database # -- name: assert output message changed value +- name: Assert output message changed value assert: that: - "output.changed | bool == changed | bool" -- name: run mysql command to show variable +- name: Run mysql command to show variable command: "{{ mysql_command }} \"-e show variables like '{{ var_name }}'\"" register: result -- name: assert output mysql variable name and value +- name: Assert output mysql variable name and value assert: that: - result is changed diff --git a/tests/integration/targets/test_mysql_variables/tasks/issue-28.yml b/tests/integration/targets/test_mysql_variables/tasks/issue-28.yml index aa01ddb..10a9154 100644 --- a/tests/integration/targets/test_mysql_variables/tasks/issue-28.yml +++ b/tests/integration/targets/test_mysql_variables/tasks/issue-28.yml @@ -9,7 +9,7 @@ mysql_parameters: &mysql_params login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' when: tls_enabled block: @@ -25,6 +25,7 @@ mysql_user: <<: *mysql_params name: '{{ user_name_1 }}' + host_all: true state: absent ignore_errors: yes @@ -32,6 +33,7 @@ mysql_user: <<: *mysql_params name: "{{ user_name_1 }}" + host: '%' password: "{{ user_password_1 }}" priv: '*.*:ALL,GRANT' tls_requires: @@ -42,7 +44,7 @@ variable: '{{ set_name }}' login_user: '{{ user_name_1 }}' login_password: '{{ user_password_1 }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' ca_cert: /tmp/cert.pem register: result @@ -51,19 +53,21 @@ - assert: that: - result is failed - when: connector_name is search('pymysql') + when: + - connector_name == 'pymysql' - assert: that: - result is succeeded - when: connector_name is not search('pymysql') + when: + - connector_name != 'pymysql' - name: attempt connection with newly created user ignoring hostname mysql_variables: variable: '{{ set_name }}' login_user: '{{ user_name_1 }}' login_password: '{{ user_password_1 }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' ca_cert: /tmp/cert.pem check_hostname: no @@ -78,5 +82,5 @@ mysql_user: <<: *mysql_params name: '{{ user_name_1 }}' - host: 127.0.0.1 + host_all: true state: absent 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 ed34966..c8ae3e8 100644 --- a/tests/integration/targets/test_mysql_variables/tasks/mysql_variables.yml +++ b/tests/integration/targets/test_mysql_variables/tasks/mysql_variables.yml @@ -23,15 +23,11 @@ mysql_parameters: &mysql_params login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' block: - - name: alias mysql command to include default options - set_fact: - mysql_command: "mysql -u{{ mysql_user }} -p{{ mysql_password }} -P{{ mysql_primary_port }} --protocol=tcp" - - set_fact: set_name: 'version' @@ -151,7 +147,7 @@ # ============================================================ # Verify mysql_variable fails when setting an incorrect value (out of range) # - - name: set mysql variable value to a number out of range + - name: Set mysql variable value to a number out of range mysql_variables: <<: *mysql_params variable: max_connect_errors @@ -160,10 +156,13 @@ ignore_errors: true - include: assert_var.yml changed=true output={{ oor_result }} var_name=max_connect_errors var_value=1 - when: connector_name is not search('pymysql') + when: + - connector_name == 'mysqlclient' + - db_engine == 'mysql' # mysqlclient returns "changed" with MariaDB - include: assert_fail_msg.yml output={{ oor_result }} msg='Truncated incorrect' - when: connector_name is search('pymysql') + when: + - connector_name == 'pymsql' # ============================================================ # Verify mysql_variable fails when setting an incorrect value (incorrect type) @@ -246,7 +245,7 @@ mysql_variables: login_user: '{{ mysql_user }}' login_password: 'wrongpassword' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' variable: '{{ set_name }}' register: result @@ -258,7 +257,7 @@ mysql_variables: login_user: '{{ mysql_user }}' login_password: 'wrongpassword' - login_host: 127.0.0.1 + login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' variable: '{{ set_name }}' value: '{{ set_value }}' @@ -360,8 +359,8 @@ - include: assert_var.yml changed=true output={{ result }} var_name={{ set_name }} var_value='{{ def_val }}' when: - - mysql_version is version('8.0', '>=') - - install_type == 'mysql' + - db_engine == 'mysql' + - db_version is version('8.0', '>=') # Bugfix of https://github.com/ansible/ansible/issues/54239 # - name: set variable containing dot diff --git a/tests/integration/test_connection.yml b/tests/integration/test_connection.yml new file mode 100644 index 0000000..160cfba --- /dev/null +++ b/tests/integration/test_connection.yml @@ -0,0 +1,81 @@ +--- + +- name: Playbook to test bug to connect to MySQL/MariaDB server + hosts: all + gather_facts: false + vars: + mysql_parameters: &mysql_params + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: '{{ mysql_host }}' + login_port: '{{ mysql_primary_port }}' + tasks: + + # Create default MySQL config file with credentials + - name: mysql_info - create default config file + template: + src: my.cnf.j2 + dest: /root/.my.cnf + mode: '0400' + + # Create non-default MySQL config file with credentials + - name: mysql_info - create non-default config file + template: + src: tests/integration/targets/test_mysql_info/templates/my.cnf.j2 + dest: /root/non-default_my.cnf + mode: '0400' + + ############### + # Do tests + + # Access by default cred file + - name: mysql_info - collect default cred file + mysql_info: + login_user: '{{ mysql_user }}' + login_host: '{{ mysql_host }}' + login_port: '{{ mysql_primary_port }}' + register: result + + - assert: + that: + - result is not changed + - db_version in result.version.full + - result.settings != {} + - result.global_status != {} + - result.databases != {} + - result.engines != {} + - result.users != {} + + # Access by non-default cred file + - name: mysql_info - check non-default cred file + mysql_info: + login_user: '{{ mysql_user }}' + login_host: '{{ mysql_host }}' + login_port: '{{ mysql_primary_port }}' + config_file: /root/non-default_my.cnf + register: result + + - assert: + that: + - result is not changed + - result.version != {} + + # Remove cred files + - name: mysql_info - remove cred files + file: + path: '{{ item }}' + state: absent + with_items: + - /root/.my.cnf + - /root/non-default_my.cnf + + # Access with password + - name: mysql_info - check access with password + mysql_info: + <<: *mysql_params + register: result + + - assert: + that: + - result is not changed + - result.version != {} From e2aa655762a9ac5c7343a6f53e9aed1c2d86b629 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20Inderm=C3=BChle?= Date: Fri, 24 Mar 2023 10:16:36 +0100 Subject: [PATCH 138/217] Fix issues and documentation with integration tests after merge of #490. (#514) * Fix explanation about containers images * Add definitive URI to the containers images * Document that new images must be set as public * Add makefile options possible values * Document that any mysql and mariadb tag can be use * Add computation of docker_image path * Refactor pre-command to separate commands for cleaner GHA output * Refactor to use GHA test matrix * Cut docker_image from documentation since it's now automatic * Document how to use run_all_test.py to display the test matrix * Temp: Add path to images in my fork to validate integrations tests --- .github/workflows/ansible-test-plugins.yml | 419 ++++++++---------- ...ker-image-mariadb-py310-mysqlclient211.yml | 19 + .../docker-image-mariadb-py310-pymysql102.yml | 19 + ...cker-image-mariadb-py38-mysqlclient201.yml | 19 + .../docker-image-mariadb-py38-pymysql093.yml | 19 + ...cker-image-mariadb-py39-mysqlclient203.yml | 19 + .../docker-image-mariadb-py39-pymysql093.yml | 19 + ...r-image-mariadb103-py38-mysqlclient201.yml | 19 - ...ocker-image-mariadb103-py38-pymysql093.yml | 19 - ...r-image-mariadb103-py39-mysqlclient203.yml | 19 - ...ocker-image-mariadb103-py39-pymysql093.yml | 19 - ...-image-mariadb106-py310-mysqlclient211.yml | 19 - ...cker-image-mariadb106-py310-pymysql102.yml | 19 - ...docker-image-my80-py310-mysqlclient211.yml | 19 - .../docker-image-my80-py310-pymysql102.yml | 19 - .../docker-image-my80-py38-mysqlclient201.yml | 19 - .../docker-image-my80-py38-pymysql093.yml | 19 - .../docker-image-my80-py39-mysqlclient203.yml | 19 - .../docker-image-my80-py39-pymysql093.yml | 19 - ...ocker-image-mysql-py310-mysqlclient211.yml | 19 + .../docker-image-mysql-py310-pymysql102.yml | 19 + ...docker-image-mysql-py38-mysqlclient201.yml | 19 + .../docker-image-mysql-py38-pymysql093.yml | 19 + ...docker-image-mysql-py39-mysqlclient203.yml | 19 + .../docker-image-mysql-py39-pymysql093.yml | 19 + Makefile | 42 +- TESTING.md | 135 ++++-- run_all_tests.py | 95 ++-- .../Dockerfile | 0 .../Dockerfile | 0 .../Dockerfile | 0 .../Dockerfile | 0 .../Dockerfile | 0 .../Dockerfile | 0 .../Dockerfile | 0 .../Dockerfile | 0 .../Dockerfile | 0 .../Dockerfile | 0 .../Dockerfile | 0 .../Dockerfile | 0 .../setup_controller/tasks/setvars.yml | 24 +- 41 files changed, 637 insertions(+), 534 deletions(-) create mode 100644 .github/workflows/docker-image-mariadb-py310-mysqlclient211.yml create mode 100644 .github/workflows/docker-image-mariadb-py310-pymysql102.yml create mode 100644 .github/workflows/docker-image-mariadb-py38-mysqlclient201.yml create mode 100644 .github/workflows/docker-image-mariadb-py38-pymysql093.yml create mode 100644 .github/workflows/docker-image-mariadb-py39-mysqlclient203.yml create mode 100644 .github/workflows/docker-image-mariadb-py39-pymysql093.yml delete mode 100644 .github/workflows/docker-image-mariadb103-py38-mysqlclient201.yml delete mode 100644 .github/workflows/docker-image-mariadb103-py38-pymysql093.yml delete mode 100644 .github/workflows/docker-image-mariadb103-py39-mysqlclient203.yml delete mode 100644 .github/workflows/docker-image-mariadb103-py39-pymysql093.yml delete mode 100644 .github/workflows/docker-image-mariadb106-py310-mysqlclient211.yml delete mode 100644 .github/workflows/docker-image-mariadb106-py310-pymysql102.yml delete mode 100644 .github/workflows/docker-image-my80-py310-mysqlclient211.yml delete mode 100644 .github/workflows/docker-image-my80-py310-pymysql102.yml delete mode 100644 .github/workflows/docker-image-my80-py38-mysqlclient201.yml delete mode 100644 .github/workflows/docker-image-my80-py38-pymysql093.yml delete mode 100644 .github/workflows/docker-image-my80-py39-mysqlclient203.yml delete mode 100644 .github/workflows/docker-image-my80-py39-pymysql093.yml create mode 100644 .github/workflows/docker-image-mysql-py310-mysqlclient211.yml create mode 100644 .github/workflows/docker-image-mysql-py310-pymysql102.yml create mode 100644 .github/workflows/docker-image-mysql-py38-mysqlclient201.yml create mode 100644 .github/workflows/docker-image-mysql-py38-pymysql093.yml create mode 100644 .github/workflows/docker-image-mysql-py39-mysqlclient203.yml create mode 100644 .github/workflows/docker-image-mysql-py39-pymysql093.yml rename test-containers/{mariadb106-py310-mysqlclient211 => mariadb-py310-mysqlclient211}/Dockerfile (100%) rename test-containers/{mariadb106-py310-pymysql102 => mariadb-py310-pymysql102}/Dockerfile (100%) rename test-containers/{mariadb103-py38-mysqlclient201 => mariadb-py38-mysqlclient201}/Dockerfile (100%) rename test-containers/{mariadb103-py38-pymysql093 => mariadb-py38-pymysql093}/Dockerfile (100%) rename test-containers/{mariadb103-py39-mysqlclient203 => mariadb-py39-mysqlclient203}/Dockerfile (100%) rename test-containers/{mariadb103-py39-pymysql093 => mariadb-py39-pymysql093}/Dockerfile (100%) rename test-containers/{my80-py310-mysqlclient211 => mysql-py310-mysqlclient211}/Dockerfile (100%) rename test-containers/{my80-py310-pymysql102 => mysql-py310-pymysql102}/Dockerfile (100%) rename test-containers/{my80-py38-mysqlclient201 => mysql-py38-mysqlclient201}/Dockerfile (100%) rename test-containers/{my80-py38-pymysql093 => mysql-py38-pymysql093}/Dockerfile (100%) rename test-containers/{my80-py39-mysqlclient203 => mysql-py39-mysqlclient203}/Dockerfile (100%) rename test-containers/{my80-py39-pymysql093 => mysql-py39-pymysql093}/Dockerfile (100%) diff --git a/.github/workflows/ansible-test-plugins.yml b/.github/workflows/ansible-test-plugins.yml index 5aeee56..b961550 100644 --- a/.github/workflows/ansible-test-plugins.yml +++ b/.github/workflows/ansible-test-plugins.yml @@ -35,12 +35,35 @@ jobs: pull-request-change-detection: true integration: - name: "Integration (Python: ${{ matrix.python }}, Ansible: ${{ matrix.ansible }}, MySQL: ${{ matrix.db_engine_version }}, Connector: ${{ matrix.connector }})" + name: "Integration (Python: ${{ matrix.python }}, Ansible: ${{ matrix.ansible }}, DB: ${{ matrix.db_engine_name }} ${{ matrix.db_engine_version }}, connector: ${{ matrix.connector_name }} ${{ matrix.connector_version }})" runs-on: ubuntu-20.04 strategy: fail-fast: false matrix: - include: + ansible: + - stable-2.12 + - stable-2.13 + - stable-2.14 + - devel + db_engine_name: + - mysql + - mariadb + db_engine_version: + - 5.7.40 + - 8.0.31 + - 10.4.27 + - 10.5.18 + - 10.6.11 + python: + - '3.8' + - '3.9' + - '3.10' + connector_name: + - pymysql + - mysqlclient + connector_version: + - 0.7.11 + - 0.9.3 # Before we can activate test with pymysql 1.0.2 we should debug the # following plugins: # @@ -49,244 +72,128 @@ jobs: # # mysql_replication: # test "Assert that startreplica is not changed" failed + # - 1.0.2 + - 2.0.1 + - 2.0.3 + - 2.1.1 + exclude: + - db_engine_name: mysql + db_engine_version: 10.4.27 - # ================================================================== - # mysql-client 5.7 + Python 3.8 - # ================================================================== - - ansible: stable-2.12 - db_engine_version: mysql:5.7.40 - python: '3.8' - connector: pymysql==0.7.11 - docker_image: ghcr.io/laurent-indermuehle/test-container-my57-py38-pymysql0711:latest - - ansible: stable-2.12 - db_engine_version: mysql:5.7.40 - python: '3.8' - connector: pymysql==0.9.3 - docker_image: ghcr.io/laurent-indermuehle/test-container-my57-py38-pymysql093:latest - - ansible: stable-2.12 - db_engine_version: mysql:5.7.40 - python: '3.8' - connector: mysqlclient==2.0.1 - docker_image: ghcr.io/laurent-indermuehle/test-container-my57-py38-mysqlclient201:latest + - db_engine_name: mysql + db_engine_version: 10.5.18 + - db_engine_name: mysql + db_engine_version: 10.6.11 - # ================================================================== - # mysql-client 8 + Python 3.8 - # ================================================================== - - ansible: stable-2.12 - db_engine_version: mysql:8.0.31 - python: '3.8' - connector: pymysql==0.9.3 - docker_image: ghcr.io/laurent-indermuehle/test-container-my80-py38-pymysql093:latest - - ansible: stable-2.12 - db_engine_version: mysql:8.0.31 - python: '3.8' - connector: mysqlclient==2.0.1 - docker_image: ghcr.io/laurent-indermuehle/test-container-my80-py38-mysqlclient201:latest + - db_engine_name: mariadb + db_engine_version: 5.7.40 + - db_engine_name: mariadb + db_engine_version: 8.0.31 - # ================================================================== - # mysql-client 8 + Python 3.9 - # ================================================================== - - ansible: stable-2.13 - db_engine_version: mysql:8.0.31 + - connector_name: pymysql + connector_version: 2.0.1 + + - connector_name: pymysql + connector_version: 2.0.3 + + - connector_name: pymysql + connector_version: 2.1.1 + + - connector_name: mysqlclient + connector_version: 0.7.11 + + - connector_name: mysqlclient + connector_version: 0.9.3 + + - connector_name: mysqlclient + connector_version: 1.0.2 + + - db_engine_name: mariadb + connector_version: 0.7.11 + + - db_engine_version: 5.7.40 python: '3.9' - connector: pymysql==0.9.3 - docker_image: ghcr.io/laurent-indermuehle/test-container-my80-py39-pymysql093:latest - - ansible: stable-2.13 - db_engine_version: mysql:8.0.31 - python: '3.9' - connector: mysqlclient==2.0.3 - docker_image: ghcr.io/laurent-indermuehle/test-container-my80-py39-mysqlclient203:latest - - ansible: stable-2.14 - db_engine_version: mysql:8.0.31 - python: '3.9' - connector: pymysql==0.9.3 - docker_image: ghcr.io/laurent-indermuehle/test-container-my80-py39-pymysql093:latest - - ansible: stable-2.14 - db_engine_version: mysql:8.0.31 - python: '3.9' - connector: mysqlclient==2.0.3 - docker_image: ghcr.io/laurent-indermuehle/test-container-my80-py39-mysqlclient203:latest - - - # ================================================================== - # mysql-client 8 + Python 3.10 - # ================================================================== - # - ansible: stable-2.13 - # db_engine_version: mysql:8.0.31 - # python: '3.10' - # connector: pymysql==1.0.2 - # docker_image: ghcr.io/laurent-indermuehle/test-container-my80-py310-pymysql102:latest - - ansible: stable-2.13 - db_engine_version: mysql:8.0.31 + - db_engine_version: 5.7.40 python: '3.10' - connector: mysqlclient==2.1.1 - docker_image: ghcr.io/laurent-indermuehle/test-container-my80-py310-mysqlclient211:latest - # - ansible: stable-2.14 - # db_engine_version: mysql:8.0.31 - # python: '3.10' - # connector: pymysql==1.0.2 - # docker_image: ghcr.io/laurent-indermuehle/test-container-my80-py310-pymysql102:latest - - ansible: stable-2.14 - db_engine_version: mysql:8.0.31 - python: '3.10' - connector: mysqlclient==2.1.1 - docker_image: ghcr.io/laurent-indermuehle/test-container-my80-py310-mysqlclient211:latest + - db_engine_version: 5.7.40 + ansible: stable-2.13 - # - ansible: devel - # db_engine_version: mysql:8.0.31 - # python: '3.10' - # connector: pymysql==1.0.2 - # docker_image: ghcr.io/laurent-indermuehle/test-container-my80-py310-pymysql102:latest - - ansible: devel - db_engine_version: mysql:8.0.31 - python: '3.10' - connector: mysqlclient==2.1.1 - docker_image: ghcr.io/laurent-indermuehle/test-container-my80-py310-mysqlclient211:latest + - db_engine_version: 5.7.40 + ansible: stable-2.14 - # ================================================================== - # mariadb-client 10.3 + Python 3.8 - # ================================================================== - - ansible: stable-2.12 - db_engine_version: mariadb:10.4.27 + - db_engine_version: 5.7.40 + ansible: devel + + - db_engine_version: 8.0.31 python: '3.8' - connector: pymysql==0.9.3 - docker_image: ghcr.io/laurent-indermuehle/test-container-mariadb103-py38-pymysql093:latest - - ansible: stable-2.12 - db_engine_version: mariadb:10.4.27 + + - db_engine_version: 8.0.31 python: '3.8' - connector: mysqlclient==2.0.1 - docker_image: ghcr.io/laurent-indermuehle/test-container-mariadb103-py38-mysqlclient201:latest - - ansible: stable-2.12 - db_engine_version: mariadb:10.5.18 + + - db_engine_version: 10.4.27 + python: '3.10' + + - db_engine_version: 10.4.27 + ansible: devel + + - db_engine_version: 10.6.11 python: '3.8' - connector: pymysql==0.9.3 - docker_image: ghcr.io/laurent-indermuehle/test-container-mariadb103-py38-pymysql093:latest - - ansible: stable-2.12 - db_engine_version: mariadb:10.5.18 - python: '3.8' - connector: mysqlclient==2.0.1 - docker_image: ghcr.io/laurent-indermuehle/test-container-mariadb103-py38-mysqlclient201:latest + - db_engine_version: 10.6.11 + python: '3.9' - # ================================================================== - # mariadb-client 10.3 + Python 3.9 - # ================================================================== - - ansible: stable-2.13 - db_engine_version: mariadb:10.4.27 - python: '3.9' - connector: pymysql==0.9.3 - docker_image: ghcr.io/laurent-indermuehle/test-container-mariadb103-py39-pymysql093:latest - - ansible: stable-2.13 - db_engine_version: mariadb:10.4.27 - python: '3.9' - connector: mysqlclient==2.0.3 - docker_image: ghcr.io/laurent-indermuehle/test-container-mariadb103-py39-mysqlclient203:latest - - ansible: stable-2.13 - db_engine_version: mariadb:10.5.18 - python: '3.9' - connector: pymysql==0.9.3 - docker_image: ghcr.io/laurent-indermuehle/test-container-mariadb103-py39-pymysql093:latest - - ansible: stable-2.13 - db_engine_version: mariadb:10.5.18 - python: '3.9' - connector: mysqlclient==2.0.3 - docker_image: ghcr.io/laurent-indermuehle/test-container-mariadb103-py39-mysqlclient203:latest + - python: '3.8' + connector_version: 2.0.3 - - ansible: stable-2.14 - db_engine_version: mariadb:10.4.27 - python: '3.9' - connector: pymysql==0.9.3 - docker_image: ghcr.io/laurent-indermuehle/test-container-mariadb103-py39-pymysql093:latest - - ansible: stable-2.14 - db_engine_version: mariadb:10.4.27 - python: '3.9' - connector: mysqlclient==2.0.3 - docker_image: ghcr.io/laurent-indermuehle/test-container-mariadb103-py39-mysqlclient203:latest - - ansible: stable-2.14 - db_engine_version: mariadb:10.5.18 - python: '3.9' - connector: pymysql==0.9.3 - docker_image: ghcr.io/laurent-indermuehle/test-container-mariadb103-py39-pymysql093:latest - - ansible: stable-2.14 - db_engine_version: mariadb:10.5.18 - python: '3.9' - connector: mysqlclient==2.0.3 - docker_image: ghcr.io/laurent-indermuehle/test-container-mariadb103-py39-mysqlclient203:latest + - python: '3.8' + connector_version: 2.1.1 + - python: '3.9' + connector_version: 0.7.11 - # ================================================================== - # mariadb-client 10.6 + Python 3.10 - # ================================================================== - # - ansible: stable-2.13 - # db_engine_version: mariadb:10.5.18 - # python: '3.10' - # connector: pymysql==1.0.2 - # docker_image: ghcr.io/laurent-indermuehle/test-container-mariadb106-py310-pymysql102:latest - - ansible: stable-2.13 - db_engine_version: mariadb:10.5.18 - python: '3.10' - connector: mysqlclient==2.1.1 - docker_image: ghcr.io/laurent-indermuehle/test-container-mariadb106-py310-mysqlclient211:latest - # - ansible: stable-2.13 - # db_engine_version: mariadb:10.6.11 - # python: '3.10' - # connector: pymysql==1.0.2 - # docker_image: ghcr.io/laurent-indermuehle/test-container-mariadb106-py310-pymysql102:latest - - ansible: stable-2.13 - db_engine_version: mariadb:10.6.11 - python: '3.10' - connector: mysqlclient==2.1.1 - docker_image: ghcr.io/laurent-indermuehle/test-container-mariadb106-py310-mysqlclient211:latest + - python: '3.9' + connector_version: 2.0.1 - # - ansible: stable-2.14 - # db_engine_version: mariadb:10.5.18 - # python: '3.10' - # connector: pymysql==1.0.2 - # docker_image: ghcr.io/laurent-indermuehle/test-container-mariadb106-py310-pymysql102:latest - - ansible: stable-2.14 - db_engine_version: mariadb:10.5.18 - python: '3.10' - connector: mysqlclient==2.1.1 - docker_image: ghcr.io/laurent-indermuehle/test-container-mariadb106-py310-mysqlclient211:latest - # - ansible: stable-2.14 - # db_engine_version: mariadb:10.6.11 - # python: '3.10' - # connector: pymysql==1.0.2 - # docker_image: ghcr.io/laurent-indermuehle/test-container-mariadb106-py310-pymysql102:latest - - ansible: stable-2.14 - db_engine_version: mariadb:10.6.11 - python: '3.10' - connector: mysqlclient==2.1.1 - docker_image: ghcr.io/laurent-indermuehle/test-container-mariadb106-py310-mysqlclient211:latest + - python: '3.9' + connector_version: 2.1.1 - # - ansible: devel - # db_engine_version: mariadb:10.5.18 - # python: '3.10' - # connector: pymysql==1.0.2 - # docker_image: ghcr.io/laurent-indermuehle/test-container-mariadb106-py310-pymysql102:latest - - ansible: devel - db_engine_version: mariadb:10.5.18 - python: '3.10' - connector: mysqlclient==2.1.1 - docker_image: ghcr.io/laurent-indermuehle/test-container-mariadb106-py310-mysqlclient211:latest - # - ansible: devel - # db_engine_version: mariadb:10.6.11 - # python: '3.10' - # connector: pymysql==1.0.2 - # docker_image: ghcr.io/laurent-indermuehle/test-container-mariadb106-py310-pymysql102:latest - - ansible: devel - db_engine_version: mariadb:10.6.11 - python: '3.10' - connector: mysqlclient==2.1.1 - docker_image: ghcr.io/laurent-indermuehle/test-container-mariadb106-py310-mysqlclient211:latest + - python: '3.10' + connector_version: 0.7.11 + + - python: '3.10' + connector_version: 0.9.3 + + - python: '3.10' + connector_version: 2.0.1 + + - python: '3.10' + connector_version: 2.0.3 + + - python: '3.8' + ansible: stable-2.13 + + - python: '3.8' + ansible: stable-2.14 + + - python: '3.8' + ansible: devel + + - python: '3.9' + ansible: stable-2.12 + + - python: '3.9' + ansible: devel + + - python: '3.10' + ansible: stable-2.12 services: db_primary: - image: docker.io/library/${{ matrix.db_engine_version }} + image: docker.io/library/${{ matrix.db_engine_name }}:${{ matrix.db_engine_version }} env: MARIADB_ROOT_PASSWORD: msandbox MYSQL_ROOT_PASSWORD: msandbox @@ -302,7 +209,7 @@ jobs: --health-retries 6 db_replica1: - image: docker.io/library/${{ matrix.db_engine_version }} + image: docker.io/library/${{ matrix.db_engine_name }}:${{ matrix.db_engine_version }} env: MARIADB_ROOT_PASSWORD: msandbox MYSQL_ROOT_PASSWORD: msandbox @@ -316,7 +223,7 @@ jobs: --health-retries 6 db_replica2: - image: docker.io/library/${{ matrix.db_engine_version }} + image: docker.io/library/${{ matrix.db_engine_name }}:${{ matrix.db_engine_version }} env: MARIADB_ROOT_PASSWORD: msandbox MYSQL_ROOT_PASSWORD: msandbox @@ -343,8 +250,46 @@ jobs: docker restart -t 30 ${{ job.services.db_replica2.id }} - name: Wait for the primary to be healthy - run: | - while ! /usr/bin/docker inspect --format="{{if .Config.Healthcheck}}{{print .State.Health.Status}}{{end}}" ${{ job.services.db_primary.id }} | grep healthy && [[ "$SECONDS" -lt 120 ]]; do sleep 1; done + run: > + while ! /usr/bin/docker inspect + --format="{{if .Config.Healthcheck}}{{print .State.Health.Status}}{{end}}" + ${{ job.services.db_primary.id }} + | grep healthy && [[ "$SECONDS" -lt 120 ]]; do sleep 1; done + + - name: Compute docker_image - Set python_version_flat + run: > + echo "python_version_flat=$(echo ${{ matrix.python }} + | tr -d '.')" >> $GITHUB_ENV + + - name: Compute docker_image - Set connector_version_flat + run: > + echo "connector_version_flat=$(echo ${{ matrix.connector_version }} + |tr -d .)" >> $GITHUB_ENV + + - name: Compute docker_image - Set db_engine_version_flat + run: > + echo "db_engine_version_flat=$(echo ${{ matrix.db_engine_version }} + | awk -F '.' '{print $1 $2}')" >> $GITHUB_ENV + + - name: Compute docker_image - Set db_client + run: > + if [[ ${{ env.db_engine_version_flat }} == 57 ]]; then + echo "db_client=my57" >> $GITHUB_ENV; + else + echo "db_client=$(echo ${{ matrix.db_engine_name }})" >> $GITHUB_ENV; + fi + + - name: Set docker_image + run: > + docker_image_multiline=(" + ghcr.io/ansible-collections/community.mysql\ + /test-container-${{ env.db_client }}\ + -py${{ env.python_version_flat }}\ + -${{ matrix.connector_name }}${{ env.connector_version_flat }}\ + :latest") + + echo "docker_image=$(printf '%s' $docker_image_multiline)" + >> $GITHUB_ENV - name: >- Perform integration testing against @@ -354,15 +299,31 @@ jobs: with: ansible-core-version: ${{ matrix.ansible }} pre-test-cmd: >- - echo Setting db_engine_version to "${{ matrix.db_engine_version }}"...; - echo -n "${{ matrix.db_engine_version }}" > tests/integration/db_engine_version; - echo Setting Connector version to "${{ matrix.connector }}"...; - echo -n "${{ matrix.connector }}" > tests/integration/connector; - echo Setting Python version to "${{ matrix.python }}"...; - echo -n "${{ matrix.python }}" > tests/integration/python; - echo Setting Ansible version to "${{ matrix.ansible }}"...; - echo -n "${{ matrix.ansible }}" > tests/integration/ansible - docker-image: ${{ matrix.docker_image }} + echo Setting db_engine_name to "${{ matrix.db_engine_name }}"...; + echo -n "${{ matrix.db_engine_name }}" + > tests/integration/db_engine_name; + + echo Setting db_engine_version to \ + "${{ matrix.db_engine_version }}"...; + echo -n "${{ matrix.db_engine_version }}" + > tests/integration/db_engine_version; + + echo Setting Connector name to "${{ matrix.connector_name }}"...; + echo -n "${{ matrix.connector_name }}" + > tests/integration/connector_name; + + echo Setting Connector name to "${{ matrix.connector_version }}"...; + echo -n "${{ matrix.connector_version }}" + > tests/integration/connector_version; + + echo Setting Python version to "${{ matrix.python }}"...; + echo -n "${{ matrix.python }}" + > tests/integration/python; + + echo Setting Ansible version to "${{ matrix.ansible }}"...; + echo -n "${{ matrix.ansible }}" + > tests/integration/ansible + docker-image: ${{ env.docker_image }} target-python-version: ${{ matrix.python }} testing-type: integration diff --git a/.github/workflows/docker-image-mariadb-py310-mysqlclient211.yml b/.github/workflows/docker-image-mariadb-py310-mysqlclient211.yml new file mode 100644 index 0000000..a893d3b --- /dev/null +++ b/.github/workflows/docker-image-mariadb-py310-mysqlclient211.yml @@ -0,0 +1,19 @@ +--- +name: Docker Image CI mariadb-py310-mysqlclient211 + +on: + push: + paths: + - 'test-containers/mariadb-py310-mysqlclient211/**' + - '.github/workflows/docker-image-mariadb-py310-mysqlclient211.yml' + - '.github/workflows/build-docker-image.yml' + +jobs: + + call-workflow-passing-data: + uses: ./.github/workflows/build-docker-image.yml + secrets: inherit + with: + registry: ghcr.io + image_name: test-container-mariadb-py310-mysqlclient211 + context: test-containers/mariadb-py310-mysqlclient211 diff --git a/.github/workflows/docker-image-mariadb-py310-pymysql102.yml b/.github/workflows/docker-image-mariadb-py310-pymysql102.yml new file mode 100644 index 0000000..dbd8a9d --- /dev/null +++ b/.github/workflows/docker-image-mariadb-py310-pymysql102.yml @@ -0,0 +1,19 @@ +--- +name: Docker Image CI mariadb-py310-pymysql102 + +on: + push: + paths: + - 'test-containers/mariadb-py310-pymysql102/**' + - '.github/workflows/docker-image-mariadb-py310-pymysql102.yml' + - '.github/workflows/build-docker-image.yml' + +jobs: + + call-workflow-passing-data: + uses: ./.github/workflows/build-docker-image.yml + secrets: inherit + with: + registry: ghcr.io + image_name: test-container-mariadb-py310-pymysql102 + context: test-containers/mariadb-py310-pymysql102 diff --git a/.github/workflows/docker-image-mariadb-py38-mysqlclient201.yml b/.github/workflows/docker-image-mariadb-py38-mysqlclient201.yml new file mode 100644 index 0000000..59fcf00 --- /dev/null +++ b/.github/workflows/docker-image-mariadb-py38-mysqlclient201.yml @@ -0,0 +1,19 @@ +--- +name: Docker Image CI mariadb-py38-mysqlclient201 + +on: + push: + paths: + - 'test-containers/mariadb-py38-mysqlclient201/**' + - '.github/workflows/docker-image-mariadb-py38-mysqlclient201.yml' + - '.github/workflows/build-docker-image.yml' + +jobs: + + call-workflow-passing-data: + uses: ./.github/workflows/build-docker-image.yml + secrets: inherit + with: + registry: ghcr.io + image_name: test-container-mariadb-py38-mysqlclient201 + context: test-containers/mariadb-py38-mysqlclient201 diff --git a/.github/workflows/docker-image-mariadb-py38-pymysql093.yml b/.github/workflows/docker-image-mariadb-py38-pymysql093.yml new file mode 100644 index 0000000..2602ebe --- /dev/null +++ b/.github/workflows/docker-image-mariadb-py38-pymysql093.yml @@ -0,0 +1,19 @@ +--- +name: Docker Image CI mariadb-py38-pymysql093 + +on: + push: + paths: + - 'test-containers/mariadb-py38-pymysql093/**' + - '.github/workflows/docker-image-mariadb-py38-pymysql093.yml' + - '.github/workflows/build-docker-image.yml' + +jobs: + + call-workflow-passing-data: + uses: ./.github/workflows/build-docker-image.yml + secrets: inherit + with: + registry: ghcr.io + image_name: test-container-mariadb-py38-pymysql093 + context: test-containers/mariadb-py38-pymysql093 diff --git a/.github/workflows/docker-image-mariadb-py39-mysqlclient203.yml b/.github/workflows/docker-image-mariadb-py39-mysqlclient203.yml new file mode 100644 index 0000000..3f260e5 --- /dev/null +++ b/.github/workflows/docker-image-mariadb-py39-mysqlclient203.yml @@ -0,0 +1,19 @@ +--- +name: Docker Image CI mariadb-py39-mysqlclient203 + +on: + push: + paths: + - 'test-containers/mariadb-py39-mysqlclient203/**' + - '.github/workflows/docker-image-mariadb-py39-mysqlclient203.yml' + - '.github/workflows/build-docker-image.yml' + +jobs: + + call-workflow-passing-data: + uses: ./.github/workflows/build-docker-image.yml + secrets: inherit + with: + registry: ghcr.io + image_name: test-container-mariadb-py39-mysqlclient203 + context: test-containers/mariadb-py39-mysqlclient203 diff --git a/.github/workflows/docker-image-mariadb-py39-pymysql093.yml b/.github/workflows/docker-image-mariadb-py39-pymysql093.yml new file mode 100644 index 0000000..77bb664 --- /dev/null +++ b/.github/workflows/docker-image-mariadb-py39-pymysql093.yml @@ -0,0 +1,19 @@ +--- +name: Docker Image CI mariadb-py39-pymysql093 + +on: + push: + paths: + - 'test-containers/mariadb-py39-pymysql093/**' + - '.github/workflows/docker-image-mariadb-py39-pymysql093.yml' + - '.github/workflows/build-docker-image.yml' + +jobs: + + call-workflow-passing-data: + uses: ./.github/workflows/build-docker-image.yml + secrets: inherit + with: + registry: ghcr.io + image_name: test-container-mariadb-py39-pymysql093 + context: test-containers/mariadb-py39-pymysql093 diff --git a/.github/workflows/docker-image-mariadb103-py38-mysqlclient201.yml b/.github/workflows/docker-image-mariadb103-py38-mysqlclient201.yml deleted file mode 100644 index 3d90270..0000000 --- a/.github/workflows/docker-image-mariadb103-py38-mysqlclient201.yml +++ /dev/null @@ -1,19 +0,0 @@ ---- -name: Docker Image CI mariadb103-py38-mysqlclient201 - -on: - push: - paths: - - 'test-containers/mariadb103-py38-mysqlclient201/**' - - '.github/workflows/docker-image-mariadb103-py38-mysqlclient201.yml' - - '.github/workflows/build-docker-image.yml' - -jobs: - - call-workflow-passing-data: - uses: ./.github/workflows/build-docker-image.yml - secrets: inherit - with: - registry: ghcr.io - image_name: test-container-mariadb103-py38-mysqlclient201 - context: test-containers/mariadb103-py38-mysqlclient201 diff --git a/.github/workflows/docker-image-mariadb103-py38-pymysql093.yml b/.github/workflows/docker-image-mariadb103-py38-pymysql093.yml deleted file mode 100644 index 1ca4600..0000000 --- a/.github/workflows/docker-image-mariadb103-py38-pymysql093.yml +++ /dev/null @@ -1,19 +0,0 @@ ---- -name: Docker Image CI mariadb103-py38-pymysql093 - -on: - push: - paths: - - 'test-containers/mariadb103-py38-pymysql093/**' - - '.github/workflows/docker-image-mariadb103-py38-pymysql093.yml' - - '.github/workflows/build-docker-image.yml' - -jobs: - - call-workflow-passing-data: - uses: ./.github/workflows/build-docker-image.yml - secrets: inherit - with: - registry: ghcr.io - image_name: test-container-mariadb103-py38-pymysql093 - context: test-containers/mariadb103-py38-pymysql093 diff --git a/.github/workflows/docker-image-mariadb103-py39-mysqlclient203.yml b/.github/workflows/docker-image-mariadb103-py39-mysqlclient203.yml deleted file mode 100644 index 37e91ee..0000000 --- a/.github/workflows/docker-image-mariadb103-py39-mysqlclient203.yml +++ /dev/null @@ -1,19 +0,0 @@ ---- -name: Docker Image CI mariadb103-py39-mysqlclient203 - -on: - push: - paths: - - 'test-containers/mariadb103-py39-mysqlclient203/**' - - '.github/workflows/docker-image-mariadb103-py39-mysqlclient203.yml' - - '.github/workflows/build-docker-image.yml' - -jobs: - - call-workflow-passing-data: - uses: ./.github/workflows/build-docker-image.yml - secrets: inherit - with: - registry: ghcr.io - image_name: test-container-mariadb103-py39-mysqlclient203 - context: test-containers/mariadb103-py39-mysqlclient203 diff --git a/.github/workflows/docker-image-mariadb103-py39-pymysql093.yml b/.github/workflows/docker-image-mariadb103-py39-pymysql093.yml deleted file mode 100644 index 30acfc1..0000000 --- a/.github/workflows/docker-image-mariadb103-py39-pymysql093.yml +++ /dev/null @@ -1,19 +0,0 @@ ---- -name: Docker Image CI mariadb103-py39-pymysql093 - -on: - push: - paths: - - 'test-containers/mariadb103-py39-pymysql093/**' - - '.github/workflows/docker-image-mariadb103-py39-pymysql093.yml' - - '.github/workflows/build-docker-image.yml' - -jobs: - - call-workflow-passing-data: - uses: ./.github/workflows/build-docker-image.yml - secrets: inherit - with: - registry: ghcr.io - image_name: test-container-mariadb103-py39-pymysql093 - context: test-containers/mariadb103-py39-pymysql093 diff --git a/.github/workflows/docker-image-mariadb106-py310-mysqlclient211.yml b/.github/workflows/docker-image-mariadb106-py310-mysqlclient211.yml deleted file mode 100644 index 0fa7403..0000000 --- a/.github/workflows/docker-image-mariadb106-py310-mysqlclient211.yml +++ /dev/null @@ -1,19 +0,0 @@ ---- -name: Docker Image CI mariadb106-py310-mysqlclient211 - -on: - push: - paths: - - 'test-containers/mariadb106-py310-mysqlclient211/**' - - '.github/workflows/docker-image-mariadb106-py310-mysqlclient211.yml' - - '.github/workflows/build-docker-image.yml' - -jobs: - - call-workflow-passing-data: - uses: ./.github/workflows/build-docker-image.yml - secrets: inherit - with: - registry: ghcr.io - image_name: test-container-mariadb106-py310-mysqlclient211 - context: test-containers/mariadb106-py310-mysqlclient211 diff --git a/.github/workflows/docker-image-mariadb106-py310-pymysql102.yml b/.github/workflows/docker-image-mariadb106-py310-pymysql102.yml deleted file mode 100644 index adfe9e3..0000000 --- a/.github/workflows/docker-image-mariadb106-py310-pymysql102.yml +++ /dev/null @@ -1,19 +0,0 @@ ---- -name: Docker Image CI mariadb106-py310-pymysql102 - -on: - push: - paths: - - 'test-containers/mariadb106-py310-pymysql102/**' - - '.github/workflows/docker-image-mariadb106-py310-pymysql102.yml' - - '.github/workflows/build-docker-image.yml' - -jobs: - - call-workflow-passing-data: - uses: ./.github/workflows/build-docker-image.yml - secrets: inherit - with: - registry: ghcr.io - image_name: test-container-mariadb106-py310-pymysql102 - context: test-containers/mariadb106-py310-pymysql102 diff --git a/.github/workflows/docker-image-my80-py310-mysqlclient211.yml b/.github/workflows/docker-image-my80-py310-mysqlclient211.yml deleted file mode 100644 index 824f77c..0000000 --- a/.github/workflows/docker-image-my80-py310-mysqlclient211.yml +++ /dev/null @@ -1,19 +0,0 @@ ---- -name: Docker Image CI my80-py310-mysqlclient211 - -on: - push: - paths: - - 'test-containers/my80-py310-mysqlclient211/**' - - '.github/workflows/docker-image-my80-py310-mysqlclient211.yml' - - '.github/workflows/build-docker-image.yml' - -jobs: - - call-workflow-passing-data: - uses: ./.github/workflows/build-docker-image.yml - secrets: inherit - with: - registry: ghcr.io - image_name: test-container-my80-py310-mysqlclient211 - context: test-containers/my80-py310-mysqlclient211 diff --git a/.github/workflows/docker-image-my80-py310-pymysql102.yml b/.github/workflows/docker-image-my80-py310-pymysql102.yml deleted file mode 100644 index 0c54e12..0000000 --- a/.github/workflows/docker-image-my80-py310-pymysql102.yml +++ /dev/null @@ -1,19 +0,0 @@ ---- -name: Docker Image CI my80-py310-pymysql102 - -on: - push: - paths: - - 'test-containers/my80-py310-pymysql102/**' - - '.github/workflows/docker-image-my80-py310-pymysql102.yml' - - '.github/workflows/build-docker-image.yml' - -jobs: - - call-workflow-passing-data: - uses: ./.github/workflows/build-docker-image.yml - secrets: inherit - with: - registry: ghcr.io - image_name: test-container-my80-py310-pymysql102 - context: test-containers/my80-py310-pymysql102 diff --git a/.github/workflows/docker-image-my80-py38-mysqlclient201.yml b/.github/workflows/docker-image-my80-py38-mysqlclient201.yml deleted file mode 100644 index 0ac76b2..0000000 --- a/.github/workflows/docker-image-my80-py38-mysqlclient201.yml +++ /dev/null @@ -1,19 +0,0 @@ ---- -name: Docker Image CI my80-py38-mysqlclient201 - -on: - push: - paths: - - 'test-containers/my80-py38-mysqlclient201/**' - - '.github/workflows/docker-image-my80-py38-mysqlclient201.yml' - - '.github/workflows/build-docker-image.yml' - -jobs: - - call-workflow-passing-data: - uses: ./.github/workflows/build-docker-image.yml - secrets: inherit - with: - registry: ghcr.io - image_name: test-container-my80-py38-mysqlclient201 - context: test-containers/my80-py38-mysqlclient201 diff --git a/.github/workflows/docker-image-my80-py38-pymysql093.yml b/.github/workflows/docker-image-my80-py38-pymysql093.yml deleted file mode 100644 index 1677be6..0000000 --- a/.github/workflows/docker-image-my80-py38-pymysql093.yml +++ /dev/null @@ -1,19 +0,0 @@ ---- -name: Docker Image CI my80-py38-pymysql093 - -on: - push: - paths: - - 'test-containers/my80-py38-pymysql093/**' - - '.github/workflows/docker-image-my80-py38-pymysql093.yml' - - '.github/workflows/build-docker-image.yml' - -jobs: - - call-workflow-passing-data: - uses: ./.github/workflows/build-docker-image.yml - secrets: inherit - with: - registry: ghcr.io - image_name: test-container-my80-py38-pymysql093 - context: test-containers/my80-py38-pymysql093 diff --git a/.github/workflows/docker-image-my80-py39-mysqlclient203.yml b/.github/workflows/docker-image-my80-py39-mysqlclient203.yml deleted file mode 100644 index e6b41db..0000000 --- a/.github/workflows/docker-image-my80-py39-mysqlclient203.yml +++ /dev/null @@ -1,19 +0,0 @@ ---- -name: Docker Image CI my80-py39-mysqlclient203 - -on: - push: - paths: - - 'test-containers/my80-py39-mysqlclient203/**' - - '.github/workflows/docker-image-my80-py39-mysqlclient203.yml' - - '.github/workflows/build-docker-image.yml' - -jobs: - - call-workflow-passing-data: - uses: ./.github/workflows/build-docker-image.yml - secrets: inherit - with: - registry: ghcr.io - image_name: test-container-my80-py39-mysqlclient203 - context: test-containers/my80-py39-mysqlclient203 diff --git a/.github/workflows/docker-image-my80-py39-pymysql093.yml b/.github/workflows/docker-image-my80-py39-pymysql093.yml deleted file mode 100644 index 72ffd60..0000000 --- a/.github/workflows/docker-image-my80-py39-pymysql093.yml +++ /dev/null @@ -1,19 +0,0 @@ ---- -name: Docker Image CI my80-py39-pymysql093 - -on: - push: - paths: - - 'test-containers/my80-py39-pymysql093/*' - - '.github/workflows/docker-image-my80-py39-pymysql093.yml' - - '.github/workflows/build-docker-image.yml' - -jobs: - - call-workflow-passing-data: - uses: ./.github/workflows/build-docker-image.yml - secrets: inherit - with: - registry: ghcr.io - image_name: test-container-my80-py39-pymysql093 - context: test-containers/my80-py39-pymysql093 diff --git a/.github/workflows/docker-image-mysql-py310-mysqlclient211.yml b/.github/workflows/docker-image-mysql-py310-mysqlclient211.yml new file mode 100644 index 0000000..70eea1c --- /dev/null +++ b/.github/workflows/docker-image-mysql-py310-mysqlclient211.yml @@ -0,0 +1,19 @@ +--- +name: Docker Image CI mysql-py310-mysqlclient211 + +on: + push: + paths: + - 'test-containers/mysql-py310-mysqlclient211/**' + - '.github/workflows/docker-image-mysql-py310-mysqlclient211.yml' + - '.github/workflows/build-docker-image.yml' + +jobs: + + call-workflow-passing-data: + uses: ./.github/workflows/build-docker-image.yml + secrets: inherit + with: + registry: ghcr.io + image_name: test-container-mysql-py310-mysqlclient211 + context: test-containers/mysql-py310-mysqlclient211 diff --git a/.github/workflows/docker-image-mysql-py310-pymysql102.yml b/.github/workflows/docker-image-mysql-py310-pymysql102.yml new file mode 100644 index 0000000..bcf88fa --- /dev/null +++ b/.github/workflows/docker-image-mysql-py310-pymysql102.yml @@ -0,0 +1,19 @@ +--- +name: Docker Image CI mysql-py310-pymysql102 + +on: + push: + paths: + - 'test-containers/mysql-py310-pymysql102/**' + - '.github/workflows/docker-image-mysql-py310-pymysql102.yml' + - '.github/workflows/build-docker-image.yml' + +jobs: + + call-workflow-passing-data: + uses: ./.github/workflows/build-docker-image.yml + secrets: inherit + with: + registry: ghcr.io + image_name: test-container-mysql-py310-pymysql102 + context: test-containers/mysql-py310-pymysql102 diff --git a/.github/workflows/docker-image-mysql-py38-mysqlclient201.yml b/.github/workflows/docker-image-mysql-py38-mysqlclient201.yml new file mode 100644 index 0000000..7fcfb60 --- /dev/null +++ b/.github/workflows/docker-image-mysql-py38-mysqlclient201.yml @@ -0,0 +1,19 @@ +--- +name: Docker Image CI mysql-py38-mysqlclient201 + +on: + push: + paths: + - 'test-containers/mysql-py38-mysqlclient201/**' + - '.github/workflows/docker-image-mysql-py38-mysqlclient201.yml' + - '.github/workflows/build-docker-image.yml' + +jobs: + + call-workflow-passing-data: + uses: ./.github/workflows/build-docker-image.yml + secrets: inherit + with: + registry: ghcr.io + image_name: test-container-mysql-py38-mysqlclient201 + context: test-containers/mysql-py38-mysqlclient201 diff --git a/.github/workflows/docker-image-mysql-py38-pymysql093.yml b/.github/workflows/docker-image-mysql-py38-pymysql093.yml new file mode 100644 index 0000000..5a43ab8 --- /dev/null +++ b/.github/workflows/docker-image-mysql-py38-pymysql093.yml @@ -0,0 +1,19 @@ +--- +name: Docker Image CI mysql-py38-pymysql093 + +on: + push: + paths: + - 'test-containers/mysql-py38-pymysql093/**' + - '.github/workflows/docker-image-mysql-py38-pymysql093.yml' + - '.github/workflows/build-docker-image.yml' + +jobs: + + call-workflow-passing-data: + uses: ./.github/workflows/build-docker-image.yml + secrets: inherit + with: + registry: ghcr.io + image_name: test-container-mysql-py38-pymysql093 + context: test-containers/mysql-py38-pymysql093 diff --git a/.github/workflows/docker-image-mysql-py39-mysqlclient203.yml b/.github/workflows/docker-image-mysql-py39-mysqlclient203.yml new file mode 100644 index 0000000..4486a6e --- /dev/null +++ b/.github/workflows/docker-image-mysql-py39-mysqlclient203.yml @@ -0,0 +1,19 @@ +--- +name: Docker Image CI mysql-py39-mysqlclient203 + +on: + push: + paths: + - 'test-containers/mysql-py39-mysqlclient203/**' + - '.github/workflows/docker-image-mysql-py39-mysqlclient203.yml' + - '.github/workflows/build-docker-image.yml' + +jobs: + + call-workflow-passing-data: + uses: ./.github/workflows/build-docker-image.yml + secrets: inherit + with: + registry: ghcr.io + image_name: test-container-mysql-py39-mysqlclient203 + context: test-containers/mysql-py39-mysqlclient203 diff --git a/.github/workflows/docker-image-mysql-py39-pymysql093.yml b/.github/workflows/docker-image-mysql-py39-pymysql093.yml new file mode 100644 index 0000000..d06dc74 --- /dev/null +++ b/.github/workflows/docker-image-mysql-py39-pymysql093.yml @@ -0,0 +1,19 @@ +--- +name: Docker Image CI mysql-py39-pymysql093 + +on: + push: + paths: + - 'test-containers/mysql-py39-pymysql093/*' + - '.github/workflows/docker-image-mysql-py39-pymysql093.yml' + - '.github/workflows/build-docker-image.yml' + +jobs: + + call-workflow-passing-data: + uses: ./.github/workflows/build-docker-image.yml + secrets: inherit + with: + registry: ghcr.io + image_name: test-container-mysql-py39-pymysql093 + context: test-containers/mysql-py39-pymysql093 diff --git a/Makefile b/Makefile index a94ffd8..dc6d6d0 100644 --- a/Makefile +++ b/Makefile @@ -11,12 +11,32 @@ ifdef continue_on_errors _continue_on_errors = --retry-on-error --continue-on-error endif + +db_ver_tuple := $(subst ., , $(db_engine_version)) +db_engine_version_flat := $(word 1, $(db_ver_tuple))$(word 2, $(db_ver_tuple)) + +con_ver_tuple := $(subst ., , $(connector_version)) +connector_version_flat := $(word 1, $(con_ver_tuple))$(word 2, $(con_ver_tuple))$(word 3, $(con_ver_tuple)) + +py_ver_tuple := $(subst ., , $(python)) +python_version_flat := $(word 1, $(py_ver_tuple))$(word 2, $(py_ver_tuple)) + +ifeq ($(db_engine_version_flat), 57) + db_client := my57 +else + db_client := $(db_engine_name) +endif + + .PHONY: test-integration test-integration: - echo -n $(db_engine_version) > tests/integration/db_engine_version - echo -n $(connector) > tests/integration/connector - echo -n $(python) > tests/integration/python - echo -n $(ansible) > tests/integration/ansible + @echo -n $(db_engine_name) > tests/integration/db_engine_name + @echo -n $(db_engine_version) > tests/integration/db_engine_version + @echo -n $(connector_name) > tests/integration/connector_name + @echo -n $(connector_version) > tests/integration/connector_version + @echo -n $(python) > tests/integration/python + @echo -n $(ansible) > tests/integration/ansible + # Create podman network for systems missing it. Error can be ignored podman network create podman || true podman run \ @@ -28,7 +48,7 @@ test-integration: --network podman \ --publish 3307:3306 \ --health-cmd 'mysqladmin ping -P 3306 -pmsandbox | grep alive || exit 1' \ - docker.io/library/$(db_engine_version) \ + docker.io/library/$(db_engine_name):$(db_engine_version) \ mysqld podman run \ --detach \ @@ -39,7 +59,7 @@ test-integration: --network podman \ --publish 3308:3306 \ --health-cmd 'mysqladmin ping -P 3306 -pmsandbox | grep alive || exit 1' \ - docker.io/library/$(db_engine_version) \ + docker.io/library/$(db_engine_name):$(db_engine_version) \ mysqld podman run \ --detach \ @@ -50,7 +70,7 @@ test-integration: --network podman \ --publish 3309:3306 \ --health-cmd 'mysqladmin ping -P 3306 -pmsandbox | grep alive || exit 1' \ - docker.io/library/$(db_engine_version) \ + docker.io/library/$(db_engine_name):$(db_engine_version) \ mysqld # Setup replication and restart containers podman exec primary bash -c 'echo -e [mysqld]\\nserver-id=1\\nlog-bin=/var/lib/mysql/primary-bin > /etc/mysql/conf.d/replication.cnf' @@ -69,9 +89,13 @@ test-integration: source .venv/$(ansible)/bin/activate python$(local_python_version) -m ensurepip python$(local_python_version) -m pip install --disable-pip-version-check https://github.com/ansible/ansible/archive/$(ansible).tar.gz - -set -x; ansible-test integration $(target) -v --color --coverage --diff --docker $(docker_image) --docker-network podman $(_continue_on_errors) $(_keep_containers_alive) --python $(python); set +x + -set -x; ansible-test integration $(target) -v --color --coverage --diff \ + --docker ghcr.io/ansible-collections/community.mysql/test-container-$(db_client)-py$(python_version_flat)-$(connector_name)$(connector_version_flat):latest \ + --docker-network podman $(_continue_on_errors) $(_keep_containers_alive) --python $(python); set +x + rm tests/integration/db_engine_name rm tests/integration/db_engine_version - rm tests/integration/connector + rm tests/integration/connector_name + rm tests/integration/connector_version rm tests/integration/python rm tests/integration/ansible ifndef keep_containers_alive diff --git a/TESTING.md b/TESTING.md index 9aad0f5..a24193a 100644 --- a/TESTING.md +++ b/TESTING.md @@ -2,7 +2,7 @@ This collection uses GitHub Actions to run ansible-test to validate its content. Three type of tests are used: Sanity, Integration and Units. -The tests covers the code for plugins and roles (no role available yet, but tests are ready) and can be found here: +The tests covers plugins and roles (no role available yet, but tests are ready) and can be found here: - Plugins: *.github/workflows/ansible-test-plugins.yml* - Roles: *.github/workflows/ansible-test-roles.yml* (unused yet) @@ -16,48 +16,125 @@ You can use GitHub to run ansible-test either on the community repo or your fork For now, the makefile only supports Podman. + ### Requirements - python >= 3.8 and <= 3.10 - make +- podman - Minimum 15GB of free space on the device storing containers images and volumes. You can use this command to check: `podman system info --format='{{.Store.GraphRoot}}'|xargs findmnt --noheadings --nofsroot --output SOURCE --target|xargs df -h --output=size,used,avail,pcent,target` - Minimum 2GB of RAM +### Custom ansible-test containers + +Our integrations tests use custom containers for ansible-test. Those images have their definition file stored in the directory [test-containers](test-containers/). We build and publish the images on ghcr.io under the ansible-collection namespace: E.G.: +`ghcr.io/ansible-collections/community.mysql/test-container-mariadb106-py310-mysqlclient211:latest`. + +Availables images are listed [here](https://github.com/orgs/ansible-collections/packages). + + ### Makefile options -The Makefile accept the following options: +The Makefile accept the following options -- **local_python_version**: This option can be omitted if your system has a version supported by Ansible. You can check with `python -V`. -- **ansible**: Mandatory version of ansible to install in a venv to run ansible-test. -- **docker_image**: - The container image to use to run our tests. Those images Dockerfile are in https://github.com/community.mysql/test-containers and then pushed to quay.io: E.G.: - `quay.io/mws/community-mysql-test-containers-my57-py38-mysqlclient201-pymysql0711:latest`. Look in the link above for a complete list of available containers. You can also look into `.github/workflows/ansible-test-plugins.yml` - Unfortunatly you must provide the right container_image yourself. And you still need to provides db_engine_version, python, etc... because ansible-test won't do black magic to try to detect what we expect. Explicit is better than implicit anyway. - To minimise the amount of images, pymysql 0.7.11 and mysqlclient are shipped together. -- **db_engine_version**: The name of the container to use for the service containers that will host a primary database and two replicas. Either MYSQL or MariaDB. Use ':' as a separator. Do not use short version, like mysql:8 for instance. Our tests expect a full version to filter tests precisely. For instance: `when: db_version is version ('8.0.22', '>')`. -- **connector**: The name of the python package of the connector along with its version number. Use '==' as a separator. -- **python**: The python version to use in the controller. -- **target** : If omitted, all test targets will run. But you can limit the tests to a single target to speed up your tests. -- **keep_containers_alive**: This option keeps all tree databases containers and the ansible-test container alive at the end of tests or in case of failure. This is useful to enter one of the containers with `podman exec -it bash` for debugging. Rerunning the -test will recreate those containers. -- **continue_on_errors**: Tells ansible-test to retry on errors and also continue on errors. This is the way the GitHub Action's workflow runs the tests. If you develop a new target, this option can be used to validate that your tests cleanup everything so a new run can restart without errors like "Failed to create database x because it already exists". +- `local_python_version` + - Mandatory: false + - Choices: + - "3.8" + - "3.9" + - "3.10" + - Description: If `Python -V` shows an unsupported version, use this option and choose one of the version available on your system. Use `ls /usr/bin/python3*|grep -v config` to list them. -Examples: +- `ansible` + - Mandatory: true + - Choices: + - "stable-2.12" + - "stable-2.13" + - "stable-2.14" + - "devel" + - Description: Version of ansible to install in a venv to run ansible-test + +- `db_engine_name` + - Mandatory: true + - Choices: + - "mysql" + - "mariadb" + - Description: The name of the database engine to use for the service containers that will host a primary database and two replicas. + +- `db_engine_version` + - Mandatory: true + - Choices: + - "5.7.40" <- mysql + - "8.0.31" <- mysql + - "10.4.24" <- mariadb + - "10.5.18" <- mariadb + - "10.6.11" <- mariadb + - Description: The tag of the container to use for the service containers that will host a primary database and two replicas. Do not use short version, like `mysql:8` (don't do that) because our tests expect a full version to filter tests precisely. For instance: `when: db_version is version ('8.0.22', '>')`. You can use any tag available on [hub.docker.com/_/mysql](https://hub.docker.com/_/mysql) and [hub.docker.com/_/mariadb](https://hub.docker.com/_/mariadb) but GitHub Action will only use the versions listed above. + +- `connector_name` + - Mandatory: true + - Choices: + - "pymysql + - "mysqlclient" + - Description: The python package of the connector to use. This value is used to filter tests meant for other connectors. + +- `connector_version` + - Mandatory: true + - Choices: + - "0.7.11" <- Only for MySQL 5.7 + - "0.9.3" + - "1.0.2" <- Not working, need fix + - "2.0.1" + - "2.0.3" + - "2.1.1" + - Description: The version of the python package of the connector to use. This value is used to filter tests meant for other connectors. + +- `python` + - Mandatory: true + - Choices: + - "3.8" + - "3.9" + - "3.10" + - Description: The python version to use in the controller (ansible-test container). + +- `target` + - Mandatory: false + - Choices: + - "test_mysql_db" + - "test_mysql_info" + - "test_mysql_query" + - "test_mysql_replication" + - "test_mysql_role" + - "test_mysql_user" + - "test_mysql_variables" + - Description: If omitted, all test targets will run. But you can limit the tests to a single target to speed up your tests. + +- `keep_containers_alive` + - Mandatory: false + - Description: This option keeps all tree databases containers and the ansible-test container alive at the end of tests or in case of failure. This is useful to enter one of the containers with `podman exec -it bash` for debugging. Rerunning the +test will recreate those containers so no need to kill it. Add any value to activate this option: `keep_containers_alive=1` + +- `continue_on_errors` + - Mandatory: false + - Description: Tells ansible-test to retry on errors and also continue on errors. This is the way the GitHub Action's workflow runs the tests. This can be use to catch all errors in a single run, but you'll need to scroll up to find them. Add any value to activate this option: `continue_on_errors=1` + + +#### Makefile usage examples: ```sh # Run all targets -make ansible="stable-2.12" db_engine_version="mysql:5.7.40" python="3.8" connector="pymysql==0.7.11" docker_image="ghcr.io/community.mysql/test-container-my57-py38-pymysql0711:latest" +make ansible="stable-2.12" db_engine_name="mysql" db_engine_version="5.7.40" python="3.8" connector_name="pymysql" connector_version="0.7.11" # A single target -make ansible="stable-2.14" db_engine_version="mysql:5.7.40" python="3.8" connector="pymysql==0.7.11" docker_image="ghcr.io/community.mysql/test-container-my57-py38-pymysql0711:latest" target="test_mysql_db" +make ansible="stable-2.14" db_engine_name="mysql" db_engine_version="5.7.40" python="3.8" connector_name="pymysql" connector_version="0.7.11" # Keep databases and ansible tests containers alives # A single target and continue on errors -make ansible="stable-2.14" db_engine_version="mysql:8.0.31" python="3.9" connector="mysqlclient==2.0.3" docker_image="ghcr.io/community.mysql/test-container-my80-py39-mysqlclient203:latest" target="test_mysql_db" keep_containers_alive=1 continue_on_errors=1 +make ansible="stable-2.14" db_engine_name="mysql" db_engine_version="8.0.31" python="3.9" connector_name="mysqlclient" connector_version="2.0.3" # If your system has an usupported version of Python: -make local_python_version="3.8" ansible="stable-2.14" db_engine_version="mariadb:10.6.11" python="3.9" connector="pymysql==0.9.3" docker_image="ghcr.io/community.mysql/test-container-mariadb103-py39-pymysql093:latest" +make local_python_version="3.8" ansible="stable-2.14" db_engine_name="mariadb" db_engine_version="10.6.11" python="3.9" connector_name="pymysql" connector_version="0.9.3" ``` @@ -74,14 +151,16 @@ python run_all_tests.py ### Add a new Python, Connector or Database version +You can look into `[.github/workflows/ansible-test-plugins.yml](https://github.com/ansible-collections/community.mysql/tree/main/.github/workflows)` to see how those containers are built using [build-docker-image.yml](https://github.com/ansible-collections/community.mysql/blob/main/.github/workflows/build-docker-image.yml) and all [docker-image-xxx.yml](https://github.com/ansible-collections/community.mysql/blob/main/.github/workflows/docker-image-mariadb103-py38-mysqlclient201.yml) files. + 1. Add a workflow in [.github/workflows/](.github/workflows) 1. Add a new folder in [test-containers](test-containers) containing a new Dockerfile. Your container must contains 3 things: - - The python interpreter - - The python package to connect to the database (pymysql, mysqlclient, ...) - - A mysql client to query the database before to prepare tests before our tests starts. This client must provide both `mysql` and `mysqldump` commands. -1. Add your version in *.github/workflows/ansible-test-plugins.yml* + - Python + - A connector: The python package to connect to the database (pymysql, mysqlclient, ...) + - A mysql client to prepare databases before our tests starts. This client must provide both `mysql` and `mysqldump` commands. +1. Add your version in the matrix of *.github/workflows/ansible-test-plugins.yml*. You can use [run_all_tests.py](run_all_tests.py) to help you see what the matrix will be. Simply comment out the line `os.system(make_cmd)` before runing the script. You can also add `print(len(matrix))` to display how many tests there will be on GitHub Action. +1. Ask the lead maintainer to mark your new image(s) as `public` under [https://github.com/orgs/ansible-collections/packages](https://github.com/orgs/ansible-collections/packages) -After pushing the commit to the remote, the container will be build and published on ghcr.io. Have a look in the "Action" tab to see if it worked. In case of error `failed to copy: io: read/write on closed pipe` re-run the workflow, this append unfortunately a lot. - -To see the docker image produced, go to the main GitHub page of your fork or community.mysql (depending were you pushed) and look for the link "Packages" on the right hand side of the page. This page indicate a "Published x days ago" that is updated infrequently. To see the last time the container has been updated you must click on its title and look in the right hands side bellow the title "Last published". +After pushing your commit to the remote, the container will be built and published on ghcr.io. Have a look in the "Action" tab to see if it worked. In case of error `failed to copy: io: read/write on closed pipe` re-run the workflow, this append unfortunately a lot. +To see the docker image produced, go to the package page in the ansible-collection namespace [https://github.com/orgs/ansible-collections/packages](https://github.com/orgs/ansible-collections/packages). This page indicate a "Published x days ago" that is updated infrequently. To see the last time the container has been updated you must click on its title and look in the right hands side bellow the title "Last published". diff --git a/run_all_tests.py b/run_all_tests.py index b7779a5..94cf799 100755 --- a/run_all_tests.py +++ b/run_all_tests.py @@ -28,54 +28,83 @@ def extract_matrix(workflow_yaml): return matrix -# def is_exclude(exclude_list, test_suite): -# test_is_excluded = False -# for excl in exclude_list: -# match = 0 +def is_exclude(exclude_list, test_suite): + test_is_excluded = False + for excl in exclude_list: + match = 0 -# if 'ansible' in excl: -# if excl.get('ansible') == test_suite[0]: -# match += 1 + if 'ansible' in excl: + if excl.get('ansible') == test_suite.get('ansible'): + match += 1 -# if 'db_engine_version' in excl: -# if excl.get('db_engine_version') == test_suite[1]: -# match += 1 + if 'db_engine_name' in excl: + if excl.get('db_engine_name') == test_suite.get('db_engine_name'): + match += 1 -# if 'python' in excl: -# if excl.get('python') == test_suite[2]: -# match += 1 + if 'db_engine_version' in excl: + if excl.get('db_engine_version') == test_suite.get('db_engine_version'): + match += 1 -# if 'connector' in excl: -# if excl.get('connector') == test_suite[3]: -# match += 1 + if 'python' in excl: + if excl.get('python') == test_suite.get('python'): + match += 1 -# if match > 1: -# test_is_excluded = True + if 'connector_name' in excl: + if excl.get('connector_name') == test_suite.get('connector_name'): + match += 1 -# return test_is_excluded + if 'connector_version' in excl: + if excl.get('connector_version') == test_suite.get('connector_version'): + match += 1 + + if match > 1: + test_is_excluded = True + return test_is_excluded + + return test_is_excluded def main(): workflow_yaml = read_github_workflow_file() tests_matrix_yaml = extract_matrix(workflow_yaml) - # matrix = [] - # exclude_list = tests_matrix_yaml.get('exclude') - # for ansible in tests_matrix_yaml.get('ansible'): - # for db_engine in tests_matrix_yaml.get('db_engine_version'): - # for python in tests_matrix_yaml.get('python'): - # for connector in tests_matrix_yaml.get('connector'): - # if not is_exclude(exclude_list, (ansible, db_engine, python, connector)): - # matrix.append((ansible, db_engine, python, connector)) + matrix = [] + exclude_list = tests_matrix_yaml.get('exclude') + for ansible in tests_matrix_yaml.get('ansible'): + for db_engine_name in tests_matrix_yaml.get('db_engine_name'): + for db_engine_version in tests_matrix_yaml.get('db_engine_version'): + for python in tests_matrix_yaml.get('python'): + for connector_name in tests_matrix_yaml.get('connector_name'): + for connector_version in tests_matrix_yaml.get('connector_version'): + test_suite = { + 'ansible': ansible, + 'db_engine_name': db_engine_name, + 'db_engine_version': db_engine_version, + 'python': python, + 'connector_name': connector_name, + 'connector_version': connector_version + } + if not is_exclude(exclude_list, test_suite): + matrix.append(test_suite) - for tests in tests_matrix_yaml.get('include'): + for tests in matrix: a = tests.get('ansible') - d = tests.get('db_engine_version') + dn = tests.get('db_engine_name') + dv = tests.get('db_engine_version') p = tests.get('python') - c = tests.get('connector') - i = tests.get('docker_image') - make_cmd = f'make ansible="{a}" db_engine_version="{d}" python="{p}" connector="{c}" docker_image="{i}" test-integration' - print(f'Run tests for: Ansible: {a}, DB: {d}, Python: {p}, Connector: {c}, Docker image: {i}') + cn = tests.get('connector_name') + cv = tests.get('connector_version') + make_cmd = ( + f'make ' + f'ansible="{a}" ' + f'db_engine_name="{dn}" ' + f'db_engine_version="{dv}" ' + f'python="{p}" ' + f'connector_name="{cn}" ' + f'connector_version="{cv}" ' + f'test-integration' + ) + print(f'Run tests for: Ansible: {a}, DB: {dn} {dv}, Python: {p}, Connector: {cn} {cv}') os.system(make_cmd) # TODO, allow for CTRL+C to break the loop more easily # TODO, store the failures from this iteration diff --git a/test-containers/mariadb106-py310-mysqlclient211/Dockerfile b/test-containers/mariadb-py310-mysqlclient211/Dockerfile similarity index 100% rename from test-containers/mariadb106-py310-mysqlclient211/Dockerfile rename to test-containers/mariadb-py310-mysqlclient211/Dockerfile diff --git a/test-containers/mariadb106-py310-pymysql102/Dockerfile b/test-containers/mariadb-py310-pymysql102/Dockerfile similarity index 100% rename from test-containers/mariadb106-py310-pymysql102/Dockerfile rename to test-containers/mariadb-py310-pymysql102/Dockerfile diff --git a/test-containers/mariadb103-py38-mysqlclient201/Dockerfile b/test-containers/mariadb-py38-mysqlclient201/Dockerfile similarity index 100% rename from test-containers/mariadb103-py38-mysqlclient201/Dockerfile rename to test-containers/mariadb-py38-mysqlclient201/Dockerfile diff --git a/test-containers/mariadb103-py38-pymysql093/Dockerfile b/test-containers/mariadb-py38-pymysql093/Dockerfile similarity index 100% rename from test-containers/mariadb103-py38-pymysql093/Dockerfile rename to test-containers/mariadb-py38-pymysql093/Dockerfile diff --git a/test-containers/mariadb103-py39-mysqlclient203/Dockerfile b/test-containers/mariadb-py39-mysqlclient203/Dockerfile similarity index 100% rename from test-containers/mariadb103-py39-mysqlclient203/Dockerfile rename to test-containers/mariadb-py39-mysqlclient203/Dockerfile diff --git a/test-containers/mariadb103-py39-pymysql093/Dockerfile b/test-containers/mariadb-py39-pymysql093/Dockerfile similarity index 100% rename from test-containers/mariadb103-py39-pymysql093/Dockerfile rename to test-containers/mariadb-py39-pymysql093/Dockerfile diff --git a/test-containers/my80-py310-mysqlclient211/Dockerfile b/test-containers/mysql-py310-mysqlclient211/Dockerfile similarity index 100% rename from test-containers/my80-py310-mysqlclient211/Dockerfile rename to test-containers/mysql-py310-mysqlclient211/Dockerfile diff --git a/test-containers/my80-py310-pymysql102/Dockerfile b/test-containers/mysql-py310-pymysql102/Dockerfile similarity index 100% rename from test-containers/my80-py310-pymysql102/Dockerfile rename to test-containers/mysql-py310-pymysql102/Dockerfile diff --git a/test-containers/my80-py38-mysqlclient201/Dockerfile b/test-containers/mysql-py38-mysqlclient201/Dockerfile similarity index 100% rename from test-containers/my80-py38-mysqlclient201/Dockerfile rename to test-containers/mysql-py38-mysqlclient201/Dockerfile diff --git a/test-containers/my80-py38-pymysql093/Dockerfile b/test-containers/mysql-py38-pymysql093/Dockerfile similarity index 100% rename from test-containers/my80-py38-pymysql093/Dockerfile rename to test-containers/mysql-py38-pymysql093/Dockerfile diff --git a/test-containers/my80-py39-mysqlclient203/Dockerfile b/test-containers/mysql-py39-mysqlclient203/Dockerfile similarity index 100% rename from test-containers/my80-py39-mysqlclient203/Dockerfile rename to test-containers/mysql-py39-mysqlclient203/Dockerfile diff --git a/test-containers/my80-py39-pymysql093/Dockerfile b/test-containers/mysql-py39-pymysql093/Dockerfile similarity index 100% rename from test-containers/my80-py39-pymysql093/Dockerfile rename to test-containers/mysql-py39-pymysql093/Dockerfile diff --git a/tests/integration/targets/setup_controller/tasks/setvars.yml b/tests/integration/targets/setup_controller/tasks/setvars.yml index d74136d..3e070a9 100644 --- a/tests/integration/targets/setup_controller/tasks/setvars.yml +++ b/tests/integration/targets/setup_controller/tasks/setvars.yml @@ -8,12 +8,22 @@ - name: "{{ role_name }} | Setvars | Set Fact" ansible.builtin.set_fact: gateway_addr: "{{ ip_route_output.stdout }}" - connector_name_version: >- + connector_name_lookup: >- {{ lookup( 'file', - '/root/ansible_collections/community/mysql/tests/integration/connector' + '/root/ansible_collections/community/mysql/tests/integration/connector_name' ) }} - db_engine_version: >- + connector_version_lookup: >- + {{ lookup( + 'file', + '/root/ansible_collections/community/mysql/tests/integration/connector_version' + ) }} + db_engine_name_lookup: >- + {{ lookup( + 'file', + '/root/ansible_collections/community/mysql/tests/integration/db_engine_name' + ) }} + db_engine_version_lookup: >- {{ lookup( 'file', '/root/ansible_collections/community/mysql/tests/integration/db_engine_version' @@ -31,10 +41,10 @@ - name: "{{ role_name }} | Setvars | Set Fact using above facts" ansible.builtin.set_fact: - connector_name: "{{ connector_name_version.split('=')[0].strip() }}" - connector_version: "{{ connector_name_version.split('=')[2].strip() }}" - db_engine: "{{ db_engine_version.split(':')[0].strip() }}" - db_version: "{{ db_engine_version.split(':')[1].strip() }}" + connector_name: "{{ connector_name_lookup.strip() }}" + connector_version: "{{ connector_version_lookup.strip() }}" + db_engine: "{{ db_engine_name_lookup.strip() }}" + db_version: "{{ db_engine_version_lookup.strip() }}" python_version: "{{ python_version_lookup.strip() }}" test_ansible_version: >- {%- if ansible_version_lookup == 'devel' -%} From 754387c7e520effaf4d05421efe58160f45eff8d Mon Sep 17 00:00:00 2001 From: IBims1NicerTobi <54948543+IBims1NicerTobi@users.noreply.github.com> Date: Fri, 31 Mar 2023 13:27:48 +0200 Subject: [PATCH 139/217] Added formatting behaviour to documentation (#516) * Added formatting behaviour to documentation * Update plugins/modules/mysql_query.py Co-authored-by: Andrew Klychkov --------- Co-authored-by: Andrew Klychkov --- plugins/modules/mysql_query.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/modules/mysql_query.py b/plugins/modules/mysql_query.py index a3d7ce2..17fa62e 100644 --- a/plugins/modules/mysql_query.py +++ b/plugins/modules/mysql_query.py @@ -22,6 +22,9 @@ options: description: - SQL query to run. Multiple queries can be passed using YAML list syntax. - Must be a string or YAML list containing strings. + - If you use I(named_args) or I(positional_args) any C(%) will be interpreted + as a formatting character. All literal C(%) characters in the query should be + escaped as C(%%). - Note that if you use the C(IF EXISTS/IF NOT EXISTS) clauses in your query and C(mysqlclient) connector, the module will report that the state has been changed even if it has not. If it is important in your From 21e42b57777f803cd5b8c8725ca4eadc43bcbc59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20Inderm=C3=BChle?= Date: Fri, 31 Mar 2023 13:48:41 +0200 Subject: [PATCH 140/217] Add filter to prevent rebuild container on push on stable-(1|2) (#522) --- .github/workflows/docker-image-mariadb-py310-mysqlclient211.yml | 2 ++ .github/workflows/docker-image-mariadb-py310-pymysql102.yml | 2 ++ .github/workflows/docker-image-mariadb-py38-mysqlclient201.yml | 2 ++ .github/workflows/docker-image-mariadb-py38-pymysql093.yml | 2 ++ .github/workflows/docker-image-mariadb-py39-mysqlclient203.yml | 2 ++ .github/workflows/docker-image-mariadb-py39-pymysql093.yml | 2 ++ .github/workflows/docker-image-my57-py38-mysqlclient201.yml | 2 ++ .github/workflows/docker-image-my57-py38-pymysql0711.yml | 2 ++ .github/workflows/docker-image-my57-py38-pymysql093.yml | 2 ++ .github/workflows/docker-image-mysql-py310-mysqlclient211.yml | 2 ++ .github/workflows/docker-image-mysql-py310-pymysql102.yml | 2 ++ .github/workflows/docker-image-mysql-py38-mysqlclient201.yml | 2 ++ .github/workflows/docker-image-mysql-py38-pymysql093.yml | 2 ++ .github/workflows/docker-image-mysql-py39-mysqlclient203.yml | 2 ++ .github/workflows/docker-image-mysql-py39-pymysql093.yml | 2 ++ 15 files changed, 30 insertions(+) diff --git a/.github/workflows/docker-image-mariadb-py310-mysqlclient211.yml b/.github/workflows/docker-image-mariadb-py310-mysqlclient211.yml index a893d3b..be252b7 100644 --- a/.github/workflows/docker-image-mariadb-py310-mysqlclient211.yml +++ b/.github/workflows/docker-image-mariadb-py310-mysqlclient211.yml @@ -7,6 +7,8 @@ on: - 'test-containers/mariadb-py310-mysqlclient211/**' - '.github/workflows/docker-image-mariadb-py310-mysqlclient211.yml' - '.github/workflows/build-docker-image.yml' + branches-ignore: + - stable-* jobs: diff --git a/.github/workflows/docker-image-mariadb-py310-pymysql102.yml b/.github/workflows/docker-image-mariadb-py310-pymysql102.yml index dbd8a9d..90fec0e 100644 --- a/.github/workflows/docker-image-mariadb-py310-pymysql102.yml +++ b/.github/workflows/docker-image-mariadb-py310-pymysql102.yml @@ -7,6 +7,8 @@ on: - 'test-containers/mariadb-py310-pymysql102/**' - '.github/workflows/docker-image-mariadb-py310-pymysql102.yml' - '.github/workflows/build-docker-image.yml' + branches-ignore: + - stable-* jobs: diff --git a/.github/workflows/docker-image-mariadb-py38-mysqlclient201.yml b/.github/workflows/docker-image-mariadb-py38-mysqlclient201.yml index 59fcf00..c9c04f4 100644 --- a/.github/workflows/docker-image-mariadb-py38-mysqlclient201.yml +++ b/.github/workflows/docker-image-mariadb-py38-mysqlclient201.yml @@ -7,6 +7,8 @@ on: - 'test-containers/mariadb-py38-mysqlclient201/**' - '.github/workflows/docker-image-mariadb-py38-mysqlclient201.yml' - '.github/workflows/build-docker-image.yml' + branches-ignore: + - stable-* jobs: diff --git a/.github/workflows/docker-image-mariadb-py38-pymysql093.yml b/.github/workflows/docker-image-mariadb-py38-pymysql093.yml index 2602ebe..92d0a74 100644 --- a/.github/workflows/docker-image-mariadb-py38-pymysql093.yml +++ b/.github/workflows/docker-image-mariadb-py38-pymysql093.yml @@ -7,6 +7,8 @@ on: - 'test-containers/mariadb-py38-pymysql093/**' - '.github/workflows/docker-image-mariadb-py38-pymysql093.yml' - '.github/workflows/build-docker-image.yml' + branches-ignore: + - stable-* jobs: diff --git a/.github/workflows/docker-image-mariadb-py39-mysqlclient203.yml b/.github/workflows/docker-image-mariadb-py39-mysqlclient203.yml index 3f260e5..afad5af 100644 --- a/.github/workflows/docker-image-mariadb-py39-mysqlclient203.yml +++ b/.github/workflows/docker-image-mariadb-py39-mysqlclient203.yml @@ -7,6 +7,8 @@ on: - 'test-containers/mariadb-py39-mysqlclient203/**' - '.github/workflows/docker-image-mariadb-py39-mysqlclient203.yml' - '.github/workflows/build-docker-image.yml' + branches-ignore: + - stable-* jobs: diff --git a/.github/workflows/docker-image-mariadb-py39-pymysql093.yml b/.github/workflows/docker-image-mariadb-py39-pymysql093.yml index 77bb664..1aa5a04 100644 --- a/.github/workflows/docker-image-mariadb-py39-pymysql093.yml +++ b/.github/workflows/docker-image-mariadb-py39-pymysql093.yml @@ -7,6 +7,8 @@ on: - 'test-containers/mariadb-py39-pymysql093/**' - '.github/workflows/docker-image-mariadb-py39-pymysql093.yml' - '.github/workflows/build-docker-image.yml' + branches-ignore: + - stable-* jobs: diff --git a/.github/workflows/docker-image-my57-py38-mysqlclient201.yml b/.github/workflows/docker-image-my57-py38-mysqlclient201.yml index 2c18f63..7aaf7e3 100644 --- a/.github/workflows/docker-image-my57-py38-mysqlclient201.yml +++ b/.github/workflows/docker-image-my57-py38-mysqlclient201.yml @@ -7,6 +7,8 @@ on: - 'test-containers/my57-py38-mysqlclient201/**' - '.github/workflows/docker-image-my57-py38-mysqlclient201.yml' - '.github/workflows/build-docker-image.yml' + branches-ignore: + - stable-* jobs: diff --git a/.github/workflows/docker-image-my57-py38-pymysql0711.yml b/.github/workflows/docker-image-my57-py38-pymysql0711.yml index 1568d22..0bc2a9d 100644 --- a/.github/workflows/docker-image-my57-py38-pymysql0711.yml +++ b/.github/workflows/docker-image-my57-py38-pymysql0711.yml @@ -7,6 +7,8 @@ on: - 'test-containers/my57-py38-pymysql0711/**' - '.github/workflows/docker-image-my57-py38-pymysql0711.yml' - '.github/workflows/build-docker-image.yml' + branches-ignore: + - stable-* jobs: diff --git a/.github/workflows/docker-image-my57-py38-pymysql093.yml b/.github/workflows/docker-image-my57-py38-pymysql093.yml index 39bb583..462324b 100644 --- a/.github/workflows/docker-image-my57-py38-pymysql093.yml +++ b/.github/workflows/docker-image-my57-py38-pymysql093.yml @@ -7,6 +7,8 @@ on: - 'test-containers/my57-py38-pymysql093/**' - '.github/workflows/docker-image-my57-py38-pymysql093.yml' - '.github/workflows/build-docker-image.yml' + branches-ignore: + - stable-* jobs: diff --git a/.github/workflows/docker-image-mysql-py310-mysqlclient211.yml b/.github/workflows/docker-image-mysql-py310-mysqlclient211.yml index 70eea1c..307aea7 100644 --- a/.github/workflows/docker-image-mysql-py310-mysqlclient211.yml +++ b/.github/workflows/docker-image-mysql-py310-mysqlclient211.yml @@ -7,6 +7,8 @@ on: - 'test-containers/mysql-py310-mysqlclient211/**' - '.github/workflows/docker-image-mysql-py310-mysqlclient211.yml' - '.github/workflows/build-docker-image.yml' + branches-ignore: + - stable-* jobs: diff --git a/.github/workflows/docker-image-mysql-py310-pymysql102.yml b/.github/workflows/docker-image-mysql-py310-pymysql102.yml index bcf88fa..6f7bf3f 100644 --- a/.github/workflows/docker-image-mysql-py310-pymysql102.yml +++ b/.github/workflows/docker-image-mysql-py310-pymysql102.yml @@ -7,6 +7,8 @@ on: - 'test-containers/mysql-py310-pymysql102/**' - '.github/workflows/docker-image-mysql-py310-pymysql102.yml' - '.github/workflows/build-docker-image.yml' + branches-ignore: + - stable-* jobs: diff --git a/.github/workflows/docker-image-mysql-py38-mysqlclient201.yml b/.github/workflows/docker-image-mysql-py38-mysqlclient201.yml index 7fcfb60..e0da5df 100644 --- a/.github/workflows/docker-image-mysql-py38-mysqlclient201.yml +++ b/.github/workflows/docker-image-mysql-py38-mysqlclient201.yml @@ -7,6 +7,8 @@ on: - 'test-containers/mysql-py38-mysqlclient201/**' - '.github/workflows/docker-image-mysql-py38-mysqlclient201.yml' - '.github/workflows/build-docker-image.yml' + branches-ignore: + - stable-* jobs: diff --git a/.github/workflows/docker-image-mysql-py38-pymysql093.yml b/.github/workflows/docker-image-mysql-py38-pymysql093.yml index 5a43ab8..3cc1e0a 100644 --- a/.github/workflows/docker-image-mysql-py38-pymysql093.yml +++ b/.github/workflows/docker-image-mysql-py38-pymysql093.yml @@ -7,6 +7,8 @@ on: - 'test-containers/mysql-py38-pymysql093/**' - '.github/workflows/docker-image-mysql-py38-pymysql093.yml' - '.github/workflows/build-docker-image.yml' + branches-ignore: + - stable-* jobs: diff --git a/.github/workflows/docker-image-mysql-py39-mysqlclient203.yml b/.github/workflows/docker-image-mysql-py39-mysqlclient203.yml index 4486a6e..0a3a256 100644 --- a/.github/workflows/docker-image-mysql-py39-mysqlclient203.yml +++ b/.github/workflows/docker-image-mysql-py39-mysqlclient203.yml @@ -7,6 +7,8 @@ on: - 'test-containers/mysql-py39-mysqlclient203/**' - '.github/workflows/docker-image-mysql-py39-mysqlclient203.yml' - '.github/workflows/build-docker-image.yml' + branches-ignore: + - stable-* jobs: diff --git a/.github/workflows/docker-image-mysql-py39-pymysql093.yml b/.github/workflows/docker-image-mysql-py39-pymysql093.yml index d06dc74..b974420 100644 --- a/.github/workflows/docker-image-mysql-py39-pymysql093.yml +++ b/.github/workflows/docker-image-mysql-py39-pymysql093.yml @@ -7,6 +7,8 @@ on: - 'test-containers/mysql-py39-pymysql093/*' - '.github/workflows/docker-image-mysql-py39-pymysql093.yml' - '.github/workflows/build-docker-image.yml' + branches-ignore: + - stable-* jobs: From 526e674e6fb0f9acc91959fdf544b8fb5f3d4aa7 Mon Sep 17 00:00:00 2001 From: Maximilian Stinsky <26960620+mstinsky@users.noreply.github.com> Date: Fri, 7 Apr 2023 10:20:49 +0200 Subject: [PATCH 141/217] Add MAX_STATEMENT_TIME resource limit (#523) * Add MAX_STATEMENT_TIME to resource_limits * Move version check for resource_limits to implementations --- ...-add-max_statement_time_resource-limit.yml | 2 + .../implementations/mariadb/user.py | 6 + .../implementations/mysql/user.py | 6 + plugins/module_utils/user.py | 43 ++--- plugins/modules/mysql_user.py | 2 +- .../tasks/test_resource_limits.yml | 147 ++++++++++++++++++ 6 files changed, 177 insertions(+), 29 deletions(-) create mode 100644 changelogs/fragments/523-add-max_statement_time_resource-limit.yml diff --git a/changelogs/fragments/523-add-max_statement_time_resource-limit.yml b/changelogs/fragments/523-add-max_statement_time_resource-limit.yml new file mode 100644 index 0000000..b42d63c --- /dev/null +++ b/changelogs/fragments/523-add-max_statement_time_resource-limit.yml @@ -0,0 +1,2 @@ +minor_changes: + - mysql_user - add ``MAX_STATEMENT_TIME`` support for mariadb to the ``resource_limits`` argument (https://github.com/ansible-collections/community.mysql/issues/211). diff --git a/plugins/module_utils/implementations/mariadb/user.py b/plugins/module_utils/implementations/mariadb/user.py index b87ff69..c1d2b61 100644 --- a/plugins/module_utils/implementations/mariadb/user.py +++ b/plugins/module_utils/implementations/mariadb/user.py @@ -17,3 +17,9 @@ def use_old_user_mgmt(cursor): def supports_identified_by_password(cursor): return True + + +def server_supports_alter_user(cursor): + version = get_server_version(cursor) + + return LooseVersion(version) >= LooseVersion("10.2") diff --git a/plugins/module_utils/implementations/mysql/user.py b/plugins/module_utils/implementations/mysql/user.py index b141903..1bdad57 100644 --- a/plugins/module_utils/implementations/mysql/user.py +++ b/plugins/module_utils/implementations/mysql/user.py @@ -18,3 +18,9 @@ def use_old_user_mgmt(cursor): def supports_identified_by_password(cursor): version = get_server_version(cursor) return LooseVersion(version) < LooseVersion("8") + + +def server_supports_alter_user(cursor): + version = get_server_version(cursor) + + return LooseVersion(version) >= LooseVersion("5.6") diff --git a/plugins/module_utils/user.py b/plugins/module_utils/user.py index fc4c40e..a63ad89 100644 --- a/plugins/module_utils/user.py +++ b/plugins/module_utils/user.py @@ -753,33 +753,6 @@ def convert_priv_dict_to_str(priv): return '/'.join(priv_list) -# Alter user is supported since MySQL 5.6 and MariaDB 10.2.0 -def server_supports_alter_user(cursor): - """Check if the server supports ALTER USER statement or doesn't. - - Args: - cursor (cursor): DB driver cursor object. - - Returns: True if supports, False otherwise. - """ - cursor.execute("SELECT VERSION()") - version_str = cursor.fetchone()[0] - version = version_str.split('.') - - if 'mariadb' in version_str.lower(): - # MariaDB 10.2 and later - if int(version[0]) * 1000 + int(version[1]) >= 10002: - return True - else: - return False - else: - # MySQL 5.6 and later - if int(version[0]) * 1000 + int(version[1]) >= 5006: - return True - else: - return False - - def get_resource_limits(cursor, user, host): """Get user resource limits. @@ -808,6 +781,15 @@ def get_resource_limits(cursor, user, host): 'MAX_CONNECTIONS_PER_HOUR': res[2], 'MAX_USER_CONNECTIONS': res[3], } + + cursor.execute("SELECT VERSION()") + if 'mariadb' in cursor.fetchone()[0].lower(): + query = ('SELECT max_statement_time AS MAX_STATEMENT_TIME ' + 'FROM mysql.user WHERE User = %s AND Host = %s') + cursor.execute(query, (user, host)) + res_max_statement_time = cursor.fetchone() + current_limits['MAX_STATEMENT_TIME'] = res_max_statement_time[0] + return current_limits @@ -860,10 +842,15 @@ def limit_resources(module, cursor, user, host, resource_limits, check_mode): Returns: True, if changed, False otherwise. """ - if not server_supports_alter_user(cursor): + if not impl.server_supports_alter_user(cursor): module.fail_json(msg="The server version does not match the requirements " "for resource_limits parameter. See module's documentation.") + cursor.execute("SELECT VERSION()") + if 'mariadb' not in cursor.fetchone()[0].lower(): + if 'MAX_STATEMENT_TIME' in resource_limits: + module.fail_json(msg="MAX_STATEMENT_TIME resource limit is only supported by MariaDB.") + current_limits = get_resource_limits(cursor, user, host) needs_to_change = match_resource_limits(module, current_limits, resource_limits) diff --git a/plugins/modules/mysql_user.py b/plugins/modules/mysql_user.py index e1808c8..e87fe12 100644 --- a/plugins/modules/mysql_user.py +++ b/plugins/modules/mysql_user.py @@ -145,7 +145,7 @@ options: description: - Limit the user for certain server resources. Provided since MySQL 5.6 / MariaDB 10.2. - "Available options are C(MAX_QUERIES_PER_HOUR: num), C(MAX_UPDATES_PER_HOUR: num), - C(MAX_CONNECTIONS_PER_HOUR: num), C(MAX_USER_CONNECTIONS: num)." + C(MAX_CONNECTIONS_PER_HOUR: num), C(MAX_USER_CONNECTIONS: num), C(MAX_STATEMENT_TIME: num) (supported only for MariaDB since collection version 3.7.0)." - Used when I(state=present), ignored otherwise. type: dict version_added: '0.1.0' diff --git a/tests/integration/targets/test_mysql_user/tasks/test_resource_limits.yml b/tests/integration/targets/test_mysql_user/tasks/test_resource_limits.yml index 7c2b97b..a390a4e 100644 --- a/tests/integration/targets/test_mysql_user/tasks/test_resource_limits.yml +++ b/tests/integration/targets/test_mysql_user/tasks/test_resource_limits.yml @@ -129,4 +129,151 @@ that: - result.rowcount[0] == 1 + - name: Resource limits | Drop mysql user {{ user_name_1 }} if exists + community.mysql.mysql_user: + <<: *mysql_params + name: '{{ user_name_1 }}' + host_all: true + state: absent + + - name: Resource limits | Create mysql user {{ user_name_1 }} with MAX_STATEMENT_TIME in check_mode + community.mysql.mysql_user: + <<: *mysql_params + name: '{{ user_name_1 }}' + password: '{{ user_password_1 }}' + state: present + resource_limits: + MAX_QUERIES_PER_HOUR: 10 + MAX_STATEMENT_TIME: 1 + check_mode: true + register: result + ignore_errors: true + + - name: Resource limits | Assert that create user with MAX_STATEMENT_TIME is changed for mariadb + ansible.builtin.assert: + that: + - result is changed + when: db_engine == 'mariadb' + + - name: Resource limits | Assert that create user with MAX_STATEMENT_TIME is failed for mysql + ansible.builtin.assert: + that: + - result is failed + when: db_engine == 'mysql' + + - name: Resource limits | Create mysql user {{ user_name_1 }} with MAX_STATEMENT_TIME in actual mode + community.mysql.mysql_user: + <<: *mysql_params + name: '{{ user_name_1 }}' + password: '{{ user_password_1 }}' + state: present + resource_limits: + MAX_QUERIES_PER_HOUR: 10 + MAX_STATEMENT_TIME: 1 + register: result + ignore_errors: true + + - name: Resource limits | Assert that create user with MAX_STATEMENT_TIME is changed for MariaDB + ansible.builtin.assert: + that: + - result is changed + when: db_engine == 'mariadb' + + - name: Resource limits | Assert that create user with MAX_STATEMENT_TIME is failed for MySQL + ansible.builtin.assert: + that: + - result is failed + when: db_engine == 'mysql' + + - name: Resource limits | Retrieve user with MAX_STATEMENT_TIME + community.mysql.mysql_query: + <<: *mysql_params + query: > + SELECT User FROM mysql.user + WHERE User = '{{ user_name_1 }}' + AND Host = 'localhost' + AND max_questions = 10 + AND max_statement_time = 1 + register: result + when: db_engine == 'mariadb' + + - name: Resource limits | Assert that rowcount is 1 with MAX_STATEMENT_TIME + ansible.builtin.assert: + that: + - result.rowcount[0] == 1 + when: db_engine == 'mariadb' + + - name: Resource limits | Try to set the same limits with MAX_STATEMENT_TIME again in check mode + community.mysql.mysql_user: + <<: *mysql_params + name: '{{ user_name_1 }}' + password: '{{ user_password_1 }}' + state: present + resource_limits: + MAX_QUERIES_PER_HOUR: 10 + MAX_STATEMENT_TIME: 1 + check_mode: true + register: result + when: db_engine == 'mariadb' + + - name: Resource limits | Assert that set same limits with MAX_STATEMENT_TIME again is not changed + ansible.builtin.assert: + that: + - result is not changed + when: db_engine == 'mariadb' + + - name: Resource limits | Try to set the same limits with MAX_STATEMENT_TIME again in actual mode + community.mysql.mysql_user: + <<: *mysql_params + name: '{{ user_name_1 }}' + password: '{{ user_password_1 }}' + state: present + resource_limits: + MAX_QUERIES_PER_HOUR: 10 + MAX_STATEMENT_TIME: 1 + register: result + when: db_engine == 'mariadb' + + - name: Resource limits | Assert that set same limits with MAX_STATEMENT_TIME again in actual mode is not changed + ansible.builtin.assert: + that: + - result is not changed + when: db_engine == 'mariadb' + + - name: Resource limits | Change limits with MAX_STATEMENT_TIME + community.mysql.mysql_user: + <<: *mysql_params + name: '{{ user_name_1 }}' + password: '{{ user_password_1 }}' + state: present + resource_limits: + MAX_QUERIES_PER_HOUR: 5 + MAX_STATEMENT_TIME: 2 + register: result + when: db_engine == 'mariadb' + + - name: Resource limits | Assert limits with MAX_STATEMENT_TIME changed + ansible.builtin.assert: + that: + - result is changed + when: db_engine == 'mariadb' + + - name: Resource limits | Get user limits with MAX_STATEMENT_TIME + community.mysql.mysql_query: + <<: *mysql_params + query: > + SELECT User FROM mysql.user + WHERE User = '{{ user_name_1 }}' + AND Host = 'localhost' + AND max_questions = 5 + AND max_statement_time = 2 + register: result + when: db_engine == 'mariadb' + + - name: Resource limits | Assert limit with MAX_STATEMENT_TIME row count + ansible.builtin.assert: + that: + - result.rowcount[0] == 1 + when: db_engine == 'mariadb' + when: (ansible_distribution == 'Ubuntu' and ansible_distribution_major_version >= '18') or (ansible_distribution == 'CentOS' and ansible_distribution_major_version >= '8') From 9124b1f575b6d578060d69682fd84afb1779178a Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Fri, 7 Apr 2023 13:36:27 +0200 Subject: [PATCH 142/217] Copy ignore.txt for the devel branch (#529) --- tests/sanity/ignore-2.16.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 tests/sanity/ignore-2.16.txt diff --git a/tests/sanity/ignore-2.16.txt b/tests/sanity/ignore-2.16.txt new file mode 100644 index 0000000..da0354c --- /dev/null +++ b/tests/sanity/ignore-2.16.txt @@ -0,0 +1,10 @@ +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_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_variables.py validate-modules:doc-required-mismatch +plugins/module_utils/mysql.py pylint:unused-import +plugins/module_utils/version.py pylint:unused-import From 426084a131a280133434aee8f6cfb0d63b24a500 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20Inderm=C3=BChle?= Date: Tue, 25 Apr 2023 16:19:41 +0200 Subject: [PATCH 143/217] Fix the Makefile for integration tests not using the Python Venv (#532) * Fix venv not being used by keeping the same shell Also fix "-set -x" command not found. * Fix missing option in the command usage documentation * Document connector-version relationship * Fix missing option in the command usage documentation * Rephrase commands descriptions * Document that you need to kill the ansible-test container yourself --- Makefile | 20 ++++++++++++++------ TESTING.md | 22 +++++++++++----------- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/Makefile b/Makefile index dc6d6d0..7ea0785 100644 --- a/Makefile +++ b/Makefile @@ -86,12 +86,20 @@ test-integration: while ! podman healthcheck run primary && [[ "$$SECONDS" -lt 120 ]]; do sleep 1; done mkdir -p .venv/$(ansible) python$(local_python_version) -m venv .venv/$(ansible) - source .venv/$(ansible)/bin/activate - python$(local_python_version) -m ensurepip - python$(local_python_version) -m pip install --disable-pip-version-check https://github.com/ansible/ansible/archive/$(ansible).tar.gz - -set -x; ansible-test integration $(target) -v --color --coverage --diff \ - --docker ghcr.io/ansible-collections/community.mysql/test-container-$(db_client)-py$(python_version_flat)-$(connector_name)$(connector_version_flat):latest \ - --docker-network podman $(_continue_on_errors) $(_keep_containers_alive) --python $(python); set +x + + # Start venv (use `; \` to keep the same shell) + source .venv/$(ansible)/bin/activate; \ + python$(local_python_version) -m ensurepip; \ + python$(local_python_version) -m pip install --disable-pip-version-check \ + https://github.com/ansible/ansible/archive/$(ansible).tar.gz; \ + set -x; \ + ansible-test integration $(target) -v --color --coverage --diff \ + --docker ghcr.io/ansible-collections/community.mysql/test-container\ + -$(db_client)-py$(python_version_flat)-$(connector_name)$(connector_version_flat):latest \ + --docker-network podman $(_continue_on_errors) $(_keep_containers_alive) --python $(python); \ + set +x + # End of venv + rm tests/integration/db_engine_name rm tests/integration/db_engine_version rm tests/integration/connector_name diff --git a/TESTING.md b/TESTING.md index a24193a..37bbaf6 100644 --- a/TESTING.md +++ b/TESTING.md @@ -77,17 +77,17 @@ The Makefile accept the following options - Choices: - "pymysql - "mysqlclient" - - Description: The python package of the connector to use. This value is used to filter tests meant for other connectors. + - Description: The python package of the connector to use. In addition to selecting the test container, this value is also used for tests filtering: `when: connector_name == 'pymysql'`. - `connector_version` - Mandatory: true - Choices: - - "0.7.11" <- Only for MySQL 5.7 - - "0.9.3" - - "1.0.2" <- Not working, need fix - - "2.0.1" - - "2.0.3" - - "2.1.1" + - "0.7.11" <- pymysql (Only for MySQL 5.7) + - "0.9.3" <- pymysql + - "1.0.2" <- pymysql (Not working, need fix) + - "2.0.1" <- mysqlclient + - "2.0.3" <- mysqlclient + - "2.1.1" <- mysqlclient - Description: The version of the python package of the connector to use. This value is used to filter tests meant for other connectors. - `python` @@ -113,11 +113,11 @@ The Makefile accept the following options - `keep_containers_alive` - Mandatory: false - Description: This option keeps all tree databases containers and the ansible-test container alive at the end of tests or in case of failure. This is useful to enter one of the containers with `podman exec -it bash` for debugging. Rerunning the -test will recreate those containers so no need to kill it. Add any value to activate this option: `keep_containers_alive=1` +tests will overwrite the 3 databases containers so no need to kill them in advance. But nothing will kill the ansible-test container. You must do that using `podman stop` and `podman rm`. Add any value to activate this option: `keep_containers_alive=1` - `continue_on_errors` - Mandatory: false - - Description: Tells ansible-test to retry on errors and also continue on errors. This is the way the GitHub Action's workflow runs the tests. This can be use to catch all errors in a single run, but you'll need to scroll up to find them. Add any value to activate this option: `continue_on_errors=1` + - Description: Tells ansible-test to retry on errors and also continue on errors. This is the way the GitHub Action's workflow runs the tests. This can be used to catch all errors in a single run, but you'll need to scroll up to find them. Add any value to activate this option: `continue_on_errors=1` #### Makefile usage examples: @@ -127,11 +127,11 @@ test will recreate those containers so no need to kill it. Add any value to acti make ansible="stable-2.12" db_engine_name="mysql" db_engine_version="5.7.40" python="3.8" connector_name="pymysql" connector_version="0.7.11" # A single target -make ansible="stable-2.14" db_engine_name="mysql" db_engine_version="5.7.40" python="3.8" connector_name="pymysql" connector_version="0.7.11" +make ansible="stable-2.14" db_engine_name="mysql" db_engine_version="5.7.40" python="3.8" connector_name="pymysql" connector_version="0.7.11" target="test_mysql_info" # Keep databases and ansible tests containers alives # A single target and continue on errors -make ansible="stable-2.14" db_engine_name="mysql" db_engine_version="8.0.31" python="3.9" connector_name="mysqlclient" connector_version="2.0.3" +make ansible="stable-2.14" db_engine_name="mysql" db_engine_version="8.0.31" python="3.9" connector_name="mysqlclient" connector_version="2.0.3" target="test_mysql_query" keep_containers_alive=1 continue_on_errors=1 # If your system has an usupported version of Python: make local_python_version="3.8" ansible="stable-2.14" db_engine_name="mariadb" db_engine_version="10.6.11" python="3.9" connector_name="pymysql" connector_version="0.9.3" From 30a2015f6cef3863e8104402485edc362ffb96df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20Inderm=C3=BChle?= Date: Thu, 4 May 2023 11:14:58 +0200 Subject: [PATCH 144/217] feat: Add support for the connector pymysql 1.0.2 (#533) * Document connector-version relationship * Fix missing option in the command usage documentation * Rephrase commands descriptions * Document that pymysql 0.10.0 disabled its warnings * Disable tests for pymysql newer than 0.10.0 because the behavior changed * Enable integration tests for pymysql 1.0.2 * Add exclusion to avoid requesting nonexistent test containers * Cut comments about PyMySQL 1.0.2 need to be fixed * docs: explain PyMySQL 0.10.0+ returns changed when using IF EXISTS --- .github/workflows/ansible-test-plugins.yml | 16 +++++++--------- README.md | 2 +- TESTING.md | 2 +- plugins/modules/mysql_query.py | 9 +++++---- .../tasks/mysql_query_initial.yml | 14 ++++++++++---- .../tasks/mysql_replication_initial.yml | 6 ++++-- 6 files changed, 28 insertions(+), 21 deletions(-) diff --git a/.github/workflows/ansible-test-plugins.yml b/.github/workflows/ansible-test-plugins.yml index b961550..6533f94 100644 --- a/.github/workflows/ansible-test-plugins.yml +++ b/.github/workflows/ansible-test-plugins.yml @@ -64,15 +64,7 @@ jobs: connector_version: - 0.7.11 - 0.9.3 - # Before we can activate test with pymysql 1.0.2 we should debug the - # following plugins: - # - # mysql_query: - # test "Assert that create table IF NOT EXISTS is not changed with pymysql" failed - # - # mysql_replication: - # test "Assert that startreplica is not changed" failed - # - 1.0.2 + - 1.0.2 - 2.0.1 - 2.0.3 - 2.1.1 @@ -146,6 +138,9 @@ jobs: - db_engine_version: 10.6.11 python: '3.9' + - python: '3.8' + connector_version: 1.0.2 + - python: '3.8' connector_version: 2.0.3 @@ -155,6 +150,9 @@ jobs: - python: '3.9' connector_version: 0.7.11 + - python: '3.9' + connector_version: 1.0.2 + - python: '3.9' connector_version: 2.0.1 diff --git a/README.md b/README.md index 07c3214..79110d2 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ For MariaDB, only Long Term releases are tested. - pymysql 0.7.11 (Only tested with MySQL 5.7) - pymysql 0.9.3 -- pymysql 1.0.2 (only collection version >= ???) !!! Unsuported until future release !!! +- pymysql 1.0.2 (only collection version >= 3.6.1) - mysqlclient 2.0.1 - mysqlclient 2.0.3 (only collection version >= 3.5.2) - mysqlclient 2.1.1 (only collection version >= 3.5.2) diff --git a/TESTING.md b/TESTING.md index 37bbaf6..7bbafc3 100644 --- a/TESTING.md +++ b/TESTING.md @@ -84,7 +84,7 @@ The Makefile accept the following options - Choices: - "0.7.11" <- pymysql (Only for MySQL 5.7) - "0.9.3" <- pymysql - - "1.0.2" <- pymysql (Not working, need fix) + - "1.0.2" <- pymysql - "2.0.1" <- mysqlclient - "2.0.3" <- mysqlclient - "2.1.1" <- mysqlclient diff --git a/plugins/modules/mysql_query.py b/plugins/modules/mysql_query.py index 17fa62e..12d5a56 100644 --- a/plugins/modules/mysql_query.py +++ b/plugins/modules/mysql_query.py @@ -26,9 +26,9 @@ options: as a formatting character. All literal C(%) characters in the query should be escaped as C(%%). - Note that if you use the C(IF EXISTS/IF NOT EXISTS) clauses in your query - and C(mysqlclient) connector, the module will report that - the state has been changed even if it has not. If it is important in your - workflow, use the C(PyMySQL) connector instead. + and C(mysqlclient) or C(PyMySQL 0.10.0+) connectors, the module will report + that the state has been changed even if it has not. If it is important in your + workflow, use the C(PyMySQL 0.9.3) connector instead. type: raw required: true positional_args: @@ -222,7 +222,8 @@ def main(): # When something is run with IF NOT EXISTS # and there's "already exists" MySQL warning, # set the flag as True. - # PyMySQL throws the warning, mysqlclinet does NOT. + # PyMySQL < 0.10.0 throws the warning, mysqlclient + # and PyMySQL 0.10.0+ does NOT. already_exists = True except Exception as e: diff --git a/tests/integration/targets/test_mysql_query/tasks/mysql_query_initial.yml b/tests/integration/targets/test_mysql_query/tasks/mysql_query_initial.yml index d97c554..82665af 100644 --- a/tests/integration/targets/test_mysql_query/tasks/mysql_query_initial.yml +++ b/tests/integration/targets/test_mysql_query/tasks/mysql_query_initial.yml @@ -363,21 +363,27 @@ - name: Assert that create table IF NOT EXISTS is not changed with pymysql assert: that: - # PyMySQL driver throws a warning, so the following is correct + # PyMySQL driver throws a warning for version before 0.10.0 - result is not changed when: - connector_name == 'pymysql' + - connector_version is version('0.10.0', '<') # Issue https://github.com/ansible-collections/community.mysql/issues/268 - name: Assert that create table IF NOT EXISTS is changed with mysqlclient assert: that: - # Mysqlclient 2.0.1, driver throws nothing with mysql, so it's - # impossible to figure out if the state was changed or not. + # Mysqlclient 2.0.1 and pymysql 0.10.0+ drivers throws no warning, + # so it's impossible to figure out if the state was changed or not. # We assume that it was for DDL queries by default in the code - result is changed when: - - connector_name == 'mysqlclient' + - > + connector_name == 'mysqlclient' + or ( + connector_name == 'pymysql' + and connector_version is version('0.10.0', '>') + ) - name: Drop db {{ test_db }} mysql_query: 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 1dd4c88..ca7301c 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 @@ -247,13 +247,14 @@ fail_on_error: true register: result - # mysqlclient 2.0.1 always return "changed" + # mysqlclient 2.0.1 and pymysql 0.10.0+ always return "changed" - name: Assert that startreplica is not changed assert: that: - result is not changed when: - connector_name == 'pymysql' + - connector_version is version('0.10.0', '<') # Test stopreplica mode: - name: Stop replica @@ -274,7 +275,7 @@ timeout: 2 # Test stopreplica mode: - # mysqlclient 2.0.1 always return "changed" + # mysqlclient 2.0.1 and pymysql 0.10.0+ always return "changed" - name: Stop replica that is no longer running mysql_replication: <<: *mysql_params @@ -289,6 +290,7 @@ - result is not changed when: - connector_name == 'pymysql' + - connector_version is version('0.10.0', '<') # master / slave related choices were removed in 3.0.0 # https://github.com/ansible-collections/community.mysql/pull/252 From 04e197fe5555ea1a1343d9777474a64b3fca87c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20Inderm=C3=BChle?= Date: Fri, 5 May 2023 13:32:40 +0200 Subject: [PATCH 145/217] Release 3.7.0 commit (#544) --- CHANGELOG.rst | 16 ++++++++++++++++ changelogs/changelog.yaml | 17 +++++++++++++++++ ...-change_deprecated_connection_parameters.yml | 2 -- .../490_refactor_integration_tests.yml | 6 ------ ...23-add-max_statement_time_resource-limit.yml | 2 -- galaxy.yml | 3 ++- 6 files changed, 35 insertions(+), 11 deletions(-) delete mode 100644 changelogs/fragments/177-change_deprecated_connection_parameters.yml delete mode 100644 changelogs/fragments/490_refactor_integration_tests.yml delete mode 100644 changelogs/fragments/523-add-max_statement_time_resource-limit.yml diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 720ea41..95fef3d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,22 @@ Community MySQL Collection Release Notes This changelog describes changes after version 2.0.0. +v3.7.0 +====== + +Release Summary +--------------- + +This is the minor release of the ``community.mysql`` collection. +This changelog contains all changes to the modules and plugins in this collection +that have been made after the previous release. + +Minor Changes +------------- + +- mysql module utils - change deprecated connection parameters ``passwd`` and ``db`` to ``password`` and ``database`` (https://github.com/ansible-collections/community.mysql/pull/177). +- mysql_user - add ``MAX_STATEMENT_TIME`` support for mariadb to the ``resource_limits`` argument (https://github.com/ansible-collections/community.mysql/issues/211). + v3.6.0 ====== diff --git a/changelogs/changelog.yaml b/changelogs/changelog.yaml index e272941..def5b73 100644 --- a/changelogs/changelog.yaml +++ b/changelogs/changelog.yaml @@ -300,3 +300,20 @@ releases: - 503-fix-revoke-grant-only.yml - mysql_variables_allow_uppercase_identifiers.yml release_date: '2023-02-08' + 3.7.0: + changes: + minor_changes: + - mysql module utils - change deprecated connection parameters ``passwd`` and + ``db`` to ``password`` and ``database`` (https://github.com/ansible-collections/community.mysql/pull/177). + - mysql_user - add ``MAX_STATEMENT_TIME`` support for mariadb to the ``resource_limits`` + argument (https://github.com/ansible-collections/community.mysql/issues/211). + release_summary: 'This is the minor release of the ``community.mysql`` collection. + + This changelog contains all changes to the modules and plugins in this collection + + that have been made after the previous release.' + fragments: + - 3.7.0.yml + - 177-change_deprecated_connection_parameters.yml + - 523-add-max_statement_time_resource-limit.yml + release_date: '2023-05-05' diff --git a/changelogs/fragments/177-change_deprecated_connection_parameters.yml b/changelogs/fragments/177-change_deprecated_connection_parameters.yml deleted file mode 100644 index 3c9e088..0000000 --- a/changelogs/fragments/177-change_deprecated_connection_parameters.yml +++ /dev/null @@ -1,2 +0,0 @@ -minor_changes: -- mysql module utils - change deprecated connection parameters ``passwd`` and ``db`` to ``password`` and ``database`` (https://github.com/ansible-collections/community.mysql/pull/177). \ No newline at end of file diff --git a/changelogs/fragments/490_refactor_integration_tests.yml b/changelogs/fragments/490_refactor_integration_tests.yml deleted file mode 100644 index 0762adf..0000000 --- a/changelogs/fragments/490_refactor_integration_tests.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -minor_changes: - - Integration tests - Add more versions of MariaDB - - Integration tests - Carefully verify every component of the tests in the new target 'setup_controller' to ensure expected versions are correct Python, Ansible, connector and MySQL/MariaDB. - - Integration tests - Add tools to test locally the same as on GHA by using same containers and virtualenv. Custom test containers are published in ghcr.io by this repo's workflows. MySQL/MariaDB are official Docker Hub images. - - Integration tests - New name for many tasks to makes it easier to find failing tests. Rename duplicates. Add name for tasks which doesn't had one, refactor some tests files to better group tests by subject, ... diff --git a/changelogs/fragments/523-add-max_statement_time_resource-limit.yml b/changelogs/fragments/523-add-max_statement_time_resource-limit.yml deleted file mode 100644 index b42d63c..0000000 --- a/changelogs/fragments/523-add-max_statement_time_resource-limit.yml +++ /dev/null @@ -1,2 +0,0 @@ -minor_changes: - - mysql_user - add ``MAX_STATEMENT_TIME`` support for mariadb to the ``resource_limits`` argument (https://github.com/ansible-collections/community.mysql/issues/211). diff --git a/galaxy.yml b/galaxy.yml index bb7e2be..6c1df2b 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -1,6 +1,7 @@ +--- namespace: community name: mysql -version: 3.6.0 +version: 3.7.0 readme: README.md authors: - Ansible community From bd90ce7cc63b796c0bc9c1d035e7de25d2696300 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Wed, 10 May 2023 12:54:25 +0200 Subject: [PATCH 146/217] MAINTAINERS: add new maintainer (#548) --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS b/MAINTAINERS index 597aa6c..2228e00 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1,3 +1,4 @@ +betanummeric bmalynovytch Jorge-Rodriguez rsicart From b03c9aac57629fc4c0420581a1191555a124ee83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20Inderm=C3=BChle?= Date: Wed, 10 May 2023 13:10:20 +0200 Subject: [PATCH 147/217] Document the Releases Support Timeline (#543) * docs: add releases support timeline * docs: clarify when the 2 years of support starts Co-authored-by: Andrew Klychkov * docs: fix support status of the current branch Co-authored-by: Andrew Klychkov * docs: fix date to end of support for branch 2.x.y * fix README.md --------- Co-authored-by: Andrew Klychkov Co-authored-by: betanummeric <40263343+betanummeric@users.noreply.github.com> --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index 79110d2..5cb2271 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,19 @@ Every voice is important and every idea is valuable. If you have something on yo - [mysql_user](https://docs.ansible.com/ansible/latest/collections/community/mysql/mysql_user_module.html) - [mysql_variables](https://docs.ansible.com/ansible/latest/collections/community/mysql/mysql_variables_module.html) + +## Releases Support Timeline + +It has been [decided](https://github.com/ansible-collections/community.mysql/discussions/537) to maintain each major release (1.x.y, 2.x.y, ...) for two years after the next major version is released. + +Here is the table for the support timeline: + +- 1.x.y: released 2020-08-17, EOL +- 2.x.y: released 2021-04-15, supported until 2023-12-01 +- 3.x.y: released 2021-12-01, current +- 4.x.y: To be released + + ## Tested with ### ansible-core From 7f7b2f76a663be0fc6487d7ebac09ab51e6f0168 Mon Sep 17 00:00:00 2001 From: betanummeric <40263343+betanummeric@users.noreply.github.com> Date: Thu, 18 May 2023 09:28:34 +0200 Subject: [PATCH 148/217] fix connection arguments mysql driver compatability (#551) * only use the "database" connection argument with driver versions where "db" is deprecated/removed * connection arguments: fix KeyError * connection arguments: fix KeyError * connection arguments: use 'passwd' instead of 'password' with older drivers * add changelog fragment * refactoring: use "get_connector_name" in "mysql_connect" --------- Co-authored-by: Felix Hamme --- ...ection_arguments_driver_compatability.yaml | 2 ++ plugins/module_utils/mysql.py | 20 +++++++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 changelogs/fragments/551-fix_connection_arguments_driver_compatability.yaml diff --git a/changelogs/fragments/551-fix_connection_arguments_driver_compatability.yaml b/changelogs/fragments/551-fix_connection_arguments_driver_compatability.yaml new file mode 100644 index 0000000..be18f56 --- /dev/null +++ b/changelogs/fragments/551-fix_connection_arguments_driver_compatability.yaml @@ -0,0 +1,2 @@ +bugfixes: + - mysql module utils - use the connection arguments ``db`` instead of ``database`` and ``passwd`` instead of ``password`` when running with older mysql drivers (MySQLdb < 2.1.0 or PyMySQL < 1.0.0) (https://github.com/ansible-collections/community.mysql/pull/551). diff --git a/plugins/module_utils/mysql.py b/plugins/module_utils/mysql.py index 6aeebe5..713aba8 100644 --- a/plugins/module_utils/mysql.py +++ b/plugins/module_utils/mysql.py @@ -134,18 +134,34 @@ def mysql_connect(module, login_user=None, login_password=None, config_file='', if connect_timeout is not None: config['connect_timeout'] = connect_timeout if check_hostname is not None: - if mysql_driver.__name__ == "pymysql": + if get_connector_name(mysql_driver) == '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': + if get_connector_name(mysql_driver) == 'pymysql': # In case of PyMySQL driver: + if mysql_driver.version_info[0] < 1: + # for PyMySQL < 1.0.0, use 'db' instead of 'database' and 'passwd' instead of 'password' + if 'database' in config: + config['db'] = config['database'] + del config['database'] + if 'password' in config: + config['passwd'] = config['password'] + del config['password'] db_connection = mysql_driver.connect(autocommit=autocommit, **config) else: # In case of MySQLdb driver + if mysql_driver.version_info[0] < 2 and mysql_driver.version_info[1] < 1: + # for MySQLdb < 2.1.0, use 'db' instead of 'database' and 'passwd' instead of 'password' + if 'database' in config: + config['db'] = config['database'] + del config['database'] + if 'password' in config: + config['passwd'] = config['password'] + del config['password'] db_connection = mysql_driver.connect(**config) if autocommit: db_connection.autocommit(True) From bff05ce8ddb99f53270ad11e753c153df604adb5 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Mon, 22 May 2023 09:34:59 +0200 Subject: [PATCH 149/217] Release 3.7.1 commit (#552) --- CHANGELOG.rst | 15 +++++++++++++++ changelogs/changelog.yaml | 17 ++++++++++++++++- ...nnection_arguments_driver_compatability.yaml | 2 -- galaxy.yml | 2 +- 4 files changed, 32 insertions(+), 4 deletions(-) delete mode 100644 changelogs/fragments/551-fix_connection_arguments_driver_compatability.yaml diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 95fef3d..d381f5c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,21 @@ Community MySQL Collection Release Notes This changelog describes changes after version 2.0.0. +v3.7.1 +====== + +Release Summary +--------------- + +This is a patch release of the community.mysql collection. +This changelog contains all changes to the modules and plugins in this collection +that have been made after the previous release. + +Bugfixes +-------- + +- mysql module utils - use the connection arguments ``db`` instead of ``database`` and ``passwd`` instead of ``password`` when running with older mysql drivers (MySQLdb < 2.1.0 or PyMySQL < 1.0.0) (https://github.com/ansible-collections/community.mysql/pull/551). + v3.7.0 ====== diff --git a/changelogs/changelog.yaml b/changelogs/changelog.yaml index def5b73..196a6bd 100644 --- a/changelogs/changelog.yaml +++ b/changelogs/changelog.yaml @@ -313,7 +313,22 @@ releases: that have been made after the previous release.' fragments: - - 3.7.0.yml - 177-change_deprecated_connection_parameters.yml + - 3.7.0.yml - 523-add-max_statement_time_resource-limit.yml release_date: '2023-05-05' + 3.7.1: + changes: + bugfixes: + - mysql module utils - use the connection arguments ``db`` instead of ``database`` + and ``passwd`` instead of ``password`` when running with older mysql drivers + (MySQLdb < 2.1.0 or PyMySQL < 1.0.0) (https://github.com/ansible-collections/community.mysql/pull/551). + release_summary: 'This is a patch release of the community.mysql collection. + + This changelog contains all changes to the modules and plugins in this collection + + that have been made after the previous release.' + fragments: + - 3.7.1.yml + - 551-fix_connection_arguments_driver_compatability.yaml + release_date: '2023-05-22' diff --git a/changelogs/fragments/551-fix_connection_arguments_driver_compatability.yaml b/changelogs/fragments/551-fix_connection_arguments_driver_compatability.yaml deleted file mode 100644 index be18f56..0000000 --- a/changelogs/fragments/551-fix_connection_arguments_driver_compatability.yaml +++ /dev/null @@ -1,2 +0,0 @@ -bugfixes: - - mysql module utils - use the connection arguments ``db`` instead of ``database`` and ``passwd`` instead of ``password`` when running with older mysql drivers (MySQLdb < 2.1.0 or PyMySQL < 1.0.0) (https://github.com/ansible-collections/community.mysql/pull/551). diff --git a/galaxy.yml b/galaxy.yml index 6c1df2b..f725615 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -1,7 +1,7 @@ --- namespace: community name: mysql -version: 3.7.0 +version: 3.7.1 readme: README.md authors: - Ansible community From b6ad472c7805b390c63755dedeb9d557a62392d5 Mon Sep 17 00:00:00 2001 From: betanummeric <40263343+betanummeric@users.noreply.github.com> Date: Tue, 23 May 2023 15:32:21 +0200 Subject: [PATCH 150/217] fix connection arguments for MySQLdb <2.0 !=1.0 (#553) * fix connection arguments for MySQLdb <2.0 !=1.0 * add changelog fragment --------- Co-authored-by: Felix Hamme --- .../553_fix_connection_arguemnts_for_old_mysqldb_driver.yaml | 2 ++ plugins/module_utils/mysql.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 changelogs/fragments/553_fix_connection_arguemnts_for_old_mysqldb_driver.yaml diff --git a/changelogs/fragments/553_fix_connection_arguemnts_for_old_mysqldb_driver.yaml b/changelogs/fragments/553_fix_connection_arguemnts_for_old_mysqldb_driver.yaml new file mode 100644 index 0000000..d0f5316 --- /dev/null +++ b/changelogs/fragments/553_fix_connection_arguemnts_for_old_mysqldb_driver.yaml @@ -0,0 +1,2 @@ +bugfixes: + - mysql module utils - use the connection arguments ``db`` instead of ``database`` and ``passwd`` instead of ``password`` when running with MySQLdb < 2.0.0 (https://github.com/ansible-collections/community.mysql/pull/553). diff --git a/plugins/module_utils/mysql.py b/plugins/module_utils/mysql.py index 713aba8..b95d20d 100644 --- a/plugins/module_utils/mysql.py +++ b/plugins/module_utils/mysql.py @@ -154,7 +154,7 @@ def mysql_connect(module, login_user=None, login_password=None, config_file='', db_connection = mysql_driver.connect(autocommit=autocommit, **config) else: # In case of MySQLdb driver - if mysql_driver.version_info[0] < 2 and mysql_driver.version_info[1] < 1: + if mysql_driver.version_info[0] < 2 or (mysql_driver.version_info[0] == 2 and mysql_driver.version_info[1] < 1): # for MySQLdb < 2.1.0, use 'db' instead of 'database' and 'passwd' instead of 'password' if 'database' in config: config['db'] = config['database'] From 2fcfb103f60d3a21c9bba44a25a9249325b4c148 Mon Sep 17 00:00:00 2001 From: betanummeric <40263343+betanummeric@users.noreply.github.com> Date: Wed, 24 May 2023 10:00:47 +0200 Subject: [PATCH 151/217] fix tests (`include` deprecation) (#554) * tests: change deprecated "include" to "include_tasks" * tests: fix syntax --------- Co-authored-by: Felix Hamme --- .../targets/test_mysql_info/tasks/main.yml | 2 +- .../targets/test_mysql_query/tasks/main.yml | 2 +- .../test_mysql_replication/tasks/main.yml | 4 +- .../targets/test_mysql_role/tasks/main.yml | 8 +- .../test_mysql_user/tasks/issue-265.yml | 18 ++- .../test_mysql_user/tasks/issue-29511.yaml | 4 +- .../targets/test_mysql_user/tasks/main.yml | 100 ++++++++++----- .../tasks/test_idempotency.yml | 10 +- .../tasks/test_priv_append.yml | 4 +- .../test_mysql_user/tasks/test_priv_dict.yml | 4 +- .../tasks/test_priv_subtract.yml | 4 +- .../test_mysql_user/tasks/test_privs.yml | 16 ++- .../tasks/test_revoke_only_grant.yml | 8 +- .../tasks/test_tls_requirements.yml | 28 +++-- .../test_user_grants_with_roles_applied.yml | 4 +- .../tasks/test_user_password.yml | 46 +++++-- .../tasks/test_user_plugin_auth.yml | 74 ++++++++--- .../test_mysql_variables/tasks/main.yml | 2 +- .../tasks/mysql_variables.yml | 115 +++++++++++++++--- 19 files changed, 352 insertions(+), 101 deletions(-) diff --git a/tests/integration/targets/test_mysql_info/tasks/main.yml b/tests/integration/targets/test_mysql_info/tasks/main.yml index a01f915..be367f0 100644 --- a/tests/integration/targets/test_mysql_info/tasks/main.yml +++ b/tests/integration/targets/test_mysql_info/tasks/main.yml @@ -196,7 +196,7 @@ name: acme state: absent - - include: issue-28.yml + - include_tasks: issue-28.yml # https://github.com/ansible-collections/community.mysql/issues/204 - name: Create database containing only views diff --git a/tests/integration/targets/test_mysql_query/tasks/main.yml b/tests/integration/targets/test_mysql_query/tasks/main.yml index 6d17308..ffb54e2 100644 --- a/tests/integration/targets/test_mysql_query/tasks/main.yml +++ b/tests/integration/targets/test_mysql_query/tasks/main.yml @@ -6,4 +6,4 @@ # mysql_query module initial CI tests - import_tasks: mysql_query_initial.yml -- include: issue-28.yml +- include_tasks: issue-28.yml diff --git a/tests/integration/targets/test_mysql_replication/tasks/main.yml b/tests/integration/targets/test_mysql_replication/tasks/main.yml index 1574921..ab5b4a3 100644 --- a/tests/integration/targets/test_mysql_replication/tasks/main.yml +++ b/tests/integration/targets/test_mysql_replication/tasks/main.yml @@ -10,7 +10,7 @@ - import_tasks: mysql_replication_initial.yml # Tests of replication filters and force_context -- include: issue-265.yml +- include_tasks: issue-265.yml # Tests of primary_delay parameter: - import_tasks: mysql_replication_primary_delay.yml @@ -24,4 +24,4 @@ # Tests of resetprimary mode: - import_tasks: mysql_replication_resetprimary_mode.yml -- include: issue-28.yml +- include_tasks: issue-28.yml diff --git a/tests/integration/targets/test_mysql_role/tasks/main.yml b/tests/integration/targets/test_mysql_role/tasks/main.yml index c3c9bd3..b517fc0 100644 --- a/tests/integration/targets/test_mysql_role/tasks/main.yml +++ b/tests/integration/targets/test_mysql_role/tasks/main.yml @@ -12,5 +12,9 @@ # Test that subtract_privs will only revoke the grants given by priv # (https://github.com/ansible-collections/community.mysql/issues/331) -- include: test_priv_subtract.yml enable_check_mode=no -- include: test_priv_subtract.yml enable_check_mode=yes +- include_tasks: test_priv_subtract.yml + vars: + enable_check_mode: no +- include_tasks: test_priv_subtract.yml + vars: + enable_check_mode: yes diff --git a/tests/integration/targets/test_mysql_user/tasks/issue-265.yml b/tests/integration/targets/test_mysql_user/tasks/issue-265.yml index bea41a8..2d8db77 100644 --- a/tests/integration/targets/test_mysql_user/tasks/issue-265.yml +++ b/tests/integration/targets/test_mysql_user/tasks/issue-265.yml @@ -31,7 +31,10 @@ that: - result is changed - - include: utils/assert_user.yml user_name={{ user_name_1 }} user_host=localhost + - include_tasks: utils/assert_user.yml + vars: + user_name: "{{ user_name_1 }}" + user_host: localhost # Test user removal - name: Issue-265 | remove mysql user {{ user_name_1 }} @@ -86,7 +89,9 @@ that: - result is not changed - - include: utils/assert_no_user.yml user_name={{user_name_1}} + - include_tasks: utils/assert_no_user.yml + vars: + user_name: "{{user_name_1}}" # Tests with force_context: no # Test user creation @@ -114,7 +119,10 @@ that: - result is changed - - include: utils/assert_user.yml user_name={{ user_name_1 }} user_host=localhost + - include_tasks: utils/assert_user.yml + vars: + user_name: "{{ user_name_1 }}" + user_host: localhost # Test user removal - name: Issue-265 | Remove mysql user {{ user_name_1 }} @@ -168,4 +176,6 @@ that: - result is not changed - - include: utils/assert_no_user.yml user_name={{ user_name_1 }} + - include_tasks: utils/assert_no_user.yml + vars: + user_name: "{{ user_name_1 }}" diff --git a/tests/integration/targets/test_mysql_user/tasks/issue-29511.yaml b/tests/integration/targets/test_mysql_user/tasks/issue-29511.yaml index 17eb200..c95acc2 100644 --- a/tests/integration/targets/test_mysql_user/tasks/issue-29511.yaml +++ b/tests/integration/targets/test_mysql_user/tasks/issue-29511.yaml @@ -79,4 +79,6 @@ - foo - bar - - include: utils/remove_user.yml user_name="{{ user_name_2 }}" + - include_tasks: utils/remove_user.yml + vars: + user_name: "{{ 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 188628f..dc5c9d3 100644 --- a/tests/integration/targets/test_mysql_user/tasks/main.yml +++ b/tests/integration/targets/test_mysql_user/tasks/main.yml @@ -35,13 +35,13 @@ block: - - include: issue-121.yml + - include_tasks: issue-121.yml - - include: issue-28.yml + - include_tasks: issue-28.yml - - include: test_resource_limits.yml + - include_tasks: test_resource_limits.yml - - include: test_idempotency.yml + - include_tasks: test_idempotency.yml # ============================================================ # Create user with no privileges and verify default privileges are assign @@ -54,11 +54,20 @@ state: present register: result - - include: utils/assert_user.yml user_name={{ user_name_1 }} user_host=localhost priv=USAGE + - include_tasks: utils/assert_user.yml + vars: + user_name: "{{ user_name_1 }}" + user_host: localhost + priv: USAGE - - include: utils/remove_user.yml user_name={{ user_name_1 }} + - include_tasks: utils/remove_user.yml + vars: + user_name: "{{ user_name_1 }}" + + - include_tasks: utils/assert_no_user.yml + vars: + user_name: "{{ user_name_1 }}" - - include: utils/assert_no_user.yml user_name={{ user_name_1 }} # ============================================================ # Create user with select privileges and verify select privileges are assign @@ -72,11 +81,20 @@ priv: '*.*:SELECT' register: result - - include: utils/assert_user.yml user_name={{ user_name_2 }} user_host=localhost priv=SELECT + - include_tasks: utils/assert_user.yml + vars: + user_name: "{{ user_name_2 }}" + user_host: localhost + priv: SELECT - - include: utils/remove_user.yml user_name={{ user_name_2 }} + - include_tasks: utils/remove_user.yml + vars: + user_name: "{{ user_name_2 }}" + + - include_tasks: utils/assert_no_user.yml + vars: + user_name: "{{ user_name_2 }}" - - include: utils/assert_no_user.yml user_name={{ user_name_2 }} # ============================================================ # Assert user has access to multiple databases @@ -112,9 +130,13 @@ - "'{{ item }}' in result.stdout" with_items: "{{db_names}}" - - include: utils/remove_user.yml user_name={{ user_name_1 }} + - include_tasks: utils/remove_user.yml + vars: + user_name: "{{ user_name_1 }}" - - include: utils/remove_user.yml user_name={{ user_name_2 }} + - include_tasks: utils/remove_user.yml + vars: + user_name: "{{ user_name_2 }}" - name: Give user SELECT access to database via wildcard mysql_user: @@ -172,59 +194,81 @@ - "'%db' in result.stdout" - "'INSERT' in result.stdout" - - include: utils/remove_user.yml user_name={{user_name_1}} + - include_tasks: utils/remove_user.yml + vars: + user_name: "{{user_name_1}}" # ============================================================ # Test plaintext and encrypted password scenarios. # - - include: test_user_password.yml + - include_tasks: test_user_password.yml # ============================================================ # Test plugin authentication scenarios. # # FIXME: mariadb sql syntax for create/update user is not compatible - - include: test_user_plugin_auth.yml + - include_tasks: test_user_plugin_auth.yml when: db_engine == 'mysql' # ============================================================ # Assert create user with SELECT privileges, attempt to create database and update privileges to create database # - - include: test_privs.yml current_privilege=SELECT current_append_privs=no + - include_tasks: test_privs.yml + vars: + current_privilege: SELECT + current_append_privs: no # ============================================================ # Assert creating user with SELECT privileges, attempt to create database and append privileges to create database # - - include: test_privs.yml current_privilege=DROP current_append_privs=yes + - include_tasks: test_privs.yml + vars: + current_privilege: DROP + current_append_privs: yes # ============================================================ # Assert create user with SELECT privileges, attempt to create database and update privileges to create database # - - include: test_privs.yml current_privilege='UPDATE,ALTER' current_append_privs=no + - include_tasks: test_privs.yml + vars: + current_privilege: 'UPDATE,ALTER' + current_append_privs: no # ============================================================ # Assert creating user with SELECT privileges, attempt to create database and append privileges to create database # - - include: test_privs.yml current_privilege='INSERT,DELETE' current_append_privs=yes + - include_tasks: test_privs.yml + vars: + current_privilege: 'INSERT,DELETE' + current_append_privs: yes # Tests for the priv parameter with dict value (https://github.com/ansible/ansible/issues/57533) - - include: test_priv_dict.yml + - include_tasks: test_priv_dict.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 - - include: test_priv_append.yml enable_check_mode=yes + - include_tasks: test_priv_append.yml + vars: + enable_check_mode: no + - include_tasks: test_priv_append.yml + vars: + enable_check_mode: yes # Test that subtract_privs will only revoke the grants given by priv # (https://github.com/ansible-collections/community.mysql/issues/331) - - include: test_priv_subtract.yml enable_check_mode=no - - include: test_priv_subtract.yml enable_check_mode=yes + - include_tasks: test_priv_subtract.yml + vars: + enable_check_mode: no + - include_tasks: test_priv_subtract.yml + vars: + enable_check_mode: yes - import_tasks: test_privs_issue_465.yml tags: - issue_465 # Tests for the TLS requires dictionary - - include: test_tls_requirements.yml + - include_tasks: test_tls_requirements.yml - import_tasks: issue-29511.yaml tags: @@ -236,9 +280,9 @@ # Test that mysql_user still works with force_context enabled (database set to "mysql") # (https://github.com/ansible-collections/community.mysql/issues/265) - - include: issue-265.yml + - include_tasks: issue-265.yml # https://github.com/ansible-collections/community.mysql/issues/231 - - include: test_user_grants_with_roles_applied.yml + - include_tasks: test_user_grants_with_roles_applied.yml - - include: test_revoke_only_grant.yml + - include_tasks: test_revoke_only_grant.yml diff --git a/tests/integration/targets/test_mysql_user/tasks/test_idempotency.yml b/tests/integration/targets/test_mysql_user/tasks/test_idempotency.yml index cc6850c..fb60139 100644 --- a/tests/integration/targets/test_mysql_user/tasks/test_idempotency.yml +++ b/tests/integration/targets/test_mysql_user/tasks/test_idempotency.yml @@ -10,7 +10,10 @@ # ======================================================================== # Creation # ======================================================================== - - include: utils/create_user.yml user_name={{ user_name_1 }} user_password={{ user_password_1 }} + - include_tasks: utils/create_user.yml + vars: + user_name: "{{ user_name_1 }}" + user_password: "{{ user_password_1 }}" - name: Idempotency | Create user that already exist (expect changed=false) mysql_user: @@ -55,7 +58,10 @@ # ======================================================================== # Create blank user to be removed later - - include: utils/create_user.yml user_name="" user_password='KJFDY&D*Sfuysf' + - include_tasks: utils/create_user.yml + vars: + user_name: "" + user_password: 'KJFDY&D*Sfuysf' - name: Idempotency | Remove blank user with hosts=all (expect changed) mysql_user: diff --git a/tests/integration/targets/test_mysql_user/tasks/test_priv_append.yml b/tests/integration/targets/test_mysql_user/tasks/test_priv_append.yml index 51d4a29..76b4ab1 100644 --- a/tests/integration/targets/test_mysql_user/tasks/test_priv_append.yml +++ b/tests/integration/targets/test_mysql_user/tasks/test_priv_append.yml @@ -131,4 +131,6 @@ - data1 - data2 - - include: utils/remove_user.yml user_name={{ user_name_4 }} + - include_tasks: utils/remove_user.yml + vars: + user_name: "{{ user_name_4 }}" diff --git a/tests/integration/targets/test_mysql_user/tasks/test_priv_dict.yml b/tests/integration/targets/test_mysql_user/tasks/test_priv_dict.yml index 82385e1..f162f6b 100644 --- a/tests/integration/targets/test_mysql_user/tasks/test_priv_dict.yml +++ b/tests/integration/targets/test_mysql_user/tasks/test_priv_dict.yml @@ -151,4 +151,6 @@ - data2 - data3 - - include: utils/remove_user.yml user_name="{{ user_name_3 }}" + - include_tasks: utils/remove_user.yml + vars: + user_name: "{{ user_name_3 }}" diff --git a/tests/integration/targets/test_mysql_user/tasks/test_priv_subtract.yml b/tests/integration/targets/test_mysql_user/tasks/test_priv_subtract.yml index b63f664..c63396a 100644 --- a/tests/integration/targets/test_mysql_user/tasks/test_priv_subtract.yml +++ b/tests/integration/targets/test_mysql_user/tasks/test_priv_subtract.yml @@ -172,4 +172,6 @@ loop: - data1 - - include: utils/remove_user.yml user_name="{{ user_name_4 }}" + - include_tasks: utils/remove_user.yml + vars: + user_name: "{{ user_name_4 }}" diff --git a/tests/integration/targets/test_mysql_user/tasks/test_privs.yml b/tests/integration/targets/test_mysql_user/tasks/test_privs.yml index 9801e19..95d44aa 100644 --- a/tests/integration/targets/test_mysql_user/tasks/test_privs.yml +++ b/tests/integration/targets/test_mysql_user/tasks/test_privs.yml @@ -37,7 +37,11 @@ state: present when: current_append_privs == "yes" - - include: utils/assert_user.yml user_name={{ user_name_2 }} user_host=% priv='SELECT' + - include_tasks: utils/assert_user.yml + vars: + user_name: "{{ user_name_2 }}" + user_host: "%" + priv: 'SELECT' when: current_append_privs == "yes" - name: Privs | Create user with current privileges (expect changed=true) @@ -132,7 +136,7 @@ priv: '*.*:ALL' state: present - # - include: utils/assert_user.yml user_name={{user_name_2}} user_host=% priv='ALL PRIVILEGES' + # - include_tasks: utils/assert_user.yml user_name={{user_name_2}} user_host=% priv='ALL PRIVILEGES' - name: Privs | Create database using user {{ user_name_2 }} mysql_db: @@ -188,7 +192,9 @@ that: - result is not changed - - include: utils/remove_user.yml user_name="{{ user_name_2 }}" + - include_tasks: utils/remove_user.yml + vars: + user_name: "{{ user_name_2 }}" # ============================================================ - name: Privs | Grant all privileges with grant option @@ -259,4 +265,6 @@ - result is failed - "'Error granting privileges' in result.msg" - - include: utils/remove_user.yml user_name="{{ user_name_2 }}" + - include_tasks: utils/remove_user.yml + vars: + user_name: "{{ user_name_2 }}" diff --git a/tests/integration/targets/test_mysql_user/tasks/test_revoke_only_grant.yml b/tests/integration/targets/test_mysql_user/tasks/test_revoke_only_grant.yml index de0fc62..b192273 100644 --- a/tests/integration/targets/test_mysql_user/tasks/test_revoke_only_grant.yml +++ b/tests/integration/targets/test_mysql_user/tasks/test_revoke_only_grant.yml @@ -6,7 +6,9 @@ login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' block: - - include: utils/remove_user.yml user_name={{ user_name_1 }} + - include_tasks: utils/remove_user.yml + vars: + user_name: "{{ user_name_1 }}" - name: Revoke only grants | Create user with two grants mysql_user: @@ -47,4 +49,6 @@ - result is not changed always: - - include: utils/remove_user.yml user_name={{ user_name_1 }} + - include_tasks: utils/remove_user.yml + vars: + user_name: "{{ user_name_1 }}" diff --git a/tests/integration/targets/test_mysql_user/tasks/test_tls_requirements.yml b/tests/integration/targets/test_mysql_user/tasks/test_tls_requirements.yml index f85ae3b..d8c2935 100644 --- a/tests/integration/targets/test_mysql_user/tasks/test_tls_requirements.yml +++ b/tests/integration/targets/test_mysql_user/tasks/test_tls_requirements.yml @@ -23,7 +23,9 @@ that: - result is changed - - include: utils/assert_no_user.yml user_name={{user_name_1}} + - include_tasks: utils/assert_no_user.yml + vars: + user_name: "{{user_name_1}}" - name: Tls reqs | Create user with TLS requirements state=present (expect changed=true) mysql_user: @@ -172,14 +174,26 @@ assert: that: "'REQUIRE ' not in result.stdout or 'REQUIRE NONE' in result.stdout" - - include: utils/remove_user.yml user_name={{user_name_1}} + - include_tasks: utils/remove_user.yml + vars: + user_name: "{{user_name_1}}" - - include: utils/remove_user.yml user_name={{user_name_2}} + - include_tasks: utils/remove_user.yml + vars: + user_name: "{{user_name_2}}" - - include: utils/remove_user.yml user_name={{user_name_3}} + - include_tasks: utils/remove_user.yml + vars: + user_name: "{{user_name_3}}" - - include: utils/assert_no_user.yml user_name={{user_name_1}} + - include_tasks: utils/assert_no_user.yml + vars: + user_name: "{{user_name_1}}" - - include: utils/assert_no_user.yml user_name={{user_name_2}} + - include_tasks: utils/assert_no_user.yml + vars: + user_name: "{{user_name_2}}" - - include: utils/assert_no_user.yml user_name={{user_name_3}} + - include_tasks: utils/assert_no_user.yml + vars: + user_name: "{{user_name_3}}" diff --git a/tests/integration/targets/test_mysql_user/tasks/test_user_grants_with_roles_applied.yml b/tests/integration/targets/test_mysql_user/tasks/test_user_grants_with_roles_applied.yml index c6a1327..c9714b7 100644 --- a/tests/integration/targets/test_mysql_user/tasks/test_user_grants_with_roles_applied.yml +++ b/tests/integration/targets/test_mysql_user/tasks/test_user_grants_with_roles_applied.yml @@ -81,7 +81,9 @@ - data1 - data2 - - include: utils/remove_user.yml user_name={{ user_name_3 }} + - include_tasks: utils/remove_user.yml + vars: + user_name: "{{ user_name_3 }}" - name: User grants with roles applied | Drop test role mysql_role: diff --git a/tests/integration/targets/test_mysql_user/tasks/test_user_password.yml b/tests/integration/targets/test_mysql_user/tasks/test_user_password.yml index d98c92c..cffc052 100644 --- a/tests/integration/targets/test_mysql_user/tasks/test_user_password.yml +++ b/tests/integration/targets/test_mysql_user/tasks/test_user_password.yml @@ -36,7 +36,11 @@ that: - result is changed - - include: utils/assert_user.yml user_name={{ test_user_name }} user_host=% priv={{ test_default_priv_type }} + - include_tasks: utils/assert_user.yml + vars: + user_name: "{{ test_user_name }}" + user_host: "%" + priv: "{{ test_default_priv_type }}" - name: Password | Get the MySQL version using the newly created used creds mysql_info: @@ -68,7 +72,11 @@ that: - result is not changed - - include: utils/assert_user.yml user_name={{ test_user_name }} user_host=% priv={{ test_default_priv_type }} + - include_tasks: utils/assert_user.yml + vars: + user_name: "{{ test_user_name }}" + user_host: "%" + priv: "{{ test_default_priv_type }}" - name: Password | Update the user password mysql_user: @@ -84,7 +92,11 @@ that: - result is changed - - include: utils/assert_user.yml user_name={{ test_user_name }} user_host=% priv={{ test_default_priv_type }} + - include_tasks: utils/assert_user.yml + vars: + user_name: "{{ test_user_name }}" + user_host: "%" + priv: "{{ test_default_priv_type }}" - name: Password | Get the MySQL version data using the original password (should fail) mysql_info: @@ -117,7 +129,9 @@ - result is succeeded # Cleanup - - include: utils/remove_user.yml user_name={{ test_user_name }} + - include_tasks: utils/remove_user.yml + vars: + user_name: "{{ test_user_name }}" # ============================================================ # Test setting a plaintext password and then the same password encrypted to ensure there isn't a change detected. @@ -137,7 +151,11 @@ that: - result is changed - - include: utils/assert_user.yml user_name={{ test_user_name }} user_host=localhost priv={{ test_default_priv_type }} + - include_tasks: utils/assert_user.yml + vars: + user_name: "{{ test_user_name }}" + user_host: "localhost" + priv: "{{ test_default_priv_type }}" - name: Password | Pass in the same password as before, but in the encrypted form (no change expected) mysql_user: @@ -155,7 +173,9 @@ - result is not changed # Cleanup - - include: utils/remove_user.yml user_name={{ test_user_name }} + - include_tasks: utils/remove_user.yml + vars: + user_name: "{{ test_user_name }}" # ============================================================ # Test setting an encrypted password and then the same password in plaintext to ensure there isn't a change. @@ -177,7 +197,11 @@ that: - result is changed - - include: utils/assert_user.yml user_name={{ test_user_name }} user_host=% priv={{ test_default_priv_type }} + - include_tasks: utils/assert_user.yml + vars: + user_name: "{{ test_user_name }}" + user_host: "%" + priv: "{{ test_default_priv_type }}" - name: Password | Get the MySQL version data using the new creds mysql_info: @@ -209,7 +233,9 @@ - result is not changed # Cleanup - - include: utils/remove_user.yml user_name={{ test_user_name }} + - include_tasks: utils/remove_user.yml + vars: + user_name: "{{ test_user_name }}" # ============================================================ # Test setting an empty password. @@ -274,4 +300,6 @@ - result is not changed # Cleanup - - include: utils/remove_user.yml user_name={{ test_user_name }} + - include_tasks: utils/remove_user.yml + vars: + user_name: "{{ test_user_name }}" diff --git a/tests/integration/targets/test_mysql_user/tasks/test_user_plugin_auth.yml b/tests/integration/targets/test_mysql_user/tasks/test_user_plugin_auth.yml index 8d7740b..d8ff04d 100644 --- a/tests/integration/targets/test_mysql_user/tasks/test_user_plugin_auth.yml +++ b/tests/integration/targets/test_mysql_user/tasks/test_user_plugin_auth.yml @@ -47,7 +47,11 @@ - "'{{ test_plugin_type }}' in show_create_user.stdout" when: db_engine == 'mysql' or (db_engine == 'mariadb' and db_version is version('10.3', '>=')) - - include: utils/assert_user.yml user_name={{ test_user_name }} user_host=% priv={{ test_default_priv_type }} + - include_tasks: utils/assert_user.yml + vars: + user_name: "{{ test_user_name }}" + user_host: "%" + priv: "{{ test_default_priv_type }}" - name: Plugin auth | Get the MySQL version using the newly created creds mysql_info: @@ -77,7 +81,11 @@ that: - result is changed - - include: utils/assert_user.yml user_name={{ test_user_name }} user_host=% priv={{ test_default_priv_type }} + - include_tasks: utils/assert_user.yml + vars: + user_name: "{{ test_user_name }}" + user_host: "%" + priv: "{{ test_default_priv_type }}" - name: Plugin auth | Getting the MySQL info with the new password should work mysql_info: @@ -94,7 +102,9 @@ - result is succeeded # Cleanup - - include: utils/remove_user.yml user_name={{ test_user_name }} + - include_tasks: utils/remove_user.yml + vars: + user_name: "{{ test_user_name }}" # ============================================================ # Test plugin auth initially setting a hash and then switching to a plaintext auth string. @@ -125,7 +135,11 @@ - "'{{ test_plugin_type }}' in show_create_user.stdout" when: db_engine == 'mysql' or (db_engine == 'mariadb' and db_version is version('10.3', '>=')) - - include: utils/assert_user.yml user_name={{ test_user_name }} user_host=% priv={{ test_default_priv_type }} + - include_tasks: utils/assert_user.yml + vars: + user_name: "{{ test_user_name }}" + user_host: "%" + priv: "{{ test_default_priv_type }}" - name: Plugin auth | Get the MySQL version using the newly created creds mysql_info: @@ -157,7 +171,11 @@ - result is not changed when: db_engine == 'mysql' or (db_engine == 'mariadb' and db_version is version('10.3', '>=')) - - include: utils/assert_user.yml user_name={{ test_user_name }} user_host=% priv={{ test_default_priv_type }} + - include_tasks: utils/assert_user.yml + vars: + user_name: "{{ test_user_name }}" + user_host: "%" + priv: "{{ test_default_priv_type }}" - name: Plugin auth | Change the user using the same plugin, but switch to the same auth string in plaintext form mysql_user: @@ -189,7 +207,9 @@ - result is succeeded # Cleanup - - include: utils/remove_user.yml user_name={{ test_user_name }} + - include_tasks: utils/remove_user.yml + vars: + user_name: "{{ test_user_name }}" # ============================================================ # Test plugin auth initially setting a plaintext auth string and then switching to a hash. @@ -220,7 +240,11 @@ - test_plugin_type in show_create_user.stdout when: db_engine == 'mysql' or (db_engine == 'mariadb' and db_version is version('10.3', '>=')) - - include: utils/assert_user.yml user_name={{ test_user_name }} user_host=% priv={{ test_default_priv_type }} + - include_tasks: utils/assert_user.yml + vars: + user_name: "{{ test_user_name }}" + user_host: "%" + priv: "{{ test_default_priv_type }}" - name: Plugin auth | Get the MySQL version using the newly created creds mysql_info: @@ -252,7 +276,11 @@ that: - result is changed - - include: utils/assert_user.yml user_name={{ test_user_name }} user_host=% priv={{ test_default_priv_type }} + - include_tasks: utils/assert_user.yml + vars: + user_name: "{{ test_user_name }}" + user_host: "%" + priv: "{{ test_default_priv_type }}" - name: Plugin auth | Change the user using the same plugin, but switch to the same auth string in hash form mysql_user: @@ -283,7 +311,9 @@ - result is succeeded # Cleanup - - include: utils/remove_user.yml user_name={{ test_user_name }} + - include_tasks: utils/remove_user.yml + vars: + user_name: "{{ test_user_name }}" # ============================================================ # Test plugin auth with an empty auth string. @@ -313,7 +343,11 @@ - "'{{ test_plugin_type }}' in show_create_user.stdout" when: db_engine == 'mysql' or (db_engine == 'mariadb' and db_version is version('10.3', '>=')) - - include: utils/assert_user.yml user_name={{ test_user_name }} user_host=% priv={{ test_default_priv_type }} + - include_tasks: utils/assert_user.yml + vars: + user_name: "{{ test_user_name }}" + user_host: "%" + priv: "{{ test_default_priv_type }}" - name: Plugin auth | Get the MySQL version using an empty password for the newly created user mysql_info: @@ -360,7 +394,9 @@ - result is not changed # Cleanup - - include: utils/remove_user.yml user_name={{ test_user_name }} + - include_tasks: utils/remove_user.yml + vars: + user_name: "{{ test_user_name }}" # ============================================================ # Test plugin auth switching from one type of plugin to another without an auth string or hash. The only other @@ -400,7 +436,11 @@ - test_plugin_type in show_create_user.stdout when: db_engine == 'mysql' or (db_engine == 'mariadb' and db_version is version('10.3', '>=')) - - include: utils/assert_user.yml user_name={{ test_user_name }} user_host=localhost priv={{ test_default_priv_type }} + - include_tasks: utils/assert_user.yml + vars: + user_name: "{{ test_user_name }}" + user_host: localhost + priv: "{{ test_default_priv_type }}" - name: Plugin auth | Switch user to sha256_password auth plugin mysql_user: @@ -425,7 +465,13 @@ - "'sha256_password' in show_create_user.stdout" when: db_engine == 'mysql' or (db_engine == 'mariadb' and db_version is version('10.3', '>=')) - - include: utils/assert_user.yml user_name={{ test_user_name }} user_host=localhost priv={{ test_default_priv_type }} + - include_tasks: utils/assert_user.yml + vars: + user_name: "{{ test_user_name }}" + user_host: localhost + priv: "{{ test_default_priv_type }}" # Cleanup - - include: utils/remove_user.yml user_name={{ test_user_name }} + - include_tasks: utils/remove_user.yml + vars: + user_name: "{{ test_user_name }}" diff --git a/tests/integration/targets/test_mysql_variables/tasks/main.yml b/tests/integration/targets/test_mysql_variables/tasks/main.yml index 9c4cd7d..052b279 100644 --- a/tests/integration/targets/test_mysql_variables/tasks/main.yml +++ b/tests/integration/targets/test_mysql_variables/tasks/main.yml @@ -5,4 +5,4 @@ - import_tasks: mysql_variables.yml -- include: issue-28.yml +- include_tasks: 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 c8ae3e8..2d2318e 100644 --- a/tests/integration/targets/test_mysql_variables/tasks/mysql_variables.yml +++ b/tests/integration/targets/test_mysql_variables/tasks/mysql_variables.yml @@ -37,7 +37,11 @@ variable: '{{ set_name }}' register: result - - include: assert_var_output.yml changed=false output={{ result }} var_name={{ set_name }} + - include_tasks: assert_var_output.yml + vars: + changed: false + output: "{{ result }}" + var_name: "{{ set_name }}" # ============================================================ # Verify mysql_variable successfully updates a variable (issue:4568) @@ -59,7 +63,12 @@ value: '{{ set_value }}' register: result - - include: assert_var.yml changed=false output={{ result }} var_name={{ set_name }} var_value={{ set_value }} + - include_tasks: assert_var.yml + vars: + changed: false + output: "{{ result }}" + var_name: "{{ set_name }}" + var_value: "{{ set_value }}" # ============================================================ # Verify mysql_variable successfully updates a variable using single quotes @@ -85,7 +94,12 @@ that: - result.queries == ["SET GLOBAL `{{ set_name }}` = {{ set_value }}"] - - include: assert_var.yml changed=true output={{ result }} var_name={{ set_name }} var_value='{{ set_value }}' + - include_tasks: assert_var.yml + vars: + changed: true + output: "{{ result }}" + var_name: "{{ set_name }}" + var_value: '{{ set_value }}' # ============================================================ # Verify mysql_variable successfully updates a variable using double quotes @@ -107,7 +121,12 @@ value: '{{ set_value }}' register: result - - include: assert_var.yml changed=true output={{ result }} var_name={{ set_name }} var_value='{{ set_value }}' + - include_tasks: assert_var.yml + vars: + changed: true + output: "{{ result }}" + var_name: "{{ set_name }}" + var_value: '{{ set_value }}' # ============================================================ # Verify mysql_variable successfully updates a variable using no quotes @@ -129,7 +148,12 @@ value: '{{ set_value }}' register: result - - include: assert_var.yml changed=true output={{ result }} var_name={{ set_name }} var_value='{{ set_value }}' + - include_tasks: assert_var.yml + vars: + changed: true + output: "{{ result }}" + var_name: "{{ set_name }}" + var_value: '{{ set_value }}' # ============================================================ # Verify mysql_variable successfully updates a variable using an expression (e.g. 1024*4) @@ -142,7 +166,10 @@ register: result ignore_errors: true - - include: assert_fail_msg.yml output={{ result }} msg='Incorrect argument type to variable' + - include_tasks: assert_fail_msg.yml + vars: + output: "{{ result }}" + msg: 'Incorrect argument type to variable' # ============================================================ # Verify mysql_variable fails when setting an incorrect value (out of range) @@ -155,12 +182,20 @@ register: oor_result ignore_errors: true - - include: assert_var.yml changed=true output={{ oor_result }} var_name=max_connect_errors var_value=1 + - include_tasks: assert_var.yml + vars: + changed: true + output: "{{ oor_result }}" + var_name: max_connect_errors + var_value: 1 when: - connector_name == 'mysqlclient' - db_engine == 'mysql' # mysqlclient returns "changed" with MariaDB - - include: assert_fail_msg.yml output={{ oor_result }} msg='Truncated incorrect' + - include_tasks: assert_fail_msg.yml + vars: + output: "{{ oor_result }}" + msg: 'Truncated incorrect' when: - connector_name == 'pymsql' @@ -175,7 +210,10 @@ register: nvv_result ignore_errors: true - - include: assert_fail_msg.yml output={{ nvv_result }} msg='Incorrect argument type to variable' + - include_tasks: assert_fail_msg.yml + vars: + output: "{{ nvv_result }}" + msg: 'Incorrect argument type to variable' # ============================================================ # Verify mysql_variable fails when setting an unknown variable @@ -188,7 +226,10 @@ register: result ignore_errors: true - - include: assert_fail_msg.yml output={{ result }} msg='Variable not available' + - include_tasks: assert_fail_msg.yml + vars: + output: "{{ result }}" + msg: 'Variable not available' # ============================================================ # Verify mysql_variable fails when setting a read-only variable @@ -201,7 +242,10 @@ register: result ignore_errors: true - - include: assert_fail_msg.yml output={{ result }} msg='read only variable' + - include_tasks: assert_fail_msg.yml + vars: + output: "{{ result }}" + msg: 'read only variable' #============================================================= # Verify mysql_variable works with the login_user and login_password parameters @@ -216,7 +260,11 @@ variable: '{{ set_name }}' register: result - - include: assert_var_output.yml changed=false output={{ result }} var_name={{ set_name }} + - include_tasks: assert_var_output.yml + vars: + changed: false + output: "{{ result }}" + var_name: "{{ set_name }}" - name: set mysql variable to temp value using user login and password (expect changed=true) mysql_variables: @@ -232,7 +280,12 @@ value: '{{set_value}}' register: result - - include: assert_var.yml changed=true output={{result}} var_name={{set_name}} var_value='{{set_value}}' + - include_tasks: assert_var.yml + vars: + changed: true + output: "{{result}}" + var_name: "{{set_name}}" + var_value: '{{set_value}}' #============================================================ # Verify mysql_variable fails with an incorrect login_password parameter @@ -251,7 +304,10 @@ register: result ignore_errors: true - - include: assert_fail_msg.yml output={{ result }} msg='unable to connect to database' + - include_tasks: assert_fail_msg.yml + vars: + output: "{{ result }}" + msg: 'unable to connect to database' - name: update mysql variable value using incorrect login_password (expect failed=true) mysql_variables: @@ -264,7 +320,10 @@ register: result ignore_errors: true - - include: assert_fail_msg.yml output={{ result }} msg='unable to connect to database' + - include_tasks: assert_fail_msg.yml + vars: + output: "{{ result }}" + msg: 'unable to connect to database' #============================================================ # Verify mysql_variable fails with an incorrect login_host parameter @@ -280,7 +339,10 @@ register: result ignore_errors: true - - include: assert_fail_msg.yml output={{ result }} msg='unable to connect to database' + - include_tasks: assert_fail_msg.yml + vars: + output: "{{ result }}" + msg: 'unable to connect to database' - block: @@ -299,7 +361,12 @@ that: - result.queries == ["SET PERSIST `{{ set_name }}` = {{ set_value }}"] - - include: assert_var.yml changed=true output={{ result }} var_name={{ set_name }} var_value='{{ set_value }}' + - include_tasks: assert_var.yml + vars: + changed: true + output: "{{ result }}" + var_name: "{{ set_name }}" + var_value: '{{ set_value }}' - name: try to update mysql variable value (expect changed=false) in persist mode again mysql_variables: @@ -309,7 +376,12 @@ mode: persist register: result - - include: assert_var.yml changed=false output={{ result }} var_name={{ set_name }} var_value='{{ set_value }}' + - include_tasks: assert_var.yml + vars: + changed: false + output: "{{ result }}" + var_name: "{{ set_name }}" + var_value: '{{ set_value }}' - name: set mysql variable to a temp value mysql_variables: @@ -356,7 +428,12 @@ mode: persist_only register: result - - include: assert_var.yml changed=true output={{ result }} var_name={{ set_name }} var_value='{{ def_val }}' + - include_tasks: assert_var.yml + vars: + changed: true + output: "{{ result }}" + var_name: "{{ set_name }}" + var_value: '{{ def_val }}' when: - db_engine == 'mysql' From a81b6fc0816a52d181505509a1edc8412f0acf94 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Thu, 25 May 2023 13:30:17 +0200 Subject: [PATCH 152/217] Release 3.7.2 commit (#556) --- CHANGELOG.rst | 15 +++++++++++++++ changelogs/changelog.yaml | 14 ++++++++++++++ ...nnection_arguemnts_for_old_mysqldb_driver.yaml | 2 -- galaxy.yml | 2 +- 4 files changed, 30 insertions(+), 3 deletions(-) delete mode 100644 changelogs/fragments/553_fix_connection_arguemnts_for_old_mysqldb_driver.yaml diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d381f5c..31ee41a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,21 @@ Community MySQL Collection Release Notes This changelog describes changes after version 2.0.0. +v3.7.2 +====== + +Release Summary +--------------- + +This is a patch release of the community.mysql collection. +This changelog contains all changes to the modules and plugins in this collection +that have been made after the previous release. + +Bugfixes +-------- + +- mysql module utils - use the connection arguments ``db`` instead of ``database`` and ``passwd`` instead of ``password`` when running with MySQLdb < 2.0.0 (https://github.com/ansible-collections/community.mysql/pull/553). + v3.7.1 ====== diff --git a/changelogs/changelog.yaml b/changelogs/changelog.yaml index 196a6bd..e3431f3 100644 --- a/changelogs/changelog.yaml +++ b/changelogs/changelog.yaml @@ -332,3 +332,17 @@ releases: - 3.7.1.yml - 551-fix_connection_arguments_driver_compatability.yaml release_date: '2023-05-22' + 3.7.2: + changes: + bugfixes: + - mysql module utils - use the connection arguments ``db`` instead of ``database`` + and ``passwd`` instead of ``password`` when running with MySQLdb < 2.0.0 (https://github.com/ansible-collections/community.mysql/pull/553). + release_summary: 'This is a patch release of the community.mysql collection. + + This changelog contains all changes to the modules and plugins in this collection + + that have been made after the previous release.' + fragments: + - 3.7.2.yml + - 553_fix_connection_arguemnts_for_old_mysqldb_driver.yaml + release_date: '2023-05-25' diff --git a/changelogs/fragments/553_fix_connection_arguemnts_for_old_mysqldb_driver.yaml b/changelogs/fragments/553_fix_connection_arguemnts_for_old_mysqldb_driver.yaml deleted file mode 100644 index d0f5316..0000000 --- a/changelogs/fragments/553_fix_connection_arguemnts_for_old_mysqldb_driver.yaml +++ /dev/null @@ -1,2 +0,0 @@ -bugfixes: - - mysql module utils - use the connection arguments ``db`` instead of ``database`` and ``passwd`` instead of ``password`` when running with MySQLdb < 2.0.0 (https://github.com/ansible-collections/community.mysql/pull/553). diff --git a/galaxy.yml b/galaxy.yml index f725615..39a271e 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -1,7 +1,7 @@ --- namespace: community name: mysql -version: 3.7.1 +version: 3.7.2 readme: README.md authors: - Ansible community From b79fd94d51eb986f413e4e6778d45ab9944ff653 Mon Sep 17 00:00:00 2001 From: Pavel Rabel <128324708+elpavel@users.noreply.github.com> Date: Mon, 29 May 2023 08:25:19 +0100 Subject: [PATCH 153/217] Doc Attributes (#555) * Added Attributes section * Added Attributes section --- plugins/doc_fragments/mysql.py | 3 +++ plugins/modules/mysql_db.py | 5 +++-- plugins/modules/mysql_info.py | 5 ++++- plugins/modules/mysql_query.py | 3 +++ plugins/modules/mysql_replication.py | 5 ++++- plugins/modules/mysql_role.py | 5 ++++- plugins/modules/mysql_user.py | 6 ++++-- plugins/modules/mysql_variables.py | 5 +++-- 8 files changed, 28 insertions(+), 9 deletions(-) diff --git a/plugins/doc_fragments/mysql.py b/plugins/doc_fragments/mysql.py index 939126c..27ec650 100644 --- a/plugins/doc_fragments/mysql.py +++ b/plugins/doc_fragments/mysql.py @@ -110,4 +110,7 @@ notes: - Alternatively, to avoid using I(login_unix_socket) argument on each invocation you can specify the socket path using the `socket` option in your MySQL config file (usually C(~/.my.cnf)) on the destination host, for example C(socket=/var/lib/mysql/mysql.sock). +attributes: + check_mode: + description: Can run in check_mode and return changed status prediction without modifying target. ''' diff --git a/plugins/modules/mysql_db.py b/plugins/modules/mysql_db.py index 5a8fe3e..a425361 100644 --- a/plugins/modules/mysql_db.py +++ b/plugins/modules/mysql_db.py @@ -188,13 +188,14 @@ requirements: - mysql (command line binary) - mysqldump (command line binary) notes: - - Supports C(check_mode). - Requires the mysql and mysqldump binaries on the remote host. - This module is B(not idempotent) when I(state) is C(import), and will import the dump file each time if run more than once. +attributes: + check_mode: + support: full extends_documentation_fragment: - community.mysql.mysql - ''' EXAMPLES = r''' diff --git a/plugins/modules/mysql_info.py b/plugins/modules/mysql_info.py index 11b1a80..cb9f029 100644 --- a/plugins/modules/mysql_info.py +++ b/plugins/modules/mysql_info.py @@ -47,7 +47,10 @@ options: notes: - Calculating the size of a database might be slow, depending on the number and size of tables in it. To avoid this, use I(exclude_fields=db_size). -- Supports C(check_mode). + +attributes: + check_mode: + support: full seealso: - module: community.mysql.mysql_variables diff --git a/plugins/modules/mysql_query.py b/plugins/modules/mysql_query.py index 12d5a56..9123d60 100644 --- a/plugins/modules/mysql_query.py +++ b/plugins/modules/mysql_query.py @@ -50,6 +50,9 @@ options: - Where passed queries run in a single transaction (C(yes)) or commit them one-by-one (C(no)). type: bool default: false +attributes: + check_mode: + support: none seealso: - module: community.mysql.mysql_db author: diff --git a/plugins/modules/mysql_replication.py b/plugins/modules/mysql_replication.py index 33e14bc..8029a5a 100644 --- a/plugins/modules/mysql_replication.py +++ b/plugins/modules/mysql_replication.py @@ -190,10 +190,13 @@ options: notes: - If an empty value for the parameter of string type is needed, use an empty string. +attributes: + check_mode: + support: none + extends_documentation_fragment: - community.mysql.mysql - seealso: - module: community.mysql.mysql_info - name: MySQL replication reference diff --git a/plugins/modules/mysql_role.py b/plugins/modules/mysql_role.py index 070d793..7d672d7 100644 --- a/plugins/modules/mysql_role.py +++ b/plugins/modules/mysql_role.py @@ -125,7 +125,10 @@ notes: - Pay attention that the module runs C(SET DEFAULT ROLE ALL TO) all the I(members) passed by default when the state has changed. If you want to avoid this behavior, set I(set_default_role_all) to C(no). - - Supports C(check_mode). + +attributes: + check_mode: + support: full seealso: - module: community.mysql.mysql_user diff --git a/plugins/modules/mysql_user.py b/plugins/modules/mysql_user.py index e87fe12..38e5124 100644 --- a/plugins/modules/mysql_user.py +++ b/plugins/modules/mysql_user.py @@ -163,7 +163,10 @@ notes: 2) drop a C(~/.my.cnf) file containing the new root credentials. Subsequent runs of the playbook will then succeed by reading the new credentials from the file." - Currently, there is only support for the C(mysql_native_password) encrypted password hash module. - - Supports (check_mode). + +attributes: + check_mode: + support: full seealso: - module: community.mysql.mysql_info @@ -180,7 +183,6 @@ author: - Lukasz Tomaszkiewicz (@tomaszkiewicz) extends_documentation_fragment: - community.mysql.mysql - ''' EXAMPLES = r''' diff --git a/plugins/modules/mysql_variables.py b/plugins/modules/mysql_variables.py index f404d5a..395a24c 100644 --- a/plugins/modules/mysql_variables.py +++ b/plugins/modules/mysql_variables.py @@ -44,8 +44,9 @@ options: default: global version_added: '0.1.0' -notes: -- Does not support C(check_mode). +attributes: + check_mode: + support: none seealso: - module: community.mysql.mysql_info From 94392826e1c094c4c46f46334881b25e839732d1 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Thu, 20 Jul 2023 09:57:51 +0200 Subject: [PATCH 154/217] README: Define project's mission statement (#561) * README: Define project's mission statement * Add suggestion --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5cb2271..b024453 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,12 @@ This collection is a part of the Ansible package. +## Our mission + +The Ansible `community.mysql` collection goals are to produce and maintain simple, +flexible, and powerful open-source software for automating MySQL and MariaDB related tasks +providing good documentation for easy deployment and use. + ## Code of Conduct We follow the [Ansible Code of Conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html) in all our interactions within this project. @@ -17,7 +23,7 @@ We are actively accepting new contributors. Any kind of contribution is very welcome. -You don't know how to start? Refer to our [contribution guide](https://github.com/ansible-collections/community.mysql/blob/main/CONTRIBUTING.md)! +You don't know how to start? Refer to our [contribution guide](https://github.com/ansible-collections/community.mysql/blob/main/CONTRIBUTING.md) or ask us in the [#mysql:ansible.com room](https://matrix.to/#/#mysql:ansible.com) on [Matrix](https://docs.ansible.com/ansible/devel/community/communication.html#ansible-community-on-matrix)! ## Collection maintenance From 8c2b6b0b3cce3a0d23a33bcb45195b65f717af26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20Inderm=C3=BChle?= Date: Fri, 29 Sep 2023 09:29:43 +0200 Subject: [PATCH 155/217] Add ansible-core stable-2.15 and stable-2.16 to tests matrix now that "devel" links to 2.17 (#574) * Add stable-2.15 and 2.16 now that devel link to 2.17 * document which ansible-core version we support * add sanity ignore for ansible-core 2.17 * cut sanity ignore for 2.12 and 2.13 * Cut ansible-core 2.12 and 2.13 from GHA test matrix --- .github/workflows/ansible-test-plugins.yml | 44 +++++++++++-------- .github/workflows/ansible-test-roles.yml | 19 +------- README.md | 8 ++-- TESTING.md | 2 + .../drop_ansible_core_2_12_and_2_13.yml | 11 +++++ tests/sanity/ignore-2.13.txt | 8 ---- .../{ignore-2.12.txt => ignore-2.17.txt} | 2 + 7 files changed, 47 insertions(+), 47 deletions(-) create mode 100644 changelogs/fragments/drop_ansible_core_2_12_and_2_13.yml delete mode 100644 tests/sanity/ignore-2.13.txt rename tests/sanity/{ignore-2.12.txt => ignore-2.17.txt} (84%) diff --git a/.github/workflows/ansible-test-plugins.yml b/.github/workflows/ansible-test-plugins.yml index 6533f94..78644bb 100644 --- a/.github/workflows/ansible-test-plugins.yml +++ b/.github/workflows/ansible-test-plugins.yml @@ -22,9 +22,9 @@ jobs: strategy: matrix: ansible: - - stable-2.12 - - stable-2.13 - stable-2.14 + - stable-2.15 + - stable-2.16 - devel steps: - name: Perform sanity testing @@ -41,9 +41,9 @@ jobs: fail-fast: false matrix: ansible: - - stable-2.12 - - stable-2.13 - stable-2.14 + - stable-2.15 + - stable-2.16 - devel db_engine_name: - mysql @@ -112,10 +112,13 @@ jobs: python: '3.10' - db_engine_version: 5.7.40 - ansible: stable-2.13 + ansible: stable-2.14 - db_engine_version: 5.7.40 - ansible: stable-2.14 + ansible: stable-2.15 + + - db_engine_version: 5.7.40 + ansible: stable-2.16 - db_engine_version: 5.7.40 ansible: devel @@ -171,24 +174,27 @@ jobs: - python: '3.10' connector_version: 2.0.3 - - python: '3.8' - ansible: stable-2.13 - - python: '3.8' ansible: stable-2.14 + - python: '3.8' + ansible: stable-2.15 + + - python: '3.8' + ansible: stable-2.16 + - python: '3.8' ansible: devel - python: '3.9' - ansible: stable-2.12 + ansible: stable-2.15 + + - python: '3.9' + ansible: stable-2.16 - python: '3.9' ansible: devel - - python: '3.10' - ansible: stable-2.12 - services: db_primary: image: docker.io/library/${{ matrix.db_engine_name }}:${{ matrix.db_engine_version }} @@ -334,22 +340,22 @@ jobs: fail-fast: true matrix: ansible: - - stable-2.12 - - stable-2.13 - stable-2.14 + - stable-2.15 + - stable-2.16 - devel python: - 3.8 - 3.9 exclude: - - python: '3.8' - ansible: stable-2.13 - python: '3.8' ansible: stable-2.14 + - python: '3.8' + ansible: stable-2.15 + - python: '3.8' + ansible: stable-2.16 - python: '3.8' ansible: devel - - python: '3.9' - ansible: stable-2.12 steps: - name: >- diff --git a/.github/workflows/ansible-test-roles.yml b/.github/workflows/ansible-test-roles.yml index 13e7d41..da8a805 100644 --- a/.github/workflows/ansible-test-roles.yml +++ b/.github/workflows/ansible-test-roles.yml @@ -24,31 +24,16 @@ jobs: mysql: - 2.0.12 ansible: - - stable-2.11 - - stable-2.12 - stable-2.13 + - stable-2.14 + - stable-2.15 - devel python: - - 3.6 - 3.8 - 3.9 exclude: - - python: 3.6 - ansible: stable-2.12 - - python: 3.6 - ansible: stable-2.13 - - python: 3.6 - ansible: devel - - python: 3.8 - ansible: stable-2.11 - - python: 3.8 - ansible: stable-2.13 - python: 3.8 ansible: devel - - python: 3.9 - ansible: stable-2.11 - - python: 3.9 - ansible: stable-2.12 steps: diff --git a/README.md b/README.md index b024453..f7e062c 100644 --- a/README.md +++ b/README.md @@ -82,9 +82,11 @@ Here is the table for the support timeline: ### ansible-core -- 2.12 -- 2.13 -- 2.14 +- stable-2.12 +- stable-2.13 +- stable-2.14 +- stable-2.15 +- stable-2.16 - current development version ### Databases diff --git a/TESTING.md b/TESTING.md index 7bbafc3..7025391 100644 --- a/TESTING.md +++ b/TESTING.md @@ -52,6 +52,8 @@ The Makefile accept the following options - "stable-2.12" - "stable-2.13" - "stable-2.14" + - "stable-2.15" + - "stable-2.16" - "devel" - Description: Version of ansible to install in a venv to run ansible-test diff --git a/changelogs/fragments/drop_ansible_core_2_12_and_2_13.yml b/changelogs/fragments/drop_ansible_core_2_12_and_2_13.yml new file mode 100644 index 0000000..29a363e --- /dev/null +++ b/changelogs/fragments/drop_ansible_core_2_12_and_2_13.yml @@ -0,0 +1,11 @@ +--- + +major_changes: + + - The community.mysql collection no longer supports ``ansible-core 2.12`` and + ``ansible-core 2.13``. While we take no active measures to prevent usage + and there are no plans to introduce incompatible code to the modules, we + will stop testing those versions. Both are or will soon be End of Life and + if you are still using them, you should consider upgrading to the + ``latest Ansible / ansible-core 2.15 or later`` as soon as possible + (https://github.com/ansible-collections/community.mysql/pull/574). diff --git a/tests/sanity/ignore-2.13.txt b/tests/sanity/ignore-2.13.txt deleted file mode 100644 index c0323af..0000000 --- a/tests/sanity/ignore-2.13.txt +++ /dev/null @@ -1,8 +0,0 @@ -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_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_variables.py validate-modules:doc-required-mismatch diff --git a/tests/sanity/ignore-2.12.txt b/tests/sanity/ignore-2.17.txt similarity index 84% rename from tests/sanity/ignore-2.12.txt rename to tests/sanity/ignore-2.17.txt index c0323af..da0354c 100644 --- a/tests/sanity/ignore-2.12.txt +++ b/tests/sanity/ignore-2.17.txt @@ -6,3 +6,5 @@ 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_variables.py validate-modules:doc-required-mismatch +plugins/module_utils/mysql.py pylint:unused-import +plugins/module_utils/version.py pylint:unused-import From 033b4c74f906daea3e1e06c27a4d797ed4df250a Mon Sep 17 00:00:00 2001 From: kmarse <43994323+kmarse@users.noreply.github.com> Date: Fri, 6 Oct 2023 08:08:46 -0600 Subject: [PATCH 156/217] Fix column uppercasing (#569) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add integrations tests for column case sensitive name * add a warning when column_case_sensitive in not set * add announce default will change in in 4.0.0 * fix tests for engine that don't wrap column in backticks * add filter because only MySQL 5.7 is case sensitive for users privs * add kmarse and myself to the authors * add kmarse to the contributors list --------- Co-authored-by: Laurent Indermühle Co-authored-by: Andrew Klychkov --- CONTRIBUTORS | 1 + changelogs/569_fix_column_uppercasing.yml | 21 +++ plugins/module_utils/user.py | 13 +- plugins/modules/mysql_role.py | 25 ++- plugins/modules/mysql_user.py | 25 ++- .../targets/test_mysql_role/tasks/main.yml | 4 + .../tasks/test_column_case_sensitive.yml | 149 ++++++++++++++++++ .../targets/test_mysql_user/tasks/main.yml | 4 + .../tasks/test_column_case_sensitive.yml | 134 ++++++++++++++++ .../plugins/module_utils/test_mysql_user.py | 21 ++- 10 files changed, 389 insertions(+), 8 deletions(-) create mode 100644 changelogs/569_fix_column_uppercasing.yml create mode 100644 tests/integration/targets/test_mysql_role/tasks/test_column_case_sensitive.yml create mode 100644 tests/integration/targets/test_mysql_user/tasks/test_column_case_sensitive.yml diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 3acc8f3..36e8a08 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -141,6 +141,7 @@ kalaisubbiah kenichi-ogawa-1988 kkeane klingac +kmarse koleo kotso kuntalFreshBooks diff --git a/changelogs/569_fix_column_uppercasing.yml b/changelogs/569_fix_column_uppercasing.yml new file mode 100644 index 0000000..781304e --- /dev/null +++ b/changelogs/569_fix_column_uppercasing.yml @@ -0,0 +1,21 @@ +--- +minor_changes: + + - mysql_user - add ``column_case_sensitive`` option to prevent field names + from being uppercased + (https://github.com/ansible-collections/community.mysql/pull/569). + - mysql_role - add ``column_case_sensitive`` option to prevent field names + from being uppercased + (https://github.com/ansible-collections/community.mysql/pull/569). + +major_changes: + - mysql_user - the ``column_case_sensitive`` argument's default value will be + changed to ``true`` in community.mysql 4.0.0. If your playbook expected the + column to be automatically uppercased for your users privileges, you should + set this to false explicitly + (https://github.com/ansible-collections/community.mysql/issues/577). + - mysql_role - the ``column_case_sensitive`` argument's default value will be + changed to ``true`` in community.mysql 4.0.0. If your playbook expected the + column to be automatically uppercased for your roles privileges, you should + set this to false explicitly + (https://github.com/ansible-collections/community.mysql/issues/578). diff --git a/plugins/module_utils/user.py b/plugins/module_utils/user.py index a63ad89..e1d80ab 100644 --- a/plugins/module_utils/user.py +++ b/plugins/module_utils/user.py @@ -627,7 +627,7 @@ def sort_column_order(statement): return '%s(%s)' % (priv_name, ', '.join(columns)) -def privileges_unpack(priv, mode, ensure_usage=True): +def privileges_unpack(priv, mode, column_case_sensitive, ensure_usage=True): """ Take a privileges string, typically passed as a parameter, and unserialize it into a dictionary, the same format as privileges_get() above. We have this custom format to avoid using YAML/JSON strings inside YAML playbooks. Example @@ -663,9 +663,14 @@ def privileges_unpack(priv, mode, ensure_usage=True): pieces[0] = object_type + '.'.join(dbpriv) if '(' in pieces[1]: - output[pieces[0]] = re.split(r',\s*(?=[^)]*(?:\(|$))', pieces[1].upper()) - for i in output[pieces[0]]: - privs.append(re.sub(r'\s*\(.*\)', '', i)) + if column_case_sensitive is True: + output[pieces[0]] = re.split(r',\s*(?=[^)]*(?:\(|$))', pieces[1]) + for i in output[pieces[0]]: + privs.append(re.sub(r'\s*\(.*\)', '', i)) + else: + output[pieces[0]] = re.split(r',\s*(?=[^)]*(?:\(|$))', pieces[1].upper()) + for i in output[pieces[0]]: + privs.append(re.sub(r'\s*\(.*\)', '', i)) else: output[pieces[0]] = pieces[1].upper().split(',') privs = output[pieces[0]] diff --git a/plugins/modules/mysql_role.py b/plugins/modules/mysql_role.py index 7d672d7..e892093 100644 --- a/plugins/modules/mysql_role.py +++ b/plugins/modules/mysql_role.py @@ -121,6 +121,16 @@ options: type: bool default: true + column_case_sensitive: + description: + - The default is C(false). + - When C(true), the module will not uppercase the field in the privileges. + - When C(false), the field names will be upper-cased. This was the default before this + feature was introduced but since MySQL/MariaDB is case sensitive you should set this + to C(true) in most cases. + type: bool + version_added: '3.8.0' + notes: - Pay attention that the module runs C(SET DEFAULT ROLE ALL TO) all the I(members) passed by default when the state has changed. @@ -139,6 +149,8 @@ seealso: author: - Andrew Klychkov (@Andersson007) - Felix Hamme (@betanummeric) + - kmarse (@kmarse) + - Laurent Indermühle (@laurent-indermuehle) extends_documentation_fragment: - community.mysql.mysql @@ -957,7 +969,8 @@ def main(): detach_members=dict(type='bool', default=False), check_implicit_admin=dict(type='bool', default=False), set_default_role_all=dict(type='bool', default=True), - members_must_exist=dict(type='bool', default=True) + members_must_exist=dict(type='bool', default=True), + column_case_sensitive=dict(type='bool', default=None), # TODO 4.0.0 add default=True ) module = AnsibleModule( argument_spec=argument_spec, @@ -992,6 +1005,7 @@ def main(): db = '' set_default_role_all = module.params['set_default_role_all'] members_must_exist = module.params['members_must_exist'] + column_case_sensitive = module.params['column_case_sensitive'] if priv and not isinstance(priv, (str, dict)): msg = ('The "priv" parameter must be str or dict ' @@ -1004,6 +1018,13 @@ def main(): if mysql_driver is None: module.fail_json(msg=mysql_driver_fail_msg) + # TODO Release 4.0.0 : Remove this test and variable assignation + if column_case_sensitive is None: + column_case_sensitive = False + module.warn("Option column_case_sensitive is not provided. " + "The default is now false, so the column's name will be uppercased. " + "The default will be changed to true in community.mysql 4.0.0.") + cursor = None try: if check_implicit_admin: @@ -1041,7 +1062,7 @@ def main(): module.fail_json(msg=to_native(e)) try: - priv = privileges_unpack(priv, mode, ensure_usage=not subtract_privs) + priv = privileges_unpack(priv, mode, column_case_sensitive, ensure_usage=not subtract_privs) except Exception as e: module.fail_json(msg='Invalid privileges string: %s' % to_native(e)) diff --git a/plugins/modules/mysql_user.py b/plugins/modules/mysql_user.py index 38e5124..3e914e6 100644 --- a/plugins/modules/mysql_user.py +++ b/plugins/modules/mysql_user.py @@ -156,6 +156,16 @@ options: type: dict version_added: '3.6.0' + column_case_sensitive: + description: + - The default is C(false). + - When C(true), the module will not uppercase the field names in the privileges. + - When C(false), the field names will be upper-cased. This is the default + - This feature was introduced because MySQL 8 and above uses case sensitive + fields names in privileges. + type: bool + version_added: '3.8.0' + notes: - "MySQL server installs with default I(login_user) of C(root) and no password. To secure this user as part of an idempotent playbook, you must create at least two tasks: @@ -181,6 +191,9 @@ author: - Jonathan Mainguy (@Jmainguy) - Benjamin Malynovytch (@bmalynovytch) - Lukasz Tomaszkiewicz (@tomaszkiewicz) +- kmarse (@kmarse) +- Laurent Indermühle (@laurent-indermuehle) + extends_documentation_fragment: - community.mysql.mysql ''' @@ -401,6 +414,7 @@ def main(): resource_limits=dict(type='dict'), force_context=dict(type='bool', default=False), session_vars=dict(type='dict'), + column_case_sensitive=dict(type='bool', default=None), # TODO 4.0.0 add default=True ) module = AnsibleModule( argument_spec=argument_spec, @@ -436,6 +450,7 @@ def main(): plugin_auth_string = module.params["plugin_auth_string"] resource_limits = module.params["resource_limits"] session_vars = module.params["session_vars"] + column_case_sensitive = module.params["column_case_sensitive"] 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)) @@ -462,6 +477,13 @@ def main(): 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))) + # TODO Release 4.0.0 : Remove this test and variable assignation + if column_case_sensitive is None: + column_case_sensitive = False + module.warn("Option column_case_sensitive is not provided. " + "The default is now false, so the column's name will be uppercased. " + "The default will be changed to true in community.mysql 4.0.0.") + if not sql_log_bin: cursor.execute("SET SQL_LOG_BIN=0;") @@ -475,7 +497,8 @@ def main(): mode = get_mode(cursor) except Exception as e: module.fail_json(msg=to_native(e)) - priv = privileges_unpack(priv, mode, ensure_usage=not subtract_privs) + + priv = privileges_unpack(priv, mode, column_case_sensitive, ensure_usage=not subtract_privs) password_changed = False if state == "present": if user_exists(cursor, user, host, host_all): diff --git a/tests/integration/targets/test_mysql_role/tasks/main.yml b/tests/integration/targets/test_mysql_role/tasks/main.yml index b517fc0..44e3308 100644 --- a/tests/integration/targets/test_mysql_role/tasks/main.yml +++ b/tests/integration/targets/test_mysql_role/tasks/main.yml @@ -18,3 +18,7 @@ - include_tasks: test_priv_subtract.yml vars: enable_check_mode: yes + +- name: Test column case sensitive + ansible.builtin.import_tasks: + file: test_column_case_sensitive.yml diff --git a/tests/integration/targets/test_mysql_role/tasks/test_column_case_sensitive.yml b/tests/integration/targets/test_mysql_role/tasks/test_column_case_sensitive.yml new file mode 100644 index 0000000..74849e0 --- /dev/null +++ b/tests/integration/targets/test_mysql_role/tasks/test_column_case_sensitive.yml @@ -0,0 +1,149 @@ +--- + +- vars: + mysql_parameters: &mysql_params + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: '{{ mysql_host }}' + login_port: '{{ mysql_primary_port }}' + + block: + + # ========================= Prepare ======================================= + # We use query to prevent our module of changing the case + - name: Mysql_role Column case sensitive | Create a test table + community.mysql.mysql_query: + <<: *mysql_params + query: + - CREATE DATABASE mysql_role_column_case + - >- + CREATE TABLE mysql_role_column_case.t1 + (a int, B int, cC int, Dd int) + - >- + INSERT INTO mysql_role_column_case.t1 + (a, B, cC, Dd) VALUES (1,2,3,4) + + - name: Mysql_role Column case sensitive | Create users + community.mysql.mysql_user: + <<: *mysql_params + name: column_case_sensitive + host: '%' + password: 'msandbox' + + # ================= Reproduce failure ===================================== + + - name: Mysql_role Column case sensitive | Create role + community.mysql.mysql_role: + <<: *mysql_params + name: 'role_column_case_sensitive' + state: present + members: + - 'column_case_sensitive@%' + priv: + 'mysql_role_column_case.t1': 'SELECT(a, B, cC, Dd)' + + - name: Mysql_role Column case sensitive | Assert role privileges are all caps + community.mysql.mysql_query: + <<: *mysql_params + query: + - SHOW GRANTS FOR role_column_case_sensitive + register: column_case_insensitive_grants + failed_when: + # Column order may vary, thus test each separately + - >- + column_case_insensitive_grants.query_result[0][1] + is not search("A", ignorecase=false) + or column_case_insensitive_grants.query_result[0][1] + is not search("B", ignorecase=false) + or column_case_insensitive_grants.query_result[0][1] + is not search("CC", ignorecase=false) + or column_case_insensitive_grants.query_result[0][1] + is not search("DD", ignorecase=false) + + - name: Mysql_role Column case sensitive | Assert 1 column is accessible on MySQL + community.mysql.mysql_query: + <<: *mysql_params + login_user: column_case_sensitive + query: + - DESC mysql_role_column_case.t1 + register: assert_1_col_accessible + failed_when: + - assert_1_col_accessible.rowcount[0] | int != 1 + when: + - db_engine == 'mysql' + + - name: Mysql_role Column case sensitive | Assert 4 column are accessible on MariaDB + community.mysql.mysql_query: + <<: *mysql_params + login_user: column_case_sensitive + query: + - SET ROLE role_column_case_sensitive + - DESC mysql_role_column_case.t1 + register: assert_4_col_accessible + failed_when: + - assert_4_col_accessible.rowcount[1] | int != 4 + when: + - db_engine == 'mariadb' + + # ====================== Test the fix ===================================== + + - name: Mysql_role Column case sensitive | Recreate role with case sensitive + community.mysql.mysql_role: + <<: *mysql_params + name: 'role_column_case_sensitive' + state: present + members: + - 'column_case_sensitive@%' + priv: + 'mysql_role_column_case.t1': 'SELECT(a, B, cC, Dd)' + column_case_sensitive: true + + - name: Mysql_role Column case sensitive | Assert role privileges are case sensitive + community.mysql.mysql_query: + <<: *mysql_params + query: + - SHOW GRANTS FOR role_column_case_sensitive + register: column_case_sensitive_grants + failed_when: + # Column order may vary, thus test each separately + - >- + column_case_sensitive_grants.query_result[0][1] + is not search("a", ignorecase=false) + or column_case_sensitive_grants.query_result[0][1] + is not search("B", ignorecase=false) + or column_case_sensitive_grants.query_result[0][1] + is not search("cC", ignorecase=false) + or column_case_sensitive_grants.query_result[0][1] + is not search("Dd", ignorecase=false) + + - name: Mysql_role Column case sensitive | Assert 4 columns are accessible + community.mysql.mysql_query: + <<: *mysql_params + login_user: column_case_sensitive + query: + - SET ROLE role_column_case_sensitive + - DESC mysql_role_column_case.t1 + register: assert_4_col_accessible + failed_when: + - assert_4_col_accessible.rowcount[1] | int != 4 + + # ========================= Teardown ====================================== + + - name: Mysql_role Column case sensitive | Delete test users + community.mysql.mysql_user: + <<: *mysql_params + name: column_case_sensitive + host_all: true + state: absent + + - name: Mysql_role Column case sensitive | Delete role + community.mysql.mysql_role: + <<: *mysql_params + name: 'role_column_case_sensitive' + state: absent + + - name: Mysql_role Column case sensitive | Delete test database + community.mysql.mysql_db: + <<: *mysql_params + name: mysql_role_column_case + state: absent diff --git a/tests/integration/targets/test_mysql_user/tasks/main.yml b/tests/integration/targets/test_mysql_user/tasks/main.yml index dc5c9d3..4816805 100644 --- a/tests/integration/targets/test_mysql_user/tasks/main.yml +++ b/tests/integration/targets/test_mysql_user/tasks/main.yml @@ -286,3 +286,7 @@ - include_tasks: test_user_grants_with_roles_applied.yml - include_tasks: test_revoke_only_grant.yml + + - name: Mysql_user - test column case sensitive + ansible.builtin.import_tasks: + file: test_column_case_sensitive.yml diff --git a/tests/integration/targets/test_mysql_user/tasks/test_column_case_sensitive.yml b/tests/integration/targets/test_mysql_user/tasks/test_column_case_sensitive.yml new file mode 100644 index 0000000..68e95aa --- /dev/null +++ b/tests/integration/targets/test_mysql_user/tasks/test_column_case_sensitive.yml @@ -0,0 +1,134 @@ +--- + +- vars: + mysql_parameters: &mysql_params + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: '{{ mysql_host }}' + login_port: '{{ mysql_primary_port }}' + + block: + + # ========================= Prepare ======================================= + # We use query to prevent our module of changing the case + - name: Mysql_user Column case sensitive | Create a test table + community.mysql.mysql_query: + <<: *mysql_params + query: + - CREATE DATABASE mysql_user_column_case + - >- + CREATE TABLE mysql_user_column_case.t1 + (a int, B int, cC int, Dd int) + - >- + INSERT INTO mysql_user_column_case.t1 + (a, B, cC, Dd) VALUES (1,2,3,4) + + # ================= Reproduce failure ===================================== + + - name: Mysql_user Column case sensitive | Create test user + community.mysql.mysql_user: + <<: *mysql_params + name: column_case_sensitive + host: '%' + password: 'msandbox' + priv: + 'mysql_user_column_case.t1': 'SELECT(a, B, cC, Dd)' + + - name: Mysql_user Column case sensitive | Assert user privileges are all caps + community.mysql.mysql_query: + <<: *mysql_params + query: + - SHOW GRANTS FOR column_case_sensitive@'%' + register: column_case_insensitive_grants + failed_when: + # Column order may vary, thus test each separately + - >- + column_case_insensitive_grants.query_result[0][1] + is not search("A", ignorecase=false) + or column_case_insensitive_grants.query_result[0][1] + is not search("B", ignorecase=false) + or column_case_insensitive_grants.query_result[0][1] + is not search("CC", ignorecase=false) + or column_case_insensitive_grants.query_result[0][1] + is not search("DD", ignorecase=false) + + - name: Mysql_user Column case sensitive | Assert 1 column is accessible on MySQL 5.7 + community.mysql.mysql_query: + <<: *mysql_params + login_user: column_case_sensitive + query: + - DESC mysql_user_column_case.t1 + register: assert_1_col_accessible + failed_when: + - assert_1_col_accessible.rowcount[0] | int != 1 + when: + - db_engine == 'mysql' and db_version is version('5.7', '<=') + + - name: Mysql_user Column case sensitive | Assert 4 column are accessible on MariaDB and MySQL 8+ + community.mysql.mysql_query: + <<: *mysql_params + login_user: column_case_sensitive + query: + - DESC mysql_user_column_case.t1 + register: assert_4_col_accessible + failed_when: + - assert_4_col_accessible.rowcount[0] | int != 4 + when: + - >- + db_engine == 'mariadb' + or (db_engine == 'mysql' and db_version is version('8.0', '>=')) + + # ======================== Test fix ====================================== + + - name: Mysql_user Column case sensitive | Create users with case sensitive + community.mysql.mysql_user: + <<: *mysql_params + name: column_case_sensitive + host: '%' + password: 'msandbox' + priv: + 'mysql_user_column_case.t1': 'SELECT(a, B, cC, Dd)' + column_case_sensitive: true + + - name: Mysql_user Column case sensitive | Assert user privileges are case sensitive + community.mysql.mysql_query: + <<: *mysql_params + query: + - SHOW GRANTS FOR column_case_sensitive@'%' + register: column_case_sensitive_grants + failed_when: + # Column order may vary, thus test each separately + - >- + column_case_sensitive_grants.query_result[0][1] + is not search("a", ignorecase=false) + or column_case_sensitive_grants.query_result[0][1] + is not search("B", ignorecase=false) + or column_case_sensitive_grants.query_result[0][1] + is not search("cC", ignorecase=false) + or column_case_sensitive_grants.query_result[0][1] + is not search("Dd", ignorecase=false) + + - name: Mysql_user Column case sensitive | Assert 4 columns are accessible + community.mysql.mysql_query: + <<: *mysql_params + login_user: column_case_sensitive + query: + - DESC mysql_user_column_case.t1 + register: assert_4_col_accessible + failed_when: + - assert_4_col_accessible.rowcount[0] | int != 4 + + # ========================= Teardown ====================================== + + - name: Mysql_user Column case sensitive | Delete test users + community.mysql.mysql_user: + <<: *mysql_params + name: column_case_sensitive + host_all: true + state: absent + + - name: Mysql_user Column case sensitive | Delete test database + community.mysql.mysql_db: + <<: *mysql_params + name: mysql_user_column_case + state: absent diff --git a/tests/unit/plugins/module_utils/test_mysql_user.py b/tests/unit/plugins/module_utils/test_mysql_user.py index 46b3b8e..bb1ec24 100644 --- a/tests/unit/plugins/module_utils/test_mysql_user.py +++ b/tests/unit/plugins/module_utils/test_mysql_user.py @@ -9,7 +9,8 @@ from ansible_collections.community.mysql.plugins.module_utils.user import ( handle_grant_on_col, has_grant_on_col, normalize_col_grants, - sort_column_order + sort_column_order, + privileges_unpack, ) @@ -92,3 +93,21 @@ def test_handle_grant_on_col(privileges, start, end, output): def test_normalize_col_grants(input_, expected): """Tests normalize_col_grants function.""" assert normalize_col_grants(input_) == expected + + +@pytest.mark.parametrize( + 'priv,expected,mode,column_case_sensitive,ensure_usage', + [ + ('mydb.*:SELECT', {'"mydb".*': ['SELECT']}, 'ANSI', False, False), + ('mydb.*:SELECT', {'`mydb`.*': ['SELECT']}, 'NOTANSI', False, False), + ('mydb.*:SELECT', {'"mydb".*': ['SELECT'], '*.*': ['USAGE']}, 'ANSI', False, True), + ('mydb.*:SELECT', {'`mydb`.*': ['SELECT'], '*.*': ['USAGE']}, 'NOTANSI', False, True), + ('mydb.*:SELECT (a)', {'`mydb`.*': ['SELECT (A)']}, 'NOTANSI', False, False), + ('mydb.*:UPDATE (b, a)', {'`mydb`.*': ['UPDATE (a, b)']}, 'NOTANSI', True, False), + ('mydb.*:SELECT (b, a, c)', {'`mydb`.*': ['SELECT (A, B, C)']}, 'NOTANSI', False, False), + ('mydb.*:SELECT (b, a, c)', {'`mydb`.*': ['SELECT (a, b, c)']}, 'NOTANSI', True, False), + ] +) +def test_privileges_unpack(priv, mode, column_case_sensitive, ensure_usage, expected): + """Tests privileges_unpack function.""" + assert privileges_unpack(priv, mode, column_case_sensitive, ensure_usage) == expected From 6b7cc14989ba4b93003f4db457564ab63fd3c851 Mon Sep 17 00:00:00 2001 From: lkthomas Date: Thu, 12 Oct 2023 19:50:54 +0800 Subject: [PATCH 157/217] switch "PRIMARY" to "MASTER" on description (#573) * switch "PRIMARY" to "MASTER" on description * Update plugins/modules/mysql_replication.py * Add author to contributors lists --------- Co-authored-by: Thomas Lau Co-authored-by: Thomas Lau --- CONTRIBUTORS | 1 + plugins/modules/mysql_replication.py | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 36e8a08..06fb579 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -152,6 +152,7 @@ ldesgrange leeadh LeonB leucos +lkthomas loomsen lorin lowwalker diff --git a/plugins/modules/mysql_replication.py b/plugins/modules/mysql_replication.py index 8029a5a..934b479 100644 --- a/plugins/modules/mysql_replication.py +++ b/plugins/modules/mysql_replication.py @@ -23,12 +23,12 @@ options: mode: description: - Module operating mode. Could be - C(changeprimary) (CHANGE PRIMARY TO), - C(getprimary) (SHOW PRIMARY STATUS), - C(getreplica) (SHOW REPLICA), + C(changeprimary) (CHANGE MASTER TO), + C(getprimary) (SHOW MASTER STATUS), + C(getreplica) (SHOW REPLICA STATUS), C(startreplica) (START REPLICA), C(stopreplica) (STOP REPLICA), - C(resetprimary) (RESET PRIMARY) - supported since community.mysql 0.1.0, + C(resetprimary) (RESET MASTER) - supported since community.mysql 0.1.0, C(resetreplica) (RESET REPLICA), C(resetreplicaall) (RESET REPLICA ALL). type: str From 3ef9bda95f995eb74705ed17a12bcc874a71c6b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20Inderm=C3=BChle?= Date: Mon, 23 Oct 2023 11:26:46 +0200 Subject: [PATCH 158/217] feat[mysql_info]: add 'users_info' filter (#580) * add documentation for new mysql_info users_info filter * Add integration tests for mysql_info users_info * fix list parsing when cursor come from mysql_info Mysql_info use a DictCursor and mysql_user a normal cursor. * fix case when an account as same user but different host and password * document why certain authentications plugins cause issues * add version_added for users_info to the documentation * Add 'users' description to differentiate it from 'users_info' --------- Co-authored-by: Andrew Klychkov --- .../fragments/lie_mysql_info_users_info.yml | 5 + plugins/module_utils/user.py | 59 +++- plugins/modules/mysql_info.py | 149 +++++++++- .../files/users_info_create_procedure.sql | 7 + .../tasks/filter_users_info.yml | 280 ++++++++++++++++++ .../targets/test_mysql_info/tasks/main.yml | 4 + 6 files changed, 492 insertions(+), 12 deletions(-) create mode 100644 changelogs/fragments/lie_mysql_info_users_info.yml create mode 100644 tests/integration/targets/test_mysql_info/files/users_info_create_procedure.sql create mode 100644 tests/integration/targets/test_mysql_info/tasks/filter_users_info.yml diff --git a/changelogs/fragments/lie_mysql_info_users_info.yml b/changelogs/fragments/lie_mysql_info_users_info.yml new file mode 100644 index 0000000..5d7526f --- /dev/null +++ b/changelogs/fragments/lie_mysql_info_users_info.yml @@ -0,0 +1,5 @@ +--- + +minor_changes: + + - mysql_info - add filter ``users_info`` (https://github.com/ansible-collections/community.mysql/pull/580). diff --git a/plugins/module_utils/user.py b/plugins/module_utils/user.py index e1d80ab..a88b32e 100644 --- a/plugins/module_utils/user.py +++ b/plugins/module_utils/user.py @@ -112,23 +112,40 @@ def get_grants(cursor, user, host): return grants.split(", ") -def get_existing_authentication(cursor, user): +def get_existing_authentication(cursor, user, host): # Return the plugin and auth_string if there is exactly one distinct existing plugin and auth_string. cursor.execute("SELECT VERSION()") - if 'mariadb' in cursor.fetchone()[0].lower(): + srv_type = cursor.fetchone() + # Mysql_info use a DictCursor so we must convert back to a list + # otherwise we get KeyError 0 + if isinstance(srv_type, dict): + srv_type = list(srv_type.values()) + + if 'mariadb' in srv_type[0].lower(): # before MariaDB 10.2.19 and 10.3.11, "password" and "authentication_string" can differ # when using mysql_native_password cursor.execute("""select plugin, auth from ( select plugin, password as auth from mysql.user where user=%(user)s + and host=%(host)s union select plugin, authentication_string as auth from mysql.user where user=%(user)s - ) x group by plugin, auth limit 2 - """, {'user': user}) + and host=%(host)s) x group by plugin, auth limit 2 + """, {'user': user, 'host': host}) else: - cursor.execute("""select plugin, authentication_string as auth from mysql.user where user=%(user)s - group by plugin, authentication_string limit 2""", {'user': user}) + cursor.execute("""select plugin, authentication_string as auth + from mysql.user where user=%(user)s and host=%(host)s + group by plugin, authentication_string limit 2""", {'user': user, 'host': host}) rows = cursor.fetchall() - if len(rows) == 1: - return {'plugin': rows[0][0], 'auth_string': rows[0][1]} + + # Mysql_info use a DictCursor so we must convert back to a list + # otherwise we get KeyError 0 + if isinstance(rows, dict): + rows = list(rows.values()) + + if isinstance(rows[0], tuple): + return {'plugin': rows[0][0], 'plugin_auth_string': rows[0][1]} + + if isinstance(rows[0], dict): + return {'plugin': rows[0].get('plugin'), 'plugin_auth_string': rows[0].get('auth')} return None @@ -149,7 +166,7 @@ def user_add(cursor, user, host, host_all, password, encrypted, used_existing_password = False if reuse_existing_password: - existing_auth = get_existing_authentication(cursor, user) + existing_auth = get_existing_authentication(cursor, user, host) if existing_auth: plugin = existing_auth['plugin'] plugin_hash_string = existing_auth['auth_string'] @@ -478,6 +495,12 @@ def privileges_get(cursor, user, host, maria_role=False): return x for grant in grants: + + # Mysql_info use a DictCursor so we must convert back to a list + # otherwise we get KeyError 0 + if isinstance(grant, dict): + grant = list(grant.values()) + if not maria_role: res = re.match("""GRANT (.+) ON (.+) TO (['`"]).*\\3@(['`"]).*\\4( IDENTIFIED BY PASSWORD (['`"]).+\\6)? ?(.*)""", grant[0]) else: @@ -777,6 +800,11 @@ def get_resource_limits(cursor, user, host): cursor.execute(query, (user, host)) res = cursor.fetchone() + # Mysql_info use a DictCursor so we must convert back to a list + # otherwise we get KeyError 0 + if isinstance(res, dict): + res = list(res.values()) + if not res: return None @@ -788,11 +816,22 @@ def get_resource_limits(cursor, user, host): } cursor.execute("SELECT VERSION()") - if 'mariadb' in cursor.fetchone()[0].lower(): + srv_type = cursor.fetchone() + # Mysql_info use a DictCursor so we must convert back to a list + # otherwise we get KeyError 0 + if isinstance(srv_type, dict): + srv_type = list(srv_type.values()) + + if 'mariadb' in srv_type[0].lower(): query = ('SELECT max_statement_time AS MAX_STATEMENT_TIME ' 'FROM mysql.user WHERE User = %s AND Host = %s') cursor.execute(query, (user, host)) res_max_statement_time = cursor.fetchone() + + # Mysql_info use a DictCursor so we must convert back to a list + # otherwise we get KeyError 0 + if isinstance(res_max_statement_time, dict): + res_max_statement_time = list(res_max_statement_time.values()) current_limits['MAX_STATEMENT_TIME'] = res_max_statement_time[0] return current_limits diff --git a/plugins/modules/mysql_info.py b/plugins/modules/mysql_info.py index cb9f029..73e403a 100644 --- a/plugins/modules/mysql_info.py +++ b/plugins/modules/mysql_info.py @@ -19,7 +19,7 @@ options: description: - Limit the collected information by comma separated string or YAML list. - Allowable values are C(version), C(databases), C(settings), C(global_status), - C(users), C(engines), C(master_status), C(slave_status), C(slave_hosts). + C(users), C(users_info), C(engines), C(master_status), C(slave_status), C(slave_hosts). - By default, collects all subsets. - You can use '!' before value (for example, C(!settings)) to exclude it from the information. - If you pass including and excluding values to the filter, for example, I(filter=!settings,version), @@ -74,6 +74,9 @@ EXAMPLES = r''' # Display only databases and users info: # ansible mysql-hosts -m mysql_info -a 'filter=databases,users' +# Display all users privileges: +# ansible mysql-hosts -m mysql_info -a 'filter=users_info' + # Display only slave status: # ansible standby -m mysql_info -a 'filter=slave_status' @@ -122,6 +125,38 @@ EXAMPLES = r''' - databases exclude_fields: db_size return_empty_dbs: true + +- name: Clone users from one server to another + block: + # Step 1 + - name: Fetch information from a source server + delegate_to: server_source + community.mysql.mysql_info: + filter: + - users_info + register: result + + # Step 2 + # Don't work with sha256_password and cache_sha2_password + - name: Clone users fetched in a previous task to a target server + community.mysql.mysql_user: + name: "{{ item.name }}" + host: "{{ item.host }}" + plugin: "{{ item.plugin | default(omit) }}" + plugin_auth_string: "{{ item.plugin_auth_string | default(omit) }}" + plugin_hash_string: "{{ item.plugin_hash_string | default(omit) }}" + tls_require: "{{ item.tls_require | default(omit) }}" + priv: "{{ item.priv | default(omit) }}" + resource_limits: "{{ item.resource_limits | default(omit) }}" + column_case_sensitive: true + state: present + loop: "{{ result.users_info }}" + loop_control: + label: "{{ item.name }}@{{ item.host }}" + when: + - item.name != 'root' # In case you don't want to import admin accounts + - item.name != 'mariadb.sys' + - item.name != 'mysql' ''' RETURN = r''' @@ -181,11 +216,31 @@ global_status: sample: - { "Innodb_buffer_pool_read_requests": 123, "Innodb_buffer_pool_reads": 32 } users: - description: Users information. + description: Return a dictionnary of users grouped by host and with global privileges only. returned: if not excluded by filter type: dict sample: - { "localhost": { "root": { "Alter_priv": "Y", "Alter_routine_priv": "Y" } } } +users_info: + description: + - Information about users accounts. + - The output can be used as an input of the M(community.mysql.mysql_user) plugin. + - Useful when migrating accounts to another server or to create an inventory. + - Does not support proxy privileges. If an account has proxy privileges, they won't appear in the output. + - Causes issues with authentications plugins C(sha256_password) and C(caching_sha2_password). + If the output is fed to M(community.mysql.mysql_user), the + ``plugin_auth_string`` will most likely be unreadable due to non-binary + characters. + returned: if not excluded by filter + type: dict + sample: + - { "plugin_auth_string": '*1234567', + "name": "user1", + "host": "host.com", + "plugin": "mysql_native_password", + "priv": "db1.*:SELECT/db2.*:SELECT", + "resource_limits": { "MAX_USER_CONNECTIONS": 100 } } + version_added: '3.8.0' engines: description: Information about the server's storage engines. returned: if not excluded by filter @@ -238,6 +293,12 @@ from ansible_collections.community.mysql.plugins.module_utils.mysql import ( get_connector_name, get_connector_version, ) + +from ansible_collections.community.mysql.plugins.module_utils.user import ( + privileges_get, + get_resource_limits, + get_existing_authentication, +) from ansible.module_utils.six import iteritems from ansible.module_utils._text import to_native @@ -274,6 +335,7 @@ class MySQL_Info(object): 'global_status': {}, 'engines': {}, 'users': {}, + 'users_info': {}, 'master_status': {}, 'slave_hosts': {}, 'slave_status': {}, @@ -342,6 +404,9 @@ class MySQL_Info(object): if 'users' in wanted: self.__get_users() + if 'users_info' in wanted: + self.__get_users_info() + if 'master_status' in wanted: self.__get_master_status() @@ -480,6 +545,86 @@ class MySQL_Info(object): if vname not in ('Host', 'User'): self.info['users'][host][user][vname] = self.__convert(val) + def __get_users_info(self): + """Get user privileges, passwords, resources_limits, ... + + Query the server to get all the users and return a string + of privileges that can be used by the mysql_user plugin. + For instance: + + "users_info": [ + { + "host": "users_info.com", + "priv": "*.*: ALL,GRANT", + "name": "users_info_adm" + }, + { + "host": "users_info.com", + "priv": "`mysql`.*: SELECT/`users_info_db`.*: SELECT", + "name": "users_info_multi" + } + ] + """ + res = self.__exec_sql('SELECT * FROM mysql.user') + if not res: + return None + + output = list() + for line in res: + user = line['User'] + host = line['Host'] + + user_priv = privileges_get(self.cursor, user, host) + + if not user_priv: + self.module.warn("No privileges found for %s on host %s" % (user, host)) + continue + + priv_string = list() + for db_table, priv in user_priv.items(): + # Proxy privileges are hard to work with because of different quotes or + # backticks like ''@'', ''@'%' or even ``@``. In addition, MySQL will + # forbid you to grant a proxy privileges through TCP. + if set(priv) == {'PROXY', 'GRANT'} or set(priv) == {'PROXY'}: + continue + + unquote_db_table = db_table.replace('`', '').replace("'", '') + priv_string.append('%s:%s' % (unquote_db_table, ','.join(priv))) + + # Only keep *.* USAGE if it's the only user privilege given + if len(priv_string) > 1 and '*.*:USAGE' in priv_string: + priv_string.remove('*.*:USAGE') + + resource_limits = get_resource_limits(self.cursor, user, host) + + copy_ressource_limits = dict.copy(resource_limits) + output_dict = { + 'name': user, + 'host': host, + 'priv': '/'.join(priv_string), + 'resource_limits': copy_ressource_limits, + } + + # Prevent returning a resource limit if empty + if resource_limits: + for key, value in resource_limits.items(): + if value == 0: + del output_dict['resource_limits'][key] + if len(output_dict['resource_limits']) == 0: + del output_dict['resource_limits'] + + authentications = get_existing_authentication(self.cursor, user, host) + if authentications: + output_dict.update(authentications) + + # TODO password_option + # TODO lock_option + # but both are not supported by mysql_user atm. So no point yet. + + output.append(output_dict) + + self.info['users_info'] = output + def __get_databases(self, exclude_fields, return_empty_dbs): """Get info about databases.""" if not exclude_fields: diff --git a/tests/integration/targets/test_mysql_info/files/users_info_create_procedure.sql b/tests/integration/targets/test_mysql_info/files/users_info_create_procedure.sql new file mode 100644 index 0000000..5a358f0 --- /dev/null +++ b/tests/integration/targets/test_mysql_info/files/users_info_create_procedure.sql @@ -0,0 +1,7 @@ +DELIMITER // +DROP PROCEDURE IF EXISTS users_info_db.get_all_items; +CREATE PROCEDURE users_info_db.get_all_items() +BEGIN +SELECT * from users_info_db.t1; +END // +DELIMITER ; diff --git a/tests/integration/targets/test_mysql_info/tasks/filter_users_info.yml b/tests/integration/targets/test_mysql_info/tasks/filter_users_info.yml new file mode 100644 index 0000000..2c126c1 --- /dev/null +++ b/tests/integration/targets/test_mysql_info/tasks/filter_users_info.yml @@ -0,0 +1,280 @@ +--- + +- module_defaults: + community.mysql.mysql_db: &mysql_defaults + login_user: "{{ mysql_user }}" + login_password: "{{ mysql_password }}" + login_host: "{{ mysql_host }}" + login_port: "{{ mysql_primary_port }}" + community.mysql.mysql_query: *mysql_defaults + community.mysql.mysql_info: *mysql_defaults + community.mysql.mysql_user: *mysql_defaults + + block: + + # ================================ Prepare ============================== + - name: Mysql_info users_info | Create databases + community.mysql.mysql_db: + name: + - users_info_db + - users_info_db2 + - users_info_db3 + state: present + + - name: Mysql_info users_info | Create tables + community.mysql.mysql_query: + query: + - >- + CREATE TABLE IF NOT EXISTS users_info_db.t1 + (id int, name varchar(9)) + - >- + CREATE TABLE IF NOT EXISTS users_info_db.T_UPPER + (id int, name1 varchar(9), NAME2 varchar(9), Name3 varchar(9)) + + # I failed to create a procedure using community.mysql.mysql_query. + # Maybe it's because we must changed the delimiter. + - name: Mysql_info users_info | Create procedure SQL file + ansible.builtin.template: + src: files/users_info_create_procedure.sql + dest: /root/create_procedure.sql + owner: root + group: root + mode: '0700' + + - name: Mysql_info users_info | Create a procedure + community.mysql.mysql_db: + name: all + state: import + target: /root/create_procedure.sql + + # Use a query instead of mysql_user, because we want to caches differences + # at the end and a bug in mysql_user would be invisible to this tests + - name: Mysql_info users_info | Prepare common tests users + community.mysql.mysql_query: + query: + - >- + CREATE USER users_info_adm@'users_info.com' IDENTIFIED WITH + mysql_native_password AS '*6C387FC3893DBA1E3BA155E74754DA6682D04747' + - > + GRANT ALL ON *.* to users_info_adm@'users_info.com' WITH GRANT + OPTION + + - >- + CREATE USER users_info_schema@'users_info.com' IDENTIFIED WITH + mysql_native_password AS '*6C387FC3893DBA1E3BA155E74754DA6682D04747' + - >- + GRANT SELECT, INSERT, UPDATE, DELETE ON users_info_db.* TO + users_info_schema@'users_info.com' + + - >- + CREATE USER users_info_table@'users_info.com' IDENTIFIED WITH + mysql_native_password AS '*6C387FC3893DBA1E3BA155E74754DA6682D04747' + - >- + GRANT SELECT, INSERT, UPDATE ON users_info_db.t1 TO + users_info_table@'users_info.com' + + - >- + CREATE USER users_info_col@'users_info.com' IDENTIFIED WITH + mysql_native_password AS '*6C387FC3893DBA1E3BA155E74754DA6682D04747' + WITH MAX_USER_CONNECTIONS 100 + - >- + GRANT SELECT (id) ON users_info_db.t1 TO + users_info_col@'users_info.com' + + - >- + CREATE USER users_info_proc@'users_info.com' IDENTIFIED WITH + mysql_native_password AS '*6C387FC3893DBA1E3BA155E74754DA6682D04747' + WITH MAX_USER_CONNECTIONS 2 MAX_CONNECTIONS_PER_HOUR 60 + - >- + GRANT EXECUTE ON PROCEDURE users_info_db.get_all_items TO + users_info_proc@'users_info.com' + + - >- + CREATE USER users_info_multi@'users_info.com' IDENTIFIED WITH + mysql_native_password AS '*6C387FC3893DBA1E3BA155E74754DA6682D04747' + - >- + GRANT SELECT ON mysql.* TO + users_info_multi@'users_info.com' + - >- + GRANT ALL ON users_info_db.* TO + users_info_multi@'users_info.com' + - >- + GRANT ALL ON users_info_db2.* TO + users_info_multi@'users_info.com' + - >- + GRANT ALL ON users_info_db3.* TO + users_info_multi@'users_info.com' + + - >- + CREATE USER users_info_usage_only@'users_info.com' IDENTIFIED WITH + mysql_native_password AS '*6C387FC3893DBA1E3BA155E74754DA6682D04747' + - >- + GRANT USAGE ON *.* TO + users_info_usage_only@'users_info.com' + + - >- + CREATE USER users_info_columns_uppercase@'users_info.com' + IDENTIFIED WITH mysql_native_password AS + '*6C387FC3893DBA1E3BA155E74754DA6682D04747' + - >- + GRANT SELECT,UPDATE(name1,NAME2,Name3) ON users_info_db.T_UPPER TO + users_info_columns_uppercase@'users_info.com' + + - >- + CREATE USER users_info_multi_hosts@'%' + IDENTIFIED WITH mysql_native_password AS + '*6C387FC3893DBA1E3BA155E74754DA6682D04747' + - GRANT SELECT ON users_info_db.* TO users_info_multi_hosts@'%' + + - >- + CREATE USER users_info_multi_hosts@'localhost' + IDENTIFIED WITH mysql_native_password AS + '*6C387FC3893DBA1E3BA155E74754DA6682D04747' + - >- + GRANT SELECT ON users_info_db.* TO + users_info_multi_hosts@'localhost' + + - >- + CREATE USER users_info_multi_hosts@'host1' + IDENTIFIED WITH mysql_native_password AS + '*6C387FC3893DBA1E3BA155E74754DA6682D04747' + - GRANT SELECT ON users_info_db.* TO users_info_multi_hosts@'host1' + + # Different password than the others users_info_multi_hosts + - >- + CREATE USER users_info_multi_hosts@'host2' + IDENTIFIED WITH mysql_native_password AS + '*CB3326D5279DE7915FE5D743232165EE887883CA' + - GRANT SELECT ON users_info_db.* TO users_info_multi_hosts@'host2' + + - name: Mysql_info users_info | Prepare tests users for MariaDB + community.mysql.mysql_user: + name: "{{ item.name }}" + host: "users_info.com" + plugin: "{{ item.plugin | default(omit) }}" + plugin_auth_string: "{{ item.plugin_auth_string | default(omit) }}" + plugin_hash_string: "{{ item.plugin_hash_string | default(omit) }}" + tls_require: "{{ item.tls_require | default(omit) }}" + priv: "{{ item.priv }}" + resource_limits: "{{ item.resource_limits | default(omit) }}" + column_case_sensitive: true + state: present + loop: + - name: users_info_socket # Only for MariaDB + priv: + '*.*': 'ALL' + plugin: 'unix_socket' + when: + - db_engine == 'mariadb' + + - name: Mysql_info users_info | Prepare tests users for MySQL + community.mysql.mysql_user: + name: "{{ item.name }}" + host: "users_info.com" + plugin: "{{ item.plugin | default(omit) }}" + plugin_auth_string: "{{ item.plugin_auth_string | default(omit) }}" + plugin_hash_string: "{{ item.plugin_hash_string | default(omit) }}" + tls_require: "{{ item.tls_require | default(omit) }}" + priv: "{{ item.priv }}" + resource_limits: "{{ item.resource_limits | default(omit) }}" + column_case_sensitive: true + state: present + loop: + - name: users_info_sha256 # Only for MySQL + priv: + '*.*': 'ALL' + plugin_auth_string: + '$5$/=') + + # ================================== Tests ============================== + + - name: Mysql_info users_info | Collect users_info + community.mysql.mysql_info: + filter: + - users_info + register: result + + - name: Recreate users from mysql_info users_info result + community.mysql.mysql_user: + name: "{{ item.name }}" + host: "{{ item.host }}" + plugin: "{{ item.plugin | default(omit) }}" + plugin_auth_string: "{{ item.plugin_auth_string | default(omit) }}" + plugin_hash_string: "{{ item.plugin_hash_string | default(omit) }}" + tls_require: "{{ item.tls_require | default(omit) }}" + priv: "{{ item.priv | default(omit) }}" + resource_limits: "{{ item.resource_limits | default(omit) }}" + column_case_sensitive: true + state: present + loop: "{{ result.users_info }}" + loop_control: + label: "{{ item.name }}@{{ item.host }}" + register: recreate_users_result + failed_when: + - recreate_users_result is changed + when: + - item.name != 'root' + - item.name != 'mysql' + - item.name != 'mariadb.sys' + - item.name != 'mysql.sys' + - item.name != 'mysql.infoschema' + + + # ================================== Cleanup ============================ + + - name: Mysql_info users_info | Cleanup users_info + community.mysql.mysql_user: + name: "{{ item }}" + host_all: true + column_case_sensitive: true + state: absent + loop: + - users_info_adm + - users_info_schema + - users_info_table + - users_info_col + - users_info_proc + - users_info_multi + - users_info_db + - users_info_usage_only + - users_info_columns_uppercase + - users_info_multi_hosts + + - name: Mysql_info users_info | Cleanup databases + community.mysql.mysql_db: + name: + - users_info_db + - users_info_db2 + - users_info_db3 + state: absent + + - name: Mysql_info users_info | Cleanup sql file for the procedure + ansible.builtin.file: + path: /root/create_procedure.sql + 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 be367f0..5d34da9 100644 --- a/tests/integration/targets/test_mysql_info/tasks/main.yml +++ b/tests/integration/targets/test_mysql_info/tasks/main.yml @@ -219,3 +219,7 @@ assert: that: - result.databases.allviews.size == 0 + + - name: Import tasks file to tests users_info filter + ansible.builtin.import_tasks: + file: filter_users_info.yml From f31d5a10c17357c25fcb7dea23e025811e7be6f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20Inderm=C3=BChle?= Date: Wed, 25 Oct 2023 15:11:40 +0200 Subject: [PATCH 159/217] fix list of tested ansible-core versions (#582) --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index f7e062c..3a393a1 100644 --- a/README.md +++ b/README.md @@ -82,8 +82,6 @@ Here is the table for the support timeline: ### ansible-core -- stable-2.12 -- stable-2.13 - stable-2.14 - stable-2.15 - stable-2.16 From fd0b1919c3ecc831efe98ebdc1bedf8895a01cd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20Inderm=C3=BChle?= Date: Thu, 26 Oct 2023 11:08:35 +0200 Subject: [PATCH 160/217] Release 3.8.0 commit (#583) --- CHANGELOG.rst | 24 +++++++++++++ changelogs/569_fix_column_uppercasing.yml | 21 ------------ changelogs/changelog.yaml | 34 +++++++++++++++++++ .../drop_ansible_core_2_12_and_2_13.yml | 11 ------ .../fragments/lie_mysql_info_users_info.yml | 5 --- galaxy.yml | 2 +- 6 files changed, 59 insertions(+), 38 deletions(-) delete mode 100644 changelogs/569_fix_column_uppercasing.yml delete mode 100644 changelogs/fragments/drop_ansible_core_2_12_and_2_13.yml delete mode 100644 changelogs/fragments/lie_mysql_info_users_info.yml diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 31ee41a..f6c6cb8 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,30 @@ Community MySQL Collection Release Notes This changelog describes changes after version 2.0.0. +v3.8.0 +====== + +Release Summary +--------------- + +This is the minor release of the ``community.mysql`` collection. +This changelog contains all changes to the modules and plugins in this +collection that have been made after the previous release. + +Major Changes +------------- + +- The community.mysql collection no longer supports ``ansible-core 2.12`` and ``ansible-core 2.13``. While we take no active measures to prevent usage and there are no plans to introduce incompatible code to the modules, we will stop testing those versions. Both are or will soon be End of Life and if you are still using them, you should consider upgrading to the ``latest Ansible / ansible-core 2.15 or later`` as soon as possible (https://github.com/ansible-collections/community.mysql/pull/574). +- mysql_role - the ``column_case_sensitive`` argument's default value will be changed to ``true`` in community.mysql 4.0.0. If your playbook expected the column to be automatically uppercased for your roles privileges, you should set this to false explicitly (https://github.com/ansible-collections/community.mysql/issues/578). +- mysql_user - the ``column_case_sensitive`` argument's default value will be changed to ``true`` in community.mysql 4.0.0. If your playbook expected the column to be automatically uppercased for your users privileges, you should set this to false explicitly (https://github.com/ansible-collections/community.mysql/issues/577). + +Minor Changes +------------- + +- mysql_info - add filter ``users_info`` (https://github.com/ansible-collections/community.mysql/pull/580). +- mysql_role - add ``column_case_sensitive`` option to prevent field names from being uppercased (https://github.com/ansible-collections/community.mysql/pull/569). +- mysql_user - add ``column_case_sensitive`` option to prevent field names from being uppercased (https://github.com/ansible-collections/community.mysql/pull/569). + v3.7.2 ====== diff --git a/changelogs/569_fix_column_uppercasing.yml b/changelogs/569_fix_column_uppercasing.yml deleted file mode 100644 index 781304e..0000000 --- a/changelogs/569_fix_column_uppercasing.yml +++ /dev/null @@ -1,21 +0,0 @@ ---- -minor_changes: - - - mysql_user - add ``column_case_sensitive`` option to prevent field names - from being uppercased - (https://github.com/ansible-collections/community.mysql/pull/569). - - mysql_role - add ``column_case_sensitive`` option to prevent field names - from being uppercased - (https://github.com/ansible-collections/community.mysql/pull/569). - -major_changes: - - mysql_user - the ``column_case_sensitive`` argument's default value will be - changed to ``true`` in community.mysql 4.0.0. If your playbook expected the - column to be automatically uppercased for your users privileges, you should - set this to false explicitly - (https://github.com/ansible-collections/community.mysql/issues/577). - - mysql_role - the ``column_case_sensitive`` argument's default value will be - changed to ``true`` in community.mysql 4.0.0. If your playbook expected the - column to be automatically uppercased for your roles privileges, you should - set this to false explicitly - (https://github.com/ansible-collections/community.mysql/issues/578). diff --git a/changelogs/changelog.yaml b/changelogs/changelog.yaml index e3431f3..a97b2a8 100644 --- a/changelogs/changelog.yaml +++ b/changelogs/changelog.yaml @@ -346,3 +346,37 @@ releases: - 3.7.2.yml - 553_fix_connection_arguemnts_for_old_mysqldb_driver.yaml release_date: '2023-05-25' + 3.8.0: + changes: + major_changes: + - The community.mysql collection no longer supports ``ansible-core 2.12`` and + ``ansible-core 2.13``. While we take no active measures to prevent usage and + there are no plans to introduce incompatible code to the modules, we will + stop testing those versions. Both are or will soon be End of Life and if you + are still using them, you should consider upgrading to the ``latest Ansible + / ansible-core 2.15 or later`` as soon as possible (https://github.com/ansible-collections/community.mysql/pull/574). + - mysql_role - the ``column_case_sensitive`` argument's default value will be + changed to ``true`` in community.mysql 4.0.0. If your playbook expected the + column to be automatically uppercased for your roles privileges, you should + set this to false explicitly (https://github.com/ansible-collections/community.mysql/issues/578). + - mysql_user - the ``column_case_sensitive`` argument's default value will be + changed to ``true`` in community.mysql 4.0.0. If your playbook expected the + column to be automatically uppercased for your users privileges, you should + set this to false explicitly (https://github.com/ansible-collections/community.mysql/issues/577). + minor_changes: + - mysql_info - add filter ``users_info`` (https://github.com/ansible-collections/community.mysql/pull/580). + - mysql_role - add ``column_case_sensitive`` option to prevent field names from + being uppercased (https://github.com/ansible-collections/community.mysql/pull/569). + - mysql_user - add ``column_case_sensitive`` option to prevent field names from + being uppercased (https://github.com/ansible-collections/community.mysql/pull/569). + release_summary: 'This is the minor release of the ``community.mysql`` collection. + + This changelog contains all changes to the modules and plugins in this + + collection that have been made after the previous release.' + fragments: + - 3.8.0.yml + - 569_fix_column_uppercasing.yml + - drop_ansible_core_2_12_and_2_13.yml + - lie_mysql_info_users_info.yml + release_date: '2023-10-25' diff --git a/changelogs/fragments/drop_ansible_core_2_12_and_2_13.yml b/changelogs/fragments/drop_ansible_core_2_12_and_2_13.yml deleted file mode 100644 index 29a363e..0000000 --- a/changelogs/fragments/drop_ansible_core_2_12_and_2_13.yml +++ /dev/null @@ -1,11 +0,0 @@ ---- - -major_changes: - - - The community.mysql collection no longer supports ``ansible-core 2.12`` and - ``ansible-core 2.13``. While we take no active measures to prevent usage - and there are no plans to introduce incompatible code to the modules, we - will stop testing those versions. Both are or will soon be End of Life and - if you are still using them, you should consider upgrading to the - ``latest Ansible / ansible-core 2.15 or later`` as soon as possible - (https://github.com/ansible-collections/community.mysql/pull/574). diff --git a/changelogs/fragments/lie_mysql_info_users_info.yml b/changelogs/fragments/lie_mysql_info_users_info.yml deleted file mode 100644 index 5d7526f..0000000 --- a/changelogs/fragments/lie_mysql_info_users_info.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- - -minor_changes: - - - mysql_info - add filter ``users_info`` (https://github.com/ansible-collections/community.mysql/pull/580). diff --git a/galaxy.yml b/galaxy.yml index 39a271e..c443a7b 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -1,7 +1,7 @@ --- namespace: community name: mysql -version: 3.7.2 +version: 3.8.0 readme: README.md authors: - Ansible community From 0dbedf57cb988c3a5c3444f79d2da996e101edf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20Inderm=C3=BChle?= Date: Thu, 26 Oct 2023 14:21:28 +0200 Subject: [PATCH 161/217] Document MySQL and MariaDB don't store roles with same manner (#584) --- plugins/module_utils/user.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/plugins/module_utils/user.py b/plugins/module_utils/user.py index a88b32e..dbc1c9b 100644 --- a/plugins/module_utils/user.py +++ b/plugins/module_utils/user.py @@ -743,6 +743,14 @@ def privileges_grant(cursor, user, host, db_table, priv, tls_requires, maria_rol priv_string = ",".join([p for p in priv if p not in ('GRANT', )]) query = ["GRANT %s ON %s" % (priv_string, db_table)] + # MySQL and MariaDB don't store roles in the user table the same manner: + # select user, host from mysql.user; + # +------------------+-----------+ + # | user | host | + # +------------------+-----------+ + # | role_foo | % | <- MySQL + # | role_foo | | <- MariaDB + # +------------------+-----------+ if not maria_role: query.append("TO %s@%s") params = (user, host) From 8dfab12bae0dfe9bbcb4d40f7cdd7670e457c5fa Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Mon, 13 Nov 2023 12:35:39 +0100 Subject: [PATCH 162/217] README: Add forum info (#589) --- README.md | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3a393a1..0e18400 100644 --- a/README.md +++ b/README.md @@ -40,9 +40,21 @@ They also should be subscribed to Ansible's [The Bullhorn newsletter](https://do ## Communication +> The `GitHub Discussions` feature is disabled in this repository. Use the `mysql` tag on the forum in the [Project Discussions](https://forum.ansible.com/new-topic?title=topic%20title&body=topic%20body&category=project&tags=mysql) or [Get Help](https://forum.ansible.com/new-topic?title=topic%20title&body=topic%20body&category=help&tags=mysql) category instead. + We announce releases and important changes through Ansible's [The Bullhorn newsletter](https://eepurl.com/gZmiEP). Be sure you are subscribed. -Join us on Matrix in the `#mysql:ansible.com` [room](https://matrix.to/#/#mysql:ansible.com), the `#users:ansible.com` [room](https://matrix.to/#/#users:ansible.com) (general use questions and support), `#ansible-community:ansible.com` [room](https://matrix.to/#/#community:ansible.com) (community and collection development questions), and other Matrix rooms or corresponding bridged Libera.Chat channels. See the [Ansible Communication Guide](https://docs.ansible.com/ansible/devel/community/communication.html) for details. +Join [our team](https://forum.ansible.com/g/MySQLTeam) on: +* The Ansible forums: + * [News & Announcements](https://forum.ansible.com/c/news/5/none) + * [Get Help](https://forum.ansible.com/c/help/6/none) + * [Social Spaces](https://forum.ansible.com/c/chat/4) + * [Posts tagged 'mysql'](https://forum.ansible.com/tag/mysql) +* Matrix: + * `#mysql:ansible.com` [room](https://matrix.to/#/#mysql:ansible.com): questions on how to contribute and use this collection. + * `#users:ansible.com` [room](https://matrix.to/#/#users:ansible.com): general use questions and support. + * `#ansible-community:ansible.com` [room](https://matrix.to/#/#community:ansible.com): community and collection development questions. + * other Matrix rooms; see the [Ansible Communication Guide](https://docs.ansible.com/ansible/devel/community/communication.html) for details. We take part in the global quarterly [Ansible Contributor Summit](https://github.com/ansible/community/wiki/Contributor-Summit) virtually or in-person. Track [The Bullhorn newsletter](https://eepurl.com/gZmiEP) and join us. @@ -50,9 +62,11 @@ For more information about communication, refer to the [Ansible Communication gu ## Governance +We, [the MySQL team](https://forum.ansible.com/g/MySQLTeam), use [the forum](https://forum.ansible.com/tag/mysql) posts tagged with `mysql` for general announcements and discussions. + The process of decision making in this collection is based on discussing and finding consensus among participants. -Every voice is important and every idea is valuable. If you have something on your mind, create an issue or dedicated discussion and let's discuss it! +Every voice is important and every idea is valuable. If you have something on your mind, create an issue or dedicated forum [discussion](https://forum.ansible.com/new-topic?title=topic%20title&body=topic%20body&category=project&tags=mysql) and let's discuss it! ## Included content @@ -68,7 +82,7 @@ Every voice is important and every idea is valuable. If you have something on yo ## Releases Support Timeline -It has been [decided](https://github.com/ansible-collections/community.mysql/discussions/537) to maintain each major release (1.x.y, 2.x.y, ...) for two years after the next major version is released. +We maintain each major release (1.x.y, 2.x.y, ...) for two years after the next major version is released. Here is the table for the support timeline: From 81ab18d56c64f64cb5bc369ce7fc79ff1aba1eed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20Inderm=C3=BChle?= Date: Thu, 30 Nov 2023 13:39:34 +0100 Subject: [PATCH 163/217] chore: fix conditional statements should not include jinja 2 templating (#599) Thanks to @tompal3 for your contribution --- .../targets/setup_controller/tasks/verify.yml | 14 ++-- .../test_mysql_db/tasks/state_dump_import.yml | 2 +- .../tasks/mysql_replication_channel.yml | 78 ++++++++++++------- .../tasks/mysql_replication_initial.yml | 30 ++++--- .../targets/test_mysql_user/tasks/main.yml | 8 +- 5 files changed, 87 insertions(+), 45 deletions(-) diff --git a/tests/integration/targets/setup_controller/tasks/verify.yml b/tests/integration/targets/setup_controller/tasks/verify.yml index 74aa0f2..e5b4c94 100644 --- a/tests/integration/targets/setup_controller/tasks/verify.yml +++ b/tests/integration/targets/setup_controller/tasks/verify.yml @@ -19,8 +19,11 @@ - name: Assert that test container runs the expected MySQL/MariaDB version assert: that: - - "'{{ primary_info.version.major }}.{{ primary_info.version.minor }}\ - .{{ primary_info.version.release }}' == '{{ db_version }}'" + - registred_db_version == db_version + vars: + registred_db_version: + "{{ primary_info.version.major }}.{{ primary_info.version.minor }}\ + .{{ primary_info.version.release }}" - name: Assert that mysql_info module used the expected version of pymysql assert: @@ -52,8 +55,9 @@ - name: Assert that we run the expected ansible version assert: that: - - > - "{{ ansible_version.major }}.{{ ansible_version.minor }}" - is version(test_ansible_version, '==') + - ansible_running_version == test_ansible_version + vars: + ansible_running_version: + "{{ ansible_version.major }}.{{ ansible_version.minor }}" when: - test_ansible_version != 'devel' # Devel will change overtime diff --git a/tests/integration/targets/test_mysql_db/tasks/state_dump_import.yml b/tests/integration/targets/test_mysql_db/tasks/state_dump_import.yml index b4f9cda..e4ae762 100644 --- a/tests/integration/targets/test_mysql_db/tasks/state_dump_import.yml +++ b/tests/integration/targets/test_mysql_db/tasks/state_dump_import.yml @@ -339,7 +339,7 @@ assert: that: - result is changed - - "result.db =='{{ db_name }}'" + - result.db == db_name # - name: Dump and Import | Assert database was backed up successfully # command: "file {{ db_file_name }}" diff --git a/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_channel.yml b/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_channel.yml index f438dbf..7d37df0 100644 --- a/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_channel.yml +++ b/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_channel.yml @@ -34,8 +34,14 @@ - assert: that: - - result is changed - - result.queries == ["CHANGE MASTER TO MASTER_HOST='{{ mysql_host }}',MASTER_USER='{{ replication_user }}',MASTER_PASSWORD='********',MASTER_PORT={{ mysql_primary_port }},MASTER_LOG_FILE='{{ mysql_primary_status.File }}',MASTER_LOG_POS={{ mysql_primary_status.Position }} FOR CHANNEL '{{ test_channel }}'"] + - result is changed + - result.queries == result_query + vars: + result_query: ["CHANGE MASTER TO MASTER_HOST='{{ mysql_host }}',\ + MASTER_USER='{{ replication_user }}',MASTER_PASSWORD='********',\ + MASTER_PORT={{ mysql_primary_port }},MASTER_LOG_FILE=\ + '{{ mysql_primary_status.File }}',MASTER_LOG_POS=\ + {{ mysql_primary_status.Position }} FOR CHANNEL '{{ test_channel }}'"] # Test startreplica mode: - name: Start replica with channel @@ -48,8 +54,11 @@ - assert: that: - - result is changed - - result.queries == ["START SLAVE FOR CHANNEL '{{ test_channel }}'"] or result.queries == ["START REPLICA FOR CHANNEL '{{ test_channel }}'"] + - result is changed + - result.queries == result_query or result_query2 + vars: + result_query: ["START SLAVE FOR CHANNEL '{{ test_channel }}'"] + result_query2: ["START REPLICA FOR CHANNEL '{{ test_channel }}'"] # Test getreplica mode: - name: Get standby status with channel @@ -62,26 +71,34 @@ - assert: that: - - replica_status.Is_Replica == true - - replica_status.Master_Host == '{{ mysql_host }}' - - replica_status.Exec_Master_Log_Pos == mysql_primary_status.Position - - replica_status.Master_Port == {{ mysql_primary_port }} - - replica_status.Last_IO_Errno == 0 - - replica_status.Last_IO_Error == '' - - replica_status.Channel_Name == '{{ test_channel }}' - - replica_status is not changed + - replica_status.Is_Replica is truthy(convert_bool=True) + - replica_status.Master_Host == mysql_host_value + - replica_status.Exec_Master_Log_Pos == mysql_primary_status.Position + - replica_status.Master_Port == mysql_primary_port_value + - replica_status.Last_IO_Errno == 0 + - replica_status.Last_IO_Error == '' + - replica_status.Channel_Name == test_channel_value + - replica_status is not changed + vars: + mysql_host_value: '{{ mysql_host }}' + mysql_primary_port_value: '{{ mysql_primary_port }}' + test_channel_value: '{{ test_channel }}' when: mysql8022_and_higher == false - assert: that: - - replica_status.Is_Replica == true - - replica_status.Source_Host == '{{ mysql_host }}' - - replica_status.Exec_Source_Log_Pos == mysql_primary_status.Position - - replica_status.Source_Port == {{ mysql_primary_port }} - - replica_status.Last_IO_Errno == 0 - - replica_status.Last_IO_Error == '' - - replica_status.Channel_Name == '{{ test_channel }}' - - replica_status is not changed + - replica_status.Is_Replica is truthy(convert_bool=True) + - replica_status.Source_Host == mysql_host_value + - replica_status.Exec_Source_Log_Pos == mysql_primary_status.Position + - replica_status.Source_Port == mysql_primary_port_value + - replica_status.Last_IO_Errno == 0 + - replica_status.Last_IO_Error == '' + - replica_status.Channel_Name == test_channel_value + - replica_status is not changed + vars: + mysql_host_value: '{{ mysql_host }}' + mysql_primary_port_value: '{{ mysql_primary_port }}' + test_channel_value: '{{ test_channel }}' when: mysql8022_and_higher == true @@ -96,8 +113,11 @@ - assert: that: - - result is changed - - result.queries == ["STOP SLAVE FOR CHANNEL '{{ test_channel }}'"] or result.queries == ["STOP REPLICA FOR CHANNEL '{{ test_channel }}'"] + - result is changed + - result.queries == result_query or result.queries == result_query2 + vars: + result_query: ["STOP SLAVE FOR CHANNEL '{{ test_channel }}'"] + result_query2: ["STOP REPLICA FOR CHANNEL '{{ test_channel }}'"] # Test reset - name: Reset replica with channel @@ -110,8 +130,11 @@ - assert: that: - - result is changed - - result.queries == ["RESET SLAVE FOR CHANNEL '{{ test_channel }}'"] or result.queries == ["RESET REPLICA FOR CHANNEL '{{ test_channel }}'"] + - result is changed + - result.queries == result_query or result.queries == result_query2 + vars: + result_query: ["RESET SLAVE FOR CHANNEL '{{ test_channel }}'"] + result_query2: ["RESET REPLICA FOR CHANNEL '{{ test_channel }}'"] # Test reset all - name: Reset replica all with channel @@ -124,5 +147,8 @@ - assert: that: - - result is changed - - result.queries == ["RESET SLAVE ALL FOR CHANNEL '{{ test_channel }}'"] or result.queries == ["RESET REPLICA ALL FOR CHANNEL '{{ test_channel }}'"] + - result is changed + - result.queries == result_query or result.queries == result_query2 + vars: + result_query: ["RESET SLAVE ALL FOR CHANNEL '{{ test_channel }}'"] + result_query2: ["RESET REPLICA ALL FOR CHANNEL '{{ test_channel }}'"] 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 ca7301c..ea7a5ac 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 @@ -158,7 +158,13 @@ assert: that: - result is changed - - result.queries == ["CHANGE MASTER TO MASTER_HOST='{{ mysql_host }}',MASTER_USER='{{ replication_user }}',MASTER_PASSWORD='********',MASTER_PORT={{ mysql_primary_port }},MASTER_LOG_FILE='{{ mysql_primary_status.File }}',MASTER_LOG_POS={{ mysql_primary_status.Position }},MASTER_SSL=0,MASTER_SSL_CA=''"] + - result.queries == expected_queries + vars: + expected_queries: ["CHANGE MASTER TO MASTER_HOST='{{ mysql_host }}',\ + MASTER_USER='{{ replication_user }}',MASTER_PASSWORD='********',\ + MASTER_PORT={{ mysql_primary_port }},MASTER_LOG_FILE=\ + '{{ mysql_primary_status.File }}',MASTER_LOG_POS=\ + {{ mysql_primary_status.Position }},MASTER_SSL=0,MASTER_SSL_CA=''"] # Test startreplica mode: - name: Start replica @@ -185,26 +191,32 @@ - name: Assert that getreplica returns expected values for MySQL older than 8.0.22 and Mariadb assert: that: - - replica_status.Is_Replica == true - - replica_status.Master_Host == '{{ mysql_host }}' + - replica_status.Is_Replica is truthy(convert_bool=True) + - replica_status.Master_Host == mysql_host_value - replica_status.Exec_Master_Log_Pos == mysql_primary_status.Position - - replica_status.Master_Port == {{ mysql_primary_port }} + - replica_status.Master_Port == mysql_primary_port_value - replica_status.Last_IO_Errno == 0 - replica_status.Last_IO_Error == '' - replica_status is not changed - when: mysql8022_and_higher == false + vars: + mysql_host_value: "{{ mysql_host }}" + mysql_primary_port_value: "{{ mysql_primary_port }}" + when: mysql8022_and_higher is falsy(convert_bool=True) - name: Assert that getreplica returns expected values for MySQL newer than 8.0.22 assert: that: - - replica_status.Is_Replica == true - - replica_status.Source_Host == '{{ mysql_host }}' + - replica_status.Is_Replica is truthy(convert_bool=True) + - replica_status.Source_Host == mysql_host_value - replica_status.Exec_Source_Log_Pos == mysql_primary_status.Position - - replica_status.Source_Port == {{ mysql_primary_port }} + - replica_status.Source_Port == mysql_primary_port_value - replica_status.Last_IO_Errno == 0 - replica_status.Last_IO_Error == '' - replica_status is not changed - when: mysql8022_and_higher == true + vars: + mysql_host_value: "{{ mysql_host }}" + mysql_primary_port_value: "{{ mysql_primary_port }}" + when: mysql8022_and_higher is truthy(convert_bool=True) # Create test table and add data to it: - name: Create test table diff --git a/tests/integration/targets/test_mysql_user/tasks/main.yml b/tests/integration/targets/test_mysql_user/tasks/main.yml index 4816805..f4247e4 100644 --- a/tests/integration/targets/test_mysql_user/tasks/main.yml +++ b/tests/integration/targets/test_mysql_user/tasks/main.yml @@ -117,8 +117,8 @@ - name: Assert grant access for user1 on multiple database assert: that: - - "'{{ item }}' in result.stdout" - with_items: "{{ db_names }}" + - item in result.stdout + loop: "{{ db_names }}" - name: Show grants access for user2 on multiple database command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ user_name_2 }}'@'localhost'\"" @@ -127,8 +127,8 @@ - name: Assert grant access for user2 on multiple database assert: that: - - "'{{ item }}' in result.stdout" - with_items: "{{db_names}}" + - item in result.stdout + loop: "{{db_names}}" - include_tasks: utils/remove_user.yml vars: From 051aa48d8d1218f7a7b666e724fbfd98fa696007 Mon Sep 17 00:00:00 2001 From: ncc <47510820+n-cc@users.noreply.github.com> Date: Fri, 19 Jan 2024 08:37:28 -0600 Subject: [PATCH 164/217] feat[mysql_user]: add support for mysql user attributes (#604) * add support for mysql user attributes * fix CI * write integration tests * requested changes pt. 1 * requested changes pt. 2 * fix changelog fragment --------- Co-authored-by: n-cc --- changelogs/fragments/604-user-attributes.yaml | 2 + plugins/module_utils/user.py | 194 +++++-- plugins/modules/mysql_role.py | 2 +- plugins/modules/mysql_user.py | 28 +- .../targets/test_mysql_user/tasks/main.yml | 3 + .../tasks/test_user_attributes.yml | 474 ++++++++++++++++++ 6 files changed, 644 insertions(+), 59 deletions(-) create mode 100644 changelogs/fragments/604-user-attributes.yaml create mode 100644 tests/integration/targets/test_mysql_user/tasks/test_user_attributes.yml diff --git a/changelogs/fragments/604-user-attributes.yaml b/changelogs/fragments/604-user-attributes.yaml new file mode 100644 index 0000000..260201d --- /dev/null +++ b/changelogs/fragments/604-user-attributes.yaml @@ -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)." diff --git a/plugins/module_utils/user.py b/plugins/module_utils/user.py index dbc1c9b..1e5a275 100644 --- a/plugins/module_utils/user.py +++ b/plugins/module_utils/user.py @@ -10,6 +10,7 @@ __metaclass__ = type # Simplified BSD License (see simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause) import string +import json import re from ansible.module_utils.six import iteritems @@ -151,13 +152,17 @@ def get_existing_authentication(cursor, user, host): def user_add(cursor, user, host, host_all, password, encrypted, plugin, plugin_hash_string, plugin_auth_string, new_priv, - tls_requires, check_mode, reuse_existing_password): + attributes, tls_requires, reuse_existing_password, module): + # If attributes are set, perform a sanity check to ensure server supports user attributes before creating user + if attributes and not get_attribute_support(cursor): + module.fail_json(msg="user attributes were specified but the server does not support user attributes") + # we cannot create users without a proper hostname if host_all: - return {'changed': False, 'password_changed': False} + return {'changed': False, 'password_changed': False, 'attributes': attributes} - if check_mode: - return {'changed': True, 'password_changed': None} + if module.check_mode: + return {'changed': True, 'password_changed': None, 'attributes': attributes} # Determine what user management method server uses old_user_mgmt = impl.use_old_user_mgmt(cursor) @@ -205,7 +210,14 @@ def user_add(cursor, user, host, host_all, password, encrypted, privileges_grant(cursor, user, host, db_table, priv, tls_requires) if tls_requires is not None: 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): @@ -218,7 +230,7 @@ def is_hash(password): def user_mod(cursor, user, host, host_all, password, encrypted, plugin, plugin_hash_string, plugin_auth_string, new_priv, - append_privs, subtract_privs, tls_requires, module, role=False, maria_role=False): + append_privs, subtract_privs, attributes, tls_requires, module, role=False, maria_role=False): changed = False msg = "User unchanged" grant_option = False @@ -278,27 +290,26 @@ def user_mod(cursor, user, host, host_all, password, encrypted, if current_pass_hash != encrypted_password: password_changed = True msg = "Password updated" - if module.check_mode: - return {'changed': True, 'msg': msg, 'password_changed': password_changed} - if old_user_mgmt: - cursor.execute("SET PASSWORD FOR %s@%s = %s", (user, host, encrypted_password)) - msg = "Password updated (old style)" - else: - try: - cursor.execute("ALTER USER %s@%s IDENTIFIED WITH mysql_native_password AS %s", (user, host, encrypted_password)) - msg = "Password updated (new style)" - except (mysql_driver.Error) as e: - # https://stackoverflow.com/questions/51600000/authentication-string-of-root-user-on-mysql - # Replacing empty root password with new authentication mechanisms fails with error 1396 - if e.args[0] == 1396: - cursor.execute( - "UPDATE mysql.user SET plugin = %s, authentication_string = %s, Password = '' WHERE User = %s AND Host = %s", - ('mysql_native_password', encrypted_password, user, host) - ) - cursor.execute("FLUSH PRIVILEGES") - msg = "Password forced update" - else: - raise e + if not module.check_mode: + if old_user_mgmt: + cursor.execute("SET PASSWORD FOR %s@%s = %s", (user, host, encrypted_password)) + msg = "Password updated (old style)" + else: + try: + cursor.execute("ALTER USER %s@%s IDENTIFIED WITH mysql_native_password AS %s", (user, host, encrypted_password)) + msg = "Password updated (new style)" + except (mysql_driver.Error) as e: + # https://stackoverflow.com/questions/51600000/authentication-string-of-root-user-on-mysql + # Replacing empty root password with new authentication mechanisms fails with error 1396 + if e.args[0] == 1396: + cursor.execute( + "UPDATE mysql.user SET plugin = %s, authentication_string = %s, Password = '' WHERE User = %s AND Host = %s", + ('mysql_native_password', encrypted_password, user, host) + ) + cursor.execute("FLUSH PRIVILEGES") + msg = "Password forced update" + else: + raise e changed = True # Handle plugin authentication @@ -352,9 +363,8 @@ def user_mod(cursor, user, host, host_all, password, encrypted, if db_table not in new_priv: if user != "root" and "PROXY" not in priv: msg = "Privileges updated" - if module.check_mode: - return {'changed': True, 'msg': msg, 'password_changed': password_changed} - privileges_revoke(cursor, user, host, db_table, priv, grant_option, maria_role) + if not module.check_mode: + privileges_revoke(cursor, user, host, db_table, priv, grant_option, maria_role) changed = True # If the user doesn't currently have any privileges on a db.table, then @@ -363,9 +373,8 @@ def user_mod(cursor, user, host, host_all, password, encrypted, for db_table, priv in iteritems(new_priv): if db_table not in curr_priv: msg = "New privileges granted" - if module.check_mode: - return {'changed': True, 'msg': msg, 'password_changed': password_changed} - privileges_grant(cursor, user, host, db_table, priv, tls_requires, maria_role) + if not module.check_mode: + privileges_grant(cursor, user, host, db_table, priv, tls_requires, maria_role) changed = True # If the db.table specification exists in both the user's current privileges @@ -404,17 +413,58 @@ def user_mod(cursor, user, host, host_all, password, encrypted, if len(grant_privs) + len(revoke_privs) > 0: msg = "Privileges updated: granted %s, revoked %s" % (grant_privs, revoke_privs) - if module.check_mode: - return {'changed': True, 'msg': msg, 'password_changed': password_changed} - if len(revoke_privs) > 0: - privileges_revoke(cursor, user, host, db_table, revoke_privs, grant_option, maria_role) - if len(grant_privs) > 0: - privileges_grant(cursor, user, host, db_table, grant_privs, tls_requires, maria_role) + if not module.check_mode: + if len(revoke_privs) > 0: + privileges_revoke(cursor, user, host, db_table, revoke_privs, grant_option, maria_role) + if len(grant_privs) > 0: + 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_priv = privileges_get(cursor, user, host, maria_role) 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: continue @@ -422,24 +472,23 @@ def user_mod(cursor, user, host, host_all, password, encrypted, current_requires = get_tls_requires(cursor, user, host) if current_requires != tls_requires: msg = "TLS requires updated" - if module.check_mode: - return {'changed': True, 'msg': msg, 'password_changed': password_changed} - if not old_user_mgmt: - pre_query = "ALTER USER" - else: - pre_query = "GRANT %s ON *.* TO" % ",".join(get_grants(cursor, user, host)) + if not module.check_mode: + if not old_user_mgmt: + pre_query = "ALTER USER" + else: + pre_query = "GRANT %s ON *.* TO" % ",".join(get_grants(cursor, user, host)) - if tls_requires is not None: - query = " ".join((pre_query, "%s@%s")) - query_with_args = mogrify_requires(query, (user, host), tls_requires) - else: - query = " ".join((pre_query, "%s@%s REQUIRE NONE")) - query_with_args = query, (user, host) + if tls_requires is not None: + query = " ".join((pre_query, "%s@%s")) + query_with_args = mogrify_requires(query, (user, host), tls_requires) + else: + query = " ".join((pre_query, "%s@%s REQUIRE NONE")) + query_with_args = query, (user, host) - cursor.execute(*query_with_args) + cursor.execute(*query_with_args) 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): @@ -924,6 +973,45 @@ def limit_resources(module, cursor, user, host, resource_limits, check_mode): return True +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. + + Args: + cursor (cursor): DB driver cursor object. + user (str): User name. + 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): global impl cursor.execute("SELECT VERSION()") diff --git a/plugins/modules/mysql_role.py b/plugins/modules/mysql_role.py index e892093..5713791 100644 --- a/plugins/modules/mysql_role.py +++ b/plugins/modules/mysql_role.py @@ -931,7 +931,7 @@ class Role(): if privs: result = user_mod(self.cursor, self.name, self.host, None, None, None, None, None, None, - privs, append_privs, subtract_privs, None, + privs, append_privs, subtract_privs, None, None, self.module, role=True, maria_role=self.is_mariadb) changed = result['changed'] diff --git a/plugins/modules/mysql_user.py b/plugins/modules/mysql_user.py index 3e914e6..c6a02fc 100644 --- a/plugins/modules/mysql_user.py +++ b/plugins/modules/mysql_user.py @@ -155,7 +155,6 @@ options: - Cannot be used to set global variables, use the M(community.mysql.mysql_variables) module instead. type: dict version_added: '3.6.0' - column_case_sensitive: description: - The default is C(false). @@ -165,6 +164,13 @@ options: fields names in privileges. type: bool 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: - "MySQL server installs with default I(login_user) of C(root) and no password. @@ -257,6 +263,13 @@ EXAMPLES = r''' FUNCTION my_db.my_function: EXECUTE 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 community.mysql.mysql_user: name: bob @@ -405,6 +418,7 @@ def main(): tls_requires=dict(type='dict'), append_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), 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), @@ -437,6 +451,7 @@ def main(): append_privs = module.boolean(module.params["append_privs"]) subtract_privs = module.boolean(module.params['subtract_privs']) update_password = module.params['update_password'] + attributes = module.params['attributes'] ssl_cert = module.params["client_cert"] ssl_key = module.params["client_key"] ssl_ca = module.params["ca_cert"] @@ -500,21 +515,23 @@ def main(): priv = privileges_unpack(priv, mode, column_case_sensitive, ensure_usage=not subtract_privs) password_changed = False + final_attributes = None if state == "present": if user_exists(cursor, user, host, host_all): try: if update_password == "always": result = user_mod(cursor, user, host, host_all, password, encrypted, plugin, plugin_hash_string, plugin_auth_string, - priv, append_privs, subtract_privs, tls_requires, module) + priv, append_privs, subtract_privs, attributes, tls_requires, module) else: result = user_mod(cursor, user, host, host_all, None, encrypted, None, None, None, - priv, append_privs, subtract_privs, tls_requires, module) + priv, append_privs, subtract_privs, attributes, tls_requires, module) changed = result['changed'] msg = result['msg'] password_changed = result['password_changed'] + final_attributes = result['attributes'] except (SQLParseError, InvalidPrivsError, mysql_driver.Error) as e: module.fail_json(msg=to_native(e)) @@ -527,9 +544,10 @@ def main(): reuse_existing_password = update_password == 'on_new_username' result = user_add(cursor, user, host, host_all, password, encrypted, plugin, plugin_hash_string, plugin_auth_string, - priv, tls_requires, module.check_mode, reuse_existing_password) + priv, attributes, tls_requires, reuse_existing_password, module) changed = result['changed'] password_changed = result['password_changed'] + final_attributes = result['attributes'] if changed: msg = "User added" @@ -546,7 +564,7 @@ def main(): else: changed = False 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__': diff --git a/tests/integration/targets/test_mysql_user/tasks/main.yml b/tests/integration/targets/test_mysql_user/tasks/main.yml index f4247e4..f5e0748 100644 --- a/tests/integration/targets/test_mysql_user/tasks/main.yml +++ b/tests/integration/targets/test_mysql_user/tasks/main.yml @@ -267,6 +267,9 @@ tags: - issue_465 + # Tests for user attributes + - include_tasks: test_user_attributes.yml + # Tests for the TLS requires dictionary - include_tasks: test_tls_requirements.yml diff --git a/tests/integration/targets/test_mysql_user/tasks/test_user_attributes.yml b/tests/integration/targets/test_mysql_user/tasks/test_user_attributes.yml new file mode 100644 index 0000000..b5cec10 --- /dev/null +++ b/tests/integration/targets/test_mysql_user/tasks/test_user_attributes.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 }}" From 852c19a78a85956135c6ceaae02b50e364bbb5f6 Mon Sep 17 00:00:00 2001 From: William Felipe Welter Date: Fri, 19 Jan 2024 14:41:29 +0000 Subject: [PATCH 165/217] Using `show all slaves status` when using MariaDB to be consistent with MySQL (#602) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Using `show all slaves status` whe using MariaDB to be consistent with the MySQL behaviour. * Fixing lint issues * Fix issue by using dict attribute * Fix unit tests * fix lint test * Add unit tests * Fix unit tests * Adding changlog fragment * Update changelogs/fragments/602-show-all-slaves-status.yaml Co-authored-by: Laurent Indermühle * Refactoring change by moving common logic to the module_utils * Fix sanity checks * Fix sanity checks * Adding lines to fix sanity checks * Fixing sanity checks * Update changelogs/fragments/602-show-all-slaves-status.yaml Co-authored-by: Andrew Klychkov * Removing is_mariadb and is_mysql functions --------- Co-authored-by: Laurent Indermühle Co-authored-by: Andrew Klychkov --- .../fragments/602-show-all-slaves-status.yaml | 2 ++ plugins/module_utils/mysql.py | 7 +++++++ plugins/modules/mysql_info.py | 14 ++++++++++--- tests/unit/plugins/module_utils/test_mysql.py | 21 ++++++++++++++++++- tests/unit/plugins/modules/test_mysql_info.py | 14 ++++++------- 5 files changed, 47 insertions(+), 11 deletions(-) create mode 100644 changelogs/fragments/602-show-all-slaves-status.yaml diff --git a/changelogs/fragments/602-show-all-slaves-status.yaml b/changelogs/fragments/602-show-all-slaves-status.yaml new file mode 100644 index 0000000..8c9320c --- /dev/null +++ b/changelogs/fragments/602-show-all-slaves-status.yaml @@ -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). diff --git a/plugins/module_utils/mysql.py b/plugins/module_utils/mysql.py index b95d20d..10ccfcf 100644 --- a/plugins/module_utils/mysql.py +++ b/plugins/module_utils/mysql.py @@ -207,6 +207,13 @@ def get_server_version(cursor): 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): """Set session vars.""" for var, value in session_vars.items(): diff --git a/plugins/modules/mysql_info.py b/plugins/modules/mysql_info.py index 73e403a..303921b 100644 --- a/plugins/modules/mysql_info.py +++ b/plugins/modules/mysql_info.py @@ -5,6 +5,7 @@ # 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 + __metaclass__ = type DOCUMENTATION = r''' @@ -292,6 +293,7 @@ from ansible_collections.community.mysql.plugins.module_utils.mysql import ( mysql_driver_fail_msg, get_connector_name, get_connector_version, + get_server_implementation, ) 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 """ - def __init__(self, module, cursor): + def __init__(self, module, cursor, server_implementation): self.module = module self.cursor = cursor + self.server_implementation = server_implementation self.info = { 'version': {}, 'databases': {}, @@ -497,7 +500,10 @@ class MySQL_Info(object): def __get_slave_status(self): """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: for line in res: host = line['Master_Host'] @@ -738,10 +744,12 @@ def main(): 'Exception message: %s' % (connector_name, connector_version, config_file, to_native(e))) module.fail_json(msg) + server_implementation = get_server_implementation(cursor) + ############################### # Create object and do main job - mysql = MySQL_Info(module, cursor) + mysql = MySQL_Info(module, cursor, server_implementation) module.exit_json(changed=False, connector_name=connector_name, diff --git a/tests/unit/plugins/module_utils/test_mysql.py b/tests/unit/plugins/module_utils/test_mysql.py index ac4de24..5410575 100644 --- a/tests/unit/plugins/module_utils/test_mysql.py +++ b/tests/unit/plugins/module_utils/test_mysql.py @@ -1,9 +1,10 @@ from __future__ import (absolute_import, division, print_function) + __metaclass__ = type 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 @@ -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) 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 diff --git a/tests/unit/plugins/modules/test_mysql_info.py b/tests/unit/plugins/modules/test_mysql_info.py index 7aa9577..6aaf66e 100644 --- a/tests/unit/plugins/modules/test_mysql_info.py +++ b/tests/unit/plugins/modules/test_mysql_info.py @@ -14,15 +14,15 @@ from ansible_collections.community.mysql.plugins.modules.mysql_info import MySQL @pytest.mark.parametrize( - 'suffix,cursor_output', + 'suffix,cursor_output,server_implementation', [ - ('mysql', '5.5.1-mysql'), - ('log', '5.7.31-log'), - ('mariadb', '10.5.0-mariadb'), - ('', '8.0.22'), + ('mysql', '5.5.1-mysql', 'mysql'), + ('log', '5.7.31-log', 'mysql'), + ('mariadb', '10.5.0-mariadb', 'mariadb'), + ('', '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): if input_parameter == "SHOW GLOBAL VARIABLES": 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.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 From 5ed3eaf3eeb2d5681cd13625dbb380348ba84f5a Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Fri, 19 Jan 2024 15:51:47 +0100 Subject: [PATCH 166/217] Version 2.*.* is EOL (#605) --- README.md | 2 +- changelogs/fragments/0-stable-2-eol.yml | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 changelogs/fragments/0-stable-2-eol.yml diff --git a/README.md b/README.md index 0e18400..40264d2 100644 --- a/README.md +++ b/README.md @@ -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: - 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 - 4.x.y: To be released diff --git a/changelogs/fragments/0-stable-2-eol.yml b/changelogs/fragments/0-stable-2-eol.yml new file mode 100644 index 0000000..afcad73 --- /dev/null +++ b/changelogs/fragments/0-stable-2-eol.yml @@ -0,0 +1,2 @@ +major_changes: +- "Collection version 2.*.* is EOL, no more bugfixes will be backported. Please consider upgrading to the latest version." From e34209b3f8462878421269f7c4bc2e3771b8ee53 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Tue, 23 Jan 2024 11:27:47 +0100 Subject: [PATCH 167/217] Fix sanity issues (#609) * Fix sanity issues * Remove ignore entries --- plugins/modules/mysql_db.py | 4 ++-- plugins/modules/mysql_info.py | 4 ++-- plugins/modules/mysql_query.py | 3 ++- plugins/modules/mysql_variables.py | 2 +- tests/sanity/ignore-2.14.txt | 6 ------ tests/sanity/ignore-2.15.txt | 6 ------ tests/sanity/ignore-2.16.txt | 6 ------ tests/sanity/ignore-2.17.txt | 6 ------ 8 files changed, 7 insertions(+), 30 deletions(-) diff --git a/plugins/modules/mysql_db.py b/plugins/modules/mysql_db.py index a425361..2cb67dc 100644 --- a/plugins/modules/mysql_db.py +++ b/plugins/modules/mysql_db.py @@ -577,14 +577,14 @@ 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']), + name=dict(type='list', elements='str', 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=[]), + ignore_tables=dict(type='list', elements='str', 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]), diff --git a/plugins/modules/mysql_info.py b/plugins/modules/mysql_info.py index 303921b..0be25fa 100644 --- a/plugins/modules/mysql_info.py +++ b/plugins/modules/mysql_info.py @@ -698,8 +698,8 @@ def main(): argument_spec = mysql_common_argument_spec() argument_spec.update( login_db=dict(type='str'), - filter=dict(type='list'), - exclude_fields=dict(type='list'), + filter=dict(type='list', elements='str'), + exclude_fields=dict(type='list', elements='str'), return_empty_dbs=dict(type='bool', default=False), ) diff --git a/plugins/modules/mysql_query.py b/plugins/modules/mysql_query.py index 9123d60..fd3a8e0 100644 --- a/plugins/modules/mysql_query.py +++ b/plugins/modules/mysql_query.py @@ -36,6 +36,7 @@ options: - List of values to be passed as positional arguments to the query. - Mutually exclusive with I(named_args). type: list + elements: raw named_args: description: - Dictionary of key-value arguments to pass to the query. @@ -141,7 +142,7 @@ def main(): argument_spec.update( query=dict(type='raw', required=True), login_db=dict(type='str'), - positional_args=dict(type='list'), + positional_args=dict(type='list', elements='raw'), named_args=dict(type='dict'), single_transaction=dict(type='bool', default=False), ) diff --git a/plugins/modules/mysql_variables.py b/plugins/modules/mysql_variables.py index 395a24c..dfe8466 100644 --- a/plugins/modules/mysql_variables.py +++ b/plugins/modules/mysql_variables.py @@ -176,7 +176,7 @@ def setvariable(cursor, mysqlvar, value, mode='global'): def main(): argument_spec = mysql_common_argument_spec() argument_spec.update( - variable=dict(type='str'), + variable=dict(type='str', required=True), value=dict(type='str'), mode=dict(type='str', choices=['global', 'persist', 'persist_only'], default='global'), ) diff --git a/tests/sanity/ignore-2.14.txt b/tests/sanity/ignore-2.14.txt index c0323af..90ddba3 100644 --- a/tests/sanity/ignore-2.14.txt +++ b/tests/sanity/ignore-2.14.txt @@ -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_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_variables.py validate-modules:doc-required-mismatch diff --git a/tests/sanity/ignore-2.15.txt b/tests/sanity/ignore-2.15.txt index da0354c..55b2904 100644 --- a/tests/sanity/ignore-2.15.txt +++ b/tests/sanity/ignore-2.15.txt @@ -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_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_variables.py validate-modules:doc-required-mismatch plugins/module_utils/mysql.py pylint:unused-import plugins/module_utils/version.py pylint:unused-import diff --git a/tests/sanity/ignore-2.16.txt b/tests/sanity/ignore-2.16.txt index da0354c..55b2904 100644 --- a/tests/sanity/ignore-2.16.txt +++ b/tests/sanity/ignore-2.16.txt @@ -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_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_variables.py validate-modules:doc-required-mismatch plugins/module_utils/mysql.py pylint:unused-import plugins/module_utils/version.py pylint:unused-import diff --git a/tests/sanity/ignore-2.17.txt b/tests/sanity/ignore-2.17.txt index da0354c..55b2904 100644 --- a/tests/sanity/ignore-2.17.txt +++ b/tests/sanity/ignore-2.17.txt @@ -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_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_variables.py validate-modules:doc-required-mismatch plugins/module_utils/mysql.py pylint:unused-import plugins/module_utils/version.py pylint:unused-import From 32718ca2956b2b776d633710a940d45c4d517431 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Thu, 25 Jan 2024 07:55:51 +0100 Subject: [PATCH 168/217] Update MAINTAINERS file (#612) --- MAINTAINERS | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index 2228e00..73feaa4 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1,6 +1,3 @@ betanummeric -bmalynovytch -Jorge-Rodriguez -rsicart laurent-indermuehle -Andersson007 (andersson007_ in #ansible-community IRC/Matrix) +Andersson007 From 21fe52d8f1c3d3aeeff1e78b7f38617c4855abe0 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Thu, 22 Feb 2024 10:19:08 +0100 Subject: [PATCH 169/217] CONTRIBUTING.md: add a detailed guide (#615) --- CONTRIBUTING.md | 81 +++++++++++++++++++++++++++++++++++++++++++++++-- TESTING.md | 4 +-- 2 files changed, 80 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 70cd555..1b6ecdf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,5 +1,80 @@ -# Contributing +# Contributing to this project -Refer to the [Ansible Contributing guidelines](https://docs.ansible.com/ansible/devel/community/index.html) to learn how to contribute to this collection. +In this guide, you will find information relevant for code contributions, though any other kinds of contribution mentioned in the [Ansible Contributing guidelines](https://docs.ansible.com/ansible/devel/community/index.html) are equally appreciated and valuable. -Refer to the [review checklist](https://docs.ansible.com/ansible/devel/community/collection_contributors/collection_reviewing.html) when triaging issues or reviewing PRs. +If you have any questions after reading, please contact the community via one or more of the [available channels](https://github.com/ansible-collections/community.mysql#communication). Any feedback on this guide is very welcome. + +## Reviewing open issue and pull requests + +Refer to the [review checklist](https://docs.ansible.com/ansible/devel/community/collection_contributors/collection_reviewing.html) when triaging issues or reviewing pull requests (hereinafter PRs). + +Most important things to pay attention to: + +- Do not let major/breaking changes sneak into a minor/bugfix release! All such changes should be discussed in a dedicated issue, added to a corresponding milestone (which can be found or created in the project's Issues), and merged right before the major release. Take a look at similar issues to see what needs to be done and reflect on the steps you did/need to do in the issue. +- Every PR (except doc, refactoring, test-related, or a PR containing a new module/plugin) contains a [changelog fragment](https://docs.ansible.com/ansible/latest/community/development_process.html#creating-a-changelog-fragment). Let's give users a chance to know about the changes. +- Every new module `DOCUMENTATION` section contains the `version_added: 'x.y.z'` field. Besides the informative purpose, it is used by the changelog-generating tool to add a corresponding entry to the changelog. As the project follows SemVer, it is typically a next minor (x.y.0) version. +- Every new module argument contains the `version_added: 'x.y.z'` field. As the project follows SemVer, it is typically a next minor (x.y.0) version. +- Non-refactoring code changes (bugfixes, new features) are covered with, at least, integration tests! There can be exceptions but generally it is a requirement. + +## Code contributions + +If you want to submit a bugfix or new feature, refer to the [Quick-start development guide](https://docs.ansible.com/ansible/devel/community/create_pr_quick_start.html) first. + +## Project-specific info + +We assume you have read the [Quick-start development guide](https://docs.ansible.com/ansible/devel/community/create_pr_quick_start.html). + +In order for any submitted PR to get merged, this project requires sanity, unit, and integration tests to pass. +Codecov job is there but not required. +We use the GitHub Actions platform to run the tests. +You can see the result in the bottom of every PR in the box listing the jobs and their results: + +- Green checkmark: the test has been passed, no more action is needed. +- Red cross: the test has failed. You can see the reason by clicking the ``Details`` link. Fix them locally and push the commit. + +Generally, all jobs must be green. +Sometimes, there can be failures unrelated to a PR, for example, when a test container is unavailable or there is another part of the code that does not satisfy recently introduced additional sanity checks. +If you think the failure does not relate to your changes, put a comment about it. + +## CI testing + +The jobs are launched automatically by GitHub Actions in every PR based on the [matrix](https://github.com/ansible-collections/community.mysql/blob/main/.github/workflows/ansible-test-plugins.yml). + +As the project is included in `ansible` community package, it is a requirement for us to test against all supported `ansible-core` versions and corresponding Python versions. +To keep the matrix relevant, we are subscribed to the [news-for-maintainers](https://github.com/ansible-collections/news-for-maintainers) repository and the [Collection maintainers & contributors](https://forum.ansible.com/g/CollectionMaintainer) forum group to track announcements affecting CI. + +If our matrix is permanently outdated, for example, when supported `ansible-core` versions are missed, the collections can get excluded from the package, so keep it updated! + +Read more about our CI implementation in the [TESTING.md](https://github.com/ansible-collections/community.mysql/blob/main/TESTING.md) file. + +## Adding tests + +If you are new here, read the [Quick-start development guide](https://docs.ansible.com/ansible/devel/community/create_pr_quick_start.html) first. + +When fixing a bug, first reproduce it by adding a task as reported to a suitable file under the ``tests/integration/targets//tasks/`` directory and run the integration tests as described below. The same is relevant for new features. + +It is not necessary but if you want you can also add unit tests to a suitable file under the ``tests/units/`` directory and run them as described below. + +## Checking your code locally + +It will make your and other people's life a bit easier if you run the tests locally and fix all failures before pushing. If you're unable to run the tests locally, please create your PR as a **draft** to avoid reviewers being added automatically. + +If you are new here, read the [Quick-start development guide](https://docs.ansible.com/ansible/devel/community/create_pr_quick_start.html) first. + +We assume you [prepared your local environment](https://docs.ansible.com/ansible/devel/community/create_pr_quick_start.html#prepare-your-environment) as described in the guide before running the following commands. Otherwise, the command will fail. + +### Sanity tests + +``` console +$ ansible-test sanity path/to/changed_file.py --docker -v +``` + +### Integration tests + +See the [TESTING.md](https://github.com/ansible-collections/community.mysql/blob/main/TESTING.md) file to learn how to run integration tests against different server/connector versions. + +### Unit tests + +``` console +$ ansible-test units tests/unit/plugins/unit_test_file.py --docker +``` diff --git a/TESTING.md b/TESTING.md index 7025391..9e0840a 100644 --- a/TESTING.md +++ b/TESTING.md @@ -77,7 +77,7 @@ The Makefile accept the following options - `connector_name` - Mandatory: true - Choices: - - "pymysql + - "pymysql" - "mysqlclient" - Description: The python package of the connector to use. In addition to selecting the test container, this value is also used for tests filtering: `when: connector_name == 'pymysql'`. @@ -153,7 +153,7 @@ python run_all_tests.py ### Add a new Python, Connector or Database version -You can look into `[.github/workflows/ansible-test-plugins.yml](https://github.com/ansible-collections/community.mysql/tree/main/.github/workflows)` to see how those containers are built using [build-docker-image.yml](https://github.com/ansible-collections/community.mysql/blob/main/.github/workflows/build-docker-image.yml) and all [docker-image-xxx.yml](https://github.com/ansible-collections/community.mysql/blob/main/.github/workflows/docker-image-mariadb103-py38-mysqlclient201.yml) files. +You can look into [.github/workflows/ansible-test-plugins.yml](https://github.com/ansible-collections/community.mysql/tree/main/.github/workflows) to see how those containers are built using [build-docker-image.yml](https://github.com/ansible-collections/community.mysql/blob/main/.github/workflows/build-docker-image.yml) and all [docker-image-xxx.yml](https://github.com/ansible-collections/community.mysql/blob/main/.github/workflows/docker-image-mariadb103-py38-mysqlclient201.yml) files. 1. Add a workflow in [.github/workflows/](.github/workflows) 1. Add a new folder in [test-containers](test-containers) containing a new Dockerfile. Your container must contains 3 things: From 40af258d86f8408d7176d9762efe09709c8c11e6 Mon Sep 17 00:00:00 2001 From: tompal3 Date: Thu, 22 Feb 2024 11:31:01 +0200 Subject: [PATCH 170/217] password_expire support for mysql_user (#598) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * initial commit for password_expire support * sanity check and default values * add one more if block for version check * some changes and integration tests * docs and sanity and integration test fix * make integration tests work * make integration tests work * fix unneeded commits * fix verify as well * Update plugins/modules/mysql_user.py Co-authored-by: Laurent Indermühle * Update tests/integration/targets/test_mysql_user/tasks/test_password_expire.yml Co-authored-by: Laurent Indermühle * Apply suggestions from code review Co-authored-by: Laurent Indermühle * Update plugins/modules/mysql_user.py Co-authored-by: Andrew Klychkov * Update plugins/modules/mysql_user.py Co-authored-by: Andrew Klychkov * Update plugins/modules/mysql_user.py Co-authored-by: Andrew Klychkov * Update plugins/modules/mysql_user.py Co-authored-by: Andrew Klychkov * Update plugins/module_utils/user.py Co-authored-by: Andrew Klychkov * Update plugins/module_utils/user.py Co-authored-by: Andrew Klychkov * Update plugins/module_utils/user.py Co-authored-by: Andrew Klychkov * typo and no_log remove for password_expire* vars * add change log fragment * move one if statement to module initialiazation * fix merge conflicts * fix order * some fixes * set no_log to true for password word containing keys * fix sanity error * Update changelogs/fragments/598-password_expire-support-for-mysql_user.yml Co-authored-by: Andrew Klychkov --------- Co-authored-by: Laurent Indermühle Co-authored-by: Andrew Klychkov --- ...password_expire-support-for-mysql_user.yml | 2 + .../implementations/mariadb/user.py | 6 + .../implementations/mysql/user.py | 6 + plugins/module_utils/user.py | 100 +++++++++- plugins/modules/mysql_role.py | 3 +- plugins/modules/mysql_user.py | 32 +++- .../targets/test_mysql_user/tasks/main.yml | 2 + .../tasks/test_password_expire.yml | 174 ++++++++++++++++++ .../utils/assert_user_password_expire.yml | 56 ++++++ 9 files changed, 375 insertions(+), 6 deletions(-) create mode 100644 changelogs/fragments/598-password_expire-support-for-mysql_user.yml create mode 100644 tests/integration/targets/test_mysql_user/tasks/test_password_expire.yml create mode 100644 tests/integration/targets/test_mysql_user/tasks/utils/assert_user_password_expire.yml diff --git a/changelogs/fragments/598-password_expire-support-for-mysql_user.yml b/changelogs/fragments/598-password_expire-support-for-mysql_user.yml new file mode 100644 index 0000000..c0fd472 --- /dev/null +++ b/changelogs/fragments/598-password_expire-support-for-mysql_user.yml @@ -0,0 +1,2 @@ +minor_changes: + - "mysql_user - add the ``password_expire`` and ``password_expire_interval`` arguments to implement the password expiration management for mysql user (https://github.com/ansible-collections/community.mysql/pull/598)." diff --git a/plugins/module_utils/implementations/mariadb/user.py b/plugins/module_utils/implementations/mariadb/user.py index c1d2b61..cdc14b2 100644 --- a/plugins/module_utils/implementations/mariadb/user.py +++ b/plugins/module_utils/implementations/mariadb/user.py @@ -23,3 +23,9 @@ def server_supports_alter_user(cursor): version = get_server_version(cursor) return LooseVersion(version) >= LooseVersion("10.2") + + +def server_supports_password_expire(cursor): + version = get_server_version(cursor) + + return LooseVersion(version) >= LooseVersion("10.4.3") diff --git a/plugins/module_utils/implementations/mysql/user.py b/plugins/module_utils/implementations/mysql/user.py index 1bdad57..4e41c05 100644 --- a/plugins/module_utils/implementations/mysql/user.py +++ b/plugins/module_utils/implementations/mysql/user.py @@ -24,3 +24,9 @@ def server_supports_alter_user(cursor): version = get_server_version(cursor) return LooseVersion(version) >= LooseVersion("5.6") + + +def server_supports_password_expire(cursor): + version = get_server_version(cursor) + + return LooseVersion(version) >= LooseVersion("5.7") diff --git a/plugins/module_utils/user.py b/plugins/module_utils/user.py index 1e5a275..17ad4b0 100644 --- a/plugins/module_utils/user.py +++ b/plugins/module_utils/user.py @@ -152,7 +152,8 @@ def get_existing_authentication(cursor, user, host): def user_add(cursor, user, host, host_all, password, encrypted, plugin, plugin_hash_string, plugin_auth_string, new_priv, - attributes, tls_requires, reuse_existing_password, module): + attributes, tls_requires, reuse_existing_password, module, + password_expire, password_expire_interval): # If attributes are set, perform a sanity check to ensure server supports user attributes before creating user if attributes and not get_attribute_support(cursor): module.fail_json(msg="user attributes were specified but the server does not support user attributes") @@ -205,6 +206,12 @@ def user_add(cursor, user, host, host_all, password, encrypted, query_with_args_and_tls_requires = query_with_args + (tls_requires,) cursor.execute(*mogrify(*query_with_args_and_tls_requires)) + if password_expire: + if not impl.server_supports_password_expire(cursor): + module.fail_json(msg="The server version does not match the requirements " + "for password_expire parameter. See module's documentation.") + set_password_expire(cursor, user, host, password_expire, password_expire_interval) + if new_priv is not None: for db_table, priv in iteritems(new_priv): privileges_grant(cursor, user, host, db_table, priv, tls_requires) @@ -230,7 +237,8 @@ def is_hash(password): def user_mod(cursor, user, host, host_all, password, encrypted, plugin, plugin_hash_string, plugin_auth_string, new_priv, - append_privs, subtract_privs, attributes, tls_requires, module, role=False, maria_role=False): + append_privs, subtract_privs, attributes, tls_requires, module, + password_expire, password_expire_interval, role=False, maria_role=False): changed = False msg = "User unchanged" grant_option = False @@ -312,6 +320,28 @@ def user_mod(cursor, user, host, host_all, password, encrypted, raise e changed = True + # Handle password expiration + if bool(password_expire): + if not impl.server_supports_password_expire(cursor): + module.fail_json(msg="The server version does not match the requirements " + "for password_expire parameter. See module's documentation.") + update = False + mariadb_role = True if "mariadb" in str(impl.__name__) else False + current_password_policy = get_password_expiration_policy(cursor, user, host, maria_role=mariadb_role) + password_expired = is_password_expired(cursor, user, host) + # Check if changes needed to be applied. + if not ((current_password_policy == -1 and password_expire == "default") or + (current_password_policy == 0 and password_expire == "never") or + (current_password_policy == password_expire_interval and password_expire == "interval") or + (password_expire == 'now' and password_expired)): + + update = True + + if not module.check_mode: + set_password_expire(cursor, user, host, password_expire, password_expire_interval) + password_changed = True + changed = True + # Handle plugin authentication if plugin and not role: cursor.execute("SELECT plugin, authentication_string FROM mysql.user " @@ -973,6 +1003,72 @@ def limit_resources(module, cursor, user, host, resource_limits, check_mode): return True +def set_password_expire(cursor, user, host, password_expire, password_expire_interval): + """Fuction to set passowrd expiration for user. + + Args: + cursor (cursor): DB driver cursor object. + user (str): User name. + host (str): User hostname. + password_expire (str): Password expiration mode. + password_expire_days (int): Invterval of days password expires. + """ + if password_expire.lower() == "never": + statement = "PASSWORD EXPIRE NEVER" + elif password_expire.lower() == "default": + statement = "PASSWORD EXPIRE DEFAULT" + elif password_expire.lower() == "interval": + statement = "PASSWORD EXPIRE INTERVAL %d DAY" % (password_expire_interval) + elif password_expire.lower() == "now": + statement = "PASSWORD EXPIRE" + + cursor.execute("ALTER USER %s@%s " + statement, (user, host)) + + +def get_password_expiration_policy(cursor, user, host, maria_role=False): + """Function to get password policy for user. + + Args: + cursor (cursor): DB driver cursor object. + user (str): User name. + host (str): User hostname. + maria_role (bool, optional): mariadb or mysql. Defaults to False. + + Returns: + policy (int): Current users password policy. + """ + if not maria_role: + statement = "SELECT IFNULL(password_lifetime, -1) FROM mysql.user \ + WHERE User = %s AND Host = %s", (user, host) + else: + statement = "SELECT JSON_EXTRACT(Priv, '$.password_lifetime') AS password_lifetime \ + FROM mysql.global_priv \ + WHERE User = %s AND Host = %s", (user, host) + cursor.execute(*statement) + policy = cursor.fetchone()[0] + return int(policy) + + +def is_password_expired(cursor, user, host): + """Function to check if password is expired + + Args: + cursor (cursor): DB driver cursor object. + user (str): User name. + host (str): User hostname. + + Returns: + expired (bool): True if expired, else False. + """ + statement = "SELECT password_expired FROM mysql.user \ + WHERE User = %s AND Host = %s", (user, host) + cursor.execute(*statement) + expired = cursor.fetchone()[0] + if str(expired) == "Y": + return True + return False + + def get_attribute_support(cursor): """Checks if the MySQL server supports user attributes. diff --git a/plugins/modules/mysql_role.py b/plugins/modules/mysql_role.py index 5713791..3e3462a 100644 --- a/plugins/modules/mysql_role.py +++ b/plugins/modules/mysql_role.py @@ -932,7 +932,8 @@ class Role(): result = user_mod(self.cursor, self.name, self.host, None, None, None, None, None, None, privs, append_privs, subtract_privs, None, None, - self.module, role=True, maria_role=self.is_mariadb) + self.module, None, None, role=True, + maria_role=self.is_mariadb) changed = result['changed'] if admin: diff --git a/plugins/modules/mysql_user.py b/plugins/modules/mysql_user.py index c6a02fc..e02b153 100644 --- a/plugins/modules/mysql_user.py +++ b/plugins/modules/mysql_user.py @@ -155,6 +155,21 @@ options: - Cannot be used to set global variables, use the M(community.mysql.mysql_variables) module instead. type: dict version_added: '3.6.0' + password_expire: + description: + - C(never) - I(password) will never expire. + - C(default) - I(password) is defined using global system variable I(default_password_lifetime) setting. + - C(interval) - I(password) will expire in days which is defined in I(password_expire_interval). + - C(now) - I(password) will expire immediately. + type: str + choices: [ now, never, default, interval ] + version_added: '3.9.0' + password_expire_interval: + description: + - Number of days I(password) will expire. Requires I(password_expire=interval). + type: int + version_added: '3.9.0' + column_case_sensitive: description: - The default is C(false). @@ -429,6 +444,8 @@ def main(): force_context=dict(type='bool', default=False), session_vars=dict(type='dict'), column_case_sensitive=dict(type='bool', default=None), # TODO 4.0.0 add default=True + password_expire=dict(type='str', choices=['now', 'never', 'default', 'interval'], no_log=True), + password_expire_interval=dict(type='int', required_if=[('password_expire', 'interval', True)], no_log=True), ) module = AnsibleModule( argument_spec=argument_spec, @@ -466,6 +483,8 @@ def main(): resource_limits = module.params["resource_limits"] session_vars = module.params["session_vars"] column_case_sensitive = module.params["column_case_sensitive"] + password_expire = module.params["password_expire"] + password_expire_interval = module.params["password_expire_interval"] 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)) @@ -476,6 +495,10 @@ def main(): if mysql_driver is None: module.fail_json(msg=mysql_driver_fail_msg) + if password_expire_interval and password_expire_interval < 1: + module.fail_json(msg="password_expire_interval value \ + should be positive number") + cursor = None try: if check_implicit_admin: @@ -522,12 +545,14 @@ def main(): if update_password == "always": result = user_mod(cursor, user, host, host_all, password, encrypted, plugin, plugin_hash_string, plugin_auth_string, - priv, append_privs, subtract_privs, attributes, tls_requires, module) + priv, append_privs, subtract_privs, attributes, tls_requires, module, + password_expire, password_expire_interval) else: result = user_mod(cursor, user, host, host_all, None, encrypted, None, None, None, - priv, append_privs, subtract_privs, attributes, tls_requires, module) + priv, append_privs, subtract_privs, attributes, tls_requires, module, + password_expire, password_expire_interval) changed = result['changed'] msg = result['msg'] password_changed = result['password_changed'] @@ -544,7 +569,8 @@ def main(): reuse_existing_password = update_password == 'on_new_username' result = user_add(cursor, user, host, host_all, password, encrypted, plugin, plugin_hash_string, plugin_auth_string, - priv, attributes, tls_requires, reuse_existing_password, module) + priv, attributes, tls_requires, reuse_existing_password, module, + password_expire, password_expire_interval) changed = result['changed'] password_changed = result['password_changed'] final_attributes = result['attributes'] diff --git a/tests/integration/targets/test_mysql_user/tasks/main.yml b/tests/integration/targets/test_mysql_user/tasks/main.yml index f5e0748..8ec0798 100644 --- a/tests/integration/targets/test_mysql_user/tasks/main.yml +++ b/tests/integration/targets/test_mysql_user/tasks/main.yml @@ -43,6 +43,8 @@ - include_tasks: test_idempotency.yml + - include_tasks: test_password_expire.yml + # ============================================================ # Create user with no privileges and verify default privileges are assign # diff --git a/tests/integration/targets/test_mysql_user/tasks/test_password_expire.yml b/tests/integration/targets/test_mysql_user/tasks/test_password_expire.yml new file mode 100644 index 0000000..7e70ece --- /dev/null +++ b/tests/integration/targets/test_mysql_user/tasks/test_password_expire.yml @@ -0,0 +1,174 @@ +--- +# Tests scenarios for password_expire + +- vars: + mysql_parameters: &mysql_params + login_user: "{{ mysql_user }}" + login_password: "{{ mysql_password }}" + login_host: "{{ mysql_host }}" + login_port: "{{ mysql_primary_port }}" + + block: + - include_tasks: utils/assert_user_password_expire.yml + vars: + username: "{{ item.username }}" + host: "{{ item.host | default('localhost')}}" + password_expire: "{{ item.password_expire }}" + password: "{{ user_password_1 }}" + expect_change: "{{ item.expect_change }}" + expect_password_expire_change: "{{ item.expect_password_expire_change }}" + expected_password_lifetime: "{{ item.expected_password_lifetime }}" + password_expire_interval: "{{ item.password_expire_interval | default(omit) }}" + expected_password_expired: "{{ item.expected_password_expired }}" + check_mode: "{{ item.check_mode | default(omit) }}" + loop: + # all variants set the password when nothing exists + # never expires + - username: "{{ user_name_1 }}" + host: "%" + password_expire: never + expect_change: true + expected_password_lifetime: "0" + expected_password_expired: "N" + # expires ussing default policy + - username: "{{ user_name_2 }}" + password_expire: default + expect_change: true + expected_password_lifetime: "-1" + expected_password_expired: "N" + # expires ussing interval + - username: "{{ user_name_3 }}" + password_expire: interval + password_expire_interval: "10" + expect_change: true + expected_password_lifetime: "10" + expected_password_expired: "N" + + # assert idempotency + - username: "{{ user_name_1 }}" + host: "%" + password_expire: never + expect_change: false + expected_password_lifetime: "0" + expected_password_expired: "N" + - username: "{{ user_name_2 }}" + password_expire: default + expect_change: false + expected_password_lifetime: "-1" + expected_password_expired: "N" + - username: "{{ user_name_3 }}" + password_expire: interval + password_expire_interval: "10" + expect_change: false + expected_password_lifetime: "10" + expected_password_expired: "N" + + # assert change is made + - username: "{{ user_name_3 }}" + password_expire: never + expect_change: true + expected_password_lifetime: "0" + expected_password_expired: "N" + - username: "{{ user_name_1 }}" + host: "%" + password_expire: default + expect_change: true + expected_password_lifetime: "-1" + expected_password_expired: "N" + - username: "{{ user_name_2 }}" + password_expire: interval + password_expire_interval: "100" + expect_change: true + expected_password_lifetime: "100" + expected_password_expired: "N" + + # assert password expires now + - username: "{{ user_name_1 }}" + host: "%" + password_expire: now + expect_change: true + expected_password_lifetime: "-1" # password lifetime should be the same + expected_password_expired: "Y" + - username: "{{ user_name_2 }}" + password_expire: now + expect_change: true + expected_password_lifetime: "100" # password lifetime should be the same + expected_password_expired: "Y" + + # assert idempotency password expires now + - username: "{{ user_name_1 }}" + host: "%" + password_expire: now + expect_change: false + expected_password_lifetime: "-1" # password lifetime should be the same + expected_password_expired: "Y" + - username: "{{ user_name_2 }}" + password_expire: now + expect_change: false + expected_password_lifetime: "100" # password lifetime should be the same + expected_password_expired: "Y" + + # assert check_mode + - username: "{{ user_name_3 }}" + password_expire: interval + password_expire_interval: 10 + check_mode: true + expect_change: false + expected_password_lifetime: "0" + expected_password_expired: "N" + + - name: password_expire | Set password_expire = interval without password_expire_interval + community.mysql.mysql_user: + <<: *mysql_params + name: '{{ user_name_4 }}' + host: '%' + password: '{{ user_password_4 }}' + password_expire: interval + state: present + register: result + ignore_errors: true + + - name: password_expire | Assert that action fails if 'password_expire_interval' not set + ansible.builtin.assert: + that: + - result is failed + + - name: password_expire | Set password_expire_interval < 1 + community.mysql.mysql_user: + <<: *mysql_params + name: '{{ user_name_4 }}' + host: '%' + password: '{{ user_password_4 }}' + password_expire: interval + password_expire_interval: -1 + state: present + register: result + ignore_errors: true + + - name: password_expire | Assert that action fails if 'password_expire_interval' is < 1 + ansible.builtin.assert: + that: + - result is failed + - "'should be positive number' in result.msg" + + - name: password_expire | check mode for user creation + community.mysql.mysql_user: + <<: *mysql_params + name: '{{ user_name_4 }}' + host: '%' + password: '{{ user_password_4 }}' + password_expire: interval + password_expire_interval: 20 + state: present + register: result + check_mode: True + failed_when: result is changed + + - include_tasks: utils/remove_user.yml + vars: + user_name: "{{ item.username }}" + loop: + - username: "{{ user_name_1 }}" + - username: "{{ user_name_2 }}" + - username: "{{ user_name_3 }}" + - username: "{{ user_name_4 }}" diff --git a/tests/integration/targets/test_mysql_user/tasks/utils/assert_user_password_expire.yml b/tests/integration/targets/test_mysql_user/tasks/utils/assert_user_password_expire.yml new file mode 100644 index 0000000..3798802 --- /dev/null +++ b/tests/integration/targets/test_mysql_user/tasks/utils/assert_user_password_expire.yml @@ -0,0 +1,56 @@ +--- +- name: Utils | Assert user password_expire | Create modify {{ username }} with password_expire + community.mysql.mysql_user: + login_user: "{{ mysql_parameters.login_user }}" + login_password: "{{ mysql_parameters.login_password }}" + login_host: "{{ mysql_parameters.login_host }}" + login_port: "{{ mysql_parameters.login_port }}" + state: present + name: "{{ username }}" + host: "{{ host }}" + password: "{{ password }}" + password_expire: "{{ password_expire }}" + password_expire_interval: "{{ password_expire_interval | default(omit) }}" + register: result + check_mode: "{{ check_mode | default(false) }}" + failed_when: result.changed != expect_change_value + vars: + expect_change_value: "{{ expect_change }}" + +- name: Utils | Assert user password_lifetime | Query user '{{ username }}' + ansible.builtin.command: + cmd: > + {{ mysql_command }} -BNe "SELECT IFNULL(password_lifetime, -1) + FROM mysql.user where user='{{ username }}' and host='{{ host }}'" + register: password_lifetime + when: + - db_engine == 'mysql' + - db_version is version('5.7.0', '>=') + failed_when: expected_password_lifetime_value not in password_lifetime.stdout_lines + vars: + expected_password_lifetime_value: "{{ expected_password_lifetime }}" + +- name: Utils | Assert user password_lifetime | Query user '{{ username }}' + ansible.builtin.command: + "{{ mysql_command }} -BNe \"SELECT JSON_EXTRACT(Priv, '$.password_lifetime') AS password_lifetime \ + FROM mysql.global_priv \ + WHERE user='{{ username }}' and host='{{ host }}'\"" + register: password_lifetime + when: + - db_engine == 'mariadb' + - db_version is version('10.4.3', '>=') + failed_when: expected_password_lifetime_value not in password_lifetime.stdout_lines + vars: + expected_password_lifetime_value: "{{ expected_password_lifetime }}" + +- name: Utils | Assert user password_expired | Query user '{{ username }}' + ansible.builtin.command: + cmd: > + {{ mysql_command }} -BNe "SELECT password_expired FROM mysql.user + WHERE user='{{ username }}' and host='{{ host }}'" + register: password_expired + when: (db_engine == 'mysql' and db_version is version('5.7.0', '>=')) or + (db_engine == 'mariadb' and db_version is version('10.4.3', '>=')) + failed_when: expected_password_expired_value not in password_expired.stdout_lines + vars: + expected_password_expired_value: "{{ expected_password_expired }}" From 52a11d72358028e6eb4ed2a439db424d13cab297 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Thu, 22 Feb 2024 10:53:01 +0100 Subject: [PATCH 171/217] Release 3.9.0 commit (#616) --- CHANGELOG.rst | 26 +++++++++++++++++++ changelogs/changelog.yaml | 26 +++++++++++++++++++ changelogs/fragments/0-stable-2-eol.yml | 2 -- ...password_expire-support-for-mysql_user.yml | 2 -- .../fragments/602-show-all-slaves-status.yaml | 2 -- changelogs/fragments/604-user-attributes.yaml | 2 -- galaxy.yml | 2 +- 7 files changed, 53 insertions(+), 9 deletions(-) delete mode 100644 changelogs/fragments/0-stable-2-eol.yml delete mode 100644 changelogs/fragments/598-password_expire-support-for-mysql_user.yml delete mode 100644 changelogs/fragments/602-show-all-slaves-status.yaml delete mode 100644 changelogs/fragments/604-user-attributes.yaml diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f6c6cb8..cc7ab85 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,32 @@ Community MySQL Collection Release Notes This changelog describes changes after version 2.0.0. +v3.9.0 +====== + +Release Summary +--------------- + +This is a minor release of the ``community.mysql`` collection. +This changelog contains all changes to the modules and plugins in this +collection that have been made after the previous release. + +Major Changes +------------- + +- Collection version 2.*.* is EOL, no more bugfixes will be backported. Please consider upgrading to the latest version. + +Minor Changes +------------- + +- mysql_user - add the ``password_expire`` and ``password_expire_interval`` arguments to implement the password expiration management for mysql user (https://github.com/ansible-collections/community.mysql/pull/598). +- mysql_user - add user attribute support via the ``attributes`` parameter and return value (https://github.com/ansible-collections/community.mysql/pull/604). + +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). + v3.8.0 ====== diff --git a/changelogs/changelog.yaml b/changelogs/changelog.yaml index a97b2a8..eb4264d 100644 --- a/changelogs/changelog.yaml +++ b/changelogs/changelog.yaml @@ -380,3 +380,29 @@ releases: - drop_ansible_core_2_12_and_2_13.yml - lie_mysql_info_users_info.yml release_date: '2023-10-25' + 3.9.0: + changes: + 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). + major_changes: + - Collection version 2.*.* is EOL, no more bugfixes will be backported. Please + consider upgrading to the latest version. + minor_changes: + - mysql_user - add the ``password_expire`` and ``password_expire_interval`` + arguments to implement the password expiration management for mysql user (https://github.com/ansible-collections/community.mysql/pull/598). + - mysql_user - add user attribute support via the ``attributes`` parameter and + return value (https://github.com/ansible-collections/community.mysql/pull/604). + release_summary: 'This is a minor release of the ``community.mysql`` collection. + + This changelog contains all changes to the modules and plugins in this + + collection that have been made after the previous release.' + fragments: + - 0-stable-2-eol.yml + - 3.9.0.yml + - 598-password_expire-support-for-mysql_user.yml + - 602-show-all-slaves-status.yaml + - 604-user-attributes.yaml + release_date: '2024-02-22' diff --git a/changelogs/fragments/0-stable-2-eol.yml b/changelogs/fragments/0-stable-2-eol.yml deleted file mode 100644 index afcad73..0000000 --- a/changelogs/fragments/0-stable-2-eol.yml +++ /dev/null @@ -1,2 +0,0 @@ -major_changes: -- "Collection version 2.*.* is EOL, no more bugfixes will be backported. Please consider upgrading to the latest version." diff --git a/changelogs/fragments/598-password_expire-support-for-mysql_user.yml b/changelogs/fragments/598-password_expire-support-for-mysql_user.yml deleted file mode 100644 index c0fd472..0000000 --- a/changelogs/fragments/598-password_expire-support-for-mysql_user.yml +++ /dev/null @@ -1,2 +0,0 @@ -minor_changes: - - "mysql_user - add the ``password_expire`` and ``password_expire_interval`` arguments to implement the password expiration management for mysql user (https://github.com/ansible-collections/community.mysql/pull/598)." diff --git a/changelogs/fragments/602-show-all-slaves-status.yaml b/changelogs/fragments/602-show-all-slaves-status.yaml deleted file mode 100644 index 8c9320c..0000000 --- a/changelogs/fragments/602-show-all-slaves-status.yaml +++ /dev/null @@ -1,2 +0,0 @@ -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). diff --git a/changelogs/fragments/604-user-attributes.yaml b/changelogs/fragments/604-user-attributes.yaml deleted file mode 100644 index 260201d..0000000 --- a/changelogs/fragments/604-user-attributes.yaml +++ /dev/null @@ -1,2 +0,0 @@ -minor_changes: - - "mysql_user - add user attribute support via the ``attributes`` parameter and return value (https://github.com/ansible-collections/community.mysql/pull/604)." diff --git a/galaxy.yml b/galaxy.yml index c443a7b..dca1e28 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -1,7 +1,7 @@ --- namespace: community name: mysql -version: 3.8.0 +version: 3.9.0 readme: README.md authors: - Ansible community From c99c19a489d0c1db85457bc8b7ffbeccf82788dd Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Tue, 27 Feb 2024 10:27:19 +0100 Subject: [PATCH 172/217] README.md: update Communication guide (#617) --- README.md | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 40264d2..0e0704e 100644 --- a/README.md +++ b/README.md @@ -42,22 +42,25 @@ They also should be subscribed to Ansible's [The Bullhorn newsletter](https://do > The `GitHub Discussions` feature is disabled in this repository. Use the `mysql` tag on the forum in the [Project Discussions](https://forum.ansible.com/new-topic?title=topic%20title&body=topic%20body&category=project&tags=mysql) or [Get Help](https://forum.ansible.com/new-topic?title=topic%20title&body=topic%20body&category=help&tags=mysql) category instead. -We announce releases and important changes through Ansible's [The Bullhorn newsletter](https://eepurl.com/gZmiEP). Be sure you are subscribed. +### Asynchronous channels + +* Join the Ansible forum: + * [MySQL Team](https://forum.ansible.com/g/MySQLTeam): by joining the team you will automatically get subscribed to the posts tagged with [mysql](https://forum.ansible.com/tag/mysql). + * [Get Help](https://forum.ansible.com/c/help/6/none): get help or help others. + * [Posts tagged with 'mysql'](https://forum.ansible.com/tag/mysql): leverage tags to narrow the scope. + * [Social Spaces](https://forum.ansible.com/c/chat/4): gather and interact with fellow enthusiasts. + * [News & Announcements](https://forum.ansible.com/c/news/5/none): track project-wide announcements. + +* The Ansible's [Bullhorn newsletter](https://forum.ansible.com/t/about-the-newsletter-category/166): we use it to announce releases and important changes. + +### Real-time channels -Join [our team](https://forum.ansible.com/g/MySQLTeam) on: -* The Ansible forums: - * [News & Announcements](https://forum.ansible.com/c/news/5/none) - * [Get Help](https://forum.ansible.com/c/help/6/none) - * [Social Spaces](https://forum.ansible.com/c/chat/4) - * [Posts tagged 'mysql'](https://forum.ansible.com/tag/mysql) * Matrix: * `#mysql:ansible.com` [room](https://matrix.to/#/#mysql:ansible.com): questions on how to contribute and use this collection. * `#users:ansible.com` [room](https://matrix.to/#/#users:ansible.com): general use questions and support. * `#ansible-community:ansible.com` [room](https://matrix.to/#/#community:ansible.com): community and collection development questions. * other Matrix rooms; see the [Ansible Communication Guide](https://docs.ansible.com/ansible/devel/community/communication.html) for details. -We take part in the global quarterly [Ansible Contributor Summit](https://github.com/ansible/community/wiki/Contributor-Summit) virtually or in-person. Track [The Bullhorn newsletter](https://eepurl.com/gZmiEP) and join us. - For more information about communication, refer to the [Ansible Communication guide](https://docs.ansible.com/ansible/devel/community/communication.html). ## Governance From bfe2fdc3ff8b94b14574cdade1639ce11877215c Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Thu, 14 Mar 2024 07:19:39 +0100 Subject: [PATCH 173/217] mysql_user: fix ed25512 plugin handling (#619) --- changelogs/fragments/0-mysql_user.yml | 2 ++ plugins/module_utils/user.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 changelogs/fragments/0-mysql_user.yml diff --git a/changelogs/fragments/0-mysql_user.yml b/changelogs/fragments/0-mysql_user.yml new file mode 100644 index 0000000..6b812ab --- /dev/null +++ b/changelogs/fragments/0-mysql_user.yml @@ -0,0 +1,2 @@ +bugfixes: +- mysql_user - add correct ``ed25519`` auth plugin handling (https://github.com/ansible-collections/community.mysql/issues/6). diff --git a/plugins/module_utils/user.py b/plugins/module_utils/user.py index 17ad4b0..f042c85 100644 --- a/plugins/module_utils/user.py +++ b/plugins/module_utils/user.py @@ -368,7 +368,7 @@ def user_mod(cursor, user, host, host_all, password, encrypted, query_with_args = "ALTER USER %s@%s IDENTIFIED WITH %s AS %s", (user, host, plugin, plugin_hash_string) elif plugin_auth_string: # Mysql and MariaDB differ in naming pam plugin and syntax to set it - if plugin == 'pam': + if plugin in ('pam', 'ed25519'): query_with_args = "ALTER USER %s@%s IDENTIFIED WITH %s USING %s", (user, host, plugin, plugin_auth_string) else: query_with_args = "ALTER USER %s@%s IDENTIFIED WITH %s BY %s", (user, host, plugin, plugin_auth_string) From f105fd9a95581ecf088837b861ae6eb5adcd30f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20Inderm=C3=BChle?= Date: Thu, 11 Apr 2024 10:46:43 +0200 Subject: [PATCH 174/217] Add tests for Ansible core 2.17 (devel is 2.18 today) and bump tests dependencies (#623) * Add tests for Ansible core 2.17 (devel is 2.18 today) * Drop tests for Ansible core 2.14 and add 2.17 * Cut duplicate exclude * Add back python 3.8 and 3.9 for stable2.15 * Bump action to prevent deprecation warnings * Cut python 3.9 for devel in roles tests * Attempt to fix GHA line folding * fix typo * Bump ubuntu Latest ansible-test doesn't work with old ubuntu. See here for more info: https://github.com/ansible-collections/collection_template/blob/main/.github/workflows/ansible-test.yml#L83-L91 * fix docker_image var assignation * fix yamllint false positive * Attempt to fix docker_image_multiline assignation * Fix empty var due to scope of each command * Attempt to fix docker_image assignation * fix error "vars should be dict" * Document URL of the repository for the action ansible-test-gh-action * Disable role tests * Document ansible-core version tested * Cut ansible-core 2.14 from testing documentation --- .github/workflows/ansible-test-plugins.yml | 48 +++++++------------ ...t-roles.yml => ansible-test-roles.yml.off} | 26 ++++++---- .github/workflows/build-docker-image.yml | 2 +- ...ker-image-mariadb-py310-mysqlclient211.yml | 2 +- .../docker-image-mariadb-py310-pymysql102.yml | 2 +- ...cker-image-mariadb-py38-mysqlclient201.yml | 2 +- .../docker-image-mariadb-py38-pymysql093.yml | 2 +- ...cker-image-mariadb-py39-mysqlclient203.yml | 2 +- .../docker-image-mariadb-py39-pymysql093.yml | 2 +- .../docker-image-my57-py38-mysqlclient201.yml | 2 +- .../docker-image-my57-py38-pymysql0711.yml | 2 +- .../docker-image-my57-py38-pymysql093.yml | 2 +- ...ocker-image-mysql-py310-mysqlclient211.yml | 2 +- .../docker-image-mysql-py310-pymysql102.yml | 2 +- ...docker-image-mysql-py38-mysqlclient201.yml | 2 +- .../docker-image-mysql-py38-pymysql093.yml | 2 +- ...docker-image-mysql-py39-mysqlclient203.yml | 2 +- .../docker-image-mysql-py39-pymysql093.yml | 2 +- README.md | 2 +- TESTING.md | 4 +- .../tasks/test_tls_requirements.yml | 10 ++-- 21 files changed, 59 insertions(+), 63 deletions(-) rename .github/workflows/{ansible-test-roles.yml => ansible-test-roles.yml.off} (77%) diff --git a/.github/workflows/ansible-test-plugins.yml b/.github/workflows/ansible-test-plugins.yml index 78644bb..77da49e 100644 --- a/.github/workflows/ansible-test-plugins.yml +++ b/.github/workflows/ansible-test-plugins.yml @@ -1,6 +1,6 @@ --- name: Plugins CI -on: +on: # yamllint disable-line rule:truthy push: paths: - 'plugins/**' @@ -18,15 +18,16 @@ on: jobs: sanity: name: "Sanity (Ansible: ${{ matrix.ansible }})" - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 strategy: matrix: ansible: - - stable-2.14 - stable-2.15 - stable-2.16 + - stable-2.17 - devel steps: + # https://github.com/ansible-community/ansible-test-gh-action - name: Perform sanity testing uses: ansible-community/ansible-test-gh-action@release/v1 with: @@ -36,14 +37,14 @@ jobs: integration: name: "Integration (Python: ${{ matrix.python }}, Ansible: ${{ matrix.ansible }}, DB: ${{ matrix.db_engine_name }} ${{ matrix.db_engine_version }}, connector: ${{ matrix.connector_name }} ${{ matrix.connector_version }})" - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: ansible: - - stable-2.14 - stable-2.15 - stable-2.16 + - stable-2.17 - devel db_engine_name: - mysql @@ -111,9 +112,6 @@ jobs: - db_engine_version: 5.7.40 python: '3.10' - - db_engine_version: 5.7.40 - ansible: stable-2.14 - - db_engine_version: 5.7.40 ansible: stable-2.15 @@ -126,9 +124,6 @@ jobs: - db_engine_version: 8.0.31 python: '3.8' - - db_engine_version: 8.0.31 - python: '3.8' - - db_engine_version: 10.4.27 python: '3.10' @@ -174,23 +169,20 @@ jobs: - python: '3.10' connector_version: 2.0.3 - - python: '3.8' - ansible: stable-2.14 - - - python: '3.8' - ansible: stable-2.15 - - python: '3.8' ansible: stable-2.16 + - python: '3.8' + ansible: stable-2.17 + - python: '3.8' ansible: devel - python: '3.9' - ansible: stable-2.15 + ansible: stable-2.16 - python: '3.9' - ansible: stable-2.16 + ansible: stable-2.17 - python: '3.9' ansible: devel @@ -284,16 +276,12 @@ jobs: fi - name: Set docker_image - run: > - docker_image_multiline=(" - ghcr.io/ansible-collections/community.mysql\ + run: |- + echo "docker_image=ghcr.io/ansible-collections/community.mysql\ /test-container-${{ env.db_client }}\ -py${{ env.python_version_flat }}\ -${{ matrix.connector_name }}${{ env.connector_version_flat }}\ - :latest") - - echo "docker_image=$(printf '%s' $docker_image_multiline)" - >> $GITHUB_ENV + :latest" >> $GITHUB_ENV - name: >- Perform integration testing against @@ -332,7 +320,7 @@ jobs: testing-type: integration units: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 name: Units (Ⓐ${{ matrix.ansible }}) strategy: # As soon as the first unit test fails, @@ -340,20 +328,20 @@ jobs: fail-fast: true matrix: ansible: - - stable-2.14 - stable-2.15 - stable-2.16 + - stable-2.17 - devel python: - 3.8 - 3.9 exclude: - - python: '3.8' - ansible: stable-2.14 - python: '3.8' ansible: stable-2.15 - python: '3.8' ansible: stable-2.16 + - python: '3.8' + ansible: stable-2.17 - python: '3.8' ansible: devel diff --git a/.github/workflows/ansible-test-roles.yml b/.github/workflows/ansible-test-roles.yml.off similarity index 77% rename from .github/workflows/ansible-test-roles.yml rename to .github/workflows/ansible-test-roles.yml.off index da8a805..a11d982 100644 --- a/.github/workflows/ansible-test-roles.yml +++ b/.github/workflows/ansible-test-roles.yml.off @@ -1,6 +1,6 @@ --- name: Roles CI -on: +on: # yamllint disable-line rule:truthy push: paths: - 'roles/**' @@ -15,7 +15,7 @@ on: jobs: molecule: name: "Molecule (Python: ${{ matrix.python }}, Ansible: ${{ matrix.ansible }}, MySQL: ${{ matrix.mysql }})" - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 env: PY_COLORS: 1 ANSIBLE_FORCE_COLOR: 1 @@ -24,26 +24,36 @@ jobs: mysql: - 2.0.12 ansible: - - stable-2.13 - - stable-2.14 - stable-2.15 + - stable-2.16 + - stable-2.17 - devel python: - - 3.8 - - 3.9 + - '3.8' + - '3.9' + - '3.10' exclude: - python: 3.8 + ansible: stable-2.17 + + - python: 3.9 + ansible: stable-2.17 + + - python: 3.8 + ansible: devel + + - python: 3.9 ansible: devel steps: - name: Check out code - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: path: ansible_collections/community/mysql - name: Set up Python ${{ matrix.python }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python }} diff --git a/.github/workflows/build-docker-image.yml b/.github/workflows/build-docker-image.yml index fa10268..0edd5ee 100644 --- a/.github/workflows/build-docker-image.yml +++ b/.github/workflows/build-docker-image.yml @@ -1,7 +1,7 @@ --- name: Build Docker Image for ansible-test -on: +on: # yamllint disable-line rule:truthy workflow_call: inputs: registry: diff --git a/.github/workflows/docker-image-mariadb-py310-mysqlclient211.yml b/.github/workflows/docker-image-mariadb-py310-mysqlclient211.yml index be252b7..77286e6 100644 --- a/.github/workflows/docker-image-mariadb-py310-mysqlclient211.yml +++ b/.github/workflows/docker-image-mariadb-py310-mysqlclient211.yml @@ -1,7 +1,7 @@ --- name: Docker Image CI mariadb-py310-mysqlclient211 -on: +on: # yamllint disable-line rule:truthy push: paths: - 'test-containers/mariadb-py310-mysqlclient211/**' diff --git a/.github/workflows/docker-image-mariadb-py310-pymysql102.yml b/.github/workflows/docker-image-mariadb-py310-pymysql102.yml index 90fec0e..c7cdfd4 100644 --- a/.github/workflows/docker-image-mariadb-py310-pymysql102.yml +++ b/.github/workflows/docker-image-mariadb-py310-pymysql102.yml @@ -1,7 +1,7 @@ --- name: Docker Image CI mariadb-py310-pymysql102 -on: +on: # yamllint disable-line rule:truthy push: paths: - 'test-containers/mariadb-py310-pymysql102/**' diff --git a/.github/workflows/docker-image-mariadb-py38-mysqlclient201.yml b/.github/workflows/docker-image-mariadb-py38-mysqlclient201.yml index c9c04f4..b5b9bb3 100644 --- a/.github/workflows/docker-image-mariadb-py38-mysqlclient201.yml +++ b/.github/workflows/docker-image-mariadb-py38-mysqlclient201.yml @@ -1,7 +1,7 @@ --- name: Docker Image CI mariadb-py38-mysqlclient201 -on: +on: # yamllint disable-line rule:truthy push: paths: - 'test-containers/mariadb-py38-mysqlclient201/**' diff --git a/.github/workflows/docker-image-mariadb-py38-pymysql093.yml b/.github/workflows/docker-image-mariadb-py38-pymysql093.yml index 92d0a74..ae6df2e 100644 --- a/.github/workflows/docker-image-mariadb-py38-pymysql093.yml +++ b/.github/workflows/docker-image-mariadb-py38-pymysql093.yml @@ -1,7 +1,7 @@ --- name: Docker Image CI mariadb-py38-pymysql093 -on: +on: # yamllint disable-line rule:truthy push: paths: - 'test-containers/mariadb-py38-pymysql093/**' diff --git a/.github/workflows/docker-image-mariadb-py39-mysqlclient203.yml b/.github/workflows/docker-image-mariadb-py39-mysqlclient203.yml index afad5af..4efeef1 100644 --- a/.github/workflows/docker-image-mariadb-py39-mysqlclient203.yml +++ b/.github/workflows/docker-image-mariadb-py39-mysqlclient203.yml @@ -1,7 +1,7 @@ --- name: Docker Image CI mariadb-py39-mysqlclient203 -on: +on: # yamllint disable-line rule:truthy push: paths: - 'test-containers/mariadb-py39-mysqlclient203/**' diff --git a/.github/workflows/docker-image-mariadb-py39-pymysql093.yml b/.github/workflows/docker-image-mariadb-py39-pymysql093.yml index 1aa5a04..a3205fb 100644 --- a/.github/workflows/docker-image-mariadb-py39-pymysql093.yml +++ b/.github/workflows/docker-image-mariadb-py39-pymysql093.yml @@ -1,7 +1,7 @@ --- name: Docker Image CI mariadb-py39-pymysql093 -on: +on: # yamllint disable-line rule:truthy push: paths: - 'test-containers/mariadb-py39-pymysql093/**' diff --git a/.github/workflows/docker-image-my57-py38-mysqlclient201.yml b/.github/workflows/docker-image-my57-py38-mysqlclient201.yml index 7aaf7e3..b256a47 100644 --- a/.github/workflows/docker-image-my57-py38-mysqlclient201.yml +++ b/.github/workflows/docker-image-my57-py38-mysqlclient201.yml @@ -1,7 +1,7 @@ --- name: Docker Image CI my57-py38-mysqlclient201 -on: +on: # yamllint disable-line rule:truthy push: paths: - 'test-containers/my57-py38-mysqlclient201/**' diff --git a/.github/workflows/docker-image-my57-py38-pymysql0711.yml b/.github/workflows/docker-image-my57-py38-pymysql0711.yml index 0bc2a9d..0064729 100644 --- a/.github/workflows/docker-image-my57-py38-pymysql0711.yml +++ b/.github/workflows/docker-image-my57-py38-pymysql0711.yml @@ -1,7 +1,7 @@ --- name: Docker Image CI my57-py38-pymysql0711 -on: +on: # yamllint disable-line rule:truthy push: paths: - 'test-containers/my57-py38-pymysql0711/**' diff --git a/.github/workflows/docker-image-my57-py38-pymysql093.yml b/.github/workflows/docker-image-my57-py38-pymysql093.yml index 462324b..58c7fed 100644 --- a/.github/workflows/docker-image-my57-py38-pymysql093.yml +++ b/.github/workflows/docker-image-my57-py38-pymysql093.yml @@ -1,7 +1,7 @@ --- name: Docker Image CI my57-py38-pymysql093 -on: +on: # yamllint disable-line rule:truthy push: paths: - 'test-containers/my57-py38-pymysql093/**' diff --git a/.github/workflows/docker-image-mysql-py310-mysqlclient211.yml b/.github/workflows/docker-image-mysql-py310-mysqlclient211.yml index 307aea7..dcb846f 100644 --- a/.github/workflows/docker-image-mysql-py310-mysqlclient211.yml +++ b/.github/workflows/docker-image-mysql-py310-mysqlclient211.yml @@ -1,7 +1,7 @@ --- name: Docker Image CI mysql-py310-mysqlclient211 -on: +on: # yamllint disable-line rule:truthy push: paths: - 'test-containers/mysql-py310-mysqlclient211/**' diff --git a/.github/workflows/docker-image-mysql-py310-pymysql102.yml b/.github/workflows/docker-image-mysql-py310-pymysql102.yml index 6f7bf3f..815b923 100644 --- a/.github/workflows/docker-image-mysql-py310-pymysql102.yml +++ b/.github/workflows/docker-image-mysql-py310-pymysql102.yml @@ -1,7 +1,7 @@ --- name: Docker Image CI mysql-py310-pymysql102 -on: +on: # yamllint disable-line rule:truthy push: paths: - 'test-containers/mysql-py310-pymysql102/**' diff --git a/.github/workflows/docker-image-mysql-py38-mysqlclient201.yml b/.github/workflows/docker-image-mysql-py38-mysqlclient201.yml index e0da5df..93359a4 100644 --- a/.github/workflows/docker-image-mysql-py38-mysqlclient201.yml +++ b/.github/workflows/docker-image-mysql-py38-mysqlclient201.yml @@ -1,7 +1,7 @@ --- name: Docker Image CI mysql-py38-mysqlclient201 -on: +on: # yamllint disable-line rule:truthy push: paths: - 'test-containers/mysql-py38-mysqlclient201/**' diff --git a/.github/workflows/docker-image-mysql-py38-pymysql093.yml b/.github/workflows/docker-image-mysql-py38-pymysql093.yml index 3cc1e0a..ac572ea 100644 --- a/.github/workflows/docker-image-mysql-py38-pymysql093.yml +++ b/.github/workflows/docker-image-mysql-py38-pymysql093.yml @@ -1,7 +1,7 @@ --- name: Docker Image CI mysql-py38-pymysql093 -on: +on: # yamllint disable-line rule:truthy push: paths: - 'test-containers/mysql-py38-pymysql093/**' diff --git a/.github/workflows/docker-image-mysql-py39-mysqlclient203.yml b/.github/workflows/docker-image-mysql-py39-mysqlclient203.yml index 0a3a256..b314e57 100644 --- a/.github/workflows/docker-image-mysql-py39-mysqlclient203.yml +++ b/.github/workflows/docker-image-mysql-py39-mysqlclient203.yml @@ -1,7 +1,7 @@ --- name: Docker Image CI mysql-py39-mysqlclient203 -on: +on: # yamllint disable-line rule:truthy push: paths: - 'test-containers/mysql-py39-mysqlclient203/**' diff --git a/.github/workflows/docker-image-mysql-py39-pymysql093.yml b/.github/workflows/docker-image-mysql-py39-pymysql093.yml index b974420..55962fb 100644 --- a/.github/workflows/docker-image-mysql-py39-pymysql093.yml +++ b/.github/workflows/docker-image-mysql-py39-pymysql093.yml @@ -1,7 +1,7 @@ --- name: Docker Image CI mysql-py39-pymysql093 -on: +on: # yamllint disable-line rule:truthy push: paths: - 'test-containers/mysql-py39-pymysql093/*' diff --git a/README.md b/README.md index 0e0704e..9853569 100644 --- a/README.md +++ b/README.md @@ -99,9 +99,9 @@ Here is the table for the support timeline: ### ansible-core -- stable-2.14 - stable-2.15 - stable-2.16 +- stable-2.17 - current development version ### Databases diff --git a/TESTING.md b/TESTING.md index 9e0840a..f31db4a 100644 --- a/TESTING.md +++ b/TESTING.md @@ -49,11 +49,9 @@ The Makefile accept the following options - `ansible` - Mandatory: true - Choices: - - "stable-2.12" - - "stable-2.13" - - "stable-2.14" - "stable-2.15" - "stable-2.16" + - "stable-2.17" - "devel" - Description: Version of ansible to install in a venv to run ansible-test diff --git a/tests/integration/targets/test_mysql_user/tasks/test_tls_requirements.yml b/tests/integration/targets/test_mysql_user/tasks/test_tls_requirements.yml index d8c2935..e7c25ce 100644 --- a/tests/integration/targets/test_mysql_user/tasks/test_tls_requirements.yml +++ b/tests/integration/targets/test_mysql_user/tasks/test_tls_requirements.yml @@ -76,14 +76,14 @@ that: - "'SSL' in reqs" vars: - - reqs: "{{((old_result.results[0] is skipped | ternary(new_result, old_result)).results | selectattr('item', 'contains', user_name_1) | first).stdout.split('REQUIRE')[1].split(separator)[0].strip()}}" + reqs: "{{ ((old_result.results[0] is skipped | ternary(new_result, old_result)).results | selectattr('item', 'contains', user_name_1) | first).stdout.split('REQUIRE')[1].split(separator)[0].strip() }}" - name: Tls reqs | Assert user2 TLS requirements assert: that: - "'X509' in reqs" vars: - - reqs: "{{((old_result.results[0] is skipped | ternary(new_result, old_result)).results | selectattr('item', 'contains', user_name_2) | first).stdout.split('REQUIRE')[1].split(separator)[0].strip()}}" + reqs: "{{ ((old_result.results[0] is skipped | ternary(new_result, old_result)).results | selectattr('item', 'contains', user_name_2) | first).stdout.split('REQUIRE')[1].split(separator)[0].strip() }}" - name: Tls reqs | Assert user3 TLS requirements assert: @@ -92,7 +92,7 @@ - "'/CN=org/O=MyDom, Inc./C=US/ST=Oregon/L=Portland' in (reqs | select('contains', 'ISSUER') | first)" - "'ECDHE-ECDSA-AES256-SHA384' in (reqs | select('contains', 'CIPHER') | first)" vars: - - reqs: "{{((old_result.results[0] is skipped | ternary(new_result, old_result)).results | selectattr('item', 'contains', user_name_3) | first).stdout.split('REQUIRE')[1].split(separator)[0].replace(\"' \", \"':\").split(\":\")}}" + reqs: "{{ ((old_result.results[0] is skipped | ternary(new_result, old_result)).results | selectattr('item', 'contains', user_name_3) | first).stdout.split('REQUIRE')[1].split(separator)[0].replace(\"' \", \"':\").split(\":\") }}" # CentOS 6 uses an older version of jinja that does not provide the selectattr filter. when: ansible_distribution != 'CentOS' or ansible_distribution_major_version != '6' @@ -129,7 +129,7 @@ assert: that: "'SSL' in reqs" vars: - - reqs: "{{(old_result is skipped | ternary(new_result, old_result)).stdout.split('REQUIRE')[1].split(separator)[0].strip()}}" + reqs: "{{ (old_result is skipped | ternary(new_result, old_result)).stdout.split('REQUIRE')[1].split(separator)[0].strip() }}" - name: Tls reqs | Modify user with TLS requirements state=present (expect changed=true) mysql_user: @@ -157,7 +157,7 @@ assert: that: "'X509' in reqs" vars: - - reqs: "{{(old_result is skipped | ternary(new_result, old_result)).stdout.split('REQUIRE')[1].split(separator)[0].strip()}}" + reqs: "{{ (old_result is skipped | ternary(new_result, old_result)).stdout.split('REQUIRE')[1].split(separator)[0].strip() }}" - name: Tls reqs | Remove TLS requirements from user (expect changed=true) mysql_user: From 0618ff6c41c0c76c923485d74fa8dd3db7177fd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20Inderm=C3=BChle?= Date: Fri, 12 Apr 2024 09:00:43 +0200 Subject: [PATCH 175/217] Fix sanity tests for ansible-core 2.18 (#627) --- tests/sanity/{ignore-2.14.txt => ignore-2.18.txt} | 2 ++ 1 file changed, 2 insertions(+) rename tests/sanity/{ignore-2.14.txt => ignore-2.18.txt} (57%) diff --git a/tests/sanity/ignore-2.14.txt b/tests/sanity/ignore-2.18.txt similarity index 57% rename from tests/sanity/ignore-2.14.txt rename to tests/sanity/ignore-2.18.txt index 90ddba3..55b2904 100644 --- a/tests/sanity/ignore-2.14.txt +++ b/tests/sanity/ignore-2.18.txt @@ -1,2 +1,4 @@ plugins/modules/mysql_db.py validate-modules:use-run-command-not-popen plugins/modules/mysql_user.py validate-modules:undocumented-parameter +plugins/module_utils/mysql.py pylint:unused-import +plugins/module_utils/version.py pylint:unused-import From 47710cfb93fad4f98c5895d5a263fadd1d0cc8c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20Inderm=C3=BChle?= Date: Tue, 16 Apr 2024 10:52:24 +0200 Subject: [PATCH 176/217] Enhance support of tls_requires in mysql_user and mysql_info (#628) * fix option name * Add tests for users using SSL * Rewrite get_tls_requires using mysql.user table * Add tls_requires to users_info filter * add more consistant test users * Add tls tests users in cleanup task * Fix tls_requires data structure inconsistencies between modules * Refactor user implementation to host get_tls_requires * fix MySQL tls_requires not removed from user passed as empty * Fix wrong variable used to return a hashed password * Fix sanity * fix unit tests * Add changelog fragment * Add PR URI to the changelog * Add more precise change log * fix documentation using wrong variable as an example * Document example returned value `tls_requires` from users_info filter * Revert changes that will be in a separate PR * Fix sanity --- .../fragments/mysql_user_tls_requires.yml | 6 ++ .../implementations/mariadb/user.py | 45 ++++++++++ .../implementations/mysql/user.py | 46 ++++++++++ plugins/module_utils/user.py | 43 +++------- plugins/modules/mysql_info.py | 22 +++-- plugins/modules/mysql_role.py | 4 +- plugins/modules/mysql_user.py | 3 - .../tasks/filter_users_info.yml | 85 +++++++++++++++++-- tests/unit/plugins/modules/test_mysql_info.py | 14 +-- 9 files changed, 213 insertions(+), 55 deletions(-) create mode 100644 changelogs/fragments/mysql_user_tls_requires.yml diff --git a/changelogs/fragments/mysql_user_tls_requires.yml b/changelogs/fragments/mysql_user_tls_requires.yml new file mode 100644 index 0000000..1fa0c94 --- /dev/null +++ b/changelogs/fragments/mysql_user_tls_requires.yml @@ -0,0 +1,6 @@ +--- +minor_changes: + - mysql_info - Add ``tls_requires`` returned value for the ``users_info`` filter (https://github.com/ansible-collections/community.mysql/pull/628). +bugfixes: + - mysql_user - Fix idempotence when using variables from the ``users_info`` filter of ``mysql_info`` as an input (https://github.com/ansible-collections/community.mysql/pull/628). + - mysql_user - Fix ``tls_requires`` not removing ``SSL`` and ``X509`` when sets as empty (https://github.com/ansible-collections/community.mysql/pull/628). diff --git a/plugins/module_utils/implementations/mariadb/user.py b/plugins/module_utils/implementations/mariadb/user.py index cdc14b2..fa9ecdf 100644 --- a/plugins/module_utils/implementations/mariadb/user.py +++ b/plugins/module_utils/implementations/mariadb/user.py @@ -29,3 +29,48 @@ def server_supports_password_expire(cursor): version = get_server_version(cursor) return LooseVersion(version) >= LooseVersion("10.4.3") + + +def get_tls_requires(cursor, user, host): + """Get user TLS requirements. + Reads directly from mysql.user table allowing for a more + readable code. + + Args: + cursor (cursor): DB driver cursor object. + user (str): User name. + host (str): User host name. + + Returns: Dictionary containing current TLS required + """ + tls_requires = dict() + + query = ('SELECT ssl_type, ssl_cipher, x509_issuer, x509_subject ' + 'FROM mysql.user WHERE User = %s AND Host = %s') + cursor.execute(query, (user, host)) + res = cursor.fetchone() + + # Mysql_info use a DictCursor so we must convert back to a list + # otherwise we get KeyError 0 + if isinstance(res, dict): + res = list(res.values()) + + # When user don't require SSL, res value is: ('', '', '', '') + if not any(res): + return None + + if res[0] == 'ANY': + tls_requires['SSL'] = None + + if res[0] == 'X509': + tls_requires['X509'] = None + + if res[1]: + tls_requires['CIPHER'] = res[1] + + if res[2]: + tls_requires['ISSUER'] = res[2] + + if res[3]: + tls_requires['SUBJECT'] = res[3] + return tls_requires diff --git a/plugins/module_utils/implementations/mysql/user.py b/plugins/module_utils/implementations/mysql/user.py index 4e41c05..700c355 100644 --- a/plugins/module_utils/implementations/mysql/user.py +++ b/plugins/module_utils/implementations/mysql/user.py @@ -8,6 +8,9 @@ __metaclass__ = type from ansible_collections.community.mysql.plugins.module_utils.version import LooseVersion from ansible_collections.community.mysql.plugins.module_utils.mysql import get_server_version +import re +import shlex + def use_old_user_mgmt(cursor): version = get_server_version(cursor) @@ -30,3 +33,46 @@ def server_supports_password_expire(cursor): version = get_server_version(cursor) return LooseVersion(version) >= LooseVersion("5.7") + + +def get_tls_requires(cursor, user, host): + """Get user TLS requirements. + We must use SHOW GRANTS because some tls fileds are encoded. + + Args: + cursor (cursor): DB driver cursor object. + user (str): User name. + host (str): User host name. + + Returns: Dictionary containing current TLS required + """ + if not use_old_user_mgmt(cursor): + query = "SHOW CREATE USER '%s'@'%s'" % (user, host) + else: + query = "SHOW GRANTS for '%s'@'%s'" % (user, host) + + cursor.execute(query) + grants = cursor.fetchone() + + # Mysql_info use a DictCursor so we must convert back to a list + # otherwise we get KeyError 0 + if isinstance(grants, dict): + grants = list(grants.values()) + grants_str = ''.join(grants) + + pattern = r"(?<=\bREQUIRE\b)(.*?)(?=(?:\bPASSWORD\b|$))" + requires_match = re.search(pattern, grants_str) + requires = requires_match.group().strip() if requires_match else "" + + if requires.startswith('NONE'): + return None + + if requires.startswith('SSL'): + return {'SSL': None} + + if requires.startswith('X509'): + return {'X509': None} + + items = iter(shlex.split(requires)) + requires = dict(zip(items, items)) + return requires or None diff --git a/plugins/module_utils/user.py b/plugins/module_utils/user.py index f042c85..d4ae9dd 100644 --- a/plugins/module_utils/user.py +++ b/plugins/module_utils/user.py @@ -17,6 +17,7 @@ from ansible.module_utils.six import iteritems from ansible_collections.community.mysql.plugins.module_utils.mysql import ( mysql_driver, + get_server_implementation, ) @@ -80,31 +81,6 @@ def do_not_mogrify_requires(query, params, tls_requires): return query, params -def get_tls_requires(cursor, user, host): - if user: - if not impl.use_old_user_mgmt(cursor): - query = "SHOW CREATE USER '%s'@'%s'" % (user, host) - else: - query = "SHOW GRANTS for '%s'@'%s'" % (user, host) - - cursor.execute(query) - require_list = [tuple[0] for tuple in filter(lambda x: "REQUIRE" in x[0], cursor.fetchall())] - require_line = require_list[0] if require_list else "" - pattern = r"(?<=\bREQUIRE\b)(.*?)(?=(?:\bPASSWORD\b|$))" - requires_match = re.search(pattern, require_line) - requires = requires_match.group().strip() if requires_match else "" - if any((requires.startswith(req) for req in ('SSL', 'X509', 'NONE'))): - requires = requires.split()[0] - if requires == 'NONE': - requires = None - else: - import shlex - - items = iter(shlex.split(requires)) - requires = dict(zip(items, items)) - return requires or None - - def get_grants(cursor, user, host): cursor.execute("SHOW GRANTS FOR %s@%s", (user, host)) grants_line = list(filter(lambda x: "ON *.*" in x[0], cursor.fetchall()))[0] @@ -166,6 +142,7 @@ def user_add(cursor, user, host, host_all, password, encrypted, return {'changed': True, 'password_changed': None, 'attributes': attributes} # Determine what user management method server uses + impl = get_user_implementation(cursor) old_user_mgmt = impl.use_old_user_mgmt(cursor) mogrify = do_not_mogrify_requires if old_user_mgmt else mogrify_requires @@ -244,6 +221,7 @@ def user_mod(cursor, user, host, host_all, password, encrypted, grant_option = False # Determine what user management method server uses + impl = get_user_implementation(cursor) old_user_mgmt = impl.use_old_user_mgmt(cursor) if host_all and not role: @@ -499,7 +477,7 @@ def user_mod(cursor, user, host, host_all, password, encrypted, continue # Handle TLS requirements - current_requires = get_tls_requires(cursor, user, host) + current_requires = sanitize_requires(impl.get_tls_requires(cursor, user, host)) if current_requires != tls_requires: msg = "TLS requires updated" if not module.check_mode: @@ -837,6 +815,7 @@ def privileges_grant(cursor, user, host, db_table, priv, tls_requires, maria_rol query.append("TO %s") params = (user) + impl = get_user_implementation(cursor) if tls_requires and impl.use_old_user_mgmt(cursor): query, params = mogrify_requires(" ".join(query), params, tls_requires) query = [query] @@ -973,6 +952,7 @@ def limit_resources(module, cursor, user, host, resource_limits, check_mode): Returns: True, if changed, False otherwise. """ + impl = get_user_implementation(cursor) if not impl.server_supports_alter_user(cursor): module.fail_json(msg="The server version does not match the requirements " "for resource_limits parameter. See module's documentation.") @@ -1108,12 +1088,11 @@ def attributes_get(cursor, user, host): return j if j else None -def get_impl(cursor): - global impl - cursor.execute("SELECT VERSION()") - if 'mariadb' in cursor.fetchone()[0].lower(): +def get_user_implementation(cursor): + db_engine = get_server_implementation(cursor) + if db_engine == 'mariadb': from ansible_collections.community.mysql.plugins.module_utils.implementations.mariadb import user as mariauser - impl = mariauser + return mariauser else: from ansible_collections.community.mysql.plugins.module_utils.implementations.mysql import user as mysqluser - impl = mysqluser + return mysqluser diff --git a/plugins/modules/mysql_info.py b/plugins/modules/mysql_info.py index 0be25fa..f30f1a1 100644 --- a/plugins/modules/mysql_info.py +++ b/plugins/modules/mysql_info.py @@ -146,7 +146,7 @@ EXAMPLES = r''' plugin: "{{ item.plugin | default(omit) }}" plugin_auth_string: "{{ item.plugin_auth_string | default(omit) }}" plugin_hash_string: "{{ item.plugin_hash_string | default(omit) }}" - tls_require: "{{ item.tls_require | default(omit) }}" + tls_requires: "{{ item.tls_requires | default(omit) }}" priv: "{{ item.priv | default(omit) }}" resource_limits: "{{ item.resource_limits | default(omit) }}" column_case_sensitive: true @@ -240,7 +240,8 @@ users_info: "host": "host.com", "plugin": "mysql_native_password", "priv": "db1.*:SELECT/db2.*:SELECT", - "resource_limits": { "MAX_USER_CONNECTIONS": 100 } } + "resource_limits": { "MAX_USER_CONNECTIONS": 100 }, + "tls_requires": { "SSL": null } } version_added: '3.8.0' engines: description: Information about the server's storage engines. @@ -300,6 +301,7 @@ from ansible_collections.community.mysql.plugins.module_utils.user import ( privileges_get, get_resource_limits, get_existing_authentication, + get_user_implementation, ) from ansible.module_utils.six import iteritems from ansible.module_utils._text import to_native @@ -327,10 +329,11 @@ class MySQL_Info(object): 5. add info about the new subset with an example to RETURN block """ - def __init__(self, module, cursor, server_implementation): + def __init__(self, module, cursor, server_implementation, user_implementation): self.module = module self.cursor = cursor self.server_implementation = server_implementation + self.user_implementation = user_implementation self.info = { 'version': {}, 'databases': {}, @@ -602,13 +605,17 @@ class MySQL_Info(object): priv_string.remove('*.*:USAGE') resource_limits = get_resource_limits(self.cursor, user, host) - copy_ressource_limits = dict.copy(resource_limits) + + tls_requires = self.user_implementation.get_tls_requires( + self.cursor, user, host) + output_dict = { 'name': user, 'host': host, 'priv': '/'.join(priv_string), 'resource_limits': copy_ressource_limits, + 'tls_requires': tls_requires, } # Prevent returning a resource limit if empty @@ -619,6 +626,10 @@ class MySQL_Info(object): if len(output_dict['resource_limits']) == 0: del output_dict['resource_limits'] + # Prevent returning tls_require if empty + if not tls_requires: + del output_dict['tls_requires'] + authentications = get_existing_authentication(self.cursor, user, host) if authentications: output_dict.update(authentications) @@ -745,11 +756,12 @@ def main(): module.fail_json(msg) server_implementation = get_server_implementation(cursor) + user_implementation = get_user_implementation(cursor) ############################### # Create object and do main job - mysql = MySQL_Info(module, cursor, server_implementation) + mysql = MySQL_Info(module, cursor, server_implementation, user_implementation) module.exit_json(changed=False, connector_name=connector_name, diff --git a/plugins/modules/mysql_role.py b/plugins/modules/mysql_role.py index 3e3462a..65ed894 100644 --- a/plugins/modules/mysql_role.py +++ b/plugins/modules/mysql_role.py @@ -309,7 +309,7 @@ from ansible_collections.community.mysql.plugins.module_utils.mysql import ( ) from ansible_collections.community.mysql.plugins.module_utils.user import ( convert_priv_dict_to_str, - get_impl, + get_user_implementation, get_mode, user_mod, privileges_grant, @@ -1054,7 +1054,7 @@ def main(): # Set defaults changed = False - get_impl(cursor) + impl = get_user_implementation(cursor) if priv is not None: try: diff --git a/plugins/modules/mysql_user.py b/plugins/modules/mysql_user.py index e02b153..fa54c7d 100644 --- a/plugins/modules/mysql_user.py +++ b/plugins/modules/mysql_user.py @@ -401,7 +401,6 @@ from ansible_collections.community.mysql.plugins.module_utils.mysql import ( ) from ansible_collections.community.mysql.plugins.module_utils.user import ( convert_priv_dict_to_str, - get_impl, get_mode, InvalidPrivsError, limit_resources, @@ -528,8 +527,6 @@ def main(): if session_vars: set_session_vars(module, cursor, session_vars) - get_impl(cursor) - if priv is not None: try: mode = get_mode(cursor) diff --git a/tests/integration/targets/test_mysql_info/tasks/filter_users_info.yml b/tests/integration/targets/test_mysql_info/tasks/filter_users_info.yml index 2c126c1..63ce190 100644 --- a/tests/integration/targets/test_mysql_info/tasks/filter_users_info.yml +++ b/tests/integration/targets/test_mysql_info/tasks/filter_users_info.yml @@ -47,7 +47,7 @@ state: import target: /root/create_procedure.sql - # Use a query instead of mysql_user, because we want to caches differences + # Use a query instead of mysql_user, because we want to catch differences # at the end and a bug in mysql_user would be invisible to this tests - name: Mysql_info users_info | Prepare common tests users community.mysql.mysql_query: @@ -147,6 +147,69 @@ '*CB3326D5279DE7915FE5D743232165EE887883CA' - GRANT SELECT ON users_info_db.* TO users_info_multi_hosts@'host2' + - >- + CREATE USER users_info_tls_none@'host' + IDENTIFIED WITH mysql_native_password AS + '*CB3326D5279DE7915FE5D743232165EE887883CA' REQUIRE NONE + - GRANT SELECT ON users_info_db.* TO users_info_tls_none@'host' + + - >- + CREATE USER users_info_tls_ssl@'host' + IDENTIFIED WITH mysql_native_password AS + '*CB3326D5279DE7915FE5D743232165EE887883CA' REQUIRE SSL + - GRANT SELECT ON users_info_db.* TO users_info_tls_ssl@'host' + + - >- + CREATE USER users_info_tls_cipher@'host' + IDENTIFIED WITH mysql_native_password AS + '*CB3326D5279DE7915FE5D743232165EE887883CA' + REQUIRE CIPHER 'ECDH-RSA-AES256-SHA384' + - GRANT SELECT ON users_info_db.* TO users_info_tls_cipher@'host' + + - >- + CREATE USER users_info_tls_x509@'host' + IDENTIFIED WITH mysql_native_password AS + '*CB3326D5279DE7915FE5D743232165EE887883CA' REQUIRE X509 + - GRANT SELECT ON users_info_db.* TO users_info_tls_x509@'host' + + - >- + CREATE USER users_info_tls_subject@'host' + IDENTIFIED WITH mysql_native_password AS + '*CB3326D5279DE7915FE5D743232165EE887883CA' + REQUIRE SUBJECT '/CN=Bob/O=MyDom/C=US/ST=Oregon/L=Portland' + - GRANT SELECT ON users_info_db.* TO users_info_tls_subject@'host' + + - >- + CREATE USER users_info_tls_issuer@'host' + IDENTIFIED WITH mysql_native_password AS + '*CB3326D5279DE7915FE5D743232165EE887883CA' + REQUIRE ISSUER '/C=FI/ST=Somewhere/L=City/ + O=CompanyX/CN=Bob/emailAddress=bob@companyx.com' + - GRANT SELECT ON users_info_db.* TO users_info_tls_issuer@'host' + + - >- + CREATE USER users_info_tls_subject_issuer@'host' + IDENTIFIED WITH mysql_native_password AS + '*CB3326D5279DE7915FE5D743232165EE887883CA' + REQUIRE SUBJECT '/CN=Bob/O=MyDom/C=US/ST=Oregon/L=Portland' + AND ISSUER '/C=FI/ST=Somewhere/L=City/ + O=CompanyX/CN=Bob/emailAddress=bob@companyx.com' + - >- + GRANT SELECT ON users_info_db.* + TO users_info_tls_subject_issuer@'host' + + - >- + CREATE USER users_info_tls_sub_issu_ciph@'host' + IDENTIFIED WITH mysql_native_password AS + '*CB3326D5279DE7915FE5D743232165EE887883CA' + REQUIRE SUBJECT '/CN=Bob/O=MyDom/C=US/ST=Oregon/L=Portland' + AND ISSUER '/C=FI/ST=Somewhere/L=City/ + O=CompanyX/CN=Bob/emailAddress=bob@companyx.com' + AND CIPHER 'ECDH-RSA-AES256-SHA384' + - >- + GRANT SELECT ON users_info_db.* + TO users_info_tls_sub_issu_ciph@'host' + - name: Mysql_info users_info | Prepare tests users for MariaDB community.mysql.mysql_user: name: "{{ item.name }}" @@ -154,7 +217,7 @@ plugin: "{{ item.plugin | default(omit) }}" plugin_auth_string: "{{ item.plugin_auth_string | default(omit) }}" plugin_hash_string: "{{ item.plugin_hash_string | default(omit) }}" - tls_require: "{{ item.tls_require | default(omit) }}" + tls_requires: "{{ item.tls_requires | default(omit) }}" priv: "{{ item.priv }}" resource_limits: "{{ item.resource_limits | default(omit) }}" column_case_sensitive: true @@ -174,7 +237,7 @@ plugin: "{{ item.plugin | default(omit) }}" plugin_auth_string: "{{ item.plugin_auth_string | default(omit) }}" plugin_hash_string: "{{ item.plugin_hash_string | default(omit) }}" - tls_require: "{{ item.tls_require | default(omit) }}" + tls_requires: "{{ item.tls_requires | default(omit) }}" priv: "{{ item.priv }}" resource_limits: "{{ item.resource_limits | default(omit) }}" column_case_sensitive: true @@ -196,7 +259,7 @@ plugin: "{{ item.plugin | default(omit) }}" plugin_auth_string: "{{ item.plugin_auth_string | default(omit) }}" plugin_hash_string: "{{ item.plugin_hash_string | default(omit) }}" - tls_require: "{{ item.tls_require | default(omit) }}" + tls_requires: "{{ item.tls_requires | default(omit) }}" priv: "{{ item.priv }}" resource_limits: "{{ item.resource_limits | default(omit) }}" column_case_sensitive: true @@ -227,7 +290,7 @@ plugin: "{{ item.plugin | default(omit) }}" plugin_auth_string: "{{ item.plugin_auth_string | default(omit) }}" plugin_hash_string: "{{ item.plugin_hash_string | default(omit) }}" - tls_require: "{{ item.tls_require | default(omit) }}" + tls_requires: "{{ item.tls_requires | default(omit) }}" priv: "{{ item.priv | default(omit) }}" resource_limits: "{{ item.resource_limits | default(omit) }}" column_case_sensitive: true @@ -237,7 +300,9 @@ label: "{{ item.name }}@{{ item.host }}" register: recreate_users_result failed_when: - - recreate_users_result is changed + - >- + recreate_users_result is changed or + recreate_users_result.msg != 'User unchanged' when: - item.name != 'root' - item.name != 'mysql' @@ -265,6 +330,14 @@ - users_info_usage_only - users_info_columns_uppercase - users_info_multi_hosts + - users_info_tls_none + - users_info_tls_ssl + - users_info_tls_cipher + - users_info_tls_x509 + - users_info_tls_subject + - users_info_tls_issuer + - users_info_tls_subject_issuer + - users_info_tls_sub_issu_ciph - name: Mysql_info users_info | Cleanup databases community.mysql.mysql_db: diff --git a/tests/unit/plugins/modules/test_mysql_info.py b/tests/unit/plugins/modules/test_mysql_info.py index 6aaf66e..0d086f4 100644 --- a/tests/unit/plugins/modules/test_mysql_info.py +++ b/tests/unit/plugins/modules/test_mysql_info.py @@ -14,15 +14,15 @@ from ansible_collections.community.mysql.plugins.modules.mysql_info import MySQL @pytest.mark.parametrize( - 'suffix,cursor_output,server_implementation', + 'suffix,cursor_output,server_implementation,user_implementation', [ - ('mysql', '5.5.1-mysql', 'mysql'), - ('log', '5.7.31-log', 'mysql'), - ('mariadb', '10.5.0-mariadb', 'mariadb'), - ('', '8.0.22', 'mysql'), + ('mysql', '5.5.1-mysql', 'mysql', 'mysql'), + ('log', '5.7.31-log', 'mysql', 'mysql'), + ('mariadb', '10.5.0-mariadb', 'mariadb', 'mariadb'), + ('', '8.0.22', 'mysql', 'mysql'), ] ) -def test_get_info_suffix(suffix, cursor_output, server_implementation): +def test_get_info_suffix(suffix, cursor_output, server_implementation, user_implementation): def __cursor_return_value(input_parameter): if input_parameter == "SHOW GLOBAL VARIABLES": cursor.fetchall.return_value = [{"Variable_name": "version", "Value": cursor_output}] @@ -32,6 +32,6 @@ def test_get_info_suffix(suffix, cursor_output, server_implementation): cursor = MagicMock() cursor.execute.side_effect = __cursor_return_value - info = MySQL_Info(MagicMock(), cursor, server_implementation) + info = MySQL_Info(MagicMock(), cursor, server_implementation, user_implementation) assert info.get_info([], [], False)['version']['suffix'] == suffix From 6ce2f49f96373bc357a71bdcf4ae1412086d8f4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20Inderm=C3=BChle?= Date: Thu, 2 May 2024 10:26:04 +0200 Subject: [PATCH 177/217] Improve get replica/primary status (#634) * Fix case where a failed fetchone() still return a dict * Fix test for MariaDB * fix case where a failed fetchone() still return a dict for primary * Add changelog fragment --- .../improve_get_replica_primary_status.yml | 4 ++++ plugins/modules/mysql_replication.py | 20 ++++++++++++------- 2 files changed, 17 insertions(+), 7 deletions(-) create mode 100644 changelogs/fragments/improve_get_replica_primary_status.yml diff --git a/changelogs/fragments/improve_get_replica_primary_status.yml b/changelogs/fragments/improve_get_replica_primary_status.yml new file mode 100644 index 0000000..512d7ef --- /dev/null +++ b/changelogs/fragments/improve_get_replica_primary_status.yml @@ -0,0 +1,4 @@ +--- +minor_changes: + + - mysql_replication - Improve detection of IsReplica and IsPrimary by inspecting the dictionary returned from the SQL query instead of relying on variable types. This ensures compatibility with changes in the connector or the output of SHOW REPLICA STATUS and SHOW MASTER STATUS, allowing for easier maintenance if these change in the future. diff --git a/plugins/modules/mysql_replication.py b/plugins/modules/mysql_replication.py index 934b479..f4f192a 100644 --- a/plugins/modules/mysql_replication.py +++ b/plugins/modules/mysql_replication.py @@ -550,20 +550,26 @@ def main(): if mode == 'getprimary': status = get_primary_status(cursor) - if not isinstance(status, dict): - status = dict(Is_Primary=False, - msg="Server is not configured as mysql primary") - else: + if status and "File" in status and "Position" in status: status['Is_Primary'] = True + else: + status = dict( + Is_Primary=False, + msg="Server is not configured as mysql primary. " + "Meaning: Binary logs are disabled") module.exit_json(queries=executed_queries, **status) elif mode == "getreplica": status = get_replica_status(cursor, connection_name, channel, replica_term) - if not isinstance(status, dict): - status = dict(Is_Replica=False, msg="Server is not configured as mysql replica") - else: + # MySQL 8.0 uses Replica_... + # MariaDB 10.6 uses Slave_... + if status and ( + "Slave_IO_Running" in status or + "Replica_IO_Running" in status): status['Is_Replica'] = True + else: + status = dict(Is_Replica=False, msg="Server is not configured as mysql replica") module.exit_json(queries=executed_queries, **status) From a80b805619f108580ecb09d7d02693316fa3765b Mon Sep 17 00:00:00 2001 From: Dennis Felipe Urtubia <33161939+dennisurtubia@users.noreply.github.com> Date: Tue, 21 May 2024 15:58:05 -0300 Subject: [PATCH 178/217] Adds support for `CHANGE REPLICATION SOURCE TO` statement (#636) * feat: adds support for 'change replication source to' statement --- ...rts_mysql_change_replication_source_to.yml | 3 + plugins/modules/mysql_replication.py | 73 ++++++++++++++++++- .../test_mysql_replication/tasks/main.yml | 5 ++ ...sql_replication_changereplication_mode.yml | 65 +++++++++++++++++ .../tasks/mysql_replication_initial.yml | 2 +- 5 files changed, 146 insertions(+), 2 deletions(-) create mode 100644 changelogs/fragments/supports_mysql_change_replication_source_to.yml create mode 100644 tests/integration/targets/test_mysql_replication/tasks/mysql_replication_changereplication_mode.yml diff --git a/changelogs/fragments/supports_mysql_change_replication_source_to.yml b/changelogs/fragments/supports_mysql_change_replication_source_to.yml new file mode 100644 index 0000000..955d62e --- /dev/null +++ b/changelogs/fragments/supports_mysql_change_replication_source_to.yml @@ -0,0 +1,3 @@ +--- +minor_changes: + - mysql_replication - Adds support for `CHANGE REPLICATION SOURCE TO` statement (https://github.com/ansible-collections/community.mysql/issues/635). diff --git a/plugins/modules/mysql_replication.py b/plugins/modules/mysql_replication.py index f4f192a..23c94c1 100644 --- a/plugins/modules/mysql_replication.py +++ b/plugins/modules/mysql_replication.py @@ -19,11 +19,13 @@ description: author: - Balazs Pocze (@banyek) - Andrew Klychkov (@Andersson007) +- Dennis Urtubia (@dennisurtubia) options: mode: description: - Module operating mode. Could be C(changeprimary) (CHANGE MASTER TO), + C(changereplication) (CHANGE REPLICATION SOURCE TO) - only supported in MySQL 8.0.23 and later, C(getprimary) (SHOW MASTER STATUS), C(getreplica) (SHOW REPLICA STATUS), C(startreplica) (START REPLICA), @@ -34,6 +36,7 @@ options: type: str choices: - changeprimary + - changereplication - getprimary - getreplica - startreplica @@ -229,6 +232,13 @@ EXAMPLES = r''' primary_log_file: mysql-bin.000009 primary_log_pos: 4578 +- name: Change replication source to replica server 192.0.2.1 and use binary log 'mysql-bin.000009' with position 4578 + community.mysql.mysql_replication: + mode: changereplication + primary_host: 192.0.2.1 + primary_log_file: mysql-bin.000009 + primary_log_pos: 4578 + - name: Check replica status using port 3308 community.mysql.mysql_replication: mode: getreplica @@ -438,6 +448,16 @@ def changeprimary(cursor, chm, connection_name='', channel=''): cursor.execute(query) +def changereplication(cursor, chm, channel=''): + query = 'CHANGE REPLICATION SOURCE TO %s' % ','.join(chm) + + if channel: + query += " FOR CHANNEL '%s'" % channel + + executed_queries.append(query) + cursor.execute(query) + + def main(): argument_spec = mysql_common_argument_spec() argument_spec.update( @@ -449,7 +469,8 @@ def main(): 'startreplica', 'resetprimary', 'resetreplica', - 'resetreplicaall']), + 'resetreplicaall', + 'changereplication']), primary_auto_position=dict(type='bool', default=False, aliases=['master_auto_position']), primary_host=dict(type='str', aliases=['master_host']), primary_user=dict(type='str', aliases=['master_user']), @@ -655,6 +676,56 @@ def main(): module.exit_json(msg="Replica reset", changed=True, queries=executed_queries) else: module.exit_json(msg="Replica already reset", changed=False, queries=executed_queries) + elif mode == 'changereplication': + chm = [] + result = {} + if primary_host is not None: + chm.append("SOURCE_HOST='%s'" % primary_host) + if primary_user is not None: + chm.append("SOURCE_USER='%s'" % primary_user) + if primary_password is not None: + chm.append("SOURCE_PASSWORD='%s'" % primary_password) + if primary_port is not None: + chm.append("SOURCE_PORT=%s" % primary_port) + if primary_connect_retry is not None: + chm.append("SOURCE_CONNECT_RETRY=%s" % primary_connect_retry) + if primary_log_file is not None: + chm.append("SOURCE_LOG_FILE='%s'" % primary_log_file) + if primary_log_pos is not None: + chm.append("SOURCE_LOG_POS=%s" % primary_log_pos) + if primary_delay is not None: + chm.append("SOURCE_DELAY=%s" % primary_delay) + if relay_log_file is not None: + chm.append("RELAY_LOG_FILE='%s'" % relay_log_file) + if relay_log_pos is not None: + chm.append("RELAY_LOG_POS=%s" % relay_log_pos) + if primary_ssl is not None: + if primary_ssl: + chm.append("SOURCE_SSL=1") + else: + chm.append("SOURCE_SSL=0") + if primary_ssl_ca is not None: + chm.append("SOURCE_SSL_CA='%s'" % primary_ssl_ca) + if primary_ssl_capath is not None: + chm.append("SOURCE_SSL_CAPATH='%s'" % primary_ssl_capath) + if primary_ssl_cert is not None: + chm.append("SOURCE_SSL_CERT='%s'" % primary_ssl_cert) + if primary_ssl_key is not None: + chm.append("SOURCE_SSL_KEY='%s'" % primary_ssl_key) + if primary_ssl_cipher is not None: + chm.append("SOURCE_SSL_CIPHER='%s'" % primary_ssl_cipher) + if primary_ssl_verify_server_cert: + chm.append("SOURCE_SSL_VERIFY_SERVER_CERT=1") + if primary_auto_position: + chm.append("SOURCE_AUTO_POSITION=1") + try: + changereplication(cursor, chm, channel) + except mysql_driver.Warning as e: + result['warning'] = to_native(e) + except Exception as e: + module.fail_json(msg='%s. Query == CHANGE REPLICATION SOURCE TO %s' % (to_native(e), chm)) + result['changed'] = True + module.exit_json(queries=executed_queries, **result) warnings.simplefilter("ignore") diff --git a/tests/integration/targets/test_mysql_replication/tasks/main.yml b/tests/integration/targets/test_mysql_replication/tasks/main.yml index ab5b4a3..2baa536 100644 --- a/tests/integration/targets/test_mysql_replication/tasks/main.yml +++ b/tests/integration/targets/test_mysql_replication/tasks/main.yml @@ -25,3 +25,8 @@ - import_tasks: mysql_replication_resetprimary_mode.yml - include_tasks: issue-28.yml + +# Tests of changereplication mode: +- import_tasks: mysql_replication_changereplication_mode.yml + when: + - db_engine == 'mysql' diff --git a/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_changereplication_mode.yml b/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_changereplication_mode.yml new file mode 100644 index 0000000..2f593ca --- /dev/null +++ b/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_changereplication_mode.yml @@ -0,0 +1,65 @@ +--- + +- vars: + mysql_params: &mysql_params + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: '{{ mysql_host }}' + + block: + # Get primary log file and log pos: + - name: Get primary status + mysql_replication: + <<: *mysql_params + login_port: '{{ mysql_primary_port }}' + mode: getprimary + register: mysql_primary_status + + # Test changereplication mode: + - name: Run replication + mysql_replication: + <<: *mysql_params + login_port: '{{ mysql_replica1_port }}' + mode: changereplication + primary_host: '{{ mysql_host }}' + primary_port: '{{ mysql_primary_port }}' + primary_user: '{{ replication_user }}' + primary_password: '{{ replication_pass }}' + primary_log_file: '{{ mysql_primary_status.File }}' + primary_log_pos: '{{ mysql_primary_status.Position }}' + primary_ssl_ca: '' + primary_ssl: no + register: result + + - name: Assert that changereplication is changed and return expected query + assert: + that: + - result is changed + - result.queries == expected_queries + vars: + expected_queries: ["CHANGE REPLICATION SOURCE TO SOURCE_HOST='{{ mysql_host }}',\ + SOURCE_USER='{{ replication_user }}',SOURCE_PASSWORD='********',\ + SOURCE_PORT={{ mysql_primary_port }},SOURCE_LOG_FILE=\ + '{{ mysql_primary_status.File }}',SOURCE_LOG_POS=\ + {{ mysql_primary_status.Position }},SOURCE_SSL=0,SOURCE_SSL_CA=''"] + + # Test changereplication mode with channel: + - name: Run replication + mysql_replication: + <<: *mysql_params + login_port: '{{ mysql_replica1_port }}' + mode: changereplication + primary_user: '{{ replication_user }}' + primary_password: '{{ replication_pass }}' + channel: '{{ test_channel }}' + + register: with_channel_result_queries + + - name: Assert that changereplication is changed and is called correctly with channel + assert: + that: + - with_channel_result_queries is changed + - with_channel_result_queries.queries == expected_queries + vars: + expected_queries: ["CHANGE REPLICATION SOURCE TO SOURCE_USER='{{ replication_user }}',\ + SOURCE_PASSWORD='********' FOR CHANNEL '{{ test_channel }}'"] 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 ea7a5ac..e08954b 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 @@ -318,5 +318,5 @@ - name: Assert that stopslave returns expected error message assert: that: - - result.msg == "value of mode must be one of{{ ":" }} getprimary, getreplica, changeprimary, stopreplica, startreplica, resetprimary, resetreplica, resetreplicaall, got{{ ":" }} stopslave" + - result.msg == "value of mode must be one of{{ ":" }} getprimary, getreplica, changeprimary, stopreplica, startreplica, resetprimary, resetreplica, resetreplicaall, changereplication, got{{ ":" }} stopslave" - result is failed From 47610347baa5a23a65f0d3221382a09ee964f0e1 Mon Sep 17 00:00:00 2001 From: Dennis Felipe Urtubia <33161939+dennisurtubia@users.noreply.github.com> Date: Thu, 30 May 2024 12:10:36 -0300 Subject: [PATCH 179/217] Adds support for show binary log status statement (#638) * feat: adds support for show binary log status statement * feat: adds support for mariadb show binlog status statement --- .../get_primary_show_binary_log_status.yml | 4 ++++ plugins/modules/mysql_replication.py | 19 +++++++++++++++---- 2 files changed, 19 insertions(+), 4 deletions(-) create mode 100644 changelogs/fragments/get_primary_show_binary_log_status.yml diff --git a/changelogs/fragments/get_primary_show_binary_log_status.yml b/changelogs/fragments/get_primary_show_binary_log_status.yml new file mode 100644 index 0000000..8757aa1 --- /dev/null +++ b/changelogs/fragments/get_primary_show_binary_log_status.yml @@ -0,0 +1,4 @@ +--- +minor_changes: + + - mysql_replication - Adds support for `SHOW BINARY LOG STATUS` and `SHOW BINLOG STATUS` on getprimary mode. diff --git a/plugins/modules/mysql_replication.py b/plugins/modules/mysql_replication.py index 23c94c1..4f668f2 100644 --- a/plugins/modules/mysql_replication.py +++ b/plugins/modules/mysql_replication.py @@ -297,8 +297,11 @@ queries: import os import warnings +from ansible_collections.community.mysql.plugins.module_utils.version import LooseVersion from ansible.module_utils.basic import AnsibleModule from ansible_collections.community.mysql.plugins.module_utils.mysql import ( + get_server_version, + get_server_implementation, mysql_connect, mysql_driver, mysql_driver_fail_msg, @@ -310,10 +313,18 @@ executed_queries = [] def get_primary_status(cursor): - # TODO: when it's available to change on MySQL's side, - # change MASTER to PRIMARY using the approach from - # get_replica_status() function. Same for other functions. - cursor.execute("SHOW MASTER STATUS") + term = "MASTER" + + version = get_server_version(cursor) + server_implementation = get_server_implementation(cursor) + if server_implementation == "mysql" and LooseVersion(version) >= LooseVersion("8.2.0"): + term = "BINARY LOG" + + if server_implementation == "mariadb" and LooseVersion(version) >= LooseVersion("10.5.2"): + term = "BINLOG" + + cursor.execute("SHOW %s STATUS" % term) + primarystatus = cursor.fetchone() return primarystatus From 6c4dca4bceda609810a5138bc5496a13359bba8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sil=C3=A9n?= Date: Fri, 31 May 2024 10:14:43 +0300 Subject: [PATCH 180/217] mention MariaDB (#640) * mention MariaDB * mention MariaDB in descriptions and notes * nits * chmod -x --- README.md | 2 +- changelogs/config.yaml | 2 +- galaxy.yml | 2 +- plugins/modules/mysql_db.py | 5 +++-- plugins/modules/mysql_info.py | 5 +++-- plugins/modules/mysql_query.py | 6 ++++-- plugins/modules/mysql_replication.py | 7 ++++--- plugins/modules/mysql_role.py | 5 +++-- plugins/modules/mysql_user.py | 5 +++-- plugins/modules/mysql_variables.py | 7 +++++-- 10 files changed, 28 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 9853569..07af184 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# MySQL collection for Ansible +# MySQL and MariaDB collection for Ansible [![Plugins CI](https://github.com/ansible-collections/community.mysql/workflows/Plugins%20CI/badge.svg?event=push)](https://github.com/ansible-collections/community.mysql/actions?query=workflow%3A"Plugins+CI") [![Roles CI](https://github.com/ansible-collections/community.mysql/workflows/Roles%20CI/badge.svg?event=push)](https://github.com/ansible-collections/community.mysql/actions?query=workflow%3A"Roles+CI") [![Codecov](https://img.shields.io/codecov/c/github/ansible-collections/community.mysql)](https://codecov.io/gh/ansible-collections/community.mysql) [![Discuss on Matrix at #mysql:ansible.com](https://img.shields.io/matrix/mysql:ansible.com.svg?server_fqdn=ansible-accounts.ems.host&label=Discuss%20on%20Matrix%20at%20%23mysql:ansible.com&logo=matrix)](https://matrix.to/#/#mysql:ansible.com) This collection is a part of the Ansible package. diff --git a/changelogs/config.yaml b/changelogs/config.yaml index 70ab036..40ac5f8 100644 --- a/changelogs/config.yaml +++ b/changelogs/config.yaml @@ -25,5 +25,5 @@ sections: - Bugfixes - - known_issues - Known Issues -title: Community MySQL Collection +title: Community MySQL and MariaDB Collection trivial_section_name: trivial diff --git a/galaxy.yml b/galaxy.yml index dca1e28..512c668 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -5,7 +5,7 @@ version: 3.9.0 readme: README.md authors: - Ansible community -description: MySQL collection for Ansible +description: MySQL and MariaDB collection for Ansible license_file: COPYING tags: - database diff --git a/plugins/modules/mysql_db.py b/plugins/modules/mysql_db.py index 2cb67dc..8742f3c 100644 --- a/plugins/modules/mysql_db.py +++ b/plugins/modules/mysql_db.py @@ -11,9 +11,9 @@ __metaclass__ = type DOCUMENTATION = r''' --- module: mysql_db -short_description: Add or remove MySQL databases from a remote host +short_description: Add or remove MySQL or MariaDB databases from a remote host description: -- Add or remove MySQL databases from a remote host. +- Add or remove MySQL or MariaDB databases from a remote host. options: name: description: @@ -188,6 +188,7 @@ requirements: - mysql (command line binary) - mysqldump (command line binary) notes: + - Compatible with MariaDB or MySQL. - Requires the mysql and mysqldump binaries on the remote host. - This module is B(not idempotent) when I(state) is C(import), and will import the dump file each time if run more than once. diff --git a/plugins/modules/mysql_info.py b/plugins/modules/mysql_info.py index f30f1a1..c119b8d 100644 --- a/plugins/modules/mysql_info.py +++ b/plugins/modules/mysql_info.py @@ -11,9 +11,9 @@ __metaclass__ = type DOCUMENTATION = r''' --- module: mysql_info -short_description: Gather information about MySQL servers +short_description: Gather information about MySQL or MariaDB servers description: -- Gathers information about MySQL servers. +- Gathers information about MySQL or MariaDB servers. options: filter: @@ -46,6 +46,7 @@ options: default: false notes: +- Compatible with MariaDB or MySQL. - Calculating the size of a database might be slow, depending on the number and size of tables in it. To avoid this, use I(exclude_fields=db_size). diff --git a/plugins/modules/mysql_query.py b/plugins/modules/mysql_query.py index fd3a8e0..13a07de 100644 --- a/plugins/modules/mysql_query.py +++ b/plugins/modules/mysql_query.py @@ -10,9 +10,9 @@ __metaclass__ = type DOCUMENTATION = r''' --- module: mysql_query -short_description: Run MySQL queries +short_description: Run MySQL or MariaDB queries description: -- Runs arbitrary MySQL queries. +- Runs arbitrary MySQL or MariaDB queries. - Pay attention, the module does not support check mode! All queries will be executed in autocommit mode. - To run SQL queries from a file, use M(community.mysql.mysql_db) module. @@ -56,6 +56,8 @@ attributes: support: none seealso: - module: community.mysql.mysql_db +notes: +- Compatible with MariaDB or MySQL. author: - Andrew Klychkov (@Andersson007) extends_documentation_fragment: diff --git a/plugins/modules/mysql_replication.py b/plugins/modules/mysql_replication.py index 4f668f2..b0caf11 100644 --- a/plugins/modules/mysql_replication.py +++ b/plugins/modules/mysql_replication.py @@ -13,9 +13,9 @@ __metaclass__ = type DOCUMENTATION = r''' --- module: mysql_replication -short_description: Manage MySQL replication +short_description: Manage MySQL or MariaDB replication description: -- Manages MySQL server replication, replica, primary status, get and change primary host. +- Manages MySQL or MariaDB server replication, replica, primary status, get and change primary host. author: - Balazs Pocze (@banyek) - Andrew Klychkov (@Andersson007) @@ -191,7 +191,8 @@ options: version_added: '0.1.0' notes: -- If an empty value for the parameter of string type is needed, use an empty string. + - Compatible with MariaDB or MySQL. + - If an empty value for the parameter of string type is needed, use an empty string. attributes: check_mode: diff --git a/plugins/modules/mysql_role.py b/plugins/modules/mysql_role.py index 65ed894..df8b5fe 100644 --- a/plugins/modules/mysql_role.py +++ b/plugins/modules/mysql_role.py @@ -11,10 +11,10 @@ DOCUMENTATION = r''' --- module: mysql_role -short_description: Adds, removes, or updates a MySQL role +short_description: Adds, removes, or updates a MySQL or MariaDB role description: - - Adds, removes, or updates a MySQL role. + - Adds, removes, or updates a MySQL or MariaDB role. - Roles are supported since MySQL 8.0.0 and MariaDB 10.0.5. version_added: '2.2.0' @@ -132,6 +132,7 @@ options: version_added: '3.8.0' notes: + - Roles are supported since MySQL 8.0.0 and MariaDB 10.0.5. - Pay attention that the module runs C(SET DEFAULT ROLE ALL TO) all the I(members) passed by default when the state has changed. If you want to avoid this behavior, set I(set_default_role_all) to C(no). diff --git a/plugins/modules/mysql_user.py b/plugins/modules/mysql_user.py index fa54c7d..55e34a3 100644 --- a/plugins/modules/mysql_user.py +++ b/plugins/modules/mysql_user.py @@ -11,9 +11,9 @@ __metaclass__ = type DOCUMENTATION = r''' --- module: mysql_user -short_description: Adds or removes a user from a MySQL database +short_description: Adds or removes a user from a MySQL or MariaDB database description: - - Adds or removes a user from a MySQL database. + - Adds or removes a user from a MySQL or MariaDB database. options: name: description: @@ -188,6 +188,7 @@ options: version_added: '3.9.0' notes: + - Compatible with MySQL or MariaDB. - "MySQL server installs with default I(login_user) of C(root) and no password. To secure this user as part of an idempotent playbook, you must create at least two tasks: 1) change the root user's password, without providing any I(login_user)/I(login_password) details, diff --git a/plugins/modules/mysql_variables.py b/plugins/modules/mysql_variables.py index dfe8466..f912a27 100644 --- a/plugins/modules/mysql_variables.py +++ b/plugins/modules/mysql_variables.py @@ -12,9 +12,9 @@ DOCUMENTATION = r''' --- module: mysql_variables -short_description: Manage MySQL global variables +short_description: Manage MySQL or MariaDB global variables description: -- Query / Set MySQL variables. +- Query / Set MySQL or MariaDB variables. author: - Balazs Pocze (@banyek) options: @@ -54,6 +54,9 @@ seealso: description: Complete reference of the MySQL SET command documentation. link: https://dev.mysql.com/doc/refman/8.0/en/set-statement.html +notes: + - Compatible with MariaDB or MySQL. + extends_documentation_fragment: - community.mysql.mysql ''' From 50e7413b88477c333800fc6fa9f8053e493b2469 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20Inderm=C3=BChle?= Date: Thu, 6 Jun 2024 13:05:31 +0200 Subject: [PATCH 181/217] Fix hashed passwords being returned by get_existing_authentication() via the plugin_auth_string variable instead of plugin_hash_string (#629) * fix returned variable from plugin_auth_string to plugin_hash_string * Refactor to keep plugin_auth_string in addition to plugin_hash_string * Add breaking_changes to the changelog --- .../lie_fix_plugin_hash_string_return.yml | 6 ++ plugins/module_utils/user.py | 14 +++- .../tasks/filter_users_info.yml | 72 +++++-------------- 3 files changed, 36 insertions(+), 56 deletions(-) create mode 100644 changelogs/fragments/lie_fix_plugin_hash_string_return.yml diff --git a/changelogs/fragments/lie_fix_plugin_hash_string_return.yml b/changelogs/fragments/lie_fix_plugin_hash_string_return.yml new file mode 100644 index 0000000..e1a71ea --- /dev/null +++ b/changelogs/fragments/lie_fix_plugin_hash_string_return.yml @@ -0,0 +1,6 @@ +--- +bugfixes: + - mysql_info - Add ``plugin_hash_string`` to ``users_info`` filter's output. The existing ``plugin_auth_string`` contained the hashed password and thus is missleading, it will be removed from community.mysql 4.0.0. (https://github.com/ansible-collections/community.mysql/pull/629). + +breaking_changes: + - mysql_info - The ``users_info`` filter returned variable ``plugin_auth_string`` contains the hashed password and it's misleading, it will be removed from community.mysql 4.0.0. Use the `plugin_hash_string` return value instead (https://github.com/ansible-collections/community.mysql/pull/629). diff --git a/plugins/module_utils/user.py b/plugins/module_utils/user.py index d4ae9dd..25b1734 100644 --- a/plugins/module_utils/user.py +++ b/plugins/module_utils/user.py @@ -118,11 +118,19 @@ def get_existing_authentication(cursor, user, host): if isinstance(rows, dict): rows = list(rows.values()) + # 'plugin_auth_string' contains the hash string. Must be removed in c.mysql 4.0 + # See https://github.com/ansible-collections/community.mysql/pull/629 if isinstance(rows[0], tuple): - return {'plugin': rows[0][0], 'plugin_auth_string': rows[0][1]} + return {'plugin': rows[0][0], + 'plugin_auth_string': rows[0][1], + 'plugin_hash_string': rows[0][1]} + # 'plugin_auth_string' contains the hash string. Must be removed in c.mysql 4.0 + # See https://github.com/ansible-collections/community.mysql/pull/629 if isinstance(rows[0], dict): - return {'plugin': rows[0].get('plugin'), 'plugin_auth_string': rows[0].get('auth')} + return {'plugin': rows[0].get('plugin'), + 'plugin_auth_string': rows[0].get('auth'), + 'plugin_hash_string': rows[0].get('auth')} return None @@ -152,7 +160,7 @@ def user_add(cursor, user, host, host_all, password, encrypted, existing_auth = get_existing_authentication(cursor, user, host) if existing_auth: plugin = existing_auth['plugin'] - plugin_hash_string = existing_auth['auth_string'] + plugin_hash_string = existing_auth['plugin_hash_string'] password = None used_existing_password = True if password and encrypted: diff --git a/tests/integration/targets/test_mysql_info/tasks/filter_users_info.yml b/tests/integration/targets/test_mysql_info/tasks/filter_users_info.yml index 63ce190..36508f3 100644 --- a/tests/integration/targets/test_mysql_info/tasks/filter_users_info.yml +++ b/tests/integration/targets/test_mysql_info/tasks/filter_users_info.yml @@ -211,66 +211,32 @@ TO users_info_tls_sub_issu_ciph@'host' - name: Mysql_info users_info | Prepare tests users for MariaDB - community.mysql.mysql_user: - name: "{{ item.name }}" - host: "users_info.com" - plugin: "{{ item.plugin | default(omit) }}" - plugin_auth_string: "{{ item.plugin_auth_string | default(omit) }}" - plugin_hash_string: "{{ item.plugin_hash_string | default(omit) }}" - tls_requires: "{{ item.tls_requires | default(omit) }}" - priv: "{{ item.priv }}" - resource_limits: "{{ item.resource_limits | default(omit) }}" - column_case_sensitive: true - state: present - loop: - - name: users_info_socket # Only for MariaDB - priv: - '*.*': 'ALL' - plugin: 'unix_socket' + community.mysql.mysql_query: + query: + - >- + CREATE USER users_info_socket@'users_info.com' IDENTIFIED WITH + unix_socket + - GRANT ALL ON *.* to users_info_socket@'users_info.com' when: - db_engine == 'mariadb' - name: Mysql_info users_info | Prepare tests users for MySQL - community.mysql.mysql_user: - name: "{{ item.name }}" - host: "users_info.com" - plugin: "{{ item.plugin | default(omit) }}" - plugin_auth_string: "{{ item.plugin_auth_string | default(omit) }}" - plugin_hash_string: "{{ item.plugin_hash_string | default(omit) }}" - tls_requires: "{{ item.tls_requires | default(omit) }}" - priv: "{{ item.priv }}" - resource_limits: "{{ item.resource_limits | default(omit) }}" - column_case_sensitive: true - state: present - loop: - - name: users_info_sha256 # Only for MySQL - priv: - '*.*': 'ALL' - plugin_auth_string: - '$5$/- + CREATE USER users_info_sha256@'users_info.com' IDENTIFIED WITH + sha256_password BY 'msandbox' + - GRANT ALL ON *.* to users_info_sha256@'users_info.com' when: - db_engine == 'mysql' - name: Mysql_info users_info | Prepare tests users for MySQL 8+ - community.mysql.mysql_user: - name: "{{ item.name }}" - host: "users_info.com" - plugin: "{{ item.plugin | default(omit) }}" - plugin_auth_string: "{{ item.plugin_auth_string | default(omit) }}" - plugin_hash_string: "{{ item.plugin_hash_string | default(omit) }}" - tls_requires: "{{ item.tls_requires | default(omit) }}" - priv: "{{ item.priv }}" - resource_limits: "{{ item.resource_limits | default(omit) }}" - column_case_sensitive: true - state: present - loop: - - name: users_info_caching_sha2 # Only for MySQL 8+ - priv: - '*.*': 'ALL' - plugin_auth_string: - '$A$005$61j/uF%Qb4-=O2xkeO82u2HNkF.lxDq0liO4U3xqi7bDUCbWM6HayRXWn1' - plugin: 'caching_sha2_password' + community.mysql.mysql_query: + query: + - >- + CREATE USER users_info_caching_sha2@'users_info.com' IDENTIFIED WITH + caching_sha2_password BY 'msandbox' + - GRANT ALL ON *.* to users_info_caching_sha2@'users_info.com' when: - db_engine == 'mysql' - db_version is version('8.0', '>=') @@ -283,7 +249,7 @@ - users_info register: result - - name: Recreate users from mysql_info users_info result + - name: Mysql_info users_info | Recreate users from mysql_info result community.mysql.mysql_user: name: "{{ item.name }}" host: "{{ item.host }}" From 0bc3e3d848f8e3714ec2e6a7748ab1b85660e216 Mon Sep 17 00:00:00 2001 From: Matthieu Bourgain Date: Tue, 11 Jun 2024 17:23:05 +0200 Subject: [PATCH 182/217] Add salt parameter to hash generation for sha256 plugins (#631) * add salt parameter to hash generation for sha256 plugin * technomax review modification * no general user test for salt --- .../add_salt_param_to_gen_sha256_hash.yml | 3 + .../implementations/mysql/hash.py | 125 ++++++++++++++++++ plugins/module_utils/user.py | 28 +++- plugins/modules/mysql_role.py | 2 +- plugins/modules/mysql_user.py | 31 ++++- .../tasks/test_user_plugin_auth.yml | 69 ++++++++++ 6 files changed, 251 insertions(+), 7 deletions(-) create mode 100644 changelogs/fragments/add_salt_param_to_gen_sha256_hash.yml create mode 100644 plugins/module_utils/implementations/mysql/hash.py diff --git a/changelogs/fragments/add_salt_param_to_gen_sha256_hash.yml b/changelogs/fragments/add_salt_param_to_gen_sha256_hash.yml new file mode 100644 index 0000000..c49ba1d --- /dev/null +++ b/changelogs/fragments/add_salt_param_to_gen_sha256_hash.yml @@ -0,0 +1,3 @@ +--- +minor_changes: + - mysql_user - Add salt parameter to generate static hash for `caching_sha2_password` and `sha256_password` plugins. diff --git a/plugins/module_utils/implementations/mysql/hash.py b/plugins/module_utils/implementations/mysql/hash.py new file mode 100644 index 0000000..0068a0c --- /dev/null +++ b/plugins/module_utils/implementations/mysql/hash.py @@ -0,0 +1,125 @@ +""" +Generate MySQL sha256 compatible plugins hash for a given password and salt + +based on + * https://www.akkadia.org/drepper/SHA-crypt.txt + * https://crypto.stackexchange.com/questions/77427/whats-the-algorithm-behind-mysqls-sha256-password-hashing-scheme/111174#111174 + * https://github.com/hashcat/hashcat/blob/master/tools/test_modules/m07400.pm +""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import hashlib + + +def _to64(v, n): + """Convert a 32-bit integer to a base-64 string""" + i64 = ( + [".", "/"] + + [chr(x) for x in range(48, 58)] + + [chr(x) for x in range(65, 91)] + + [chr(x) for x in range(97, 123)] + ) + result = "" + while n > 0: + n -= 1 + result += i64[v & 0x3F] + v >>= 6 + return result + + +def _hashlib_sha256(data): + """Return SHA-256 digest from hashlib .""" + return hashlib.sha256(data).digest() + + +def _sha256_digest(key, salt, loops): + """Return a SHA-256 digest of the concatenation of the key, the salt, and the key, repeated as necessary.""" + # https://www.akkadia.org/drepper/SHA-crypt.txt + num_bytes = 32 + bytes_key = key.encode() + bytes_salt = salt.encode() + digest_b = _hashlib_sha256(bytes_key + bytes_salt + bytes_key) + + tmp = bytes_key + bytes_salt + for i in range(len(bytes_key), 0, -num_bytes): + tmp += digest_b if i > num_bytes else digest_b[:i] + + i = len(bytes_key) + while i > 0: + tmp += digest_b if (i & 1) != 0 else bytes_key + i >>= 1 + + digest_a = _hashlib_sha256(tmp) + + tmp = b"" + for i in range(len(bytes_key)): + tmp += bytes_key + + digest_dp = _hashlib_sha256(tmp) + + byte_sequence_p = b"" + for i in range(len(bytes_key), 0, -num_bytes): + byte_sequence_p += digest_dp if i > num_bytes else digest_dp[:i] + + tmp = b"" + til = 16 + digest_a[0] + + for i in range(til): + tmp += bytes_salt + + digest_ds = _hashlib_sha256(tmp) + + byte_sequence_s = b"" + for i in range(len(bytes_salt), 0, -num_bytes): + byte_sequence_s += digest_ds if i > num_bytes else digest_ds[:i] + + digest_c = digest_a + + for i in range(loops): + tmp = byte_sequence_p if (i & 1) else digest_c + if i % 3: + tmp += byte_sequence_s + if i % 7: + tmp += byte_sequence_p + tmp += digest_c if (i & 1) else byte_sequence_p + digest_c = _hashlib_sha256(tmp) + + inc1, inc2, mod, end = (10, 21, 30, 0) + + i = 0 + tmp = "" + + while True: + tmp += _to64( + (digest_c[i] << 16) + | (digest_c[(i + inc1) % mod] << 8) + | digest_c[(i + inc1 * 2) % mod], + 4, + ) + i = (i + inc2) % mod + if i == end: + break + + tmp += _to64((digest_c[31] << 8) | digest_c[30], 3) + + return tmp + + +def mysql_sha256_password_hash(password, salt): + """Return a MySQL compatible caching_sha2_password hash in raw format.""" + if len(salt) != 20: + raise ValueError("Salt must be 20 characters long.") + + count = 5 + iteration = 1000 * count + + digest = _sha256_digest(password, salt, iteration) + return "$A${0:>03}${1}{2}".format(count, salt, digest) + + +def mysql_sha256_password_hash_hex(password, salt): + """Return a MySQL compatible caching_sha2_password hash in hex format.""" + return mysql_sha256_password_hash(password, salt).encode().hex().upper() diff --git a/plugins/module_utils/user.py b/plugins/module_utils/user.py index 25b1734..80da47e 100644 --- a/plugins/module_utils/user.py +++ b/plugins/module_utils/user.py @@ -1,4 +1,6 @@ from __future__ import (absolute_import, division, print_function) + + __metaclass__ = type # This code is part of Ansible, but is an independent component. @@ -19,6 +21,10 @@ from ansible_collections.community.mysql.plugins.module_utils.mysql import ( mysql_driver, get_server_implementation, ) +from ansible_collections.community.mysql.plugins.module_utils.implementations.mysql.hash import ( + mysql_sha256_password_hash, + mysql_sha256_password_hash_hex, +) class InvalidPrivsError(Exception): @@ -135,7 +141,7 @@ def get_existing_authentication(cursor, user, host): 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, salt, new_priv, attributes, tls_requires, reuse_existing_password, module, password_expire, password_expire_interval): # If attributes are set, perform a sanity check to ensure server supports user attributes before creating user @@ -181,6 +187,12 @@ def user_add(cursor, user, host, host_all, password, encrypted, # Mysql and MariaDB differ in naming pam plugin and Syntax to set it if plugin == 'pam': # Used by MariaDB which requires the USING keyword, not BY query_with_args = "CREATE USER %s@%s IDENTIFIED WITH %s USING %s", (user, host, plugin, plugin_auth_string) + elif salt: + if plugin in ['caching_sha2_password', 'sha256_password']: + generated_hash_string = mysql_sha256_password_hash_hex(password=plugin_auth_string, salt=salt) + else: + module.fail_json(msg="salt not handled for %s authentication plugin" % plugin) + query_with_args = ("CREATE USER %s@%s IDENTIFIED WITH %s AS 0x" + generated_hash_string), (user, host, plugin) else: query_with_args = "CREATE USER %s@%s IDENTIFIED WITH %s BY %s", (user, host, plugin, plugin_auth_string) elif plugin: @@ -221,7 +233,7 @@ def is_hash(password): 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, salt, new_priv, append_privs, subtract_privs, attributes, tls_requires, module, password_expire, password_expire_interval, role=False, maria_role=False): changed = False @@ -342,7 +354,11 @@ def user_mod(cursor, user, host, host_all, password, encrypted, if plugin_hash_string and current_plugin[1] != plugin_hash_string: update = True - if plugin_auth_string and current_plugin[1] != plugin_auth_string: + if salt: + if plugin in ['caching_sha2_password', 'sha256_password']: + if current_plugin[1] != mysql_sha256_password_hash(password=plugin_auth_string, salt=salt): + update = True + elif plugin_auth_string and current_plugin[1] != plugin_auth_string: # this case can cause more updates than expected, # as plugin can hash auth_string in any way it wants # and there's no way to figure it out for @@ -356,6 +372,12 @@ def user_mod(cursor, user, host, host_all, password, encrypted, # Mysql and MariaDB differ in naming pam plugin and syntax to set it if plugin in ('pam', 'ed25519'): query_with_args = "ALTER USER %s@%s IDENTIFIED WITH %s USING %s", (user, host, plugin, plugin_auth_string) + elif salt: + if plugin in ['caching_sha2_password', 'sha256_password']: + generated_hash_string = mysql_sha256_password_hash_hex(password=plugin_auth_string, salt=salt) + else: + module.fail_json(msg="salt not handled for %s authentication plugin" % plugin) + query_with_args = ("ALTER USER %s@%s IDENTIFIED WITH %s AS 0x" + generated_hash_string), (user, host, plugin) else: query_with_args = "ALTER USER %s@%s IDENTIFIED WITH %s BY %s", (user, host, plugin, plugin_auth_string) else: diff --git a/plugins/modules/mysql_role.py b/plugins/modules/mysql_role.py index df8b5fe..032b41e 100644 --- a/plugins/modules/mysql_role.py +++ b/plugins/modules/mysql_role.py @@ -931,7 +931,7 @@ class Role(): if privs: result = user_mod(self.cursor, self.name, self.host, - None, None, None, None, None, None, + None, None, None, None, None, None, None, privs, append_privs, subtract_privs, None, None, self.module, None, None, role=True, maria_role=self.is_mariadb) diff --git a/plugins/modules/mysql_user.py b/plugins/modules/mysql_user.py index 55e34a3..0c7021b 100644 --- a/plugins/modules/mysql_user.py +++ b/plugins/modules/mysql_user.py @@ -139,8 +139,16 @@ options: description: - User's plugin auth_string (``CREATE USER user IDENTIFIED WITH plugin BY plugin_auth_string``). - If I(plugin) is ``pam`` (MariaDB) or ``auth_pam`` (MySQL) an optional I(plugin_auth_string) can be used to choose a specific PAM service. + - You need to define a I(salt) to have idempotence on password change with ``caching_sha2_password`` and ``sha256_password`` plugins. type: str version_added: '0.1.0' + salt: + description: + - Salt used to generate password hash from I(plugin_auth_string). + - Salt length must be 20 characters. + - Salt only support ``caching_sha2_password`` or ``sha256_password`` authentication I(plugin). + type: str + version_added: '3.10.0' resource_limits: description: - Limit the user for certain server resources. Provided since MySQL 5.6 / MariaDB 10.2. @@ -369,6 +377,13 @@ EXAMPLES = r''' priv: '*.*:ALL' state: present +- name: Create user 'bob' authenticated with plugin 'caching_sha2_password' and static salt + community.mysql.mysql_user: + name: bob + plugin: caching_sha2_password + plugin_auth_string: password + salt: 1234567890abcdefghij + - name: Limit bob's resources to 10 queries per hour and 5 connections per hour community.mysql.mysql_user: name: bob @@ -440,6 +455,7 @@ def main(): plugin=dict(default=None, type='str'), plugin_hash_string=dict(default=None, type='str'), plugin_auth_string=dict(default=None, type='str'), + salt=dict(default=None, type='str'), resource_limits=dict(type='dict'), force_context=dict(type='bool', default=False), session_vars=dict(type='dict'), @@ -480,6 +496,7 @@ def main(): plugin = module.params["plugin"] plugin_hash_string = module.params["plugin_hash_string"] plugin_auth_string = module.params["plugin_auth_string"] + salt = module.params["salt"] resource_limits = module.params["resource_limits"] session_vars = module.params["session_vars"] column_case_sensitive = module.params["column_case_sensitive"] @@ -499,6 +516,14 @@ def main(): module.fail_json(msg="password_expire_interval value \ should be positive number") + if salt: + if not plugin_auth_string: + module.fail_json(msg="salt requires plugin_auth_string") + if len(salt) != 20: + module.fail_json(msg="salt must be 20 characters long") + if plugin not in ['caching_sha2_password', 'sha256_password']: + module.fail_json(msg="salt requires caching_sha2_password or sha256_password plugin") + cursor = None try: if check_implicit_admin: @@ -542,13 +567,13 @@ def main(): try: if update_password == "always": result = user_mod(cursor, user, host, host_all, password, encrypted, - plugin, plugin_hash_string, plugin_auth_string, + plugin, plugin_hash_string, plugin_auth_string, salt, priv, append_privs, subtract_privs, attributes, tls_requires, module, password_expire, password_expire_interval) else: result = user_mod(cursor, user, host, host_all, None, encrypted, - None, None, None, + None, None, None, None, priv, append_privs, subtract_privs, attributes, tls_requires, module, password_expire, password_expire_interval) changed = result['changed'] @@ -566,7 +591,7 @@ def main(): priv = None # avoid granting unwanted privileges reuse_existing_password = update_password == 'on_new_username' result = user_add(cursor, user, host, host_all, password, encrypted, - plugin, plugin_hash_string, plugin_auth_string, + plugin, plugin_hash_string, plugin_auth_string, salt, priv, attributes, tls_requires, reuse_existing_password, module, password_expire, password_expire_interval) changed = result['changed'] diff --git a/tests/integration/targets/test_mysql_user/tasks/test_user_plugin_auth.yml b/tests/integration/targets/test_mysql_user/tasks/test_user_plugin_auth.yml index d8ff04d..b5ed6c5 100644 --- a/tests/integration/targets/test_mysql_user/tasks/test_user_plugin_auth.yml +++ b/tests/integration/targets/test_mysql_user/tasks/test_user_plugin_auth.yml @@ -13,6 +13,7 @@ test_plugin_auth_string: 'Fdt8fd^34ds' test_plugin_new_hash: '*E74368AC90460FA669F6D41BFB7F2A877DB73745' test_plugin_new_auth_string: 'c$K01LsmK7nJnIR4!h' + test_salt: 'TDwqdanU82d0yNtvaabb' test_default_priv_type: 'SELECT' test_default_priv: '*.*:{{ test_default_priv_type }}' @@ -475,3 +476,71 @@ - include_tasks: utils/remove_user.yml vars: user_name: "{{ test_user_name }}" + + # ============================================================ + # Test plugin auth with a salt + # + - name: Plugin auth | Create user with plugin auth and salt + community.mysql.mysql_user: + <<: *mysql_params + name: "{{ test_user_name }}" + host: "%" + plugin: caching_sha2_password + plugin_auth_string: "{{ test_plugin_auth_string }}" + salt: "{{ test_salt }}" + priv: "{{ test_default_priv }}" + + - name: Plugin auth | Connect with user and password + ansible.builtin.command: '{{ mysql_command }} -u {{ test_user_name }} -p{{ test_plugin_auth_string }} -e "SELECT 1"' + + - name: Plugin auth | Alter user with same plugin auth and same salt + community.mysql.mysql_user: + <<: *mysql_params + name: "{{ test_user_name }}" + host: "%" + plugin: caching_sha2_password + plugin_auth_string: "{{ test_plugin_auth_string }}" + salt: "{{ test_salt }}" + priv: "{{ test_default_priv }}" + register: result + failed_when: result is changed + + - name: cleanup user + ansible.builtin.include_tasks: utils/remove_user.yml + vars: + user_name: "{{ test_user_name }}" + + - name: Plugin auth | Create user with too short salt (should fail) + community.mysql.mysql_user: + <<: *mysql_params + name: "{{ test_user_name }}" + host: "%" + plugin: caching_sha2_password + plugin_auth_string: "{{ test_plugin_auth_string }}" + salt: "1234567890az" + priv: "{{ test_default_priv }}" + register: result + failed_when: result is success + + - name: Plugin auth | Create user with salt and no plugin auth string (should fail) + community.mysql.mysql_user: + <<: *mysql_params + name: "{{ test_user_name }}" + host: "%" + plugin: caching_sha2_password + salt: "{{ test_salt }}" + priv: "{{ test_default_priv }}" + register: result + failed_when: result is success + + - name: Plugin auth | Create user with salt and plugin not handled by internal hash generation (should fail) + community.mysql.mysql_user: + <<: *mysql_params + name: "{{ test_user_name }}" + host: "%" + plugin: mysql_native_password + plugin_auth_string: "{{ test_plugin_auth_string }}" + salt: "{{ test_salt }}" + priv: "{{ test_default_priv }}" + register: result + failed_when: result is success From f266ba59c943e9912d65c3e568727b444df49771 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Wed, 19 Jun 2024 10:17:02 +0200 Subject: [PATCH 183/217] mysql_info: add server_engine return value (#649) * mysql_info: add server_engine return value * Incorporate feedback --- changelogs/fragments/1-mysql_info.yml | 2 ++ plugins/modules/mysql_info.py | 7 +++++++ tests/integration/targets/test_mysql_info/tasks/main.yml | 1 + 3 files changed, 10 insertions(+) create mode 100644 changelogs/fragments/1-mysql_info.yml diff --git a/changelogs/fragments/1-mysql_info.yml b/changelogs/fragments/1-mysql_info.yml new file mode 100644 index 0000000..1ab4d2c --- /dev/null +++ b/changelogs/fragments/1-mysql_info.yml @@ -0,0 +1,2 @@ +minor_changes: +- mysql_info - return a database server engine used (https://github.com/ansible-collections/community.mysql/issues/644). diff --git a/plugins/modules/mysql_info.py b/plugins/modules/mysql_info.py index c119b8d..6103589 100644 --- a/plugins/modules/mysql_info.py +++ b/plugins/modules/mysql_info.py @@ -162,6 +162,12 @@ EXAMPLES = r''' ''' RETURN = r''' +server_engine: + description: Database server engine. + returned: if not excluded by filter + type: str + sample: 'MariaDB' + version_added: '3.10.0' version: description: Database server version. returned: if not excluded by filter @@ -765,6 +771,7 @@ def main(): mysql = MySQL_Info(module, cursor, server_implementation, user_implementation) module.exit_json(changed=False, + server_engine='MariaDB' if server_implementation == 'mariadb' else 'MySQL', connector_name=connector_name, connector_version=connector_version, **mysql.get_info(filter_, exclude_fields, return_empty_dbs)) diff --git a/tests/integration/targets/test_mysql_info/tasks/main.yml b/tests/integration/targets/test_mysql_info/tasks/main.yml index 5d34da9..93570f2 100644 --- a/tests/integration/targets/test_mysql_info/tasks/main.yml +++ b/tests/integration/targets/test_mysql_info/tasks/main.yml @@ -56,6 +56,7 @@ - result.databases != {} - result.engines != {} - result.users != {} + - result.server_engine == 'MariaDB' or result.server_engine == 'MySQL' - name: mysql_info - Test connector informations display ansible.builtin.import_tasks: From aafe658a85d67cd6c4c23dd0b84acf86ad698da4 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Wed, 19 Jun 2024 10:20:34 +0200 Subject: [PATCH 184/217] Update README.md (#648) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 07af184..2678f31 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # MySQL and MariaDB collection for Ansible -[![Plugins CI](https://github.com/ansible-collections/community.mysql/workflows/Plugins%20CI/badge.svg?event=push)](https://github.com/ansible-collections/community.mysql/actions?query=workflow%3A"Plugins+CI") [![Roles CI](https://github.com/ansible-collections/community.mysql/workflows/Roles%20CI/badge.svg?event=push)](https://github.com/ansible-collections/community.mysql/actions?query=workflow%3A"Roles+CI") [![Codecov](https://img.shields.io/codecov/c/github/ansible-collections/community.mysql)](https://codecov.io/gh/ansible-collections/community.mysql) [![Discuss on Matrix at #mysql:ansible.com](https://img.shields.io/matrix/mysql:ansible.com.svg?server_fqdn=ansible-accounts.ems.host&label=Discuss%20on%20Matrix%20at%20%23mysql:ansible.com&logo=matrix)](https://matrix.to/#/#mysql:ansible.com) +[![Plugins CI](https://github.com/ansible-collections/community.mysql/workflows/Plugins%20CI/badge.svg?event=push)](https://github.com/ansible-collections/community.mysql/actions?query=workflow%3A"Plugins+CI") [![Codecov](https://img.shields.io/codecov/c/github/ansible-collections/community.mysql)](https://codecov.io/gh/ansible-collections/community.mysql) [![Discuss on Matrix at #mysql:ansible.com](https://img.shields.io/matrix/mysql:ansible.com.svg?server_fqdn=ansible-accounts.ems.host&label=Discuss%20on%20Matrix%20at%20%23mysql:ansible.com&logo=matrix)](https://matrix.to/#/#mysql:ansible.com) This collection is a part of the Ansible package. From 1922e7154e6228100c022d3e7350d12f23eb7d54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20Inderm=C3=BChle?= Date: Mon, 24 Jun 2024 09:36:32 +0200 Subject: [PATCH 185/217] [CI] Remove ansible-test custom containers (#650) * Cut tests containers * Cut unused flatten versions * Fix installation of mysqlclient on Ubuntu * Cut unused variables * Fix package missing on Unbuntu 22.04 * Fix variable templating * Fix test for ansible 2.17 and do remove the ignore_errors ignore_errors is bad because it makes searching for real errors difficult. --- .github/workflows/ansible-test-plugins.yml | 32 --------- .github/workflows/build-docker-image.yml | 67 ------------------- ...ker-image-mariadb-py310-mysqlclient211.yml | 21 ------ .../docker-image-mariadb-py310-pymysql102.yml | 21 ------ ...cker-image-mariadb-py38-mysqlclient201.yml | 21 ------ .../docker-image-mariadb-py38-pymysql093.yml | 21 ------ ...cker-image-mariadb-py39-mysqlclient203.yml | 21 ------ .../docker-image-mariadb-py39-pymysql093.yml | 21 ------ .../docker-image-my57-py38-mysqlclient201.yml | 21 ------ .../docker-image-my57-py38-pymysql0711.yml | 21 ------ .../docker-image-my57-py38-pymysql093.yml | 21 ------ ...ocker-image-mysql-py310-mysqlclient211.yml | 21 ------ .../docker-image-mysql-py310-pymysql102.yml | 21 ------ ...docker-image-mysql-py38-mysqlclient201.yml | 21 ------ .../docker-image-mysql-py38-pymysql093.yml | 21 ------ ...docker-image-mysql-py39-mysqlclient203.yml | 21 ------ .../docker-image-mysql-py39-pymysql093.yml | 21 ------ Makefile | 22 +----- TESTING.md | 21 ++---- .../mariadb-py310-mysqlclient211/Dockerfile | 21 ------ .../mariadb-py310-pymysql102/Dockerfile | 15 ----- .../mariadb-py38-mysqlclient201/Dockerfile | 21 ------ .../mariadb-py38-pymysql093/Dockerfile | 15 ----- .../mariadb-py39-mysqlclient203/Dockerfile | 21 ------ .../mariadb-py39-pymysql093/Dockerfile | 15 ----- .../my57-py38-mysqlclient201/Dockerfile | 21 ------ .../my57-py38-pymysql0711/Dockerfile | 21 ------ .../my57-py38-pymysql093/Dockerfile | 15 ----- .../mysql-py310-mysqlclient211/Dockerfile | 21 ------ .../mysql-py310-pymysql102/Dockerfile | 15 ----- .../mysql-py38-mysqlclient201/Dockerfile | 21 ------ .../mysql-py38-pymysql093/Dockerfile | 15 ----- .../mysql-py39-mysqlclient203/Dockerfile | 21 ------ .../mysql-py39-pymysql093/Dockerfile | 16 ----- .../targets/setup_controller/tasks/main.yml | 11 +-- .../setup_controller/tasks/requirements.yml | 20 ++++++ .../setup_controller/tasks/setvars.yml | 14 ++-- .../tasks/config_overrides_defaults.yml | 22 +++--- 38 files changed, 55 insertions(+), 743 deletions(-) delete mode 100644 .github/workflows/build-docker-image.yml delete mode 100644 .github/workflows/docker-image-mariadb-py310-mysqlclient211.yml delete mode 100644 .github/workflows/docker-image-mariadb-py310-pymysql102.yml delete mode 100644 .github/workflows/docker-image-mariadb-py38-mysqlclient201.yml delete mode 100644 .github/workflows/docker-image-mariadb-py38-pymysql093.yml delete mode 100644 .github/workflows/docker-image-mariadb-py39-mysqlclient203.yml delete mode 100644 .github/workflows/docker-image-mariadb-py39-pymysql093.yml delete mode 100644 .github/workflows/docker-image-my57-py38-mysqlclient201.yml delete mode 100644 .github/workflows/docker-image-my57-py38-pymysql0711.yml delete mode 100644 .github/workflows/docker-image-my57-py38-pymysql093.yml delete mode 100644 .github/workflows/docker-image-mysql-py310-mysqlclient211.yml delete mode 100644 .github/workflows/docker-image-mysql-py310-pymysql102.yml delete mode 100644 .github/workflows/docker-image-mysql-py38-mysqlclient201.yml delete mode 100644 .github/workflows/docker-image-mysql-py38-pymysql093.yml delete mode 100644 .github/workflows/docker-image-mysql-py39-mysqlclient203.yml delete mode 100644 .github/workflows/docker-image-mysql-py39-pymysql093.yml delete mode 100644 test-containers/mariadb-py310-mysqlclient211/Dockerfile delete mode 100644 test-containers/mariadb-py310-pymysql102/Dockerfile delete mode 100644 test-containers/mariadb-py38-mysqlclient201/Dockerfile delete mode 100644 test-containers/mariadb-py38-pymysql093/Dockerfile delete mode 100644 test-containers/mariadb-py39-mysqlclient203/Dockerfile delete mode 100644 test-containers/mariadb-py39-pymysql093/Dockerfile delete mode 100644 test-containers/my57-py38-mysqlclient201/Dockerfile delete mode 100644 test-containers/my57-py38-pymysql0711/Dockerfile delete mode 100644 test-containers/my57-py38-pymysql093/Dockerfile delete mode 100644 test-containers/mysql-py310-mysqlclient211/Dockerfile delete mode 100644 test-containers/mysql-py310-pymysql102/Dockerfile delete mode 100644 test-containers/mysql-py38-mysqlclient201/Dockerfile delete mode 100644 test-containers/mysql-py38-pymysql093/Dockerfile delete mode 100644 test-containers/mysql-py39-mysqlclient203/Dockerfile delete mode 100644 test-containers/mysql-py39-pymysql093/Dockerfile create mode 100644 tests/integration/targets/setup_controller/tasks/requirements.yml diff --git a/.github/workflows/ansible-test-plugins.yml b/.github/workflows/ansible-test-plugins.yml index 77da49e..f3f440e 100644 --- a/.github/workflows/ansible-test-plugins.yml +++ b/.github/workflows/ansible-test-plugins.yml @@ -252,37 +252,6 @@ jobs: ${{ job.services.db_primary.id }} | grep healthy && [[ "$SECONDS" -lt 120 ]]; do sleep 1; done - - name: Compute docker_image - Set python_version_flat - run: > - echo "python_version_flat=$(echo ${{ matrix.python }} - | tr -d '.')" >> $GITHUB_ENV - - - name: Compute docker_image - Set connector_version_flat - run: > - echo "connector_version_flat=$(echo ${{ matrix.connector_version }} - |tr -d .)" >> $GITHUB_ENV - - - name: Compute docker_image - Set db_engine_version_flat - run: > - echo "db_engine_version_flat=$(echo ${{ matrix.db_engine_version }} - | awk -F '.' '{print $1 $2}')" >> $GITHUB_ENV - - - name: Compute docker_image - Set db_client - run: > - if [[ ${{ env.db_engine_version_flat }} == 57 ]]; then - echo "db_client=my57" >> $GITHUB_ENV; - else - echo "db_client=$(echo ${{ matrix.db_engine_name }})" >> $GITHUB_ENV; - fi - - - name: Set docker_image - run: |- - echo "docker_image=ghcr.io/ansible-collections/community.mysql\ - /test-container-${{ env.db_client }}\ - -py${{ env.python_version_flat }}\ - -${{ matrix.connector_name }}${{ env.connector_version_flat }}\ - :latest" >> $GITHUB_ENV - - name: >- Perform integration testing against Ansible version ${{ matrix.ansible }} @@ -315,7 +284,6 @@ jobs: echo Setting Ansible version to "${{ matrix.ansible }}"...; echo -n "${{ matrix.ansible }}" > tests/integration/ansible - docker-image: ${{ env.docker_image }} target-python-version: ${{ matrix.python }} testing-type: integration diff --git a/.github/workflows/build-docker-image.yml b/.github/workflows/build-docker-image.yml deleted file mode 100644 index 0edd5ee..0000000 --- a/.github/workflows/build-docker-image.yml +++ /dev/null @@ -1,67 +0,0 @@ ---- -name: Build Docker Image for ansible-test - -on: # yamllint disable-line rule:truthy - workflow_call: - inputs: - registry: - required: true - type: string - image_name: - required: true - type: string - context: - required: true - type: string - -jobs: - - build: - - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - - steps: - # Requirement to use 'context' in docker/build-push-action@v3 - - name: Checkout repository - uses: actions/checkout@v3 - - # https://github.com/docker/login-action - - name: Log into registry ${{ inputs.registry }} - uses: docker/login-action@v2 - with: - registry: ${{ inputs.registry }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - # https://github.com/docker/metadata-action - - name: Extract Docker metadata (tags, labels) - id: meta - uses: docker/metadata-action@v4 - with: - images: - "${{ inputs.registry }}\ - /${{ github.repository }}\ - /${{ inputs.image_name }}" - tags: latest - - # Setting up Docker Buildx with docker-container driver is required - # at the moment to be able to use a subdirectory with Git context - # - # https://github.com/docker/setup-buildx-action - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - # https://github.com/docker/build-push-action - - name: Build and push Docker image with Buildx - id: build-and-push - uses: docker/build-push-action@v3 - with: - context: ${{ inputs.context }} - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max diff --git a/.github/workflows/docker-image-mariadb-py310-mysqlclient211.yml b/.github/workflows/docker-image-mariadb-py310-mysqlclient211.yml deleted file mode 100644 index 77286e6..0000000 --- a/.github/workflows/docker-image-mariadb-py310-mysqlclient211.yml +++ /dev/null @@ -1,21 +0,0 @@ ---- -name: Docker Image CI mariadb-py310-mysqlclient211 - -on: # yamllint disable-line rule:truthy - push: - paths: - - 'test-containers/mariadb-py310-mysqlclient211/**' - - '.github/workflows/docker-image-mariadb-py310-mysqlclient211.yml' - - '.github/workflows/build-docker-image.yml' - branches-ignore: - - stable-* - -jobs: - - call-workflow-passing-data: - uses: ./.github/workflows/build-docker-image.yml - secrets: inherit - with: - registry: ghcr.io - image_name: test-container-mariadb-py310-mysqlclient211 - context: test-containers/mariadb-py310-mysqlclient211 diff --git a/.github/workflows/docker-image-mariadb-py310-pymysql102.yml b/.github/workflows/docker-image-mariadb-py310-pymysql102.yml deleted file mode 100644 index c7cdfd4..0000000 --- a/.github/workflows/docker-image-mariadb-py310-pymysql102.yml +++ /dev/null @@ -1,21 +0,0 @@ ---- -name: Docker Image CI mariadb-py310-pymysql102 - -on: # yamllint disable-line rule:truthy - push: - paths: - - 'test-containers/mariadb-py310-pymysql102/**' - - '.github/workflows/docker-image-mariadb-py310-pymysql102.yml' - - '.github/workflows/build-docker-image.yml' - branches-ignore: - - stable-* - -jobs: - - call-workflow-passing-data: - uses: ./.github/workflows/build-docker-image.yml - secrets: inherit - with: - registry: ghcr.io - image_name: test-container-mariadb-py310-pymysql102 - context: test-containers/mariadb-py310-pymysql102 diff --git a/.github/workflows/docker-image-mariadb-py38-mysqlclient201.yml b/.github/workflows/docker-image-mariadb-py38-mysqlclient201.yml deleted file mode 100644 index b5b9bb3..0000000 --- a/.github/workflows/docker-image-mariadb-py38-mysqlclient201.yml +++ /dev/null @@ -1,21 +0,0 @@ ---- -name: Docker Image CI mariadb-py38-mysqlclient201 - -on: # yamllint disable-line rule:truthy - push: - paths: - - 'test-containers/mariadb-py38-mysqlclient201/**' - - '.github/workflows/docker-image-mariadb-py38-mysqlclient201.yml' - - '.github/workflows/build-docker-image.yml' - branches-ignore: - - stable-* - -jobs: - - call-workflow-passing-data: - uses: ./.github/workflows/build-docker-image.yml - secrets: inherit - with: - registry: ghcr.io - image_name: test-container-mariadb-py38-mysqlclient201 - context: test-containers/mariadb-py38-mysqlclient201 diff --git a/.github/workflows/docker-image-mariadb-py38-pymysql093.yml b/.github/workflows/docker-image-mariadb-py38-pymysql093.yml deleted file mode 100644 index ae6df2e..0000000 --- a/.github/workflows/docker-image-mariadb-py38-pymysql093.yml +++ /dev/null @@ -1,21 +0,0 @@ ---- -name: Docker Image CI mariadb-py38-pymysql093 - -on: # yamllint disable-line rule:truthy - push: - paths: - - 'test-containers/mariadb-py38-pymysql093/**' - - '.github/workflows/docker-image-mariadb-py38-pymysql093.yml' - - '.github/workflows/build-docker-image.yml' - branches-ignore: - - stable-* - -jobs: - - call-workflow-passing-data: - uses: ./.github/workflows/build-docker-image.yml - secrets: inherit - with: - registry: ghcr.io - image_name: test-container-mariadb-py38-pymysql093 - context: test-containers/mariadb-py38-pymysql093 diff --git a/.github/workflows/docker-image-mariadb-py39-mysqlclient203.yml b/.github/workflows/docker-image-mariadb-py39-mysqlclient203.yml deleted file mode 100644 index 4efeef1..0000000 --- a/.github/workflows/docker-image-mariadb-py39-mysqlclient203.yml +++ /dev/null @@ -1,21 +0,0 @@ ---- -name: Docker Image CI mariadb-py39-mysqlclient203 - -on: # yamllint disable-line rule:truthy - push: - paths: - - 'test-containers/mariadb-py39-mysqlclient203/**' - - '.github/workflows/docker-image-mariadb-py39-mysqlclient203.yml' - - '.github/workflows/build-docker-image.yml' - branches-ignore: - - stable-* - -jobs: - - call-workflow-passing-data: - uses: ./.github/workflows/build-docker-image.yml - secrets: inherit - with: - registry: ghcr.io - image_name: test-container-mariadb-py39-mysqlclient203 - context: test-containers/mariadb-py39-mysqlclient203 diff --git a/.github/workflows/docker-image-mariadb-py39-pymysql093.yml b/.github/workflows/docker-image-mariadb-py39-pymysql093.yml deleted file mode 100644 index a3205fb..0000000 --- a/.github/workflows/docker-image-mariadb-py39-pymysql093.yml +++ /dev/null @@ -1,21 +0,0 @@ ---- -name: Docker Image CI mariadb-py39-pymysql093 - -on: # yamllint disable-line rule:truthy - push: - paths: - - 'test-containers/mariadb-py39-pymysql093/**' - - '.github/workflows/docker-image-mariadb-py39-pymysql093.yml' - - '.github/workflows/build-docker-image.yml' - branches-ignore: - - stable-* - -jobs: - - call-workflow-passing-data: - uses: ./.github/workflows/build-docker-image.yml - secrets: inherit - with: - registry: ghcr.io - image_name: test-container-mariadb-py39-pymysql093 - context: test-containers/mariadb-py39-pymysql093 diff --git a/.github/workflows/docker-image-my57-py38-mysqlclient201.yml b/.github/workflows/docker-image-my57-py38-mysqlclient201.yml deleted file mode 100644 index b256a47..0000000 --- a/.github/workflows/docker-image-my57-py38-mysqlclient201.yml +++ /dev/null @@ -1,21 +0,0 @@ ---- -name: Docker Image CI my57-py38-mysqlclient201 - -on: # yamllint disable-line rule:truthy - push: - paths: - - 'test-containers/my57-py38-mysqlclient201/**' - - '.github/workflows/docker-image-my57-py38-mysqlclient201.yml' - - '.github/workflows/build-docker-image.yml' - branches-ignore: - - stable-* - -jobs: - - call-workflow-passing-data: - uses: ./.github/workflows/build-docker-image.yml - secrets: inherit - with: - registry: ghcr.io - image_name: test-container-my57-py38-mysqlclient201 - context: test-containers/my57-py38-mysqlclient201 diff --git a/.github/workflows/docker-image-my57-py38-pymysql0711.yml b/.github/workflows/docker-image-my57-py38-pymysql0711.yml deleted file mode 100644 index 0064729..0000000 --- a/.github/workflows/docker-image-my57-py38-pymysql0711.yml +++ /dev/null @@ -1,21 +0,0 @@ ---- -name: Docker Image CI my57-py38-pymysql0711 - -on: # yamllint disable-line rule:truthy - push: - paths: - - 'test-containers/my57-py38-pymysql0711/**' - - '.github/workflows/docker-image-my57-py38-pymysql0711.yml' - - '.github/workflows/build-docker-image.yml' - branches-ignore: - - stable-* - -jobs: - - call-workflow-passing-data: - uses: ./.github/workflows/build-docker-image.yml - secrets: inherit - with: - registry: ghcr.io - image_name: test-container-my57-py38-pymysql0711 - context: test-containers/my57-py38-pymysql0711 diff --git a/.github/workflows/docker-image-my57-py38-pymysql093.yml b/.github/workflows/docker-image-my57-py38-pymysql093.yml deleted file mode 100644 index 58c7fed..0000000 --- a/.github/workflows/docker-image-my57-py38-pymysql093.yml +++ /dev/null @@ -1,21 +0,0 @@ ---- -name: Docker Image CI my57-py38-pymysql093 - -on: # yamllint disable-line rule:truthy - push: - paths: - - 'test-containers/my57-py38-pymysql093/**' - - '.github/workflows/docker-image-my57-py38-pymysql093.yml' - - '.github/workflows/build-docker-image.yml' - branches-ignore: - - stable-* - -jobs: - - call-workflow-passing-data: - uses: ./.github/workflows/build-docker-image.yml - secrets: inherit - with: - registry: ghcr.io - image_name: test-container-my57-py38-pymysql093 - context: test-containers/my57-py38-pymysql093 diff --git a/.github/workflows/docker-image-mysql-py310-mysqlclient211.yml b/.github/workflows/docker-image-mysql-py310-mysqlclient211.yml deleted file mode 100644 index dcb846f..0000000 --- a/.github/workflows/docker-image-mysql-py310-mysqlclient211.yml +++ /dev/null @@ -1,21 +0,0 @@ ---- -name: Docker Image CI mysql-py310-mysqlclient211 - -on: # yamllint disable-line rule:truthy - push: - paths: - - 'test-containers/mysql-py310-mysqlclient211/**' - - '.github/workflows/docker-image-mysql-py310-mysqlclient211.yml' - - '.github/workflows/build-docker-image.yml' - branches-ignore: - - stable-* - -jobs: - - call-workflow-passing-data: - uses: ./.github/workflows/build-docker-image.yml - secrets: inherit - with: - registry: ghcr.io - image_name: test-container-mysql-py310-mysqlclient211 - context: test-containers/mysql-py310-mysqlclient211 diff --git a/.github/workflows/docker-image-mysql-py310-pymysql102.yml b/.github/workflows/docker-image-mysql-py310-pymysql102.yml deleted file mode 100644 index 815b923..0000000 --- a/.github/workflows/docker-image-mysql-py310-pymysql102.yml +++ /dev/null @@ -1,21 +0,0 @@ ---- -name: Docker Image CI mysql-py310-pymysql102 - -on: # yamllint disable-line rule:truthy - push: - paths: - - 'test-containers/mysql-py310-pymysql102/**' - - '.github/workflows/docker-image-mysql-py310-pymysql102.yml' - - '.github/workflows/build-docker-image.yml' - branches-ignore: - - stable-* - -jobs: - - call-workflow-passing-data: - uses: ./.github/workflows/build-docker-image.yml - secrets: inherit - with: - registry: ghcr.io - image_name: test-container-mysql-py310-pymysql102 - context: test-containers/mysql-py310-pymysql102 diff --git a/.github/workflows/docker-image-mysql-py38-mysqlclient201.yml b/.github/workflows/docker-image-mysql-py38-mysqlclient201.yml deleted file mode 100644 index 93359a4..0000000 --- a/.github/workflows/docker-image-mysql-py38-mysqlclient201.yml +++ /dev/null @@ -1,21 +0,0 @@ ---- -name: Docker Image CI mysql-py38-mysqlclient201 - -on: # yamllint disable-line rule:truthy - push: - paths: - - 'test-containers/mysql-py38-mysqlclient201/**' - - '.github/workflows/docker-image-mysql-py38-mysqlclient201.yml' - - '.github/workflows/build-docker-image.yml' - branches-ignore: - - stable-* - -jobs: - - call-workflow-passing-data: - uses: ./.github/workflows/build-docker-image.yml - secrets: inherit - with: - registry: ghcr.io - image_name: test-container-mysql-py38-mysqlclient201 - context: test-containers/mysql-py38-mysqlclient201 diff --git a/.github/workflows/docker-image-mysql-py38-pymysql093.yml b/.github/workflows/docker-image-mysql-py38-pymysql093.yml deleted file mode 100644 index ac572ea..0000000 --- a/.github/workflows/docker-image-mysql-py38-pymysql093.yml +++ /dev/null @@ -1,21 +0,0 @@ ---- -name: Docker Image CI mysql-py38-pymysql093 - -on: # yamllint disable-line rule:truthy - push: - paths: - - 'test-containers/mysql-py38-pymysql093/**' - - '.github/workflows/docker-image-mysql-py38-pymysql093.yml' - - '.github/workflows/build-docker-image.yml' - branches-ignore: - - stable-* - -jobs: - - call-workflow-passing-data: - uses: ./.github/workflows/build-docker-image.yml - secrets: inherit - with: - registry: ghcr.io - image_name: test-container-mysql-py38-pymysql093 - context: test-containers/mysql-py38-pymysql093 diff --git a/.github/workflows/docker-image-mysql-py39-mysqlclient203.yml b/.github/workflows/docker-image-mysql-py39-mysqlclient203.yml deleted file mode 100644 index b314e57..0000000 --- a/.github/workflows/docker-image-mysql-py39-mysqlclient203.yml +++ /dev/null @@ -1,21 +0,0 @@ ---- -name: Docker Image CI mysql-py39-mysqlclient203 - -on: # yamllint disable-line rule:truthy - push: - paths: - - 'test-containers/mysql-py39-mysqlclient203/**' - - '.github/workflows/docker-image-mysql-py39-mysqlclient203.yml' - - '.github/workflows/build-docker-image.yml' - branches-ignore: - - stable-* - -jobs: - - call-workflow-passing-data: - uses: ./.github/workflows/build-docker-image.yml - secrets: inherit - with: - registry: ghcr.io - image_name: test-container-mysql-py39-mysqlclient203 - context: test-containers/mysql-py39-mysqlclient203 diff --git a/.github/workflows/docker-image-mysql-py39-pymysql093.yml b/.github/workflows/docker-image-mysql-py39-pymysql093.yml deleted file mode 100644 index 55962fb..0000000 --- a/.github/workflows/docker-image-mysql-py39-pymysql093.yml +++ /dev/null @@ -1,21 +0,0 @@ ---- -name: Docker Image CI mysql-py39-pymysql093 - -on: # yamllint disable-line rule:truthy - push: - paths: - - 'test-containers/mysql-py39-pymysql093/*' - - '.github/workflows/docker-image-mysql-py39-pymysql093.yml' - - '.github/workflows/build-docker-image.yml' - branches-ignore: - - stable-* - -jobs: - - call-workflow-passing-data: - uses: ./.github/workflows/build-docker-image.yml - secrets: inherit - with: - registry: ghcr.io - image_name: test-container-mysql-py39-pymysql093 - context: test-containers/mysql-py39-pymysql093 diff --git a/Makefile b/Makefile index 7ea0785..1bf8fae 100644 --- a/Makefile +++ b/Makefile @@ -11,23 +11,6 @@ ifdef continue_on_errors _continue_on_errors = --retry-on-error --continue-on-error endif - -db_ver_tuple := $(subst ., , $(db_engine_version)) -db_engine_version_flat := $(word 1, $(db_ver_tuple))$(word 2, $(db_ver_tuple)) - -con_ver_tuple := $(subst ., , $(connector_version)) -connector_version_flat := $(word 1, $(con_ver_tuple))$(word 2, $(con_ver_tuple))$(word 3, $(con_ver_tuple)) - -py_ver_tuple := $(subst ., , $(python)) -python_version_flat := $(word 1, $(py_ver_tuple))$(word 2, $(py_ver_tuple)) - -ifeq ($(db_engine_version_flat), 57) - db_client := my57 -else - db_client := $(db_engine_name) -endif - - .PHONY: test-integration test-integration: @echo -n $(db_engine_name) > tests/integration/db_engine_name @@ -94,9 +77,8 @@ test-integration: https://github.com/ansible/ansible/archive/$(ansible).tar.gz; \ set -x; \ ansible-test integration $(target) -v --color --coverage --diff \ - --docker ghcr.io/ansible-collections/community.mysql/test-container\ - -$(db_client)-py$(python_version_flat)-$(connector_name)$(connector_version_flat):latest \ - --docker-network podman $(_continue_on_errors) $(_keep_containers_alive) --python $(python); \ + --docker --python $(python) \ + --docker-network podman $(_continue_on_errors) $(_keep_containers_alive); \ set +x # End of venv diff --git a/TESTING.md b/TESTING.md index f31db4a..54eb5ed 100644 --- a/TESTING.md +++ b/TESTING.md @@ -26,12 +26,9 @@ For now, the makefile only supports Podman. - Minimum 2GB of RAM -### Custom ansible-test containers +### ansible-test environment -Our integrations tests use custom containers for ansible-test. Those images have their definition file stored in the directory [test-containers](test-containers/). We build and publish the images on ghcr.io under the ansible-collection namespace: E.G.: -`ghcr.io/ansible-collections/community.mysql/test-container-mariadb106-py310-mysqlclient211:latest`. - -Availables images are listed [here](https://github.com/orgs/ansible-collections/packages). +Integration tests use the default container from ansible-test. Then required packages for the tests are installed from the `setup_controller` target located in the `tests/integration/targets` folder. ### Makefile options @@ -151,16 +148,6 @@ python run_all_tests.py ### Add a new Python, Connector or Database version -You can look into [.github/workflows/ansible-test-plugins.yml](https://github.com/ansible-collections/community.mysql/tree/main/.github/workflows) to see how those containers are built using [build-docker-image.yml](https://github.com/ansible-collections/community.mysql/blob/main/.github/workflows/build-docker-image.yml) and all [docker-image-xxx.yml](https://github.com/ansible-collections/community.mysql/blob/main/.github/workflows/docker-image-mariadb103-py38-mysqlclient201.yml) files. +New components version should be added to this file: [.github/workflows/ansible-test-plugins.yml](https://github.com/ansible-collections/community.mysql/tree/main/.github/workflows) -1. Add a workflow in [.github/workflows/](.github/workflows) -1. Add a new folder in [test-containers](test-containers) containing a new Dockerfile. Your container must contains 3 things: - - Python - - A connector: The python package to connect to the database (pymysql, mysqlclient, ...) - - A mysql client to prepare databases before our tests starts. This client must provide both `mysql` and `mysqldump` commands. -1. Add your version in the matrix of *.github/workflows/ansible-test-plugins.yml*. You can use [run_all_tests.py](run_all_tests.py) to help you see what the matrix will be. Simply comment out the line `os.system(make_cmd)` before runing the script. You can also add `print(len(matrix))` to display how many tests there will be on GitHub Action. -1. Ask the lead maintainer to mark your new image(s) as `public` under [https://github.com/orgs/ansible-collections/packages](https://github.com/orgs/ansible-collections/packages) - -After pushing your commit to the remote, the container will be built and published on ghcr.io. Have a look in the "Action" tab to see if it worked. In case of error `failed to copy: io: read/write on closed pipe` re-run the workflow, this append unfortunately a lot. - -To see the docker image produced, go to the package page in the ansible-collection namespace [https://github.com/orgs/ansible-collections/packages](https://github.com/orgs/ansible-collections/packages). This page indicate a "Published x days ago" that is updated infrequently. To see the last time the container has been updated you must click on its title and look in the right hands side bellow the title "Last published". +Be careful to not add too much tests. When adding a new version of Python, for instance, only test it agains the latest versions of Ansible and MySQL/MariaDB. When tests are run, you can see that we already start 40 virtual machines! diff --git a/test-containers/mariadb-py310-mysqlclient211/Dockerfile b/test-containers/mariadb-py310-mysqlclient211/Dockerfile deleted file mode 100644 index f7e9eb1..0000000 --- a/test-containers/mariadb-py310-mysqlclient211/Dockerfile +++ /dev/null @@ -1,21 +0,0 @@ -FROM quay.io/ansible/ubuntu2204-test-container:main -# ubuntu2204 comes with mariadb-client-10.6 - -# iproute2 # To grab docker network gateway address -# python3.10-dev # Reqs for mysqlclient -# default-libmysqlclient-dev # Reqs for mysqlclient -# build-essential # Reqs for mysqlclient -RUN apt-get update -y && \ - DEBIAN_FRONTEND=noninteractive apt-get upgrade -y --no-install-recommends && \ - DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ - python3.10 \ - python3.10-dev \ - mariadb-client \ - iproute2 \ - default-libmysqlclient-dev \ - build-essential - -RUN python3.10 -m pip install --disable-pip-version-check --no-cache-dir mysqlclient==2.1.1 - -ENV container=docker -CMD ["/sbin/init"] diff --git a/test-containers/mariadb-py310-pymysql102/Dockerfile b/test-containers/mariadb-py310-pymysql102/Dockerfile deleted file mode 100644 index afe6a77..0000000 --- a/test-containers/mariadb-py310-pymysql102/Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -FROM quay.io/ansible/ubuntu2204-test-container:main -# ubuntu2204 comes with mariadb-client-10.6 - -# iproute2 # To grab docker network gateway address -RUN apt-get update -y && \ - DEBIAN_FRONTEND=noninteractive apt-get upgrade -y --no-install-recommends && \ - DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ - python3.10 \ - mariadb-client \ - iproute2 - -RUN python3.10 -m pip install --disable-pip-version-check --no-cache-dir pymysql==1.0.2 - -ENV container=docker -CMD ["/sbin/init"] diff --git a/test-containers/mariadb-py38-mysqlclient201/Dockerfile b/test-containers/mariadb-py38-mysqlclient201/Dockerfile deleted file mode 100644 index 68ea3f6..0000000 --- a/test-containers/mariadb-py38-mysqlclient201/Dockerfile +++ /dev/null @@ -1,21 +0,0 @@ -FROM quay.io/ansible/ubuntu2004-test-container:main -# ubuntu2004 comes with mariadb-client-10.3 - -# iproute2 # To grab docker network gateway address -# python3.8-dev # Reqs for mysqlclient -# default-libmysqlclient-dev # Reqs for mysqlclient -# build-essential # Reqs for mysqlclient -RUN apt-get update -y && \ - DEBIAN_FRONTEND=noninteractive apt-get upgrade -y --no-install-recommends && \ - DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ - python3.8 \ - python3.8-dev \ - mariadb-client \ - iproute2 \ - default-libmysqlclient-dev \ - build-essential - -RUN python3.8 -m pip install --disable-pip-version-check --no-cache-dir mysqlclient==2.0.1 - -ENV container=docker -CMD ["/sbin/init"] diff --git a/test-containers/mariadb-py38-pymysql093/Dockerfile b/test-containers/mariadb-py38-pymysql093/Dockerfile deleted file mode 100644 index 22c8c57..0000000 --- a/test-containers/mariadb-py38-pymysql093/Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -FROM quay.io/ansible/ubuntu2004-test-container:main -# ubuntu2004 comes with mariadb-client-10.3 - -# iproute2 # To grab docker network gateway address -RUN apt-get update -y && \ - DEBIAN_FRONTEND=noninteractive apt-get upgrade -y --no-install-recommends && \ - DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ - python3.8 \ - mariadb-client \ - iproute2 - -RUN python3.8 -m pip install --disable-pip-version-check --no-cache-dir pymysql==0.9.3 - -ENV container=docker -CMD ["/sbin/init"] diff --git a/test-containers/mariadb-py39-mysqlclient203/Dockerfile b/test-containers/mariadb-py39-mysqlclient203/Dockerfile deleted file mode 100644 index b7837b2..0000000 --- a/test-containers/mariadb-py39-mysqlclient203/Dockerfile +++ /dev/null @@ -1,21 +0,0 @@ -FROM quay.io/ansible/ubuntu2004-test-container:main -# ubuntu2004 comes with mariadb-client-10.3 - -# iproute2 # To grab docker network gateway address -# python3.9-dev # Reqs for mysqlclient -# default-libmysqlclient-dev # Reqs for mysqlclient -# build-essential # Reqs for mysqlclient -RUN apt-get update -y && \ - DEBIAN_FRONTEND=noninteractive apt-get upgrade -y --no-install-recommends && \ - DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ - python3.9 \ - python3.9-dev \ - mariadb-client \ - iproute2 \ - default-libmysqlclient-dev \ - build-essential - -RUN python3.9 -m pip install --disable-pip-version-check --no-cache-dir mysqlclient==2.0.3 - -ENV container=docker -CMD ["/sbin/init"] diff --git a/test-containers/mariadb-py39-pymysql093/Dockerfile b/test-containers/mariadb-py39-pymysql093/Dockerfile deleted file mode 100644 index a1451ff..0000000 --- a/test-containers/mariadb-py39-pymysql093/Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -FROM quay.io/ansible/ubuntu2004-test-container:main -# ubuntu2004 comes with mariadb-client-10.3 - -# iproute2 # To grab docker network gateway address -RUN apt-get update -y && \ - DEBIAN_FRONTEND=noninteractive apt-get upgrade -y --no-install-recommends && \ - DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ - python3.9 \ - mariadb-client \ - iproute2 - -RUN python3.9 -m pip install --disable-pip-version-check --no-cache-dir pymysql==0.9.3 - -ENV container=docker -CMD ["/sbin/init"] diff --git a/test-containers/my57-py38-mysqlclient201/Dockerfile b/test-containers/my57-py38-mysqlclient201/Dockerfile deleted file mode 100644 index 0eb1778..0000000 --- a/test-containers/my57-py38-mysqlclient201/Dockerfile +++ /dev/null @@ -1,21 +0,0 @@ -FROM quay.io/ansible/ubuntu1804-test-container:main -# ubuntu1804 comes with mysql-client-5.7 - -# iproute2 # To grab docker network gateway address -# python3.8-dev # Reqs for mysqlclient -# default-libmysqlclient-dev # Reqs for mysqlclient -# build-essential # Reqs for mysqlclient -RUN apt-get update -y && \ - DEBIAN_FRONTEND=noninteractive apt-get upgrade -y --no-install-recommends && \ - DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ - python3.8 \ - python3.8-dev \ - mysql-client \ - iproute2 \ - default-libmysqlclient-dev \ - build-essential - -RUN python3.8 -m pip install --disable-pip-version-check --no-cache-dir mysqlclient==2.0.1 - -ENV container=docker -CMD ["/sbin/init"] diff --git a/test-containers/my57-py38-pymysql0711/Dockerfile b/test-containers/my57-py38-pymysql0711/Dockerfile deleted file mode 100644 index 9141709..0000000 --- a/test-containers/my57-py38-pymysql0711/Dockerfile +++ /dev/null @@ -1,21 +0,0 @@ -FROM quay.io/ansible/ubuntu1804-test-container:main -# ubuntu1804 comes with mysql-client-5.7 - -# iproute2 # To grab docker network gateway address -# python3.8-dev # Reqs for mysqlclient -# default-libmysqlclient-dev # Reqs for mysqlclient -# build-essential # Reqs for mysqlclient -RUN apt-get update -y && \ - DEBIAN_FRONTEND=noninteractive apt-get upgrade -y --no-install-recommends && \ - DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ - python3.8 \ - python3.8-dev \ - mysql-client \ - iproute2 \ - default-libmysqlclient-dev \ - build-essential - -RUN python3.8 -m pip install --disable-pip-version-check --no-cache-dir pymysql==0.7.11 - -ENV container=docker -CMD ["/sbin/init"] diff --git a/test-containers/my57-py38-pymysql093/Dockerfile b/test-containers/my57-py38-pymysql093/Dockerfile deleted file mode 100644 index 6b0f519..0000000 --- a/test-containers/my57-py38-pymysql093/Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -FROM quay.io/ansible/ubuntu1804-test-container:main -# ubuntu1804 comes with mysql-client-5.7 - -# iproute2 # To grab docker network gateway address -RUN apt-get update -y && \ - DEBIAN_FRONTEND=noninteractive apt-get upgrade -y --no-install-recommends && \ - DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ - python3.8 \ - mysql-client \ - iproute2 - -RUN python3.8 -m pip install --disable-pip-version-check --no-cache-dir pymysql==0.9.3 - -ENV container=docker -CMD ["/sbin/init"] diff --git a/test-containers/mysql-py310-mysqlclient211/Dockerfile b/test-containers/mysql-py310-mysqlclient211/Dockerfile deleted file mode 100644 index 1aea0cd..0000000 --- a/test-containers/mysql-py310-mysqlclient211/Dockerfile +++ /dev/null @@ -1,21 +0,0 @@ -FROM quay.io/ansible/ubuntu2204-test-container:main -# ubuntu2204 comes with mysql-client-8 - -# iproute2 # To grab docker network gateway address -# python3.10-dev # Reqs for mysqlclient -# default-libmysqlclient-dev # Reqs for mysqlclient -# build-essential # Reqs for mysqlclient -RUN apt-get update -y && \ - DEBIAN_FRONTEND=noninteractive apt-get upgrade -y --no-install-recommends && \ - DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ - python3.10 \ - python3.10-dev \ - mysql-client \ - iproute2 \ - default-libmysqlclient-dev \ - build-essential - -RUN python3.10 -m pip install --disable-pip-version-check --no-cache-dir mysqlclient==2.1.1 - -ENV container=docker -CMD ["/sbin/init"] diff --git a/test-containers/mysql-py310-pymysql102/Dockerfile b/test-containers/mysql-py310-pymysql102/Dockerfile deleted file mode 100644 index 871a1e4..0000000 --- a/test-containers/mysql-py310-pymysql102/Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -FROM quay.io/ansible/ubuntu2204-test-container:main -# ubuntu2204 comes with mysql-client-8 - -# iproute2 # To grab docker network gateway address -RUN apt-get update -y && \ - DEBIAN_FRONTEND=noninteractive apt-get upgrade -y --no-install-recommends && \ - DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ - python3.10 \ - mysql-client \ - iproute2 - -RUN python3.10 -m pip install --disable-pip-version-check --no-cache-dir pymysql==1.0.2 - -ENV container=docker -CMD ["/sbin/init"] diff --git a/test-containers/mysql-py38-mysqlclient201/Dockerfile b/test-containers/mysql-py38-mysqlclient201/Dockerfile deleted file mode 100644 index eb835c2..0000000 --- a/test-containers/mysql-py38-mysqlclient201/Dockerfile +++ /dev/null @@ -1,21 +0,0 @@ -FROM quay.io/ansible/ubuntu2004-test-container:main -# ubuntu2004 comes with mysql-client-8 - -# iproute2 # To grab docker network gateway address -# python3.8-dev # Reqs for mysqlclient -# default-libmysqlclient-dev # Reqs for mysqlclient -# build-essential # Reqs for mysqlclient -RUN apt-get update -y && \ - DEBIAN_FRONTEND=noninteractive apt-get upgrade -y --no-install-recommends && \ - DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ - python3.8 \ - python3.8-dev \ - mysql-client \ - iproute2 \ - default-libmysqlclient-dev \ - build-essential - -RUN python3.8 -m pip install --disable-pip-version-check --no-cache-dir mysqlclient==2.0.1 - -ENV container=docker -CMD ["/sbin/init"] diff --git a/test-containers/mysql-py38-pymysql093/Dockerfile b/test-containers/mysql-py38-pymysql093/Dockerfile deleted file mode 100644 index e97e5e2..0000000 --- a/test-containers/mysql-py38-pymysql093/Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -FROM quay.io/ansible/ubuntu2004-test-container:main -# ubuntu2004 comes with mysql-client-8 - -# iproute2 # To grab docker network gateway address -RUN apt-get update -y && \ - DEBIAN_FRONTEND=noninteractive apt-get upgrade -y --no-install-recommends && \ - DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ - python3.8 \ - mysql-client \ - iproute2 - -RUN python3.8 -m pip install --disable-pip-version-check --no-cache-dir pymysql==0.9.3 - -ENV container=docker -CMD ["/sbin/init"] diff --git a/test-containers/mysql-py39-mysqlclient203/Dockerfile b/test-containers/mysql-py39-mysqlclient203/Dockerfile deleted file mode 100644 index 396d895..0000000 --- a/test-containers/mysql-py39-mysqlclient203/Dockerfile +++ /dev/null @@ -1,21 +0,0 @@ -FROM quay.io/ansible/ubuntu2004-test-container:main -# ubuntu2004 comes with mysql-client-8 - -# iproute2 # To grab docker network gateway address -# python3.9-dev # Reqs for mysqlclient -# default-libmysqlclient-dev # Reqs for mysqlclient -# build-essential # Reqs for mysqlclient -RUN apt-get update -y && \ - DEBIAN_FRONTEND=noninteractive apt-get upgrade -y --no-install-recommends && \ - DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ - python3.9 \ - python3.9-dev \ - mysql-client \ - iproute2 \ - default-libmysqlclient-dev \ - build-essential - -RUN python3.9 -m pip install --disable-pip-version-check --no-cache-dir mysqlclient==2.0.3 - -ENV container=docker -CMD ["/sbin/init"] diff --git a/test-containers/mysql-py39-pymysql093/Dockerfile b/test-containers/mysql-py39-pymysql093/Dockerfile deleted file mode 100644 index 57ef15e..0000000 --- a/test-containers/mysql-py39-pymysql093/Dockerfile +++ /dev/null @@ -1,16 +0,0 @@ -FROM quay.io/ansible/ubuntu2004-test-container:main -# ubuntu2004 comes with mysql-client-8 - -# iproute2 # To grab docker network gateway address -RUN apt-get update -y && \ - DEBIAN_FRONTEND=noninteractive apt-get upgrade -y --no-install-recommends && \ - DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ - python3.9 \ - mysql-client \ - iproute2 - -# cffi # To connect to MySQL 8 with Python3.9 and PyMySQL -RUN python3.9 -m pip install --disable-pip-version-check --no-cache-dir cffi pymysql==0.9.3 - -ENV container=docker -CMD ["/sbin/init"] diff --git a/tests/integration/targets/setup_controller/tasks/main.yml b/tests/integration/targets/setup_controller/tasks/main.yml index 0d5e36b..91b5f82 100644 --- a/tests/integration/targets/setup_controller/tasks/main.yml +++ b/tests/integration/targets/setup_controller/tasks/main.yml @@ -4,15 +4,18 @@ # and should not be used as examples of how to write Ansible roles # #################################################################### -- name: Prepare the fake root folder +- name: "{{ role_name }} | Main | Prepare the fake root folder" ansible.builtin.import_tasks: file: fake_root.yml -# setvars.yml requires the iproute2 package installed by install.yml -- name: Set variables +- name: "{{ role_name }} | Main | Set variables" ansible.builtin.import_tasks: file: setvars.yml -- name: Verify all components version under test +- name: "{{ role_name }} | Main | Install requirements" + ansible.builtin.import_tasks: + file: requirements.yml + +- name: "{{ role_name }} | Main | Verify all components version under test" ansible.builtin.import_tasks: file: verify.yml diff --git a/tests/integration/targets/setup_controller/tasks/requirements.yml b/tests/integration/targets/setup_controller/tasks/requirements.yml new file mode 100644 index 0000000..8bab1a0 --- /dev/null +++ b/tests/integration/targets/setup_controller/tasks/requirements.yml @@ -0,0 +1,20 @@ +--- + +- name: "{{ role_name }} | Requirements | Install Linux packages" + ansible.builtin.package: + name: + - bzip2 # To test mysql_db dump compression + - "{{ db_engine }}-client" + + # The command mysql-config must be present for mysqlclient python package. + # The package libmysqlclient-dev that provides this command have a + # different name between Ubuntu 20.04 and 22.04. Luckily, libmysql++ is + # available on both. + - "{{ 'libmysql++-dev' if db_engine == 'mysql' else 'libmariadb-dev' }}" + state: present + +- name: "{{ role_name }} | Requirements | Install Python packages" + ansible.builtin.pip: + name: + - "{{ connector_name }}=={{ connector_version }}" + state: present diff --git a/tests/integration/targets/setup_controller/tasks/setvars.yml b/tests/integration/targets/setup_controller/tasks/setvars.yml index 3e070a9..7c3e03b 100644 --- a/tests/integration/targets/setup_controller/tasks/setvars.yml +++ b/tests/integration/targets/setup_controller/tasks/setvars.yml @@ -1,13 +1,17 @@ --- -- name: "{{ role_name }} | Setvars | Extract Podman/Docker Network Gateway" - ansible.builtin.shell: - cmd: ip route|grep default|awk '{print $3}' - register: ip_route_output +- name: "{{ role_name }} | Setvars | Install tools gather network facts" + ansible.builtin.package: + name: + - iproute2 + state: present + +- name: "{{ role_name }} | Setvars | Gather facts" + ansible.builtin.setup: - name: "{{ role_name }} | Setvars | Set Fact" ansible.builtin.set_fact: - gateway_addr: "{{ ip_route_output.stdout }}" + gateway_addr: "{{ ansible_default_ipv4.gateway }}" connector_name_lookup: >- {{ lookup( 'file', 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 390c6ae..dce0a43 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 @@ -93,7 +93,9 @@ - name: Config overrides | Add fake host to config file shell: 'echo "host = {{ fake_host }}" >> {{ config_file }}' -- name: Config overrides | Remove database using fake login_host +- name: >- + Config overrides | Fail to Remove database using fake login_host + because its default has been overriden by wrong value from config file mysql_db: login_user: '{{ mysql_user }}' login_password: '{{ mysql_password }}' @@ -102,15 +104,17 @@ name: '{{ db_to_create }}' state: absent config_file: '{{ config_file }}' - config_overrides_defaults: yes + config_overrides_defaults: true register: result - ignore_errors: yes - -- name: Config overrides | 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 }}'") or result.msg is search("Unknown MySQL server host '{{ fake_host }}'") + failed_when: + - result is succeeded + - result.msg is not search(pattern1) + - result.msg is not search(pattern2) + - result.msg is not search(pattern3) + vars: + pattern1: Can't connect to MySQL server on '{{ fake_host }}' + pattern2: Unknown MySQL server host '{{ fake_host }}' + pattern3: Unknown server host '{{ fake_host }}' - name: Config overrides | Clean up test database mysql_db: From 33e8754c4e0de108c5621818a9139c5d51cd2dfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20Inderm=C3=BChle?= Date: Thu, 27 Jun 2024 22:12:01 +0200 Subject: [PATCH 186/217] Fix mysql_user on_new_username IndexError (#642) * fix tuple indexerror when no accounts are found * Fix tests for update_password not executed * Add test for case where existing user have different password * lint to prevent warning about jinja templating in when clause * Refactor get_existing_authentication to return a list of all row found Previously we were returning only the first row found. We need to be able to see if there is a difference in the existing passwords. * Refactor host option to be optional This make it possible to use the same method from mysql_user to help update_password retrieve existing password for all account with the same username independently of their hostname. And from mysql_info to get the password of a specif user using WHERE user = '' AND host = '' * Add change log fragment * Add link to the PR in the change log * lint for ansible devel * Fix templating type error could not cconvert to bool with ansible devel * Revert changes made for ansible-devel that broke tests for Ansible 2.15 * Revert changes made for ansible-devel that broke tests * Cut unnecessary set, uniqueness is ensured by the group_by in the query * Cut auth plugin from returned values when multiple existing auths exists Discussed here: https://github.com/ansible-collections/community.mysql/pull/642/files#r1649720519 * fix convertion of list(dict) to list(tuple) * Fix test for empty password on MySQL 8+ --- .../lie_fix_mysql_user_on_new_username.yml | 6 ++ plugins/module_utils/user.py | 93 ++++++++++++------- plugins/modules/mysql_info.py | 2 +- .../targets/test_mysql_user/tasks/main.yml | 4 + .../tasks/test_update_password.yml | 26 ++++++ .../tasks/utils/assert_user_password.yml | 23 ++--- .../test_mysql_variables/tasks/issue-28.yml | 37 ++++---- .../tasks/mysql_variables.yml | 24 ++--- 8 files changed, 141 insertions(+), 74 deletions(-) create mode 100644 changelogs/fragments/lie_fix_mysql_user_on_new_username.yml diff --git a/changelogs/fragments/lie_fix_mysql_user_on_new_username.yml b/changelogs/fragments/lie_fix_mysql_user_on_new_username.yml new file mode 100644 index 0000000..7f13738 --- /dev/null +++ b/changelogs/fragments/lie_fix_mysql_user_on_new_username.yml @@ -0,0 +1,6 @@ +--- + +bugfixes: + + - mysql_user - Fixed an IndexError in the update_password functionality introduced in PR https://github.com/ansible-collections/community.mysql/pull/580 and released in community.mysql 3.8.0. If you used this functionality, please avoid versions 3.8.0 to 3.9.0 (https://github.com/ansible-collections/community.mysql/pull/642). + - mysql_user - Added a warning to update_password's on_new_username option if multiple accounts with the same username but different passwords exist (https://github.com/ansible-collections/community.mysql/pull/642). diff --git a/plugins/module_utils/user.py b/plugins/module_utils/user.py index 80da47e..bd71691 100644 --- a/plugins/module_utils/user.py +++ b/plugins/module_utils/user.py @@ -95,8 +95,12 @@ def get_grants(cursor, user, host): return grants.split(", ") -def get_existing_authentication(cursor, user, host): - # Return the plugin and auth_string if there is exactly one distinct existing plugin and auth_string. +def get_existing_authentication(cursor, user, host=None): + """ Return a list of dict containing the plugin and auth_string for the + specified username. + If hostname is provided, return only the information about this particular + account. + """ cursor.execute("SELECT VERSION()") srv_type = cursor.fetchone() # Mysql_info use a DictCursor so we must convert back to a list @@ -107,37 +111,50 @@ def get_existing_authentication(cursor, user, host): if 'mariadb' in srv_type[0].lower(): # before MariaDB 10.2.19 and 10.3.11, "password" and "authentication_string" can differ # when using mysql_native_password - cursor.execute("""select plugin, auth from ( - select plugin, password as auth from mysql.user where user=%(user)s - and host=%(host)s - union select plugin, authentication_string as auth from mysql.user where user=%(user)s - and host=%(host)s) x group by plugin, auth limit 2 - """, {'user': user, 'host': host}) + if host: + cursor.execute("""select plugin, auth from ( + select plugin, password as auth from mysql.user where user=%(user)s + and host=%(host)s + union select plugin, authentication_string as auth from mysql.user where user=%(user)s + and host=%(host)s) x group by plugin, auth + """, {'user': user, 'host': host}) + else: + cursor.execute("""select plugin, auth from ( + select plugin, password as auth from mysql.user where user=%(user)s + union select plugin, authentication_string as auth from mysql.user where user=%(user)s + ) x group by plugin, auth + """, {'user': user}) else: - cursor.execute("""select plugin, authentication_string as auth - from mysql.user where user=%(user)s and host=%(host)s - group by plugin, authentication_string limit 2""", {'user': user, 'host': host}) + if host: + cursor.execute("""select plugin, authentication_string as auth + from mysql.user where user=%(user)s and host=%(host)s + group by plugin, authentication_string""", {'user': user, 'host': host}) + else: + cursor.execute("""select plugin, authentication_string as auth + from mysql.user where user=%(user)s + group by plugin, authentication_string""", {'user': user}) + rows = cursor.fetchall() - # Mysql_info use a DictCursor so we must convert back to a list - # otherwise we get KeyError 0 - if isinstance(rows, dict): - rows = list(rows.values()) + if len(rows) == 0: + return [] - # 'plugin_auth_string' contains the hash string. Must be removed in c.mysql 4.0 - # See https://github.com/ansible-collections/community.mysql/pull/629 - if isinstance(rows[0], tuple): - return {'plugin': rows[0][0], - 'plugin_auth_string': rows[0][1], - 'plugin_hash_string': rows[0][1]} - - # 'plugin_auth_string' contains the hash string. Must be removed in c.mysql 4.0 - # See https://github.com/ansible-collections/community.mysql/pull/629 + # Mysql_info use a DictCursor so we must convert list(dict) + # to list(tuple) otherwise we get KeyError 0 if isinstance(rows[0], dict): - return {'plugin': rows[0].get('plugin'), - 'plugin_auth_string': rows[0].get('auth'), - 'plugin_hash_string': rows[0].get('auth')} - return None + rows = [tuple(row.values()) for row in rows] + + existing_auth_list = [] + + # 'plugin_auth_string' contains the hash string. Must be removed in c.mysql 4.0 + # See https://github.com/ansible-collections/community.mysql/pull/629 + for r in rows: + existing_auth_list.append({ + 'plugin': r[0], + 'plugin_auth_string': r[1], + 'plugin_hash_string': r[1]}) + + return existing_auth_list def user_add(cursor, user, host, host_all, password, encrypted, @@ -161,14 +178,24 @@ def user_add(cursor, user, host, host_all, password, encrypted, mogrify = do_not_mogrify_requires if old_user_mgmt else mogrify_requires + # This is for update_password: on_new_username used_existing_password = False if reuse_existing_password: - existing_auth = get_existing_authentication(cursor, user, host) + existing_auth = get_existing_authentication(cursor, user) if existing_auth: - plugin = existing_auth['plugin'] - plugin_hash_string = existing_auth['plugin_hash_string'] - password = None - used_existing_password = True + if len(existing_auth) != 1: + module.warn("An account with the username %s has a different " + "password than the others existing accounts. Thus " + "on_new_username can't decide which password to " + "reuse so it will use your provided password " + "instead. If no password is provided, the account " + "will have an empty password!" % user) + used_existing_password = False + else: + plugin_hash_string = existing_auth[0]['plugin_hash_string'] + password = None + used_existing_password = True + plugin = existing_auth[0]['plugin'] # What if plugin differ? if password and encrypted: if impl.supports_identified_by_password(cursor): query_with_args = "CREATE USER %s@%s IDENTIFIED BY PASSWORD %s", (user, host, password) diff --git a/plugins/modules/mysql_info.py b/plugins/modules/mysql_info.py index 6103589..9f0586a 100644 --- a/plugins/modules/mysql_info.py +++ b/plugins/modules/mysql_info.py @@ -639,7 +639,7 @@ class MySQL_Info(object): authentications = get_existing_authentication(self.cursor, user, host) if authentications: - output_dict.update(authentications) + output_dict.update(authentications[0]) # TODO password_option # TODO lock_option diff --git a/tests/integration/targets/test_mysql_user/tasks/main.yml b/tests/integration/targets/test_mysql_user/tasks/main.yml index 8ec0798..e77c443 100644 --- a/tests/integration/targets/test_mysql_user/tasks/main.yml +++ b/tests/integration/targets/test_mysql_user/tasks/main.yml @@ -295,3 +295,7 @@ - name: Mysql_user - test column case sensitive ansible.builtin.import_tasks: file: test_column_case_sensitive.yml + + - name: Mysql_user - test update_password + ansible.builtin.import_tasks: + file: test_update_password.yml diff --git a/tests/integration/targets/test_mysql_user/tasks/test_update_password.yml b/tests/integration/targets/test_mysql_user/tasks/test_update_password.yml index 428c1ef..adaa7c7 100644 --- a/tests/integration/targets/test_mysql_user/tasks/test_update_password.yml +++ b/tests/integration/targets/test_mysql_user/tasks/test_update_password.yml @@ -127,3 +127,29 @@ update_password: on_create - username: test3 update_password: on_new_username + + # another new user, another new password and multiple existing users with + # varying passwords without providing a password + - name: update_password | Create account with on_new_username while omit password + community.mysql.mysql_user: + login_user: '{{ mysql_parameters.login_user }}' + login_password: '{{ mysql_parameters.login_password }}' + login_host: '{{ mysql_parameters.login_host }}' + login_port: '{{ mysql_parameters.login_port }}' + state: present + name: test3 + host: '10.10.10.10' + update_password: on_new_username + + - name: update_password | Assert create account with on_new_username while omit password produce empty auth string + ansible.builtin.command: >- + {{ mysql_command }} -BNe "SELECT user, host, plugin, authentication_string + FROM mysql.user where user='test3' and host='10.10.10.10'" + register: test3_info + changed_when: false + failed_when: + # MariaDB default plugin is mysql_native_password + - "'test3\t10.10.10.10\tmysql_native_password\t' != test3_info.stdout" + + # MySQL 8+ default plugin is caching_sha2_password + - "'test3\t10.10.10.10\tcaching_sha2_password\t' != test3_info.stdout" diff --git a/tests/integration/targets/test_mysql_user/tasks/utils/assert_user_password.yml b/tests/integration/targets/test_mysql_user/tasks/utils/assert_user_password.yml index d95e53b..e6bd695 100644 --- a/tests/integration/targets/test_mysql_user/tasks/utils/assert_user_password.yml +++ b/tests/integration/targets/test_mysql_user/tasks/utils/assert_user_password.yml @@ -1,6 +1,6 @@ --- - name: Utils | Assert user password | Apply update_password to {{ username }} - mysql_user: + community.mysql.mysql_user: login_user: '{{ mysql_parameters.login_user }}' login_password: '{{ mysql_parameters.login_password }}' login_host: '{{ mysql_parameters.login_host }}' @@ -13,16 +13,17 @@ register: result - name: Utils | Assert user password | Assert a change occurred - assert: + ansible.builtin.assert: that: - - "result.changed | bool == {{ expect_change }} | bool" - - "result.password_changed == {{ expect_password_change }}" + - result.changed | bool == expect_change | bool + - result.password_changed == expect_password_change -- name: Utils | Assert user password | Query user {{ username }} - command: "{{ mysql_command }} -BNe \"SELECT plugin, authentication_string FROM mysql.user where user='{{ username }}' and host='{{ host }}'\"" +- name: Utils | Assert user password | Assert expect_hash is in user stdout for {{ username }} + ansible.builtin.command: >- + {{ mysql_command }} -BNe "SELECT plugin, authentication_string + FROM mysql.user where user='{{ username }}' and host='{{ host }}'" register: existing_user - -- name: Utils | Assert user password | Assert expect_hash is in user stdout - assert: - that: - - "'mysql_native_password\t{{ expect_password_hash }}' in existing_user.stdout_lines" + changed_when: false + failed_when: pattern not in existing_user.stdout_lines + vars: + pattern: "mysql_native_password\t{{ expect_password_hash }}" diff --git a/tests/integration/targets/test_mysql_variables/tasks/issue-28.yml b/tests/integration/targets/test_mysql_variables/tasks/issue-28.yml index 10a9154..89d3d26 100644 --- a/tests/integration/targets/test_mysql_variables/tasks/issue-28.yml +++ b/tests/integration/targets/test_mysql_variables/tasks/issue-28.yml @@ -1,8 +1,11 @@ --- - name: set fact tls_enabled - command: "{{ mysql_command }} \"-e SHOW VARIABLES LIKE 'have_ssl';\"" + ansible.builtin.command: + cmd: "{{ mysql_command }} \"-e SHOW VARIABLES LIKE 'have_ssl';\"" register: result -- set_fact: + +- name: Set tls_enabled fact + ansible.builtin.set_fact: tls_enabled: "{{ 'YES' in result.stdout | bool | default('false', true) }}" - vars: @@ -16,21 +19,21 @@ # ============================================================ - name: get server certificate - copy: + ansible.builtin.copy: content: "{{ lookup('pipe', \"openssl s_client -starttls mysql -connect localhost:3307 -showcerts 2>/dev/null = 0.7.11 is required' in result.msg + ignore_errors: true + failed_when: + - result is failed or 'pymysql >= 0.7.11 is required' not in result.msg - name: Drop mysql user - mysql_user: + community.mysql.mysql_user: <<: *mysql_params name: '{{ user_name_1 }}' host_all: true 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 2d2318e..4a7fd00 100644 --- a/tests/integration/targets/test_mysql_variables/tasks/mysql_variables.yml +++ b/tests/integration/targets/test_mysql_variables/tasks/mysql_variables.yml @@ -47,8 +47,8 @@ # Verify mysql_variable successfully updates a variable (issue:4568) # - set_fact: - set_name: 'delay_key_write' - set_value: 'ON' + set_name: 'delay_key_write' + set_value: 'ON' - name: set mysql variable mysql_variables: @@ -74,8 +74,8 @@ # Verify mysql_variable successfully updates a variable using single quotes # - set_fact: - set_name: 'wait_timeout' - set_value: '300' + set_name: 'wait_timeout' + set_value: '300' - name: set mysql variable to a temp value mysql_variables: @@ -105,8 +105,8 @@ # Verify mysql_variable successfully updates a variable using double quotes # - set_fact: - set_name: "wait_timeout" - set_value: "400" + set_name: "wait_timeout" + set_value: "400" - name: set mysql variable to a temp value mysql_variables: @@ -132,8 +132,8 @@ # Verify mysql_variable successfully updates a variable using no quotes # - set_fact: - set_name: wait_timeout - set_value: 500 + set_name: wait_timeout + set_value: 500 - name: set mysql variable to a temp value mysql_variables: @@ -251,8 +251,8 @@ # Verify mysql_variable works with the login_user and login_password parameters # - set_fact: - set_name: wait_timeout - set_value: 77 + set_name: wait_timeout + set_value: 77 - name: query mysql_variable using login_user and password_password mysql_variables: @@ -291,8 +291,8 @@ # Verify mysql_variable fails with an incorrect login_password parameter # - set_fact: - set_name: connect_timeout - set_value: 10 + set_name: connect_timeout + set_value: 10 - name: query mysql_variable using incorrect login_password mysql_variables: From 4912f1a41b9b7a79fa526119879ff8159bb7c2da Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Fri, 28 Jun 2024 11:34:59 +0200 Subject: [PATCH 187/217] mysql_variables: fix boolean value handling (#653) * mysql_variables: fix boolean value handling * fix * Fix tests * Fix tests * Fix * Fix * Fix * Fix comment --- changelogs/fragments/2-mysql_variables.yml | 2 + plugins/modules/mysql_variables.py | 21 +++++ .../tasks/mysql_variables.yml | 93 +++++++++++++++++++ .../plugins/modules/test_mysql_variables.py | 26 ++++++ 4 files changed, 142 insertions(+) create mode 100644 changelogs/fragments/2-mysql_variables.yml create mode 100644 tests/unit/plugins/modules/test_mysql_variables.py diff --git a/changelogs/fragments/2-mysql_variables.yml b/changelogs/fragments/2-mysql_variables.yml new file mode 100644 index 0000000..9ef8d80 --- /dev/null +++ b/changelogs/fragments/2-mysql_variables.yml @@ -0,0 +1,2 @@ +bugfixes: +- mysql_variables - fix the module always changes on boolean values (https://github.com/ansible-collections/community.mysql/issues/652). diff --git a/plugins/modules/mysql_variables.py b/plugins/modules/mysql_variables.py index f912a27..8632a52 100644 --- a/plugins/modules/mysql_variables.py +++ b/plugins/modules/mysql_variables.py @@ -26,6 +26,7 @@ options: value: description: - If set, then sets variable value to this. + - With boolean values, use C(0)|C(1) or quoted C("ON")|C("OFF"). type: str mode: description: @@ -74,6 +75,11 @@ EXAMPLES = r''' variable: read_only value: 1 mode: persist + +- name: Set a boolean using ON/OFF notation + mysql_variables: + variable: log_slow_replica_statements + value: "ON" # Make sure it's quoted ''' RETURN = r''' @@ -176,6 +182,18 @@ def setvariable(cursor, mysqlvar, value, mode='global'): return result +def convert_bool_setting_value_wanted(val): + """Converts passed value from 0,1,on,off to ON/OFF + as it's represented in the server. + """ + if val in ('on', 1): + val = 'ON' + elif val in ('off', 0): + val = 'OFF' + + return val + + def main(): argument_spec = mysql_common_argument_spec() argument_spec.update( @@ -243,6 +261,9 @@ def main(): # Type values before using them value_wanted = typedvalue(value) value_actual = typedvalue(mysqlvar_val) + if value_actual in ('ON', 'OFF') and value_wanted not in ('ON', 'OFF'): + value_wanted = convert_bool_setting_value_wanted(value_wanted) + value_in_auto_cnf = None if var_in_mysqld_auto_cnf is not None: value_in_auto_cnf = typedvalue(var_in_mysqld_auto_cnf) 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 4a7fd00..8194172 100644 --- a/tests/integration/targets/test_mysql_variables/tasks/mysql_variables.yml +++ b/tests/integration/targets/test_mysql_variables/tasks/mysql_variables.yml @@ -287,6 +287,99 @@ var_name: "{{set_name}}" var_value: '{{set_value}}' + #========================================================================= + # Bugfix https://github.com/ansible-collections/community.mysql/issues/652 + + - name: Get server version + register: result + mysql_info: + <<: *mysql_params + + - name: Set variable name when running on MySQL + set_fact: + log_slow_statements: log_slow_replica_statements + when: result.server_engine == 'MySQL' + + - name: Set variable name when running on MariaDB + set_fact: + log_slow_statements: log_slow_slave_statements + when: result.server_engine == 'MariaDB' + + - name: Set a boolean value using ON + mysql_variables: + <<: *mysql_params + variable: "{{ log_slow_statements }}" + value: "ON" + register: result + + - name: Check that it changed + assert: + that: + - result is changed or result.msg == "Variable is already set to requested value." + - result.msg == "Variable is already set to requested value." or result.queries == ["SET GLOBAL `{{ log_slow_statements }}` = ON"] + + - name: Set a boolean value again using ON + mysql_variables: + <<: *mysql_params + variable: "{{ log_slow_statements }}" + value: "ON" + register: result + + - name: Check that it didn't change + assert: + that: + - result is not changed + + - name: Set a boolean value again using 1 + mysql_variables: + <<: *mysql_params + variable: "{{ log_slow_statements }}" + value: 1 + register: result + + - name: Check that it didn't change + assert: + that: + - result is not changed + + - name: Set a boolean value using OFF + mysql_variables: + <<: *mysql_params + variable: "{{ log_slow_statements }}" + value: "OFF" + register: result + + - name: Check that it changed + assert: + that: + - result is changed + - result.queries == ["SET GLOBAL `{{ log_slow_statements }}` = OFF"] + + - name: Set a boolean value again using 0 + mysql_variables: + <<: *mysql_params + variable: "{{ log_slow_statements }}" + value: 0 + register: result + + - name: Check that it didn't change + assert: + that: + - result is not changed + + - name: Set a boolean value using on + mysql_variables: + <<: *mysql_params + variable: "{{ log_slow_statements }}" + value: "on" + register: result + + - name: Check that it changed + assert: + that: + - result is changed + - result.queries == ["SET GLOBAL `{{ log_slow_statements }}` = ON"] + #============================================================ # Verify mysql_variable fails with an incorrect login_password parameter # diff --git a/tests/unit/plugins/modules/test_mysql_variables.py b/tests/unit/plugins/modules/test_mysql_variables.py new file mode 100644 index 0000000..8960173 --- /dev/null +++ b/tests/unit/plugins/modules/test_mysql_variables.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import pytest + +from ansible_collections.community.mysql.plugins.modules.mysql_variables import ( + convert_bool_setting_value_wanted, +) + + +@pytest.mark.parametrize( + 'value,output', + [ + (1, 'ON'), + (0, 'OFF'), + (2, 2), + ('on', 'ON'), + ('off', 'OFF'), + ('ON', 'ON'), + ('OFF', 'OFF'), + ] +) +def test_convert_bool_value(value, output): + assert convert_bool_setting_value_wanted(value) == output From 83ed4af4e13233c8ca1b8528cf9ba7bc536e03ed Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Tue, 9 Jul 2024 08:20:47 +0200 Subject: [PATCH 188/217] Deprecate mysqlclient/MySQLdb connector support (#655) * Deprecate mysqlclient/MySQLdb connector support * Update README * Put in README that mysqlclient is deprecated --- README.md | 9 ++------- .../fragments/3-deprecate_mysqlclient.yml | 2 ++ plugins/doc_fragments/mysql.py | 20 +++++++------------ plugins/module_utils/mysql.py | 7 +++++++ plugins/modules/mysql_info.py | 1 - 5 files changed, 18 insertions(+), 21 deletions(-) create mode 100644 changelogs/fragments/3-deprecate_mysqlclient.yml diff --git a/README.md b/README.md index 2678f31..98e90b2 100644 --- a/README.md +++ b/README.md @@ -122,17 +122,12 @@ For MariaDB, only Long Term releases are tested. - pymysql 0.7.11 (Only tested with MySQL 5.7) - pymysql 0.9.3 - pymysql 1.0.2 (only collection version >= 3.6.1) -- mysqlclient 2.0.1 -- mysqlclient 2.0.3 (only collection version >= 3.5.2) -- mysqlclient 2.1.1 (only collection version >= 3.5.2) ## External requirements -The MySQL modules rely on a MySQL connector. The list of supported drivers is below: +The MySQL modules rely on a [PyMySQL](https://github.com/PyMySQL/PyMySQL) connector. -- [PyMySQL](https://github.com/PyMySQL/PyMySQL) -- [mysqlclient](https://github.com/PyMySQL/mysqlclient) -- Support for other Python MySQL connectors may be added in a future release. +The `mysqlclient` connector support has been [deprecated](https://github.com/ansible-collections/community.mysql/issues/654) - use `PyMySQL` connector instead! We will stop testing against it in collection version 4.0.0 and remove the related code in 5.0.0. ## Using this collection diff --git a/changelogs/fragments/3-deprecate_mysqlclient.yml b/changelogs/fragments/3-deprecate_mysqlclient.yml new file mode 100644 index 0000000..9134413 --- /dev/null +++ b/changelogs/fragments/3-deprecate_mysqlclient.yml @@ -0,0 +1,2 @@ +breaking_changes: +- collection - support of mysqlclient connector is deprecated - use PyMySQL connector instead! We will stop testing against it in collection version 4.0.0 and remove the related code in 5.0.0 (https://github.com/ansible-collections/community.mysql/issues/654). diff --git a/plugins/doc_fragments/mysql.py b/plugins/doc_fragments/mysql.py index 27ec650..a52243b 100644 --- a/plugins/doc_fragments/mysql.py +++ b/plugins/doc_fragments/mysql.py @@ -71,24 +71,21 @@ options: - Whether to validate the server host name when an SSL connection is required. Corresponds to MySQL CLIs C(--ssl) switch. - Setting this to C(false) disables hostname verification. Use with caution. - Requires pymysql >= 0.7.11. - - This option has no effect on MySQLdb. type: bool version_added: '1.1.0' requirements: - - mysqlclient (Python 3.5+) or - - PyMySQL (Python 2.7 and Python 3.x) or - - MySQLdb (Python 2.x) + - PyMySQL (Python 2.7 and Python 3.x) notes: - - Requires the PyMySQL (Python 2.7 and Python 3.X) or MySQL-python (Python 2.X) package installed on the remote host. + - Requires the PyMySQL (Python 2.7 and Python 3.X) package installed on the remote host. The Python package may be installed with apt-get install python-pymysql (Ubuntu; see M(ansible.builtin.apt)) or yum install python2-PyMySQL (RHEL/CentOS/Fedora; see M(ansible.builtin.yum)). You can also use dnf install python2-PyMySQL for newer versions of Fedora; see M(ansible.builtin.dnf). - - Be sure you have mysqlclient, PyMySQL, or MySQLdb library installed on the target machine - for the Python interpreter Ansible discovers. For example if ansible discovers and uses Python 3, you need to install - the Python 3 version of PyMySQL or mysqlclient. If ansible discovers and uses Python 2, you need to install the Python 2 - version of either PyMySQL or MySQL-python. + - Be sure you have PyMySQL library installed on the target machine + for the Python interpreter Ansible discovers. For example if ansible discovers and uses Python 3, you need to install + the Python 3 version of PyMySQL. If ansible discovers and uses Python 2, you need to install the Python 2 + version of PyMySQL. - If you have trouble, it may help to force Ansible to use the Python interpreter you need by specifying - C(ansible_python_interpreter). For more information, see + C(ansible_python_interpreter). For more information, see U(https://docs.ansible.com/ansible/latest/reference_appendices/interpreter_discovery.html). - Both C(login_password) and C(login_user) are required when you are passing credentials. If none are present, the module will attempt to read @@ -99,9 +96,6 @@ notes: and later uses the unix_socket authentication plugin by default that without using I(login_unix_socket=/var/run/mysqld/mysqld.sock) (the default path) causes the error ``Host '127.0.0.1' is not allowed to connect to this MariaDB server``. - - Alternatively, you can use the mysqlclient library instead of MySQL-python (MySQLdb) - which supports both Python 2.X and Python >=3.5. - See U(https://pypi.org/project/mysqlclient/) how to install it. - "If credentials from the config file (for example, C(/root/.my.cnf)) are not needed to connect to a database server, but the file exists and does not contain a C([client]) section, before any other valid directives, it will be read and this will cause the connection to fail, to prevent this set it to an empty string, (for example C(config_file: ''))." diff --git a/plugins/module_utils/mysql.py b/plugins/module_utils/mysql.py index 10ccfcf..9758994 100644 --- a/plugins/module_utils/mysql.py +++ b/plugins/module_utils/mysql.py @@ -154,6 +154,13 @@ def mysql_connect(module, login_user=None, login_password=None, config_file='', db_connection = mysql_driver.connect(autocommit=autocommit, **config) else: # In case of MySQLdb driver + + # Will be deprecated and dropped + # https://github.com/ansible-collections/community.mysql/issues/654 + module.warn('Support of mysqlcline/MySQLdb connector is deprecated. ' + 'We\'ll stop testing against it in collection version 4.0.0 ' + 'and remove the related code in 5.0.0. Use PyMySQL connector instead.') + if mysql_driver.version_info[0] < 2 or (mysql_driver.version_info[0] == 2 and mysql_driver.version_info[1] < 1): # for MySQLdb < 2.1.0, use 'db' instead of 'database' and 'passwd' instead of 'password' if 'database' in config: diff --git a/plugins/modules/mysql_info.py b/plugins/modules/mysql_info.py index 9f0586a..d8bc88c 100644 --- a/plugins/modules/mysql_info.py +++ b/plugins/modules/mysql_info.py @@ -280,7 +280,6 @@ connector_name: type: str sample: - "pymysql" - - "MySQLdb" version_added: '3.6.0' connector_version: description: Version of the python connector used by the module. When the connector is not identified, returns C(Unknown). From c503dc5b6bdfa06373ff8e8ec7db0f12c911938a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20Inderm=C3=BChle?= Date: Fri, 19 Jul 2024 11:04:13 +0200 Subject: [PATCH 189/217] [CI] Add 2024 versions to tests (#660) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Enable mysql_native_password for MySQL 8.2+ * Fix connection to MySQL 8 since Ubuntu 20.04 update * Cut mysqlclient form the documentation * Cut tests for Python 3.12 not supported by ansible-test * Upgrade integration controller to ubuntu2204 by removing python ansible-test uses python 3.10 if we specify ubuntu2204. Thus we lose the ability to chose specific version of python to test. But integrations tests are optional for a collection. And we don't catch a issue with Python that often (ever ? I don't recall seen one). This allow us to test MySQL 8.4, so it's a win. * Cut tests for EoL MariaDB 10.4 * Reduce number of test in the matrix * Cut support for intermediate LTS * Fix python command not found with ansible-devel and add the debug This is puzzling me. Why when using ansible devel the python command changes? I know ansible-test install python after starting ubuntu22.04 so the way python is install must changes. * Disable retry-on-error When reading log we tend to look at the bottom, but doing so we find often a idempotent error that are nothing to do with the first error. Disabling this can greatly speedup tests and makes logs more readable. Plus, now GHA jumps automatically at the latest error message. So with this modification, we will always jump to the latest real error message. * Enhance jobs title readability We can't expand the left column on GHA, so the shorter, the better. Use Ⓐ instead of Ansible. --- .github/workflows/ansible-test-plugins.yml | 250 +++++++++--------- Makefile | 27 +- README.md | 31 ++- TESTING.md | 44 ++- .../setup_controller/tasks/requirements.yml | 2 + .../setup_controller/tasks/setvars.yml | 7 - .../targets/setup_controller/tasks/verify.yml | 20 +- 7 files changed, 201 insertions(+), 180 deletions(-) diff --git a/.github/workflows/ansible-test-plugins.yml b/.github/workflows/ansible-test-plugins.yml index f3f440e..efc1537 100644 --- a/.github/workflows/ansible-test-plugins.yml +++ b/.github/workflows/ansible-test-plugins.yml @@ -17,7 +17,7 @@ on: # yamllint disable-line rule:truthy jobs: sanity: - name: "Sanity (Ansible: ${{ matrix.ansible }})" + name: "Sanity (Ⓐ${{ matrix.ansible }})" runs-on: ubuntu-22.04 strategy: matrix: @@ -35,8 +35,10 @@ jobs: testing-type: sanity pull-request-change-detection: true + # Use this to chose which version of Python vs Ansible to test: + # https://docs.ansible.com/ansible/latest/reference_appendices/release_and_maintenance.html#ansible-core-control-node-python-support integration: - name: "Integration (Python: ${{ matrix.python }}, Ansible: ${{ matrix.ansible }}, DB: ${{ matrix.db_engine_name }} ${{ matrix.db_engine_version }}, connector: ${{ matrix.connector_name }} ${{ matrix.connector_version }})" + name: "Integration (Ⓐ${{ matrix.ansible }}, DB: ${{ matrix.db_engine_name }} ${{ matrix.db_engine_version }}, connector: ${{ matrix.connector_name }} ${{ matrix.connector_version }})" runs-on: ubuntu-22.04 strategy: fail-fast: false @@ -50,142 +52,117 @@ jobs: - mysql - mariadb db_engine_version: - - 5.7.40 - - 8.0.31 - - 10.4.27 - - 10.5.18 - - 10.6.11 - python: - - '3.8' - - '3.9' - - '3.10' + - '8.0.38' + - '8.4.1' + - '10.5.25' + - '10.11.8' connector_name: - pymysql - mysqlclient connector_version: - - 0.7.11 - - 0.9.3 - - 1.0.2 - - 2.0.1 - - 2.0.3 - - 2.1.1 + - '0.9.3' + - '1.0.2' + - '1.1.1' + - '2.0.1' + - '2.0.3' + - '2.1.1' + + include: + + # RHEL8 context + - connector_name: pymysql + connector_version: '0.10.1' + ansible: stable-2.16 + db_engine_name: mariadb + db_engine_version: '10.11.8' + + # RHEL9 context + # - connector_name: pymysql + # connector_version: '1.1.1' + # ansible: stable-2.17 + # db_engine_name: mariadb + # db_engine_version: '10.11.8' + # This tests is already included in the matrix, no need repeating + exclude: - - db_engine_name: mysql - db_engine_version: 10.4.27 - db_engine_name: mysql - db_engine_version: 10.5.18 + db_engine_version: '10.5.25' - db_engine_name: mysql - db_engine_version: 10.6.11 + db_engine_version: '10.11.8' - db_engine_name: mariadb - db_engine_version: 5.7.40 + db_engine_version: '8.0.38' - db_engine_name: mariadb - db_engine_version: 8.0.31 + db_engine_version: '8.4.1' - connector_name: pymysql - connector_version: 2.0.1 + connector_version: '2.0.1' - connector_name: pymysql - connector_version: 2.0.3 + connector_version: '2.0.3' - connector_name: pymysql - connector_version: 2.1.1 + connector_version: '2.1.1' - connector_name: mysqlclient - connector_version: 0.7.11 + connector_version: '0.9.3' - connector_name: mysqlclient - connector_version: 0.9.3 + connector_version: '1.0.2' - connector_name: mysqlclient - connector_version: 1.0.2 + connector_version: '1.1.1' - - db_engine_name: mariadb - connector_version: 0.7.11 + - db_engine_version: '8.0.38' + ansible: stable-2.17 - - db_engine_version: 5.7.40 - python: '3.9' + - db_engine_version: '10.5.25' + ansible: stable-2.17 - - db_engine_version: 5.7.40 - python: '3.10' + - db_engine_version: '8.0.38' + ansible: devel - - db_engine_version: 5.7.40 + - db_engine_version: '10.5.25' + ansible: devel + + - db_engine_version: '8.4.1' + connector_version: '0.9.3' + + - db_engine_version: '8.4.1' + connector_version: '1.0.2' + + - db_engine_version: '8.4.1' + connector_version: '2.0.1' + + - db_engine_version: '8.4.1' + connector_version: '2.0.3' + + - db_engine_version: '10.11.8' + connector_version: '0.9.3' + + - db_engine_version: '10.11.8' + connector_version: '1.0.2' + + - db_engine_version: '10.11.8' + connector_version: '2.0.1' + + - db_engine_version: '10.11.8' + connector_version: '2.0.1' + + - db_engine_version: '10.11.8' ansible: stable-2.15 - - db_engine_version: 5.7.40 - ansible: stable-2.16 + - db_engine_version: '8.4.1' + ansible: stable-2.15 - - db_engine_version: 5.7.40 - ansible: devel + - connector_version: '1.1.1' + db_engine_version: '8.0.38' - - db_engine_version: 8.0.31 - python: '3.8' - - - db_engine_version: 10.4.27 - python: '3.10' - - - db_engine_version: 10.4.27 - ansible: devel - - - db_engine_version: 10.6.11 - python: '3.8' - - - db_engine_version: 10.6.11 - python: '3.9' - - - python: '3.8' - connector_version: 1.0.2 - - - python: '3.8' - connector_version: 2.0.3 - - - python: '3.8' - connector_version: 2.1.1 - - - python: '3.9' - connector_version: 0.7.11 - - - python: '3.9' - connector_version: 1.0.2 - - - python: '3.9' - connector_version: 2.0.1 - - - python: '3.9' - connector_version: 2.1.1 - - - python: '3.10' - connector_version: 0.7.11 - - - python: '3.10' - connector_version: 0.9.3 - - - python: '3.10' - connector_version: 2.0.1 - - - python: '3.10' - connector_version: 2.0.3 - - - python: '3.8' - ansible: stable-2.16 - - - python: '3.8' - ansible: stable-2.17 - - - python: '3.8' - ansible: devel - - - python: '3.9' - ansible: stable-2.16 - - - python: '3.9' - ansible: stable-2.17 - - - python: '3.9' - ansible: devel + - connector_version: '1.1.1' + db_engine_version: '10.5.25' services: db_primary: @@ -238,9 +215,22 @@ jobs: - name: Restart MySQL server with settings for replication run: | - docker exec ${{ job.services.db_primary.id }} bash -c 'echo -e [mysqld]\\nserver-id=1\\nlog-bin=/var/lib/mysql/primary-bin > /etc/mysql/conf.d/replication.cnf' - docker exec ${{ job.services.db_replica1.id }} bash -c 'echo -e [mysqld]\\nserver-id=2\\nlog-bin=/var/lib/mysql/replica1-bin > /etc/mysql/conf.d/replication.cnf' - docker exec ${{ job.services.db_replica2.id }} bash -c 'echo -e [mysqld]\\nserver-id=3\\nlog-bin=/var/lib/mysql/replica2-bin > /etc/mysql/conf.d/replication.cnf' + db_ver="${{ matrix.db_engine_version }}" + maj="${db_ver%.*.*}" + maj_min="${db_ver%.*}" + min="${maj_min#*.}" + if [[ "${{ matrix.db_engine_name }}" == "mysql" && "$maj" -eq 8 && "$min" -ge 2 ]]; then + prima_conf='[mysqld]\\nserver-id=1\\nlog-bin=/var/lib/mysql/primary-bin\\nmysql-native-password=1' + repl1_conf='[mysqld]\\nserver-id=2\\nlog-bin=/var/lib/mysql/replica1-bin\\nmysql-native-password=1' + repl2_conf='[mysqld]\\nserver-id=3\\nlog-bin=/var/lib/mysql/replica2-bin\\nmysql-native-password=1' + else + prima_conf='[mysqld]\\nserver-id=1\\nlog-bin=/var/lib/mysql/primary-bin' + repl1_conf='[mysqld]\\nserver-id=2\\nlog-bin=/var/lib/mysql/replica1-bin' + repl2_conf='[mysqld]\\nserver-id=3\\nlog-bin=/var/lib/mysql/replica2-bin' + fi + docker exec -e cnf=$prima_conf ${{ job.services.db_primary.id }} bash -c 'echo -e ${cnf//\\n/\n} > /etc/mysql/conf.d/replication.cnf' + docker exec -e cnf=$repl1_conf ${{ job.services.db_replica1.id }} bash -c 'echo -e ${cnf//\\n/\n} > /etc/mysql/conf.d/replication.cnf' + docker exec -e cnf=$repl2_conf ${{ job.services.db_replica2.id }} bash -c 'echo -e ${cnf//\\n/\n} > /etc/mysql/conf.d/replication.cnf' docker restart -t 30 ${{ job.services.db_primary.id }} docker restart -t 30 ${{ job.services.db_replica1.id }} docker restart -t 30 ${{ job.services.db_replica2.id }} @@ -255,10 +245,10 @@ jobs: - name: >- Perform integration testing against Ansible version ${{ matrix.ansible }} - under Python ${{ matrix.python }} uses: ansible-community/ansible-test-gh-action@release/v1 with: ansible-core-version: ${{ matrix.ansible }} + docker-image: ubuntu2204 pre-test-cmd: >- echo Setting db_engine_name to "${{ matrix.db_engine_name }}"...; echo -n "${{ matrix.db_engine_name }}" @@ -277,19 +267,15 @@ jobs: echo -n "${{ matrix.connector_version }}" > tests/integration/connector_version; - echo Setting Python version to "${{ matrix.python }}"...; - echo -n "${{ matrix.python }}" - > tests/integration/python; - echo Setting Ansible version to "${{ matrix.ansible }}"...; echo -n "${{ matrix.ansible }}" > tests/integration/ansible - target-python-version: ${{ matrix.python }} testing-type: integration + integration-retry-on-error: false units: runs-on: ubuntu-22.04 - name: Units (Ⓐ${{ matrix.ansible }}) + name: Units (Ⓐ${{ matrix.ansible }}, Python${{ matrix.python }}) strategy: # As soon as the first unit test fails, # cancel the others to free up the CI queue @@ -301,22 +287,46 @@ jobs: - stable-2.17 - devel python: - - 3.8 - - 3.9 + - '3.8' + - '3.9' + - '3.10' + - '3.11' exclude: - - python: '3.8' - ansible: stable-2.15 - python: '3.8' ansible: stable-2.16 + - python: '3.8' ansible: stable-2.17 + - python: '3.8' ansible: devel + - python: '3.9' + ansible: stable-2.15 + + - python: '3.9' + ansible: stable-2.17 + + - python: '3.9' + ansible: devel + + - python: '3.10' + ansible: stable-2.15 + + - python: '3.10' + ansible: stable-2.16 + + - python: '3.11' + ansible: stable-2.15 + + - python: '3.11' + ansible: stable-2.16 + steps: - name: >- Perform unit testing against - Ansible version ${{ matrix.ansible }} + Ansible version ${{ matrix.ansible }} and + python version ${{ matrix.python }} uses: ansible-community/ansible-test-gh-action@release/v1 with: ansible-core-version: ${{ matrix.ansible }} diff --git a/Makefile b/Makefile index 1bf8fae..5a11d1b 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ endif # This match what GitHub Action will do. Disabled by default. ifdef continue_on_errors - _continue_on_errors = --retry-on-error --continue-on-error + _continue_on_errors = --continue-on-error endif .PHONY: test-integration @@ -17,7 +17,6 @@ test-integration: @echo -n $(db_engine_version) > tests/integration/db_engine_version @echo -n $(connector_name) > tests/integration/connector_name @echo -n $(connector_version) > tests/integration/connector_version - @echo -n $(python) > tests/integration/python @echo -n $(ansible) > tests/integration/ansible # Create podman network for systems missing it. Error can be ignored @@ -55,10 +54,23 @@ test-integration: --health-cmd 'mysqladmin ping -P 3306 -pmsandbox | grep alive || exit 1' \ docker.io/library/$(db_engine_name):$(db_engine_version) \ mysqld - # Setup replication and restart containers - podman exec primary bash -c 'echo -e [mysqld]\\nserver-id=1\\nlog-bin=/var/lib/mysql/primary-bin > /etc/mysql/conf.d/replication.cnf' - podman exec replica1 bash -c 'echo -e [mysqld]\\nserver-id=2\\nlog-bin=/var/lib/mysql/replica1-bin > /etc/mysql/conf.d/replication.cnf' - podman exec replica2 bash -c 'echo -e [mysqld]\\nserver-id=3\\nlog-bin=/var/lib/mysql/replica2-bin > /etc/mysql/conf.d/replication.cnf' + # Setup replication and restart containers using the same subshell to keep variables alive + db_ver=$(db_engine_version); \ + maj="$${db_ver%.*.*}"; \ + maj_min="$${db_ver%.*}"; \ + min="$${maj_min#*.}"; \ + if [[ "$(db_engine_name)" == "mysql" && "$$maj" -eq 8 && "$$min" -ge 2 ]]; then \ + prima_conf='[mysqld]\\nserver-id=1\\nlog-bin=/var/lib/mysql/primary-bin\\nmysql-native-password=1'; \ + repl1_conf='[mysqld]\\nserver-id=2\\nlog-bin=/var/lib/mysql/replica1-bin\\nmysql-native-password=1'; \ + repl2_conf='[mysqld]\\nserver-id=3\\nlog-bin=/var/lib/mysql/replica2-bin\\nmysql-native-password=1'; \ + else \ + prima_conf='[mysqld]\\nserver-id=1\\nlog-bin=/var/lib/mysql/primary-bin'; \ + repl1_conf='[mysqld]\\nserver-id=2\\nlog-bin=/var/lib/mysql/replica1-bin'; \ + repl2_conf='[mysqld]\\nserver-id=3\\nlog-bin=/var/lib/mysql/replica2-bin'; \ + fi; \ + podman exec -e cnf="$$prima_conf" primary bash -c 'echo -e "$${cnf//\\n/\n}" > /etc/mysql/conf.d/replication.cnf'; \ + podman exec -e cnf="$$repl1_conf" replica1 bash -c 'echo -e "$${cnf//\\n/\n}" > /etc/mysql/conf.d/replication.cnf'; \ + podman exec -e cnf="$$repl2_conf" replica2 bash -c 'echo -e "$${cnf//\\n/\n}" > /etc/mysql/conf.d/replication.cnf' # Don't restart a container unless it is healthy while ! podman healthcheck run primary && [[ "$$SECONDS" -lt 120 ]]; do sleep 1; done podman restart -t 30 primary @@ -77,7 +89,7 @@ test-integration: https://github.com/ansible/ansible/archive/$(ansible).tar.gz; \ set -x; \ ansible-test integration $(target) -v --color --coverage --diff \ - --docker --python $(python) \ + --docker ubuntu2204 \ --docker-network podman $(_continue_on_errors) $(_keep_containers_alive); \ set +x # End of venv @@ -86,7 +98,6 @@ test-integration: rm tests/integration/db_engine_version rm tests/integration/connector_name rm tests/integration/connector_version - rm tests/integration/python rm tests/integration/ansible ifndef keep_containers_alive podman stop --time 0 --ignore primary replica1 replica2 diff --git a/README.md b/README.md index 98e90b2..05a7bde 100644 --- a/README.md +++ b/README.md @@ -104,24 +104,35 @@ Here is the table for the support timeline: - stable-2.17 - current development version +### Python + +- 3.8 (Unit tests only) +- 3.9 (Unit tests only) +- 3.10 (Sanity, Units and integrations tests) +- 3.11 (Unit tests only, collection version >= 3.10.0) + ### Databases -For MariaDB, only Long Term releases are tested. +For MariaDB, only Long Term releases are tested. When multiple LTS are available, we test the oldest and the newest only. Usually breaking changes introduced in the versions in between are also present in the latest version. -- mysql 5.7.40 -- mysql 8.0.31 -- mariadb:10.3.34 (only collection version <= 3.5.1) -- mariadb:10.4.24 (only collection version >= 3.5.2) -- mariadb:10.5.18 (only collection version >= 3.5.2) -- mariadb:10.6.11 (only collection version >= 3.5.2) -- mariadb:10.11.?? (waiting for release) +- mysql 5.7.40 (collection version < 3.10.0) +- mysql 8.0.31 (collection version < 3.10.0) +- mysql 8.4.1 (collection version >= 3.10.0) !!! FAILING, no support yet !!! +- mariadb:10.3.34 (collection version < 3.5.1) +- mariadb:10.4.24 (collection version >= 3.5.2, < 3.10.0) +- mariadb:10.5.18 (collection version >= 3.5.2, < 3.10.0) +- mariadb:10.5.25 (collection version >= 3.10.0) +- mariadb:10.6.11 (collection version >= 3.5.2, < 3.10.0) +- mariadb:10.11.8 (collection version >= 3.10.0) ### Database connectors -- pymysql 0.7.11 (Only tested with MySQL 5.7) +- pymysql 0.7.11 (collection version < 3.10 and MySQL 5.7) - pymysql 0.9.3 -- pymysql 1.0.2 (only collection version >= 3.6.1) +- pymysql 0.10.1 (for RHEL8 context) +- pymysql 1.0.2 (collection version >= 3.6.1) +- pymysql 1.1.1 (collection version >= 3.10.0) ## External requirements diff --git a/TESTING.md b/TESTING.md index 54eb5ed..1a22832 100644 --- a/TESTING.md +++ b/TESTING.md @@ -19,7 +19,7 @@ For now, the makefile only supports Podman. ### Requirements -- python >= 3.8 and <= 3.10 +- python >= 3.8 - make - podman - Minimum 15GB of free space on the device storing containers images and volumes. You can use this command to check: `podman system info --format='{{.Store.GraphRoot}}'|xargs findmnt --noheadings --nofsroot --output SOURCE --target|xargs df -h --output=size,used,avail,pcent,target` @@ -41,7 +41,8 @@ The Makefile accept the following options - "3.8" - "3.9" - "3.10" - - Description: If `Python -V` shows an unsupported version, use this option and choose one of the version available on your system. Use `ls /usr/bin/python3*|grep -v config` to list them. + - "3.11" (for stable-2.15+) + - Description: If `Python -V` shows an unsupported version, use this option to select a compatible Python version available on your system. Use `ls /usr/bin/python3*|grep -v config` to list the available versions (You may have to install one). Unsupported versions are those that are too recent for the Ansible version you are using. In such cases, you will see an error message similar to: 'This version of ansible-test cannot be executed with Python version 3.12.3. Supported Python versions are: 3.9, 3.10, 3.11'. - `ansible` - Mandatory: true @@ -62,11 +63,10 @@ The Makefile accept the following options - `db_engine_version` - Mandatory: true - Choices: - - "5.7.40" <- mysql - - "8.0.31" <- mysql - - "10.4.24" <- mariadb - - "10.5.18" <- mariadb - - "10.6.11" <- mariadb + - "8.0.38" <- mysql + - "8.4.1" <- mysql (NOT WORKING YET, ansible-test uses Ubuntu 20.04 which is too old to install mysql-community-client 8.4) + - "10.5.25" <- mariadb + - "10.11.8" <- mariadb - Description: The tag of the container to use for the service containers that will host a primary database and two replicas. Do not use short version, like `mysql:8` (don't do that) because our tests expect a full version to filter tests precisely. For instance: `when: db_version is version ('8.0.22', '>')`. You can use any tag available on [hub.docker.com/_/mysql](https://hub.docker.com/_/mysql) and [hub.docker.com/_/mariadb](https://hub.docker.com/_/mariadb) but GitHub Action will only use the versions listed above. - `connector_name` @@ -79,22 +79,12 @@ The Makefile accept the following options - `connector_version` - Mandatory: true - Choices: - - "0.7.11" <- pymysql (Only for MySQL 5.7) - "0.9.3" <- pymysql + - "0.10.1" <- pymysql - "1.0.2" <- pymysql - - "2.0.1" <- mysqlclient - - "2.0.3" <- mysqlclient - - "2.1.1" <- mysqlclient + - "1.1.1" <- pymysql - Description: The version of the python package of the connector to use. This value is used to filter tests meant for other connectors. -- `python` - - Mandatory: true - - Choices: - - "3.8" - - "3.9" - - "3.10" - - Description: The python version to use in the controller (ansible-test container). - - `target` - Mandatory: false - Choices: @@ -114,30 +104,30 @@ tests will overwrite the 3 databases containers so no need to kill them in advan - `continue_on_errors` - Mandatory: false - - Description: Tells ansible-test to retry on errors and also continue on errors. This is the way the GitHub Action's workflow runs the tests. This can be used to catch all errors in a single run, but you'll need to scroll up to find them. Add any value to activate this option: `continue_on_errors=1` + - Description: Tells ansible-test to continue on errors. This is the way the GitHub Action's workflow runs the tests. This can be used to catch all errors in a single run, but you'll need to scroll up to find them. Add any value to activate this option: `continue_on_errors=1` #### Makefile usage examples: ```sh # Run all targets -make ansible="stable-2.12" db_engine_name="mysql" db_engine_version="5.7.40" python="3.8" connector_name="pymysql" connector_version="0.7.11" +make ansible="stable-2.16" db_engine_name="mysql" db_engine_version="8.0.31" connector_name="pymysql" connector_version="1.0.2" # A single target -make ansible="stable-2.14" db_engine_name="mysql" db_engine_version="5.7.40" python="3.8" connector_name="pymysql" connector_version="0.7.11" target="test_mysql_info" +make ansible="stable-2.16" db_engine_name="mysql" db_engine_version="8.0.31" connector_name="pymysql" connector_version="1.0.2" target="test_mysql_info" # Keep databases and ansible tests containers alives # A single target and continue on errors -make ansible="stable-2.14" db_engine_name="mysql" db_engine_version="8.0.31" python="3.9" connector_name="mysqlclient" connector_version="2.0.3" target="test_mysql_query" keep_containers_alive=1 continue_on_errors=1 +make ansible="stable-2.17" db_engine_name="mysql" db_engine_version="8.0.31" connector_name="mysqlclient" connector_version="2.0.3" target="test_mysql_query" keep_containers_alive=1 continue_on_errors=1 # If your system has an usupported version of Python: -make local_python_version="3.8" ansible="stable-2.14" db_engine_name="mariadb" db_engine_version="10.6.11" python="3.9" connector_name="pymysql" connector_version="0.9.3" +make local_python_version="3.10" ansible="stable-2.17" db_engine_name="mariadb" db_engine_version="10.6.11" connector_name="pymysql" connector_version="1.0.2" ``` ### Run all tests -GitHub Action offer a test matrix that run every combination of Python, MySQL, MariaDB and Connector against each other. To reproduce this, this repo provides a script called *run_all_tests.py*. +GitHub Action offer a test matrix that run every combination of MySQL, MariaDB and Connector against each other. To reproduce this, this repo provides a script called *run_all_tests.py*. Examples: @@ -146,8 +136,8 @@ python run_all_tests.py ``` -### Add a new Python, Connector or Database version +### Add a new Connector or Database version New components version should be added to this file: [.github/workflows/ansible-test-plugins.yml](https://github.com/ansible-collections/community.mysql/tree/main/.github/workflows) -Be careful to not add too much tests. When adding a new version of Python, for instance, only test it agains the latest versions of Ansible and MySQL/MariaDB. When tests are run, you can see that we already start 40 virtual machines! +Be careful to not add too much tests. The matrix creates an exponential number of virtual machines! diff --git a/tests/integration/targets/setup_controller/tasks/requirements.yml b/tests/integration/targets/setup_controller/tasks/requirements.yml index 8bab1a0..c939098 100644 --- a/tests/integration/targets/setup_controller/tasks/requirements.yml +++ b/tests/integration/targets/setup_controller/tasks/requirements.yml @@ -1,5 +1,7 @@ --- +# We use the ubuntu2204 image provided by ansible-test. + - name: "{{ role_name }} | Requirements | Install Linux packages" ansible.builtin.package: name: diff --git a/tests/integration/targets/setup_controller/tasks/setvars.yml b/tests/integration/targets/setup_controller/tasks/setvars.yml index 7c3e03b..0bb8c0e 100644 --- a/tests/integration/targets/setup_controller/tasks/setvars.yml +++ b/tests/integration/targets/setup_controller/tasks/setvars.yml @@ -32,11 +32,6 @@ 'file', '/root/ansible_collections/community/mysql/tests/integration/db_engine_version' ) }} - python_version_lookup: >- - {{ lookup( - 'file', - '/root/ansible_collections/community/mysql/tests/integration/python' - ) }} ansible_version_lookup: >- {{ lookup( 'file', @@ -49,7 +44,6 @@ connector_version: "{{ connector_version_lookup.strip() }}" db_engine: "{{ db_engine_name_lookup.strip() }}" db_version: "{{ db_engine_version_lookup.strip() }}" - python_version: "{{ python_version_lookup.strip() }}" test_ansible_version: >- {%- if ansible_version_lookup == 'devel' -%} {{ ansible_version_lookup }} @@ -77,7 +71,6 @@ connector_version: {{ connector_version }} db_engine: {{ db_engine }} db_version: {{ db_version }} - python_version: {{ python_version }} test_ansible_version: {{ test_ansible_version }} ansible.builtin.debug: msg: "{{ msg.split('\n') }}" diff --git a/tests/integration/targets/setup_controller/tasks/verify.yml b/tests/integration/targets/setup_controller/tasks/verify.yml index e5b4c94..b47e354 100644 --- a/tests/integration/targets/setup_controller/tasks/verify.yml +++ b/tests/integration/targets/setup_controller/tasks/verify.yml @@ -41,16 +41,20 @@ when: - connector_name == 'mysqlclient' - - name: Display the python version in use - command: - cmd: python{{ python_version }} -V + - name: Get the python version in use + ansible.builtin.command: + cmd: python -V changed_when: false - register: python_in_use + failed_when: false + register: python_version_in_use - - name: Assert that expected Python is installed - assert: - that: - - python_in_use.stdout is search(python_version) + - name: Display the python version in use + ansible.builtin.debug: + msg: > + Python in use inside the test container: + ${{ python_version_in_use }} + when: + - python_version_in_use is defined - name: Assert that we run the expected ansible version assert: From cd9f4fcf57bd9d80340148798c383ee702bb4ae1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20Inderm=C3=BChle?= Date: Mon, 5 Aug 2024 08:55:18 +0200 Subject: [PATCH 190/217] Fix deprecated options from MySQL 8.2 (#662) * Fix show master status for MySQL 8.2+ * Fix mysqldump option form --master-data to --source-data * Fix incompatibility between mysqldump 8.0 and MySQL 8.4 Installing the same version between the client and the server makes sense anyway. The incompatibility arise when you use mysqldump with --source-data. The the tool tries to perform a SHOW MASTER STATUS which is deprecated in MySQL 8.2+. * Fix missing condition * Fix unit tests * Add a query resolver depending on implementation and version * Sanity * Fix SHOW REPLICA STATUS queries * Fix mariadb's SHOW REPLICA HOSTS query * Fix CHANGE MASTER for MySQL 8.0.23+ * Fix integration test for CHANGE MASTER * Fix integration test for CHANGE MASTER * Fix replication queries for MySQL 8.0.23+ and 8.4+ * Revert file edited by mistake * Enhance tests format --- plugins/module_utils/command_resolver.py | 180 ++++++++++++++++++ plugins/modules/mysql_db.py | 25 ++- plugins/modules/mysql_info.py | 23 ++- plugins/modules/mysql_replication.py | 84 ++++---- .../targets/setup_controller/files/mysql.gpg | 49 +++++ .../setup_controller/tasks/requirements.yml | 32 ++++ .../test_mysql_db/tasks/state_dump_import.yml | 15 +- .../test_mysql_replication/tasks/main.yml | 5 +- .../tasks/mysql_replication_channel.yml | 31 ++- .../tasks/mysql_replication_initial.yml | 59 ++++-- .../tasks/mysql_replication_primary_delay.yml | 20 +- .../mysql_replication_resetprimary_mode.yml | 21 +- .../module_utils/test_command_resolver.py | 39 ++++ tests/unit/plugins/modules/test_mysql_info.py | 14 +- 14 files changed, 503 insertions(+), 94 deletions(-) create mode 100644 plugins/module_utils/command_resolver.py create mode 100644 tests/integration/targets/setup_controller/files/mysql.gpg create mode 100644 tests/unit/plugins/module_utils/test_command_resolver.py diff --git a/plugins/module_utils/command_resolver.py b/plugins/module_utils/command_resolver.py new file mode 100644 index 0000000..4374879 --- /dev/null +++ b/plugins/module_utils/command_resolver.py @@ -0,0 +1,180 @@ +# -*- coding: utf-8 -*- + +from __future__ import (absolute_import, division, print_function) +from ._version import LooseVersion +__metaclass__ = type + + +class CommandResolver(): + def __init__(self, server_implementation, server_version): + self.server_implementation = server_implementation + self.server_version = LooseVersion(server_version) + + def resolve_command(self, command): + """ + Resolves the appropriate SQL command based on the server implementation and version. + + Parameters: + command (str): The base SQL command to be resolved (e.g., "SHOW SLAVE HOSTS"). + + Returns: + str: The resolved SQL command suitable for the given server implementation and version. + + Raises: + ValueError: If the command is not supported or recognized. + + Example: + Given a server implementation `mysql` and server version `8.0.23`, and a command `SHOW SLAVE HOSTS`, + the method will resolve the command based on the following table of versions: + + Table: + [ + ("mysql", "default", "SHOW SLAVES HOSTS default"), + ("mysql", "5.7.0", "SHOW SLAVES HOSTS"), + ("mysql", "8.0.22", "SHOW REPLICAS"), + ("mysql", "8.4.0", "SHOW REPLICAS 8.4"), + ("mariadb", "10.5.1", "SHOW REPLICAS HOSTS"), + ] + + Example usage: + >>> resolver = CommandResolver("mysql", "8.0.23") + >>> resolver.resolve_command("SHOW SLAVE HOSTS") + 'SHOW REPLICAS' + + In this example, the resolver will: + - Filter and sort applicable versions: [ + ("8.4.0", "SHOW REPLICAS 8.4"), + ("8.0.22", "HOW REPLICAS"), + ("5.7.0", "SHOW SLAVES HOSTS") + ] + + - Iterate through the sorted list and find the first version less than or equal to 8.0.23, + which is 8.0.22, and return the corresponding command. + """ + + # Convert the command to uppercase to ensure case-insensitive lookup + command = command.upper() + + commands = { + "SHOW MASTER STATUS": { + ("mysql", "default"): "SHOW MASTER STATUS", + ("mariadb", "default"): "SHOW MASTER STATUS", + ("mysql", "8.2.0"): "SHOW BINARY LOG STATUS", + ("mariadb", "10.5.2"): "SHOW BINLOG STATUS", + }, + "SHOW SLAVE STATUS": { + ("mysql", "default"): "SHOW SLAVE STATUS", + ("mariadb", "default"): "SHOW SLAVE STATUS", + ("mysql", "8.0.22"): "SHOW REPLICA STATUS", + ("mariadb", "10.5.1"): "SHOW REPLICA STATUS", + }, + "SHOW SLAVE HOSTS": { + ("mysql", "default"): "SHOW SLAVE HOSTS", + ("mariadb", "default"): "SHOW SLAVE HOSTS", + ("mysql", "8.0.22"): "SHOW REPLICAS", + ("mariadb", "10.5.1"): "SHOW REPLICA HOSTS", + }, + "CHANGE MASTER": { + ("mysql", "default"): "CHANGE MASTER", + ("mariadb", "default"): "CHANGE MASTER", + ("mysql", "8.0.23"): "CHANGE REPLICATION SOURCE", + }, + "MASTER_HOST": { + ("mysql", "default"): "MASTER_HOST", + ("mariadb", "default"): "MASTER_HOST", + ("mysql", "8.0.23"): "SOURCE_HOST", + }, + "MASTER_USER": { + ("mysql", "default"): "MASTER_USER", + ("mariadb", "default"): "MASTER_USER", + ("mysql", "8.0.23"): "SOURCE_USER", + }, + "MASTER_PASSWORD": { + ("mysql", "default"): "MASTER_PASSWORD", + ("mariadb", "default"): "MASTER_PASSWORD", + ("mysql", "8.0.23"): "SOURCE_PASSWORD", + }, + "MASTER_PORT": { + ("mysql", "default"): "MASTER_PORT", + ("mariadb", "default"): "MASTER_PORT", + ("mysql", "8.0.23"): "SOURCE_PORT", + }, + "MASTER_CONNECT_RETRY": { + ("mysql", "default"): "MASTER_CONNECT_RETRY", + ("mariadb", "default"): "MASTER_CONNECT_RETRY", + ("mysql", "8.0.23"): "SOURCE_CONNECT_RETRY", + }, + "MASTER_LOG_FILE": { + ("mysql", "default"): "MASTER_LOG_FILE", + ("mariadb", "default"): "MASTER_LOG_FILE", + ("mysql", "8.0.23"): "SOURCE_LOG_FILE", + }, + "MASTER_LOG_POS": { + ("mysql", "default"): "MASTER_LOG_POS", + ("mariadb", "default"): "MASTER_LOG_POS", + ("mysql", "8.0.23"): "SOURCE_LOG_POS", + }, + "MASTER_DELAY": { + ("mysql", "default"): "MASTER_DELAY", + ("mariadb", "default"): "MASTER_DELAY", + ("mysql", "8.0.23"): "SOURCE_DELAY", + }, + "MASTER_SSL": { + ("mysql", "default"): "MASTER_SSL", + ("mariadb", "default"): "MASTER_SSL", + ("mysql", "8.0.23"): "SOURCE_SSL", + }, + "MASTER_SSL_CA": { + ("mysql", "default"): "MASTER_SSL_CA", + ("mariadb", "default"): "MASTER_SSL_CA", + ("mysql", "8.0.23"): "SOURCE_SSL_CA", + }, + "MASTER_SSL_CAPATH": { + ("mysql", "default"): "MASTER_SSL_CAPATH", + ("mariadb", "default"): "MASTER_SSL_CAPATH", + ("mysql", "8.0.23"): "SOURCE_SSL_CAPATH", + }, + "MASTER_SSL_CERT": { + ("mysql", "default"): "MASTER_SSL_CERT", + ("mariadb", "default"): "MASTER_SSL_CERT", + ("mysql", "8.0.23"): "SOURCE_SSL_CERT", + }, + "MASTER_SSL_KEY": { + ("mysql", "default"): "MASTER_SSL_KEY", + ("mariadb", "default"): "MASTER_SSL_KEY", + ("mysql", "8.0.23"): "SOURCE_SSL_KEY", + }, + "MASTER_SSL_CIPHER": { + ("mysql", "default"): "MASTER_SSL_CIPHER", + ("mariadb", "default"): "MASTER_SSL_CIPHER", + ("mysql", "8.0.23"): "SOURCE_SSL_CIPHER", + }, + "MASTER_SSL_VERIFY_SERVER_CERT": { + ("mysql", "default"): "MASTER_SSL_VERIFY_SERVER_CERT", + ("mariadb", "default"): "MASTER_SSL_VERIFY_SERVER_CERT", + ("mysql", "8.0.23"): "SOURCE_SSL_VERIFY_SERVER_CERT", + }, + "MASTER_AUTO_POSITION": { + ("mysql", "default"): "MASTER_AUTO_POSITION", + ("mariadb", "default"): "MASTER_AUTO_POSITION", + ("mysql", "8.0.23"): "SOURCE_AUTO_POSITION", + }, + "RESET MASTER": { + ("mysql", "default"): "RESET MASTER", + ("mariadb", "default"): "RESET MASTER", + ("mysql", "8.4.0"): "RESET BINARY LOGS AND GTIDS", + }, + # Add more command mappings here + } + + if command in commands: + cmd_syntaxes = commands[command] + applicable_versions = [(v, cmd) for (impl, v), cmd in cmd_syntaxes.items() if impl == self.server_implementation and v != 'default'] + applicable_versions.sort(reverse=True, key=lambda x: LooseVersion(x[0])) + + for version, cmd in applicable_versions: + if self.server_version >= LooseVersion(version): + return cmd + + return cmd_syntaxes[(self.server_implementation, "default")] + raise ValueError("Unsupported command: %s" % command) diff --git a/plugins/modules/mysql_db.py b/plugins/modules/mysql_db.py index 8742f3c..4a2c954 100644 --- a/plugins/modules/mysql_db.py +++ b/plugins/modules/mysql_db.py @@ -343,7 +343,15 @@ 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, mysql_common_argument_spec +from ansible_collections.community.mysql.plugins.module_utils.mysql import ( + mysql_connect, + mysql_driver, + mysql_driver_fail_msg, + mysql_common_argument_spec, + get_server_implementation, + get_server_version, +) +from ansible_collections.community.mysql.plugins.module_utils.version import LooseVersion from ansible.module_utils.six.moves import shlex_quote from ansible.module_utils._text import to_native @@ -372,7 +380,8 @@ def db_delete(cursor, db): def db_dump(module, host, user, password, db_name, target, all_databases, port, - config_file, socket=None, ssl_cert=None, ssl_key=None, ssl_ca=None, + config_file, server_implementation, server_version, socket=None, + ssl_cert=None, ssl_key=None, ssl_ca=None, single_transaction=None, quick=None, ignore_tables=None, hex_blob=None, encoding=None, force=False, master_data=0, skip_lock_tables=False, dump_extra_args=None, unsafe_password=False, restrict_config_file=False, @@ -431,7 +440,11 @@ def db_dump(module, host, user, password, db_name, target, all_databases, port, if hex_blob: cmd += " --hex-blob" if master_data: - cmd += " --master-data=%s" % master_data + if (server_implementation == 'mysql' and + LooseVersion(server_version) >= LooseVersion("8.2.0")): + cmd += " --source-data=%s" % master_data + else: + cmd += " --master-data=%s" % master_data if dump_extra_args is not None: cmd += " " + dump_extra_args @@ -690,6 +703,9 @@ def main(): else: module.fail_json(msg="unable to find %s. Exception message: %s" % (config_file, to_native(e))) + server_implementation = get_server_implementation(cursor) + server_version = get_server_version(cursor) + changed = False if not os.path.exists(config_file): config_file = None @@ -730,7 +746,8 @@ def main(): module.exit_json(changed=True, db=db_name, db_list=db) rc, stdout, stderr = db_dump(module, login_host, login_user, login_password, db, target, all_databases, - login_port, config_file, socket, ssl_cert, ssl_key, + login_port, config_file, server_implementation, server_version, + socket, ssl_cert, ssl_key, ssl_ca, single_transaction, quick, ignore_tables, hex_blob, encoding, force, master_data, skip_lock_tables, dump_extra_args, unsafe_login_password, restrict_config_file, diff --git a/plugins/modules/mysql_info.py b/plugins/modules/mysql_info.py index d8bc88c..2d1fe94 100644 --- a/plugins/modules/mysql_info.py +++ b/plugins/modules/mysql_info.py @@ -293,6 +293,9 @@ connector_version: from decimal import Decimal from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.mysql.plugins.module_utils.command_resolver import ( + CommandResolver +) from ansible_collections.community.mysql.plugins.module_utils.mysql import ( mysql_connect, mysql_common_argument_spec, @@ -301,6 +304,7 @@ from ansible_collections.community.mysql.plugins.module_utils.mysql import ( get_connector_name, get_connector_version, get_server_implementation, + get_server_version, ) from ansible_collections.community.mysql.plugins.module_utils.user import ( @@ -335,11 +339,13 @@ class MySQL_Info(object): 5. add info about the new subset with an example to RETURN block """ - def __init__(self, module, cursor, server_implementation, user_implementation): + def __init__(self, module, cursor, server_implementation, server_version, user_implementation): self.module = module self.cursor = cursor self.server_implementation = server_implementation + self.server_version = server_version self.user_implementation = user_implementation + self.command_resolver = CommandResolver(self.server_implementation, self.server_version) self.info = { 'version': {}, 'databases': {}, @@ -501,7 +507,8 @@ class MySQL_Info(object): def __get_master_status(self): """Get master status if the instance is a master.""" - res = self.__exec_sql('SHOW MASTER STATUS') + query = self.command_resolver.resolve_command("SHOW MASTER STATUS") + res = self.__exec_sql(query) if res: for line in res: for vname, val in iteritems(line): @@ -509,10 +516,8 @@ class MySQL_Info(object): def __get_slave_status(self): """Get slave status if the instance is a slave.""" - if self.server_implementation == "mariadb": - res = self.__exec_sql('SHOW ALL SLAVES STATUS') - else: - res = self.__exec_sql('SHOW SLAVE STATUS') + query = self.command_resolver.resolve_command("SHOW SLAVE STATUS") + res = self.__exec_sql(query) if res: for line in res: host = line['Master_Host'] @@ -533,7 +538,8 @@ class MySQL_Info(object): def __get_slaves(self): """Get slave hosts info if the instance is a master.""" - res = self.__exec_sql('SHOW SLAVE HOSTS') + query = self.command_resolver.resolve_command("SHOW SLAVE HOSTS") + res = self.__exec_sql(query) if res: for line in res: srv_id = line['Server_id'] @@ -762,12 +768,13 @@ def main(): module.fail_json(msg) server_implementation = get_server_implementation(cursor) + server_version = get_server_version(cursor) user_implementation = get_user_implementation(cursor) ############################### # Create object and do main job - mysql = MySQL_Info(module, cursor, server_implementation, user_implementation) + mysql = MySQL_Info(module, cursor, server_implementation, server_version, user_implementation) module.exit_json(changed=False, server_engine='MariaDB' if server_implementation == 'mariadb' else 'MySQL', diff --git a/plugins/modules/mysql_replication.py b/plugins/modules/mysql_replication.py index b0caf11..723fc35 100644 --- a/plugins/modules/mysql_replication.py +++ b/plugins/modules/mysql_replication.py @@ -20,11 +20,12 @@ author: - Balazs Pocze (@banyek) - Andrew Klychkov (@Andersson007) - Dennis Urtubia (@dennisurtubia) +- Laurent Indermühle (@laurent-indermuehle) options: mode: description: - Module operating mode. Could be - C(changeprimary) (CHANGE MASTER TO), + C(changeprimary) (CHANGE MASTER TO) - also works for MySQL 8.0.23 and later since community.mysql 3.10.0, C(changereplication) (CHANGE REPLICATION SOURCE TO) - only supported in MySQL 8.0.23 and later, C(getprimary) (SHOW MASTER STATUS), C(getreplica) (SHOW REPLICA STATUS), @@ -298,8 +299,10 @@ queries: import os import warnings -from ansible_collections.community.mysql.plugins.module_utils.version import LooseVersion from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.mysql.plugins.module_utils.command_resolver import ( + CommandResolver +) from ansible_collections.community.mysql.plugins.module_utils.mysql import ( get_server_version, get_server_implementation, @@ -313,18 +316,9 @@ from ansible.module_utils._text import to_native executed_queries = [] -def get_primary_status(cursor): - term = "MASTER" - - version = get_server_version(cursor) - server_implementation = get_server_implementation(cursor) - if server_implementation == "mysql" and LooseVersion(version) >= LooseVersion("8.2.0"): - term = "BINARY LOG" - - if server_implementation == "mariadb" and LooseVersion(version) >= LooseVersion("10.5.2"): - term = "BINLOG" - - cursor.execute("SHOW %s STATUS" % term) +def get_primary_status(cursor, command_resolver): + query = command_resolver.resolve_command("SHOW MASTER STATUS") + cursor.execute(query) primarystatus = cursor.fetchone() return primarystatus @@ -410,8 +404,8 @@ def reset_replica_all(module, cursor, connection_name='', channel='', fail_on_er return reset -def reset_primary(module, cursor, fail_on_error=False): - query = 'RESET MASTER' +def reset_primary(module, cursor, command_resolver, fail_on_error=False): + query = command_resolver.resolve_command('RESET MASTER') try: executed_queries.append(query) cursor.execute(query) @@ -420,7 +414,7 @@ def reset_primary(module, cursor, fail_on_error=False): reset = False except Exception as e: if fail_on_error: - module.fail_json(msg="RESET MASTER failed: %s" % to_native(e)) + module.fail_json(msg="%s failed: %s" % (command_resolver.resolve_command('RESET MASTER'), to_native(e))) reset = False return reset @@ -447,11 +441,12 @@ def start_replica(module, cursor, connection_name='', channel='', fail_on_error= return started -def changeprimary(cursor, chm, connection_name='', channel=''): +def changeprimary(cursor, command_resolver, chm, connection_name='', channel=''): + query_head = command_resolver.resolve_command("CHANGE MASTER") if connection_name: - query = "CHANGE MASTER '%s' TO %s" % (connection_name, ','.join(chm)) + query = "%s '%s' TO %s" % (query_head, connection_name, ','.join(chm)) else: - query = 'CHANGE MASTER TO %s' % ','.join(chm) + query = '%s TO %s' % (query_head, ','.join(chm)) if channel: query += " FOR CHANNEL '%s'" % channel @@ -566,8 +561,11 @@ def main(): else: module.fail_json(msg="unable to find %s. Exception message: %s" % (config_file, to_native(e))) + server_version = get_server_version(cursor) + server_implementation = get_server_implementation(cursor) + command_resolver = CommandResolver(server_implementation, server_version) cursor.execute("SELECT VERSION()") - if 'mariadb' in cursor.fetchone()["VERSION()"].lower(): + if server_implementation == 'mariadb': from ansible_collections.community.mysql.plugins.module_utils.implementations.mariadb import replication as impl else: from ansible_collections.community.mysql.plugins.module_utils.implementations.mysql import replication as impl @@ -582,7 +580,7 @@ def main(): primary_use_gtid = 'slave_pos' if mode == 'getprimary': - status = get_primary_status(cursor) + status = get_primary_status(cursor, command_resolver) if status and "File" in status and "Position" in status: status['Is_Primary'] = True else: @@ -610,52 +608,52 @@ def main(): chm = [] result = {} if primary_host is not None: - chm.append("MASTER_HOST='%s'" % primary_host) + chm.append("%s='%s'" % (command_resolver.resolve_command('MASTER_HOST'), primary_host)) if primary_user is not None: - chm.append("MASTER_USER='%s'" % primary_user) + chm.append("%s='%s'" % (command_resolver.resolve_command('MASTER_USER'), primary_user)) if primary_password is not None: - chm.append("MASTER_PASSWORD='%s'" % primary_password) + chm.append("%s='%s'" % (command_resolver.resolve_command('MASTER_PASSWORD'), primary_password)) if primary_port is not None: - chm.append("MASTER_PORT=%s" % primary_port) + chm.append("%s=%s" % (command_resolver.resolve_command('MASTER_PORT'), primary_port)) if primary_connect_retry is not None: - chm.append("MASTER_CONNECT_RETRY=%s" % primary_connect_retry) + chm.append("%s=%s" % (command_resolver.resolve_command('MASTER_CONNECT_RETRY'), primary_connect_retry)) if primary_log_file is not None: - chm.append("MASTER_LOG_FILE='%s'" % primary_log_file) + chm.append("%s='%s'" % (command_resolver.resolve_command('MASTER_LOG_FILE'), primary_log_file)) if primary_log_pos is not None: - chm.append("MASTER_LOG_POS=%s" % primary_log_pos) + chm.append("%s=%s" % (command_resolver.resolve_command('MASTER_LOG_POS'), primary_log_pos)) if primary_delay is not None: - chm.append("MASTER_DELAY=%s" % primary_delay) + chm.append("%s=%s" % (command_resolver.resolve_command('MASTER_DELAY'), primary_delay)) if relay_log_file is not None: chm.append("RELAY_LOG_FILE='%s'" % relay_log_file) if relay_log_pos is not None: chm.append("RELAY_LOG_POS=%s" % relay_log_pos) if primary_ssl is not None: if primary_ssl: - chm.append("MASTER_SSL=1") + chm.append("%s=1" % command_resolver.resolve_command('MASTER_SSL')) else: - chm.append("MASTER_SSL=0") + chm.append("%s=0" % command_resolver.resolve_command('MASTER_SSL')) if primary_ssl_ca is not None: - chm.append("MASTER_SSL_CA='%s'" % primary_ssl_ca) + chm.append("%s='%s'" % (command_resolver.resolve_command('MASTER_SSL_CA'), primary_ssl_ca)) if primary_ssl_capath is not None: - chm.append("MASTER_SSL_CAPATH='%s'" % primary_ssl_capath) + chm.append("%s='%s'" % (command_resolver.resolve_command('MASTER_SSL_CAPATH'), primary_ssl_capath)) if primary_ssl_cert is not None: - chm.append("MASTER_SSL_CERT='%s'" % primary_ssl_cert) + chm.append("%s='%s'" % (command_resolver.resolve_command('MASTER_SSL_CERT'), primary_ssl_cert)) if primary_ssl_key is not None: - chm.append("MASTER_SSL_KEY='%s'" % primary_ssl_key) + chm.append("%s='%s'" % (command_resolver.resolve_command('MASTER_SSL_KEY'), primary_ssl_key)) if primary_ssl_cipher is not None: - chm.append("MASTER_SSL_CIPHER='%s'" % primary_ssl_cipher) + chm.append("%s='%s'" % (command_resolver.resolve_command('MASTER_SSL_CIPHER'), primary_ssl_cipher)) if primary_ssl_verify_server_cert: - chm.append("SOURCE_SSL_VERIFY_SERVER_CERT=1") + chm.append("%s=1" % command_resolver.resolve_command('MASTER_SSL_VERIFY_SERVER_CERT')) if primary_auto_position: - chm.append("MASTER_AUTO_POSITION=1") + chm.append("%s=1" % command_resolver.resolve_command('MASTER_AUTO_POSITION')) if primary_use_gtid is not None: - chm.append("MASTER_USE_GTID=%s" % primary_use_gtid) + chm.append("MASTER_USE_GTID=%s" % primary_use_gtid) # MariaDB only try: - changeprimary(cursor, chm, connection_name, channel) + changeprimary(cursor, command_resolver, chm, connection_name, channel) except mysql_driver.Warning as e: result['warning'] = to_native(e) except Exception as e: - module.fail_json(msg='%s. Query == CHANGE MASTER TO %s' % (to_native(e), chm)) + module.fail_json(msg='%s. Query == %s TO %s' % (to_native(e), command_resolver.resolve_command('CHANGE MASTER'), chm)) result['changed'] = True module.exit_json(queries=executed_queries, **result) elif mode == "startreplica": @@ -671,7 +669,7 @@ def main(): else: module.exit_json(msg="Replica already stopped", changed=False, queries=executed_queries) elif mode == 'resetprimary': - reset = reset_primary(module, cursor, fail_on_error) + reset = reset_primary(module, cursor, command_resolver, fail_on_error) if reset is True: module.exit_json(msg="Primary reset", changed=True, queries=executed_queries) else: diff --git a/tests/integration/targets/setup_controller/files/mysql.gpg b/tests/integration/targets/setup_controller/files/mysql.gpg new file mode 100644 index 0000000..117f1e7 --- /dev/null +++ b/tests/integration/targets/setup_controller/files/mysql.gpg @@ -0,0 +1,49 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: SKS 1.1.6 +Comment: Hostname: pgp.mit.edu + +mQINBGU2rNoBEACSi5t0nL6/Hj3d0PwsbdnbY+SqLUIZ3uWZQm6tsNhvTnahvPPZBGdl99iW +YTt2KmXp0KeN2s9pmLKkGAbacQP1RqzMFnoHawSMf0qTUVjAvhnI4+qzMDjTNSBq9fa3nHmO +YxownnrRkpiQUM/yD7/JmVENgwWb6akZeGYrXch9jd4XV3t8OD6TGzTedTki0TDNr6YZYhC7 +jUm9fK9Zs299pzOXSxRRNGd+3H9gbXizrBu4L/3lUrNf//rM7OvV9Ho7u9YYyAQ3L3+OABK9 +FKHNhrpi8Q0cbhvWkD4oCKJ+YZ54XrOG0YTg/YUAs5/3//FATI1sWdtLjJ5pSb0onV3LIbar +RTN8lC4Le/5kd3lcot9J8b3EMXL5p9OGW7wBfmNVRSUI74Vmwt+v9gyp0Hd0keRCUn8lo/1V +0YD9i92KsE+/IqoYTjnya/5kX41jB8vr1ebkHFuJ404+G6ETd0owwxq64jLIcsp/GBZHGU0R +KKAo9DRLH7rpQ7PVlnw8TDNlOtWt5EJlBXFcPL+NgWbqkADAyA/XSNeWlqonvPlYfmasnAHA +pMd9NhPQhC7hJTjCiAwG8UyWpV8Dj07DHFQ5xBbkTnKH2OrJtguPqSNYtTASbsWz09S8ujoT +DXFT17NbFM2dMIiq0a4VQB3SzH13H2io9Cbg/TzJrJGmwgoXgwARAQABtDZNeVNRTCBSZWxl +YXNlIEVuZ2luZWVyaW5nIDxteXNxbC1idWlsZEBvc3Mub3JhY2xlLmNvbT6JAlQEEwEIAD4W +IQS8pDQXw7SF3RKOxtS3s7eIqNN4XAUCZTas2gIbAwUJA8JnAAULCQgHAgYVCgkICwIEFgID +AQIeAQIXgAAKCRC3s7eIqNN4XLzoD/9PlpWtfHlI8eQTHwGsGIwFA+fgipyDElapHw3MO+K9 +VOEYRZCZSuBXHJe9kjGEVCGUDrfImvgTuNuqYmVUV+wyhP+w46W/cWVkqZKAW0hNp0TTvu3e +Dwap7gdk80VF24Y2Wo0bbiGkpPiPmB59oybGKaJ756JlKXIL4hTtK3/hjIPFnb64Ewe4YLZy +oJu0fQOyA8gXuBoalHhUQTbRpXI0XI3tpZiQemNbfBfJqXo6LP3/LgChAuOfHIQ8alvnhCwx +hNUSYGIRqx+BEbJw1X99Az8XvGcZ36VOQAZztkW7mEfH9NDPz7MXwoEvduc61xwlMvEsUIaS +fn6SGLFzWPClA98UMSJgF6sKb+JNoNbzKaZ8V5w13msLb/pq7hab72HH99XJbyKNliYj3+KA +3q0YLf+Hgt4Y4EhIJ8x2+g690Np7zJF4KXNFbi1BGloLGm78akY1rQlzpndKSpZq5KWw8FY/ +1PEXORezg/BPD3Etp0AVKff4YdrDlOkNB7zoHRfFHAvEuuqti8aMBrbRnRSG0xunMUOEhbYS +/wOOTl0g3bF9NpAkfU1Fun57N96Us2T9gKo9AiOY5DxMe+IrBg4zaydEOovgqNi2wbU0MOBQ +b23Puhj7ZCIXcpILvcx9ygjkONr75w+XQrFDNeux4Znzay3ibXtAPqEykPMZHsZ2sbkCDQRl +NqzaARAAsdvBo8WRqZ5WVVk6lReD8b6Zx83eJUkV254YX9zn5t8KDRjYOySwS75mJIaZLsv0 +YQjJk+5rt10tejyCrJIFo9CMvCmjUKtVbgmhfS5+fUDRrYCEZBBSa0Dvn68EBLiHugr+SPXF +6o1hXEUqdMCpB6oVp6X45JVQroCKIH5vsCtw2jU8S2/IjjV0V+E/zitGCiZaoZ1f6NG7ozyF +ep1CSAReZu/sssk0pCLlfCebRd9Rz3QjSrQhWYuJa+eJmiF4oahnpUGktxMD632I9aG+IMfj +tNJNtX32MbO+Se+cCtVc3cxSa/pR+89a3cb9IBA5tFF2Qoekhqo/1mmLi93Xn6uDUhl5tVxT +nB217dBT27tw+p0hjd9hXZRQbrIZUTyh3+8EMfmAjNSIeR+th86xRd9XFRr9EOqrydnALOUr +9cT7TfXWGEkFvn6ljQX7f4RvjJOTbc4jJgVFyu8K+VU6u1NnFJgDiNGsWvnYxAf7gDDbUSXE +uC2anhWvxPvpLGmsspngge4yl+3nv+UqZ9sm6LCebR/7UZ67tYz3p6xzAOVgYsYcxoIUuEZX +jHQtsYfTZZhrjUWBJ09jrMvlKUHLnS437SLbgoXVYZmcqwAWpVNOLZf+fFm4IE5aGBG5Dho2 +CZ6ujngW9Zkn98T1d4N0MEwwXa2V6T1ijzcqD7GApZUAEQEAAYkCPAQYAQgAJhYhBLykNBfD +tIXdEo7G1Lezt4io03hcBQJlNqzaAhsMBQkDwmcAAAoJELezt4io03hcXqMP/01aPT3A3Sg7 +oTQoHdCxj04ELkzrezNWGM+YwbSKrR2LoXR8zf2tBFzc2/Tl98V0+68f/eCvkvqCuOtq4392 +Ps23j9W3r5XG+GDOwDsx0gl0E+Qkw07pwdJctA6efsmnRkjF2YVO0N9MiJA1tc8NbNXpEEHJ +Z7F8Ri5cpQrGUz/AY0eae2b7QefyP4rpUELpMZPjc8Px39Fe1DzRbT+5E19TZbrpbwlSYs1i +CzS5YGFmpCRyZcLKXo3zS6N22+82cnRBSPPipiO6WaQawcVMlQO1SX0giB+3/DryfN9VuIYd +1EWCGQa3O0MVu6o5KVHwPgl9R1P6xPZhurkDpAd0b1s4fFxin+MdxwmG7RslZA9CXRPpzo7/ +fCMW8sYOH15DP+YfUckoEreBt+zezBxbIX2CGGWEV9v3UBXadRtwxYQ6sN9bqW4jm1b41vNA +17b6CVH6sVgtU3eN+5Y9an1e5jLD6kFYx+OIeqIIId/TEqwS61csY9aav4j4KLOZFCGNU0FV +ji7NQewSpepTcJwfJDOzmtiDP4vol1ApJGLRwZZZ9PB6wsOgDOoP6sr0YrDI/NNX2RyXXbgl +nQ1yJZVSH3/3eo6knG2qTthUKHCRDNKdy9Qqc1x4WWWtSRjh+zX8AvJK2q1rVLH2/3ilxe9w +cAZUlaj3id3TxquAlud4lWDz +=h5nH +-----END PGP PUBLIC KEY BLOCK----- diff --git a/tests/integration/targets/setup_controller/tasks/requirements.yml b/tests/integration/targets/setup_controller/tasks/requirements.yml index c939098..a576ce4 100644 --- a/tests/integration/targets/setup_controller/tasks/requirements.yml +++ b/tests/integration/targets/setup_controller/tasks/requirements.yml @@ -2,6 +2,38 @@ # We use the ubuntu2204 image provided by ansible-test. +# The GPG key is imported in the files folder from: +# https://dev.mysql.com/doc/refman/8.4/en/checking-gpg-signature.html +# Downloading the key on each iteration of the tests is too slow. +- name: Install MySQL PGP public key + ansible.builtin.copy: + src: files/mysql.gpg + dest: /usr/share/keyrings/mysql.gpg + owner: root + group: root + mode: '0644' + when: + - db_engine == 'mysql' + - db_version is version('8.4', '>=') + +- name: Add Apt signing key to keyring + ansible.builtin.apt_key: + id: A8D3785C + file: /usr/share/keyrings/mysql.gpg + state: present + when: + - db_engine == 'mysql' + - db_version is version('8.4', '>=') + +- name: Add MySQL 8.4 repository + ansible.builtin.apt_repository: + repo: deb http://repo.mysql.com/apt/ubuntu/ jammy mysql-8.4-lts mysql-tools + state: present + filename: mysql + when: + - db_engine == 'mysql' + - db_version is version('8.4', '>=') + - name: "{{ role_name }} | Requirements | Install Linux packages" ansible.builtin.package: name: diff --git a/tests/integration/targets/test_mysql_db/tasks/state_dump_import.yml b/tests/integration/targets/test_mysql_db/tasks/state_dump_import.yml index e4ae762..f8d2b4b 100644 --- a/tests/integration/targets/test_mysql_db/tasks/state_dump_import.yml +++ b/tests/integration/targets/test_mysql_db/tasks/state_dump_import.yml @@ -111,11 +111,24 @@ check_implicit_admin: no register: result -- name: Dump and Import | Assert successful completion of dump operation +- name: Dump and Import | Assert successful completion of dump operation for MariaDB and MySQL < 8.2 assert: that: - result is changed - result.executed_commands[0] is search(".department --master-data=1 --skip-triggers") + when: + - > + db_engine == 'mariadb' or + (db_engine == 'mysql' and db_version is version('8.2', '<')) + +- name: Dump and Import | Assert successful completion of dump operation for MySQL >= 8.2 + assert: + that: + - result is changed + - result.executed_commands[0] is search(".department --source-data=1 --skip-triggers") + when: + - db_engine == 'mysql' + - db_version is version('8.2', '>=') - name: Dump and Import | State dump/import - file name should exist (db_file_name) file: diff --git a/tests/integration/targets/test_mysql_replication/tasks/main.yml b/tests/integration/targets/test_mysql_replication/tasks/main.yml index 2baa536..a65cabd 100644 --- a/tests/integration/targets/test_mysql_replication/tasks/main.yml +++ b/tests/integration/targets/test_mysql_replication/tasks/main.yml @@ -1,3 +1,4 @@ +--- #################################################################### # WARNING: These are designed specifically for Ansible tests # # and should not be used as examples of how to write Ansible roles # @@ -18,8 +19,7 @@ # Tests of channel parameter: - import_tasks: mysql_replication_channel.yml when: - - db_engine == 'mysql' # FIXME: mariadb introduces FOR CHANNEL in 10.7 - - mysql8022_and_higher == true # FIXME: mysql 5.7 should work, but our tets fails, why? + - db_engine == 'mysql' # FIXME: mariadb introduces FOR CHANNEL in 10.7 # Tests of resetprimary mode: - import_tasks: mysql_replication_resetprimary_mode.yml @@ -30,3 +30,4 @@ - import_tasks: mysql_replication_changereplication_mode.yml when: - db_engine == 'mysql' + - db_version is version('8.0.23', '>=') diff --git a/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_channel.yml b/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_channel.yml index 7d37df0..802865c 100644 --- a/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_channel.yml +++ b/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_channel.yml @@ -32,10 +32,15 @@ channel: '{{ test_channel }}' register: result - - assert: + - name: Assert that run replication with channel is changed and query matches for MariaDB and MySQL < 8.0.23 + ansible.builtin.assert: that: - result is changed - result.queries == result_query + when: + - > + db_engine == 'mariadb' or + (db_engine == 'mysql' and db_version is version('8.0.23', '<')) vars: result_query: ["CHANGE MASTER TO MASTER_HOST='{{ mysql_host }}',\ MASTER_USER='{{ replication_user }}',MASTER_PASSWORD='********',\ @@ -43,6 +48,21 @@ '{{ mysql_primary_status.File }}',MASTER_LOG_POS=\ {{ mysql_primary_status.Position }} FOR CHANNEL '{{ test_channel }}'"] + - name: Assert that run replication with channel is changed and query matches for MySQL >= 8.0.23 + ansible.builtin.assert: + that: + - result is changed + - result.queries == result_query + when: + - db_engine == 'mysql' + - db_version is version('8.0.23', '>=') + vars: + result_query: ["CHANGE REPLICATION SOURCE TO SOURCE_HOST='{{ mysql_host }}',\ + SOURCE_USER='{{ replication_user }}',SOURCE_PASSWORD='********',\ + SOURCE_PORT={{ mysql_primary_port }},SOURCE_LOG_FILE=\ + '{{ mysql_primary_status.File }}',SOURCE_LOG_POS=\ + {{ mysql_primary_status.Position }} FOR CHANNEL '{{ test_channel }}'"] + # Test startreplica mode: - name: Start replica with channel mysql_replication: @@ -83,7 +103,10 @@ mysql_host_value: '{{ mysql_host }}' mysql_primary_port_value: '{{ mysql_primary_port }}' test_channel_value: '{{ test_channel }}' - when: mysql8022_and_higher == false + when: + - > + db_engine == 'mariadb' or + (db_engine == 'mysql' and db_version is version('8.0.22', '<')) - assert: that: @@ -99,7 +122,9 @@ mysql_host_value: '{{ mysql_host }}' mysql_primary_port_value: '{{ mysql_primary_port }}' test_channel_value: '{{ test_channel }}' - when: mysql8022_and_higher == true + when: + - db_engine == 'mysql' + - db_version is version('8.0.22', '>=') # Test stopreplica mode: 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 e08954b..30cd99f 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 @@ -9,16 +9,6 @@ login_host: '{{ mysql_host }}' block: - - name: Set mysql8022_and_higher - set_fact: - mysql8022_and_higher: false - - - name: Set mysql8022_and_higher - set_fact: - mysql8022_and_higher: true - when: - - db_engine == 'mysql' - - db_version is version('8.0.22', '>=') # We use iF NOT EXISTS because the GITHUB Action: # "ansible-community/ansible-test-gh-action" uses "--retry-on-error". @@ -136,11 +126,10 @@ that: - result is not failed - # Test changeprimary mode: # primary_ssl_ca will be set as '' to check the module's behaviour for #23976, # must be converted to an empty string - - name: Run replication - mysql_replication: + - name: Test changeprimary mode with empty primary_ssl_ca + community.mysql.mysql_replication: <<: *mysql_params login_port: '{{ mysql_replica1_port }}' mode: changeprimary @@ -151,14 +140,18 @@ primary_log_file: '{{ mysql_primary_status.File }}' primary_log_pos: '{{ mysql_primary_status.Position }}' primary_ssl_ca: '' - primary_ssl: no + primary_ssl: false register: result - - name: Assert that changeprimmary is changed and return expected query - assert: + - name: Assert that changeprimmary is changed and return expected query for MariaDB and MySQL < 8.0.23 + ansible.builtin.assert: that: - result is changed - result.queries == expected_queries + when: + - > + db_engine == 'mariadb' or + (db_engine == 'mysql' and db_version is version('8.0.23', '<')) vars: expected_queries: ["CHANGE MASTER TO MASTER_HOST='{{ mysql_host }}',\ MASTER_USER='{{ replication_user }}',MASTER_PASSWORD='********',\ @@ -166,6 +159,22 @@ '{{ mysql_primary_status.File }}',MASTER_LOG_POS=\ {{ mysql_primary_status.Position }},MASTER_SSL=0,MASTER_SSL_CA=''"] + - name: Assert that changeprimmary is changed and return expected query for MySQL > 8.0.23 + ansible.builtin.assert: + that: + - result is changed + - result.queries == expected_queries + when: + - db_engine == 'mysql' + - db_version is version('8.0.23', '>=') + vars: + expected_queries: ["CHANGE REPLICATION SOURCE TO \ + SOURCE_HOST='{{ mysql_host }}',\ + SOURCE_USER='{{ replication_user }}',SOURCE_PASSWORD='********',\ + SOURCE_PORT={{ mysql_primary_port }},SOURCE_LOG_FILE=\ + '{{ mysql_primary_status.File }}',SOURCE_LOG_POS=\ + {{ mysql_primary_status.Position }},SOURCE_SSL=0,SOURCE_SSL_CA=''"] + # Test startreplica mode: - name: Start replica mysql_replication: @@ -201,7 +210,10 @@ vars: mysql_host_value: "{{ mysql_host }}" mysql_primary_port_value: "{{ mysql_primary_port }}" - when: mysql8022_and_higher is falsy(convert_bool=True) + when: + - > + db_engine == 'mariadb' or + (db_engine == 'mysql' and db_version is version('8.0.22', '<')) - name: Assert that getreplica returns expected values for MySQL newer than 8.0.22 assert: @@ -216,7 +228,9 @@ vars: mysql_host_value: "{{ mysql_host }}" mysql_primary_port_value: "{{ mysql_primary_port }}" - when: mysql8022_and_higher is truthy(convert_bool=True) + when: + - db_engine == 'mysql' + - db_version is version('8.0.22', '>=') # Create test table and add data to it: - name: Create test table @@ -243,13 +257,18 @@ assert: that: - replica_status.Exec_Master_Log_Pos != mysql_primary_status.Position - when: mysql8022_and_higher == false + when: + - > + db_engine == 'mariadb' or + (db_engine == 'mysql' and db_version is version('8.0.22', '<')) - name: Assert that getreplica Log_Pos is different for MySQL newer than 8.0.22 assert: that: - replica_status.Exec_Source_Log_Pos != mysql_primary_status.Position - when: mysql8022_and_higher == true + when: + - db_engine == 'mysql' + - db_version is version('8.0.22', '>=') - name: Start replica that is already running mysql_replication: diff --git a/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_primary_delay.yml b/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_primary_delay.yml index 5e967e8..3ae4339 100644 --- a/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_primary_delay.yml +++ b/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_primary_delay.yml @@ -18,10 +18,24 @@ primary_delay: '{{ test_primary_delay }}' register: result - - assert: + - name: Assert that run replication is changed and query match expectation for MariaDB and MySQL < 8.0.23 + ansible.builtin.assert: that: - - result is changed - - result.queries == ["CHANGE MASTER TO MASTER_DELAY=60"] + - result is changed + - result.queries == ["CHANGE MASTER TO MASTER_DELAY=60"] + when: + - > + db_engine == 'mariadb' or + (db_engine == 'mysql' and db_version is version('8.0.23', '<')) + + - name: Assert that run replication is changed and query match expectation for MySQL >= 8.0.23 + ansible.builtin.assert: + that: + - result is changed + - result.queries == ["CHANGE REPLICATION SOURCE TO SOURCE_DELAY=60"] + when: + - db_engine == 'mysql' + - db_version is version('8.0.23', '>=') # Auxiliary step: - name: Start replica diff --git a/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_resetprimary_mode.yml b/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_resetprimary_mode.yml index 4bccc76..8968049 100644 --- a/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_resetprimary_mode.yml +++ b/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_resetprimary_mode.yml @@ -1,3 +1,4 @@ +--- # Copyright: (c) 2019, Andrew Klychkov (@Andersson007) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) @@ -38,10 +39,24 @@ mode: resetprimary register: result - - assert: + - name: Assert that reset primary is changed and query matches for MariaDB and MySQL < 8.4 + ansible.builtin.assert: that: - - result is changed - - result.queries == ["RESET MASTER"] + - result is changed + - result.queries == ["RESET MASTER"] + when: + - > + db_engine == 'mariadb' or + (db_engine == 'mysql' and db_version is version('8.4.0', '<')) + + - name: Assert that reset primary is changed and query matches for MySQL > 8.4 + ansible.builtin.assert: + that: + - result is changed + - result.queries == ["RESET BINARY LOGS AND GTIDS"] + when: + - db_engine == 'mysql' + - db_version is version('8.4.0', '>=') # Get primary final status: - name: Get primary status diff --git a/tests/unit/plugins/module_utils/test_command_resolver.py b/tests/unit/plugins/module_utils/test_command_resolver.py new file mode 100644 index 0000000..9653418 --- /dev/null +++ b/tests/unit/plugins/module_utils/test_command_resolver.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import pytest + +from ansible_collections.community.mysql.plugins.module_utils.command_resolver import ( + CommandResolver, +) + + +@pytest.mark.parametrize( + 'server_implementation,server_version,command,expected_output,expected_exception,expected_message', + [ + ('mysql', '1.0.0', 'SHOW NOTHING', '', ValueError, 'Unsupported command: SHOW NOTHING'), + ('mysql', '8.0.20', 'SHOW MASTER STATUS', 'SHOW MASTER STATUS', None, None), # Case insensitive + ('mysql', '8.0.20', 'show master status', 'SHOW MASTER STATUS', None, None), # Case insensitive + ('mysql', '8.0.20', 'SHOW master STATUS', 'SHOW MASTER STATUS', None, None), # Case insensitive + ('mysql', '8.2.0', 'SHOW MASTER STATUS', 'SHOW BINARY LOG STATUS', None, None), + ('mysql', '9.0.0', 'SHOW MASTER STATUS', 'SHOW BINARY LOG STATUS', None, None), + ('mariadb', '10.4.23', 'SHOW MASTER STATUS', 'SHOW MASTER STATUS', None, None), # Default + ('mariadb', '10.5.1', 'SHOW MASTER STATUS', 'SHOW MASTER STATUS', None, None), # Default + ('mariadb', '10.5.2', 'SHOW MASTER STATUS', 'SHOW BINLOG STATUS', None, None), + ('mariadb', '10.6.17', 'SHOW MASTER STATUS', 'SHOW BINLOG STATUS', None, None), + ('mysql', '8.4.1', 'CHANGE MASTER', 'CHANGE REPLICATION SOURCE', None, None), + ] +) +def test_resolve_command(server_implementation, server_version, command, expected_output, expected_exception, expected_message): + """ + Tests that the CommandResolver method resolve_command return the correct query. + """ + resolver = CommandResolver(server_implementation, server_version) + if expected_exception: + with pytest.raises(expected_exception) as excinfo: + resolver.resolve_command(command) + assert str(excinfo.value) == expected_message + else: + assert resolver.resolve_command(command) == expected_output diff --git a/tests/unit/plugins/modules/test_mysql_info.py b/tests/unit/plugins/modules/test_mysql_info.py index 0d086f4..7b2de1c 100644 --- a/tests/unit/plugins/modules/test_mysql_info.py +++ b/tests/unit/plugins/modules/test_mysql_info.py @@ -14,15 +14,15 @@ from ansible_collections.community.mysql.plugins.modules.mysql_info import MySQL @pytest.mark.parametrize( - 'suffix,cursor_output,server_implementation,user_implementation', + 'suffix,cursor_output,server_implementation,server_version,user_implementation', [ - ('mysql', '5.5.1-mysql', 'mysql', 'mysql'), - ('log', '5.7.31-log', 'mysql', 'mysql'), - ('mariadb', '10.5.0-mariadb', 'mariadb', 'mariadb'), - ('', '8.0.22', 'mysql', 'mysql'), + ('mysql', '5.5.1-mysql', 'mysql', '5.5.1', 'mysql'), + ('log', '5.7.31-log', 'mysql', '5.7.31', 'mysql'), + ('mariadb', '10.5.0-mariadb', 'mariadb', '10.5.0', 'mariadb'), + ('', '8.0.22', 'mysql', '8.0.22', 'mysql'), ] ) -def test_get_info_suffix(suffix, cursor_output, server_implementation, user_implementation): +def test_get_info_suffix(suffix, cursor_output, server_implementation, server_version, user_implementation): def __cursor_return_value(input_parameter): if input_parameter == "SHOW GLOBAL VARIABLES": cursor.fetchall.return_value = [{"Variable_name": "version", "Value": cursor_output}] @@ -32,6 +32,6 @@ def test_get_info_suffix(suffix, cursor_output, server_implementation, user_impl cursor = MagicMock() cursor.execute.side_effect = __cursor_return_value - info = MySQL_Info(MagicMock(), cursor, server_implementation, user_implementation) + info = MySQL_Info(MagicMock(), cursor, server_implementation, server_version, user_implementation) assert info.get_info([], [], False)['version']['suffix'] == suffix From a9f9806728873e5003eb51eeb7fa96e6f1e783a3 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Mon, 19 Aug 2024 10:41:13 +0200 Subject: [PATCH 191/217] README: Add Communication section with Forum information (#665) --- README.md | 41 ++++++++++++++++------------------------- 1 file changed, 16 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 05a7bde..1f5b47a 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,22 @@ We follow the [Ansible Code of Conduct](https://docs.ansible.com/ansible/latest/ If you encounter abusive behavior violating the [Ansible Code of Conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html), please refer to the [policy violations](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html#policy-violations) section of the Code of Conduct for information on how to raise a complaint. +## Communication + +* Join the Ansible forum: + * [Get Help](https://forum.ansible.com/c/help/6): get help or help others. + * [Posts tagged with 'mysql'](https://forum.ansible.com/tag/mysql): leverage tags to narrow the scope. + * [MySQL Team](https://forum.ansible.com/g/MySQLTeam): by joining the team you will automatically get subscribed to the posts tagged with [mysql](https://forum.ansible.com/tag/mysql). + * [Social Spaces](https://forum.ansible.com/c/chat/4): gather and interact with fellow enthusiasts. + * [News & Announcements](https://forum.ansible.com/c/news/5): track project-wide announcements including social events. + +* The Ansible [Bullhorn newsletter](https://docs.ansible.com/ansible/devel/community/communication.html#the-bullhorn): used to announce releases and important changes. + +* Matrix chat: + * [#mysql:ansible.com](https://matrix.to/#/#mysql:ansible.com) room: questions on how to contribute to this collection. + +For more information about communication, see the [Ansible communication guide](https://docs.ansible.com/ansible/devel/community/communication.html). + ## Contributing The content of this collection is made by [people](https://github.com/ansible-collections/community.mysql/blob/main/CONTRIBUTORS) just like you, a community of individuals collaborating on making the world better through developing automation software. @@ -38,31 +54,6 @@ It is necessary for maintainers of this collection to be subscribed to: They also should be subscribed to Ansible's [The Bullhorn newsletter](https://docs.ansible.com/ansible/devel/community/communication.html#the-bullhorn). -## Communication - -> The `GitHub Discussions` feature is disabled in this repository. Use the `mysql` tag on the forum in the [Project Discussions](https://forum.ansible.com/new-topic?title=topic%20title&body=topic%20body&category=project&tags=mysql) or [Get Help](https://forum.ansible.com/new-topic?title=topic%20title&body=topic%20body&category=help&tags=mysql) category instead. - -### Asynchronous channels - -* Join the Ansible forum: - * [MySQL Team](https://forum.ansible.com/g/MySQLTeam): by joining the team you will automatically get subscribed to the posts tagged with [mysql](https://forum.ansible.com/tag/mysql). - * [Get Help](https://forum.ansible.com/c/help/6/none): get help or help others. - * [Posts tagged with 'mysql'](https://forum.ansible.com/tag/mysql): leverage tags to narrow the scope. - * [Social Spaces](https://forum.ansible.com/c/chat/4): gather and interact with fellow enthusiasts. - * [News & Announcements](https://forum.ansible.com/c/news/5/none): track project-wide announcements. - -* The Ansible's [Bullhorn newsletter](https://forum.ansible.com/t/about-the-newsletter-category/166): we use it to announce releases and important changes. - -### Real-time channels - -* Matrix: - * `#mysql:ansible.com` [room](https://matrix.to/#/#mysql:ansible.com): questions on how to contribute and use this collection. - * `#users:ansible.com` [room](https://matrix.to/#/#users:ansible.com): general use questions and support. - * `#ansible-community:ansible.com` [room](https://matrix.to/#/#community:ansible.com): community and collection development questions. - * other Matrix rooms; see the [Ansible Communication Guide](https://docs.ansible.com/ansible/devel/community/communication.html) for details. - -For more information about communication, refer to the [Ansible Communication guide](https://docs.ansible.com/ansible/devel/community/communication.html). - ## Governance We, [the MySQL team](https://forum.ansible.com/g/MySQLTeam), use [the forum](https://forum.ansible.com/tag/mysql) posts tagged with `mysql` for general announcements and discussions. From 37a718c66f5563f5d90e8af56a1e719ffa3f6c5d Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Thu, 22 Aug 2024 10:45:53 +0200 Subject: [PATCH 192/217] Release 3.10.0 commit (#667) --- CHANGELOG.rst | 43 +++++++++++++- changelogs/changelog.yaml | 59 +++++++++++++++++++ changelogs/fragments/0-mysql_user.yml | 2 - changelogs/fragments/1-mysql_info.yml | 2 - changelogs/fragments/2-mysql_variables.yml | 2 - .../fragments/3-deprecate_mysqlclient.yml | 2 - .../add_salt_param_to_gen_sha256_hash.yml | 3 - .../get_primary_show_binary_log_status.yml | 4 -- .../improve_get_replica_primary_status.yml | 4 -- .../lie_fix_mysql_user_on_new_username.yml | 6 -- .../lie_fix_plugin_hash_string_return.yml | 6 -- .../fragments/mysql_user_tls_requires.yml | 6 -- ...rts_mysql_change_replication_source_to.yml | 3 - galaxy.yml | 2 +- 14 files changed, 100 insertions(+), 44 deletions(-) delete mode 100644 changelogs/fragments/0-mysql_user.yml delete mode 100644 changelogs/fragments/1-mysql_info.yml delete mode 100644 changelogs/fragments/2-mysql_variables.yml delete mode 100644 changelogs/fragments/3-deprecate_mysqlclient.yml delete mode 100644 changelogs/fragments/add_salt_param_to_gen_sha256_hash.yml delete mode 100644 changelogs/fragments/get_primary_show_binary_log_status.yml delete mode 100644 changelogs/fragments/improve_get_replica_primary_status.yml delete mode 100644 changelogs/fragments/lie_fix_mysql_user_on_new_username.yml delete mode 100644 changelogs/fragments/lie_fix_plugin_hash_string_return.yml delete mode 100644 changelogs/fragments/mysql_user_tls_requires.yml delete mode 100644 changelogs/fragments/supports_mysql_change_replication_source_to.yml diff --git a/CHANGELOG.rst b/CHANGELOG.rst index cc7ab85..c5039ed 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,11 +1,48 @@ -======================================== -Community MySQL Collection Release Notes -======================================== +==================================================== +Community MySQL and MariaDB Collection Release Notes +==================================================== .. contents:: Topics This changelog describes changes after version 2.0.0. +v3.10.0 +======= + +Release Summary +--------------- + +This is a minor release of the ``community.mysql`` collection. +This changelog contains all changes to the modules and plugins in this +collection that have been made after the previous release. + +Minor Changes +------------- + +- mysql_info - Add ``tls_requires`` returned value for the ``users_info`` filter (https://github.com/ansible-collections/community.mysql/pull/628). +- mysql_info - return a database server engine used (https://github.com/ansible-collections/community.mysql/issues/644). +- mysql_replication - Adds support for `CHANGE REPLICATION SOURCE TO` statement (https://github.com/ansible-collections/community.mysql/issues/635). +- mysql_replication - Adds support for `SHOW BINARY LOG STATUS` and `SHOW BINLOG STATUS` on getprimary mode. +- mysql_replication - Improve detection of IsReplica and IsPrimary by inspecting the dictionary returned from the SQL query instead of relying on variable types. This ensures compatibility with changes in the connector or the output of SHOW REPLICA STATUS and SHOW MASTER STATUS, allowing for easier maintenance if these change in the future. +- mysql_user - Add salt parameter to generate static hash for `caching_sha2_password` and `sha256_password` plugins. + +Breaking Changes / Porting Guide +-------------------------------- + +- collection - support of mysqlclient connector is deprecated - use PyMySQL connector instead! We will stop testing against it in collection version 4.0.0 and remove the related code in 5.0.0 (https://github.com/ansible-collections/community.mysql/issues/654). +- mysql_info - The ``users_info`` filter returned variable ``plugin_auth_string`` contains the hashed password and it's misleading, it will be removed from community.mysql 4.0.0. Use the `plugin_hash_string` return value instead (https://github.com/ansible-collections/community.mysql/pull/629). + +Bugfixes +-------- + +- mysql_info - Add ``plugin_hash_string`` to ``users_info`` filter's output. The existing ``plugin_auth_string`` contained the hashed password and thus is missleading, it will be removed from community.mysql 4.0.0. (https://github.com/ansible-collections/community.mysql/pull/629). +- mysql_user - Added a warning to update_password's on_new_username option if multiple accounts with the same username but different passwords exist (https://github.com/ansible-collections/community.mysql/pull/642). +- mysql_user - Fix ``tls_requires`` not removing ``SSL`` and ``X509`` when sets as empty (https://github.com/ansible-collections/community.mysql/pull/628). +- mysql_user - Fix idempotence when using variables from the ``users_info`` filter of ``mysql_info`` as an input (https://github.com/ansible-collections/community.mysql/pull/628). +- mysql_user - Fixed an IndexError in the update_password functionality introduced in PR https://github.com/ansible-collections/community.mysql/pull/580 and released in community.mysql 3.8.0. If you used this functionality, please avoid versions 3.8.0 to 3.9.0 (https://github.com/ansible-collections/community.mysql/pull/642). +- mysql_user - add correct ``ed25519`` auth plugin handling (https://github.com/ansible-collections/community.mysql/issues/6). +- mysql_variables - fix the module always changes on boolean values (https://github.com/ansible-collections/community.mysql/issues/652). + v3.9.0 ====== diff --git a/changelogs/changelog.yaml b/changelogs/changelog.yaml index eb4264d..8c18264 100644 --- a/changelogs/changelog.yaml +++ b/changelogs/changelog.yaml @@ -97,6 +97,65 @@ releases: - 307-mysql_user_add_if_exists_to_drop.yml - 329-mysql_role-remove-redudant-connection-closing.yml release_date: '2022-04-26' + 3.10.0: + changes: + breaking_changes: + - collection - support of mysqlclient connector is deprecated - use PyMySQL + connector instead! We will stop testing against it in collection version 4.0.0 + and remove the related code in 5.0.0 (https://github.com/ansible-collections/community.mysql/issues/654). + - mysql_info - The ``users_info`` filter returned variable ``plugin_auth_string`` + contains the hashed password and it's misleading, it will be removed from + community.mysql 4.0.0. Use the `plugin_hash_string` return value instead (https://github.com/ansible-collections/community.mysql/pull/629). + bugfixes: + - mysql_info - Add ``plugin_hash_string`` to ``users_info`` filter's output. + The existing ``plugin_auth_string`` contained the hashed password and thus + is missleading, it will be removed from community.mysql 4.0.0. (https://github.com/ansible-collections/community.mysql/pull/629). + - mysql_user - Added a warning to update_password's on_new_username option if + multiple accounts with the same username but different passwords exist (https://github.com/ansible-collections/community.mysql/pull/642). + - mysql_user - Fix ``tls_requires`` not removing ``SSL`` and ``X509`` when sets + as empty (https://github.com/ansible-collections/community.mysql/pull/628). + - mysql_user - Fix idempotence when using variables from the ``users_info`` + filter of ``mysql_info`` as an input (https://github.com/ansible-collections/community.mysql/pull/628). + - mysql_user - Fixed an IndexError in the update_password functionality introduced + in PR https://github.com/ansible-collections/community.mysql/pull/580 and + released in community.mysql 3.8.0. If you used this functionality, please + avoid versions 3.8.0 to 3.9.0 (https://github.com/ansible-collections/community.mysql/pull/642). + - mysql_user - add correct ``ed25519`` auth plugin handling (https://github.com/ansible-collections/community.mysql/issues/6). + - mysql_variables - fix the module always changes on boolean values (https://github.com/ansible-collections/community.mysql/issues/652). + minor_changes: + - mysql_info - Add ``tls_requires`` returned value for the ``users_info`` filter + (https://github.com/ansible-collections/community.mysql/pull/628). + - mysql_info - return a database server engine used (https://github.com/ansible-collections/community.mysql/issues/644). + - mysql_replication - Adds support for `CHANGE REPLICATION SOURCE TO` statement + (https://github.com/ansible-collections/community.mysql/issues/635). + - mysql_replication - Adds support for `SHOW BINARY LOG STATUS` and `SHOW BINLOG + STATUS` on getprimary mode. + - mysql_replication - Improve detection of IsReplica and IsPrimary by inspecting + the dictionary returned from the SQL query instead of relying on variable + types. This ensures compatibility with changes in the connector or the output + of SHOW REPLICA STATUS and SHOW MASTER STATUS, allowing for easier maintenance + if these change in the future. + - mysql_user - Add salt parameter to generate static hash for `caching_sha2_password` + and `sha256_password` plugins. + release_summary: 'This is a minor release of the ``community.mysql`` collection. + + This changelog contains all changes to the modules and plugins in this + + collection that have been made after the previous release.' + fragments: + - 0-mysql_user.yml + - 1-mysql_info.yml + - 2-mysql_variables.yml + - 3-deprecate_mysqlclient.yml + - 3.10.0.yml + - add_salt_param_to_gen_sha256_hash.yml + - get_primary_show_binary_log_status.yml + - improve_get_replica_primary_status.yml + - lie_fix_mysql_user_on_new_username.yml + - lie_fix_plugin_hash_string_return.yml + - mysql_user_tls_requires.yml + - supports_mysql_change_replication_source_to.yml + release_date: '2024-08-22' 3.2.0: changes: bugfixes: diff --git a/changelogs/fragments/0-mysql_user.yml b/changelogs/fragments/0-mysql_user.yml deleted file mode 100644 index 6b812ab..0000000 --- a/changelogs/fragments/0-mysql_user.yml +++ /dev/null @@ -1,2 +0,0 @@ -bugfixes: -- mysql_user - add correct ``ed25519`` auth plugin handling (https://github.com/ansible-collections/community.mysql/issues/6). diff --git a/changelogs/fragments/1-mysql_info.yml b/changelogs/fragments/1-mysql_info.yml deleted file mode 100644 index 1ab4d2c..0000000 --- a/changelogs/fragments/1-mysql_info.yml +++ /dev/null @@ -1,2 +0,0 @@ -minor_changes: -- mysql_info - return a database server engine used (https://github.com/ansible-collections/community.mysql/issues/644). diff --git a/changelogs/fragments/2-mysql_variables.yml b/changelogs/fragments/2-mysql_variables.yml deleted file mode 100644 index 9ef8d80..0000000 --- a/changelogs/fragments/2-mysql_variables.yml +++ /dev/null @@ -1,2 +0,0 @@ -bugfixes: -- mysql_variables - fix the module always changes on boolean values (https://github.com/ansible-collections/community.mysql/issues/652). diff --git a/changelogs/fragments/3-deprecate_mysqlclient.yml b/changelogs/fragments/3-deprecate_mysqlclient.yml deleted file mode 100644 index 9134413..0000000 --- a/changelogs/fragments/3-deprecate_mysqlclient.yml +++ /dev/null @@ -1,2 +0,0 @@ -breaking_changes: -- collection - support of mysqlclient connector is deprecated - use PyMySQL connector instead! We will stop testing against it in collection version 4.0.0 and remove the related code in 5.0.0 (https://github.com/ansible-collections/community.mysql/issues/654). diff --git a/changelogs/fragments/add_salt_param_to_gen_sha256_hash.yml b/changelogs/fragments/add_salt_param_to_gen_sha256_hash.yml deleted file mode 100644 index c49ba1d..0000000 --- a/changelogs/fragments/add_salt_param_to_gen_sha256_hash.yml +++ /dev/null @@ -1,3 +0,0 @@ ---- -minor_changes: - - mysql_user - Add salt parameter to generate static hash for `caching_sha2_password` and `sha256_password` plugins. diff --git a/changelogs/fragments/get_primary_show_binary_log_status.yml b/changelogs/fragments/get_primary_show_binary_log_status.yml deleted file mode 100644 index 8757aa1..0000000 --- a/changelogs/fragments/get_primary_show_binary_log_status.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -minor_changes: - - - mysql_replication - Adds support for `SHOW BINARY LOG STATUS` and `SHOW BINLOG STATUS` on getprimary mode. diff --git a/changelogs/fragments/improve_get_replica_primary_status.yml b/changelogs/fragments/improve_get_replica_primary_status.yml deleted file mode 100644 index 512d7ef..0000000 --- a/changelogs/fragments/improve_get_replica_primary_status.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -minor_changes: - - - mysql_replication - Improve detection of IsReplica and IsPrimary by inspecting the dictionary returned from the SQL query instead of relying on variable types. This ensures compatibility with changes in the connector or the output of SHOW REPLICA STATUS and SHOW MASTER STATUS, allowing for easier maintenance if these change in the future. diff --git a/changelogs/fragments/lie_fix_mysql_user_on_new_username.yml b/changelogs/fragments/lie_fix_mysql_user_on_new_username.yml deleted file mode 100644 index 7f13738..0000000 --- a/changelogs/fragments/lie_fix_mysql_user_on_new_username.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- - -bugfixes: - - - mysql_user - Fixed an IndexError in the update_password functionality introduced in PR https://github.com/ansible-collections/community.mysql/pull/580 and released in community.mysql 3.8.0. If you used this functionality, please avoid versions 3.8.0 to 3.9.0 (https://github.com/ansible-collections/community.mysql/pull/642). - - mysql_user - Added a warning to update_password's on_new_username option if multiple accounts with the same username but different passwords exist (https://github.com/ansible-collections/community.mysql/pull/642). diff --git a/changelogs/fragments/lie_fix_plugin_hash_string_return.yml b/changelogs/fragments/lie_fix_plugin_hash_string_return.yml deleted file mode 100644 index e1a71ea..0000000 --- a/changelogs/fragments/lie_fix_plugin_hash_string_return.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -bugfixes: - - mysql_info - Add ``plugin_hash_string`` to ``users_info`` filter's output. The existing ``plugin_auth_string`` contained the hashed password and thus is missleading, it will be removed from community.mysql 4.0.0. (https://github.com/ansible-collections/community.mysql/pull/629). - -breaking_changes: - - mysql_info - The ``users_info`` filter returned variable ``plugin_auth_string`` contains the hashed password and it's misleading, it will be removed from community.mysql 4.0.0. Use the `plugin_hash_string` return value instead (https://github.com/ansible-collections/community.mysql/pull/629). diff --git a/changelogs/fragments/mysql_user_tls_requires.yml b/changelogs/fragments/mysql_user_tls_requires.yml deleted file mode 100644 index 1fa0c94..0000000 --- a/changelogs/fragments/mysql_user_tls_requires.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -minor_changes: - - mysql_info - Add ``tls_requires`` returned value for the ``users_info`` filter (https://github.com/ansible-collections/community.mysql/pull/628). -bugfixes: - - mysql_user - Fix idempotence when using variables from the ``users_info`` filter of ``mysql_info`` as an input (https://github.com/ansible-collections/community.mysql/pull/628). - - mysql_user - Fix ``tls_requires`` not removing ``SSL`` and ``X509`` when sets as empty (https://github.com/ansible-collections/community.mysql/pull/628). diff --git a/changelogs/fragments/supports_mysql_change_replication_source_to.yml b/changelogs/fragments/supports_mysql_change_replication_source_to.yml deleted file mode 100644 index 955d62e..0000000 --- a/changelogs/fragments/supports_mysql_change_replication_source_to.yml +++ /dev/null @@ -1,3 +0,0 @@ ---- -minor_changes: - - mysql_replication - Adds support for `CHANGE REPLICATION SOURCE TO` statement (https://github.com/ansible-collections/community.mysql/issues/635). diff --git a/galaxy.yml b/galaxy.yml index 512c668..353a6f8 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -1,7 +1,7 @@ --- namespace: community name: mysql -version: 3.9.0 +version: 3.10.0 readme: README.md authors: - Ansible community From 87be61ccf3601ed02711ce893c5f40af71f656ac Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Thu, 29 Aug 2024 08:47:48 +0200 Subject: [PATCH 193/217] CI: Fix sanity errors (#668) --- plugins/module_utils/user.py | 1 + plugins/modules/mysql_user.py | 1 + tests/sanity/ignore-2.15.txt | 1 - tests/sanity/ignore-2.16.txt | 1 - tests/sanity/ignore-2.17.txt | 1 - tests/sanity/ignore-2.18.txt | 1 - 6 files changed, 2 insertions(+), 4 deletions(-) diff --git a/plugins/module_utils/user.py b/plugins/module_utils/user.py index bd71691..5e0196a 100644 --- a/plugins/module_utils/user.py +++ b/plugins/module_utils/user.py @@ -393,6 +393,7 @@ def user_mod(cursor, user, host, host_all, password, encrypted, update = True if update: + query_with_args = None if plugin_hash_string: query_with_args = "ALTER USER %s@%s IDENTIFIED WITH %s AS %s", (user, host, plugin, plugin_hash_string) elif plugin_auth_string: diff --git a/plugins/modules/mysql_user.py b/plugins/modules/mysql_user.py index 0c7021b..2ee5e01 100644 --- a/plugins/modules/mysql_user.py +++ b/plugins/modules/mysql_user.py @@ -20,6 +20,7 @@ options: - Name of the user (role) to add or remove. type: str required: true + aliases: ['user'] password: description: - Set the user's password. Only for C(mysql_native_password) authentication. diff --git a/tests/sanity/ignore-2.15.txt b/tests/sanity/ignore-2.15.txt index 55b2904..152162d 100644 --- a/tests/sanity/ignore-2.15.txt +++ b/tests/sanity/ignore-2.15.txt @@ -1,4 +1,3 @@ plugins/modules/mysql_db.py validate-modules:use-run-command-not-popen -plugins/modules/mysql_user.py validate-modules:undocumented-parameter plugins/module_utils/mysql.py pylint:unused-import plugins/module_utils/version.py pylint:unused-import diff --git a/tests/sanity/ignore-2.16.txt b/tests/sanity/ignore-2.16.txt index 55b2904..152162d 100644 --- a/tests/sanity/ignore-2.16.txt +++ b/tests/sanity/ignore-2.16.txt @@ -1,4 +1,3 @@ plugins/modules/mysql_db.py validate-modules:use-run-command-not-popen -plugins/modules/mysql_user.py validate-modules:undocumented-parameter plugins/module_utils/mysql.py pylint:unused-import plugins/module_utils/version.py pylint:unused-import diff --git a/tests/sanity/ignore-2.17.txt b/tests/sanity/ignore-2.17.txt index 55b2904..152162d 100644 --- a/tests/sanity/ignore-2.17.txt +++ b/tests/sanity/ignore-2.17.txt @@ -1,4 +1,3 @@ plugins/modules/mysql_db.py validate-modules:use-run-command-not-popen -plugins/modules/mysql_user.py validate-modules:undocumented-parameter plugins/module_utils/mysql.py pylint:unused-import plugins/module_utils/version.py pylint:unused-import diff --git a/tests/sanity/ignore-2.18.txt b/tests/sanity/ignore-2.18.txt index 55b2904..152162d 100644 --- a/tests/sanity/ignore-2.18.txt +++ b/tests/sanity/ignore-2.18.txt @@ -1,4 +1,3 @@ plugins/modules/mysql_db.py validate-modules:use-run-command-not-popen -plugins/modules/mysql_user.py validate-modules:undocumented-parameter plugins/module_utils/mysql.py pylint:unused-import plugins/module_utils/version.py pylint:unused-import From 0de9685cf1db355fac194f70e154fa48ecd06705 Mon Sep 17 00:00:00 2001 From: Fran <51233345+francescsanjuanmrf@users.noreply.github.com> Date: Fri, 30 Aug 2024 11:15:16 +0200 Subject: [PATCH 194/217] Fix user plugin changes in check mode (#596) * Fix user plugin changes in check mode * Add auth plugin tests * Undo local changes * Improve task names * Fix query * Changes * Add check * Add check * Add check * Add one more check * Add one more check * Fix typo * Change parameter * Testing * Remove tests * Add tests * Test first stteps * Readd tests * Test without check mode * Test with check mode * Test with check mode * Testing * Testing * Add missing tests * Changes for ansible-lint complaints * Fix condition * Update changelogs/fragments/596-fix-check-changes.yaml Co-authored-by: Andrew Klychkov * refactor * Add more tests * Fix newpass var * Remove extra test --------- Co-authored-by: Andrew Klychkov --- .../fragments/596-fix-check-changes.yaml | 2 + plugins/module_utils/user.py | 3 +- .../tasks/test_user_plugin_auth.yml | 227 ++++++++++++------ .../tasks/utils/assert_plugin.yml | 11 + 4 files changed, 175 insertions(+), 68 deletions(-) create mode 100644 changelogs/fragments/596-fix-check-changes.yaml create mode 100644 tests/integration/targets/test_mysql_user/tasks/utils/assert_plugin.yml diff --git a/changelogs/fragments/596-fix-check-changes.yaml b/changelogs/fragments/596-fix-check-changes.yaml new file mode 100644 index 0000000..e7c24f1 --- /dev/null +++ b/changelogs/fragments/596-fix-check-changes.yaml @@ -0,0 +1,2 @@ +bugfixes: + - mysql_user - module makes changes when is executed with ``plugin_auth_string`` parameter and check mode. diff --git a/plugins/module_utils/user.py b/plugins/module_utils/user.py index 5e0196a..7d7d304 100644 --- a/plugins/module_utils/user.py +++ b/plugins/module_utils/user.py @@ -411,7 +411,8 @@ def user_mod(cursor, user, host, host_all, password, encrypted, else: query_with_args = "ALTER USER %s@%s IDENTIFIED WITH %s", (user, host, plugin) - cursor.execute(*query_with_args) + if not module.check_mode: + cursor.execute(*query_with_args) password_changed = True changed = True diff --git a/tests/integration/targets/test_mysql_user/tasks/test_user_plugin_auth.yml b/tests/integration/targets/test_mysql_user/tasks/test_user_plugin_auth.yml index b5ed6c5..f6f3c2e 100644 --- a/tests/integration/targets/test_mysql_user/tasks/test_user_plugin_auth.yml +++ b/tests/integration/targets/test_mysql_user/tasks/test_user_plugin_auth.yml @@ -24,7 +24,7 @@ # - name: Plugin auth | Create user with plugin auth (with hash string) - mysql_user: + community.mysql.mysql_user: <<: *mysql_params name: '{{ test_user_name }}' host: '%' @@ -34,28 +34,28 @@ register: result - name: Plugin auth | Get user information (with hash string) - command: "{{ mysql_command }} -e \"SELECT user, host, plugin FROM mysql.user WHERE user = '{{ test_user_name }}' and host = '%'\"" + ansible.builtin.command: "{{ mysql_command }} -e \"SELECT user, host, plugin FROM mysql.user WHERE user = '{{ test_user_name }}' and host = '%'\"" register: show_create_user - name: Plugin auth | Check that the module made a change (with hash string) - assert: + ansible.builtin.assert: that: - result is changed - name: Plugin auth | Check that the expected plugin type is set (with hash string) - assert: + ansible.builtin.assert: that: - "'{{ test_plugin_type }}' in show_create_user.stdout" when: db_engine == 'mysql' or (db_engine == 'mariadb' and db_version is version('10.3', '>=')) - - include_tasks: utils/assert_user.yml + - ansible.builtin.include_tasks: utils/assert_user.yml vars: user_name: "{{ test_user_name }}" user_host: "%" priv: "{{ test_default_priv_type }}" - name: Plugin auth | Get the MySQL version using the newly created creds - mysql_info: + community.mysql.mysql_info: login_user: '{{ test_user_name }}' login_password: '{{ test_plugin_auth_string }}' login_host: '{{ mysql_host }}' @@ -64,12 +64,12 @@ register: result - name: Plugin auth | Assert that mysql_info was successful - assert: + ansible.builtin.assert: that: - result is succeeded - name: Plugin auth | Update the user with a different hash - mysql_user: + community.mysql.mysql_user: <<: *mysql_params name: '{{ test_user_name }}' host: '%' @@ -78,18 +78,18 @@ register: result - name: Plugin auth | Check that the module makes the change because the hash changed - assert: + ansible.builtin.assert: that: - result is changed - - include_tasks: utils/assert_user.yml + - ansible.builtin.include_tasks: utils/assert_user.yml vars: user_name: "{{ test_user_name }}" user_host: "%" priv: "{{ test_default_priv_type }}" - name: Plugin auth | Getting the MySQL info with the new password should work - mysql_info: + community.mysql.mysql_info: login_user: '{{ test_user_name }}' login_password: '{{ test_plugin_new_auth_string }}' login_host: '{{ mysql_host }}' @@ -98,12 +98,12 @@ register: result - name: Plugin auth | Assert that mysql_info was successful - assert: + ansible.builtin.assert: that: - result is succeeded # Cleanup - - include_tasks: utils/remove_user.yml + - ansible.builtin.include_tasks: utils/remove_user.yml vars: user_name: "{{ test_user_name }}" @@ -112,7 +112,7 @@ # - name: Plugin auth | Create user with plugin auth (with hash string) - mysql_user: + community.mysql.mysql_user: <<: *mysql_params name: '{{ test_user_name }}' host: '%' @@ -122,28 +122,28 @@ register: result - name: Plugin auth | Get user information - command: "{{ mysql_command }} -e \"SELECT user, host, plugin FROM mysql.user WHERE user = '{{ test_user_name }}' and host = '%'\"" + ansible.builtin.command: "{{ mysql_command }} -e \"SELECT user, host, plugin FROM mysql.user WHERE user = '{{ test_user_name }}' and host = '%'\"" register: show_create_user - name: Plugin auth | Check that the module made a change (with hash string) - assert: + ansible.builtin.assert: that: - result is changed - name: Plugin auth | Check that the expected plugin type is set (with hash string) - assert: + ansible.builtin.assert: that: - "'{{ test_plugin_type }}' in show_create_user.stdout" when: db_engine == 'mysql' or (db_engine == 'mariadb' and db_version is version('10.3', '>=')) - - include_tasks: utils/assert_user.yml + - ansible.builtin.include_tasks: utils/assert_user.yml vars: user_name: "{{ test_user_name }}" user_host: "%" priv: "{{ test_default_priv_type }}" - name: Plugin auth | Get the MySQL version using the newly created creds - mysql_info: + community.mysql.mysql_info: login_user: '{{ test_user_name }}' login_password: '{{ test_plugin_auth_string }}' login_host: '{{ mysql_host }}' @@ -152,12 +152,12 @@ register: result - name: Plugin auth | Assert that mysql_info was successful - assert: + ansible.builtin.assert: that: - result is succeeded - name: Plugin auth | Update the user with the same hash (no change expected) - mysql_user: + community.mysql.mysql_user: <<: *mysql_params name: '{{ test_user_name }}' host: '%' @@ -167,19 +167,19 @@ # FIXME: on mariadb 10.2 there's always a change - name: Plugin auth | Check that the module doesn't make a change when the same hash is passed in - assert: + ansible.builtin.assert: that: - result is not changed when: db_engine == 'mysql' or (db_engine == 'mariadb' and db_version is version('10.3', '>=')) - - include_tasks: utils/assert_user.yml + - ansible.builtin.include_tasks: utils/assert_user.yml vars: user_name: "{{ test_user_name }}" user_host: "%" priv: "{{ test_default_priv_type }}" - name: Plugin auth | Change the user using the same plugin, but switch to the same auth string in plaintext form - mysql_user: + community.mysql.mysql_user: <<: *mysql_params name: '{{ test_user_name }}' host: '%' @@ -189,12 +189,12 @@ # Expecting a change is currently by design (see comment in source). - name: Plugin auth | Check that the module did not change the password - assert: + ansible.builtin.assert: that: - result is changed - name: Plugin auth | Getting the MySQL info should still work - mysql_info: + community.mysql.mysql_info: login_user: '{{ test_user_name }}' login_password: '{{ test_plugin_auth_string }}' login_host: '{{ mysql_host }}' @@ -203,12 +203,12 @@ register: result - name: Plugin auth | Assert that mysql_info was successful - assert: + ansible.builtin.assert: that: - result is succeeded # Cleanup - - include_tasks: utils/remove_user.yml + - ansible.builtin.include_tasks: utils/remove_user.yml vars: user_name: "{{ test_user_name }}" @@ -217,7 +217,7 @@ # - name: Plugin auth | Create user with plugin auth (with auth string) - mysql_user: + community.mysql.mysql_user: <<: *mysql_params name: '{{ test_user_name }}' host: '%' @@ -227,28 +227,28 @@ register: result - name: Plugin auth | Get user information(with auth string) - command: "{{ mysql_command }} -e \"SHOW CREATE USER '{{ test_user_name }}'@'%'\"" + ansible.builtin.command: "{{ mysql_command }} -e \"SHOW CREATE USER '{{ test_user_name }}'@'%'\"" register: show_create_user - name: Plugin auth | Check that the module made a change (with auth string) - assert: + ansible.builtin.assert: that: - result is changed - name: Plugin auth | Check that the expected plugin type is set (with auth string) - assert: + ansible.builtin.assert: that: - test_plugin_type in show_create_user.stdout when: db_engine == 'mysql' or (db_engine == 'mariadb' and db_version is version('10.3', '>=')) - - include_tasks: utils/assert_user.yml + - ansible.builtin.include_tasks: utils/assert_user.yml vars: user_name: "{{ test_user_name }}" user_host: "%" priv: "{{ test_default_priv_type }}" - name: Plugin auth | Get the MySQL version using the newly created creds - mysql_info: + community.mysql.mysql_info: login_user: '{{ test_user_name }}' login_password: '{{ test_plugin_auth_string }}' login_host: '{{ mysql_host }}' @@ -257,12 +257,12 @@ register: result - name: Plugin auth | Assert that mysql_info was successful - assert: + ansible.builtin.assert: that: - result is succeeded - name: Plugin auth | Update the user with the same auth string - mysql_user: + community.mysql.mysql_user: <<: *mysql_params name: '{{ test_user_name }}' host: '%' @@ -273,18 +273,18 @@ # This is the current expected behavior because there isn't a reliable way to hash the password in the mysql_user # module in order to be able to compare this password with the stored hash. See the source for more info. - name: Plugin auth | The module should detect a change even though the password is the same - assert: + ansible.builtin.assert: that: - result is changed - - include_tasks: utils/assert_user.yml + - ansible.builtin.include_tasks: utils/assert_user.yml vars: user_name: "{{ test_user_name }}" user_host: "%" priv: "{{ test_default_priv_type }}" - name: Plugin auth | Change the user using the same plugin, but switch to the same auth string in hash form - mysql_user: + community.mysql.mysql_user: <<: *mysql_params name: '{{ test_user_name }}' host: '%' @@ -293,12 +293,12 @@ register: result - name: Plugin auth | Check that the module did not change the password - assert: + ansible.builtin.assert: that: - result is not changed - name: Plugin auth | Get the MySQL version using the newly created creds - mysql_info: + community.mysql.mysql_info: login_user: '{{ test_user_name }}' login_password: '{{ test_plugin_auth_string }}' login_host: '{{ mysql_host }}' @@ -307,12 +307,12 @@ register: result - name: Plugin auth | Assert that mysql_info was successful - assert: + ansible.builtin.assert: that: - result is succeeded # Cleanup - - include_tasks: utils/remove_user.yml + - ansible.builtin.include_tasks: utils/remove_user.yml vars: user_name: "{{ test_user_name }}" @@ -321,7 +321,7 @@ # - name: Plugin auth | Create user with plugin auth (empty auth string) - mysql_user: + community.mysql.mysql_user: <<: *mysql_params name: '{{ test_user_name }}' host: '%' @@ -330,28 +330,28 @@ register: result - name: Plugin auth | Get user information (empty auth string) - command: "{{ mysql_command }} -e \"SHOW CREATE USER '{{ test_user_name }}'@'%'\"" + ansible.builtin.command: "{{ mysql_command }} -e \"SHOW CREATE USER '{{ test_user_name }}'@'%'\"" register: show_create_user - name: Plugin auth | Check that the module made a change (empty auth string) - assert: + ansible.builtin.assert: that: - result is changed - name: Plugin auth | Check that the expected plugin type is set (empty auth string) - assert: + ansible.builtin.assert: that: - "'{{ test_plugin_type }}' in show_create_user.stdout" when: db_engine == 'mysql' or (db_engine == 'mariadb' and db_version is version('10.3', '>=')) - - include_tasks: utils/assert_user.yml + - ansible.builtin.include_tasks: utils/assert_user.yml vars: user_name: "{{ test_user_name }}" user_host: "%" priv: "{{ test_default_priv_type }}" - name: Plugin auth | Get the MySQL version using an empty password for the newly created user - mysql_info: + community.mysql.mysql_info: login_user: '{{ test_user_name }}' login_password: '' login_host: '{{ mysql_host }}' @@ -361,12 +361,12 @@ ignore_errors: true - name: Plugin auth | Assert that mysql_info was successful - assert: + ansible.builtin.assert: that: - result is succeeded - name: Plugin auth | Get the MySQL version using an non-empty password (should fail) - mysql_info: + community.mysql.mysql_info: login_user: '{{ test_user_name }}' login_password: 'some_password' login_host: '{{ mysql_host }}' @@ -376,12 +376,12 @@ ignore_errors: true - name: Plugin auth | Assert that mysql_info failed - assert: + ansible.builtin.assert: that: - result is failed - name: Plugin auth | Update the user without changing the auth mechanism - mysql_user: + community.mysql.mysql_user: <<: *mysql_params name: '{{ test_user_name }}' host: '%' @@ -390,12 +390,12 @@ register: result - name: Plugin auth | Assert that the user wasn't changed because the auth string is still empty - assert: + ansible.builtin.assert: that: - result is not changed # Cleanup - - include_tasks: utils/remove_user.yml + - ansible.builtin.include_tasks: utils/remove_user.yml vars: user_name: "{{ test_user_name }}" @@ -415,7 +415,7 @@ block: - name: Plugin auth | Create user with plugin auth (empty auth string) - mysql_user: + community.mysql.mysql_user: <<: *mysql_params name: '{{ test_user_name }}' plugin: '{{ test_plugin_type }}' @@ -423,28 +423,28 @@ register: result - name: Plugin auth | Get user information (empty auth string) - command: "{{ mysql_command }} -e \"SHOW CREATE USER '{{ test_user_name }}'@'localhost'\"" + ansible.builtin.command: "{{ mysql_command }} -e \"SHOW CREATE USER '{{ test_user_name }}'@'localhost'\"" register: show_create_user - name: Plugin auth | Check that the module made a change (empty auth string) - assert: + ansible.builtin.assert: that: - result is changed - name: Plugin auth | Check that the expected plugin type is set (empty auth string) - assert: + ansible.builtin.assert: that: - test_plugin_type in show_create_user.stdout when: db_engine == 'mysql' or (db_engine == 'mariadb' and db_version is version('10.3', '>=')) - - include_tasks: utils/assert_user.yml + - ansible.builtin.include_tasks: utils/assert_user.yml vars: user_name: "{{ test_user_name }}" user_host: localhost priv: "{{ test_default_priv_type }}" - name: Plugin auth | Switch user to sha256_password auth plugin - mysql_user: + community.mysql.mysql_user: <<: *mysql_params name: '{{ test_user_name }}' plugin: sha256_password @@ -452,28 +452,28 @@ register: result - name: Plugin auth | Get user information (sha256_password) - command: "{{ mysql_command }} -e \"SHOW CREATE USER '{{ test_user_name }}'@'localhost'\"" + ansible.builtin.command: "{{ mysql_command }} -e \"SHOW CREATE USER '{{ test_user_name }}'@'localhost'\"" register: show_create_user - name: Plugin auth | Check that the module made a change (sha256_password) - assert: + ansible.builtin.assert: that: - result is changed - name: Plugin auth | Check that the expected plugin type is set (sha256_password) - assert: + ansible.builtin.assert: that: - "'sha256_password' in show_create_user.stdout" when: db_engine == 'mysql' or (db_engine == 'mariadb' and db_version is version('10.3', '>=')) - - include_tasks: utils/assert_user.yml + - ansible.builtin.include_tasks: utils/assert_user.yml vars: user_name: "{{ test_user_name }}" user_host: localhost priv: "{{ test_default_priv_type }}" # Cleanup - - include_tasks: utils/remove_user.yml + - ansible.builtin.include_tasks: utils/remove_user.yml vars: user_name: "{{ test_user_name }}" @@ -505,7 +505,7 @@ register: result failed_when: result is changed - - name: cleanup user + - name: Cleanup user ansible.builtin.include_tasks: utils/remove_user.yml vars: user_name: "{{ test_user_name }}" @@ -544,3 +544,96 @@ priv: "{{ test_default_priv }}" register: result failed_when: result is success + + # ============================================================ + # Test auth plugin change + # + + - name: Plugin auth | Test plugin auth switching which doesn't work on pymysql < 0.9 + when: + - > + connector_name != 'pymysql' + or ( + connector_name == 'pymysql' + and connector_version is version('0.9', '>=') + ) + block: + + - name: Cleanup user + ansible.builtin.include_tasks: utils/remove_user.yml + vars: + user_name: "{{ test_user_name }}" + + - name: Plugin auth | Create user with mysql_native_password + community.mysql.mysql_user: + <<: *mysql_params + name: "{{ test_user_name }}" + host: "%" + plugin: "{{ test_plugin_type }}" + password: "{{ test_plugin_auth_string }}" + priv: "{{ test_default_priv }}" + + - name: Plugin auth | Check that the expected plugin type is set + ansible.builtin.include_tasks: utils/assert_plugin.yml + vars: + user_name: "{{ test_user_name }}" + plugin_type: "{{ test_plugin_type }}" + + - name: Plugin auth | Connect with user and password + ansible.builtin.command: '{{ mysql_command }} -u {{ test_user_name }} -p{{ test_plugin_auth_string }} -e "SELECT 1"' + changed_when: false + + - name: Plugin auth | Change auth user plugin in check mode + community.mysql.mysql_user: + <<: *mysql_params + name: "{{ test_user_name }}" + host: '%' + plugin: caching_sha2_password + plugin_auth_string: "{{ test_plugin_auth_string }}" + salt: "{{ test_salt }}" + priv: "{{ test_default_priv }}" + check_mode: true + register: result + failed_when: result is not changed + + - name: Plugin auth | Check that the expected plugin type is set (not changed) + ansible.builtin.include_tasks: utils/assert_plugin.yml + vars: + user_name: "{{ test_user_name }}" + plugin_type: "{{ test_plugin_type }}" + + - name: Plugin auth | Change auth user plugin + community.mysql.mysql_user: + <<: *mysql_params + name: "{{ test_user_name }}" + host: '%' + plugin: caching_sha2_password + plugin_auth_string: "{{ test_plugin_auth_string }}" + salt: "{{ test_salt }}" + priv: "{{ test_default_priv }}" + register: result + failed_when: result is not changed + + - name: Plugin auth | Check that the expected (new) plugin type is set + ansible.builtin.include_tasks: utils/assert_plugin.yml + vars: + user_name: "{{ test_user_name }}" + plugin_type: caching_sha2_password + + - name: Plugin auth | Change auth user plugin again (should not change) + community.mysql.mysql_user: + <<: *mysql_params + name: "{{ test_user_name }}" + host: '%' + plugin: caching_sha2_password + plugin_auth_string: "{{ test_plugin_auth_string }}" + salt: "{{ test_salt }}" + priv: "{{ test_default_priv }}" + register: result + failed_when: result is changed + + - name: Plugin auth | Check that the expected (not changed) plugin type is set + ansible.builtin.include_tasks: utils/assert_plugin.yml + vars: + user_name: "{{ test_user_name }}" + plugin_type: caching_sha2_password diff --git a/tests/integration/targets/test_mysql_user/tasks/utils/assert_plugin.yml b/tests/integration/targets/test_mysql_user/tasks/utils/assert_plugin.yml new file mode 100644 index 0000000..7d3b5a1 --- /dev/null +++ b/tests/integration/targets/test_mysql_user/tasks/utils/assert_plugin.yml @@ -0,0 +1,11 @@ +--- + +- name: Utils | Assert plugin | Query for user {{ user_name }} + ansible.builtin.command: "{{ mysql_command }} -e \"SELECT plugin FROM mysql.user where user='{{ user_name }}'\"" + register: result + changed_when: False + +- name: Utils | Assert plugin | Assert plugin is correct + ansible.builtin.assert: + that: + - plugin_type in result.stdout From 59c26211ca325553214105e52f460d3bf035e561 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Mon, 2 Sep 2024 18:07:11 +0200 Subject: [PATCH 195/217] mysql_user: deprecate alias user for name argument (#670) * mysql_user: deprecate alias user for name argument * Fix module and tests --- changelogs/fragments/0-mysql_user.yml | 2 ++ plugins/modules/mysql_user.py | 10 ++++++++-- .../targets/test_mysql_user/tasks/issue-265.yml | 8 ++++---- .../targets/test_mysql_user/tasks/test_idempotency.yml | 4 ++-- 4 files changed, 16 insertions(+), 8 deletions(-) create mode 100644 changelogs/fragments/0-mysql_user.yml diff --git a/changelogs/fragments/0-mysql_user.yml b/changelogs/fragments/0-mysql_user.yml new file mode 100644 index 0000000..b75533f --- /dev/null +++ b/changelogs/fragments/0-mysql_user.yml @@ -0,0 +1,2 @@ +breaking_changes: +- mysql_user - the ``user`` alias of the ``name`` argument has been deprecated and will be removed in collection version 5.0.0. Use the ``name`` argument instead. diff --git a/plugins/modules/mysql_user.py b/plugins/modules/mysql_user.py index 2ee5e01..78f11a9 100644 --- a/plugins/modules/mysql_user.py +++ b/plugins/modules/mysql_user.py @@ -439,7 +439,13 @@ from ansible.module_utils._text import to_native def main(): argument_spec = mysql_common_argument_spec() argument_spec.update( - user=dict(type='str', required=True, aliases=['name']), + name=dict(type='str', required=True, aliases=['user'], deprecated_aliases=[ + { + 'name': 'user', + 'version': '5.0.0', + 'collection_name': 'community.mysql', + }], + ), password=dict(type='str', no_log=True), encrypted=dict(type='bool', default=False), host=dict(type='str', default='localhost'), @@ -471,7 +477,7 @@ def main(): ) login_user = module.params["login_user"] login_password = module.params["login_password"] - user = module.params["user"] + user = module.params["name"] password = module.params["password"] encrypted = module.boolean(module.params["encrypted"]) host = module.params["host"].lower() diff --git a/tests/integration/targets/test_mysql_user/tasks/issue-265.yml b/tests/integration/targets/test_mysql_user/tasks/issue-265.yml index 2d8db77..dfceda7 100644 --- a/tests/integration/targets/test_mysql_user/tasks/issue-265.yml +++ b/tests/integration/targets/test_mysql_user/tasks/issue-265.yml @@ -64,7 +64,7 @@ - name: Issue-265 | Remove blank mysql user with hosts=all (expect changed) mysql_user: <<: *mysql_params - user: "" + name: "" host_all: true state: absent force_context: yes @@ -78,7 +78,7 @@ - name: Issue-265 | Remove blank mysql user with hosts=all (expect ok) mysql_user: <<: *mysql_params - user: "" + name: "" host_all: true force_context: yes state: absent @@ -151,7 +151,7 @@ - name: Issue-265 | Remove blank mysql user with hosts=all (expect changed) mysql_user: <<: *mysql_params - user: "" + name: "" host_all: true state: absent force_context: no @@ -165,7 +165,7 @@ - name: Issue-265 | Remove blank mysql user with hosts=all (expect ok) mysql_user: <<: *mysql_params - user: "" + name: "" host_all: true force_context: no state: absent diff --git a/tests/integration/targets/test_mysql_user/tasks/test_idempotency.yml b/tests/integration/targets/test_mysql_user/tasks/test_idempotency.yml index fb60139..f76934b 100644 --- a/tests/integration/targets/test_mysql_user/tasks/test_idempotency.yml +++ b/tests/integration/targets/test_mysql_user/tasks/test_idempotency.yml @@ -66,7 +66,7 @@ - name: Idempotency | Remove blank user with hosts=all (expect changed) mysql_user: <<: *mysql_params - user: "" + name: "" host_all: true state: absent register: result @@ -79,7 +79,7 @@ - name: Idempotency | Remove blank user with hosts=all (expect ok) mysql_user: <<: *mysql_params - user: "" + name: "" host_all: true state: absent register: result From 2db131f8c054ce1dee88eb1f575603aaec4c4c8b Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Wed, 4 Sep 2024 07:19:59 +0200 Subject: [PATCH 196/217] Release 3.10.1 commit (#673) --- CHANGELOG.rst | 19 +++++++++++++++++++ changelogs/changelog.yaml | 17 +++++++++++++++++ changelogs/fragments/0-mysql_user.yml | 2 -- .../fragments/596-fix-check-changes.yaml | 2 -- galaxy.yml | 2 +- 5 files changed, 37 insertions(+), 5 deletions(-) delete mode 100644 changelogs/fragments/0-mysql_user.yml delete mode 100644 changelogs/fragments/596-fix-check-changes.yaml diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c5039ed..19b018b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,25 @@ Community MySQL and MariaDB Collection Release Notes This changelog describes changes after version 2.0.0. +v3.10.1 +======= + +Release Summary +--------------- + +This is a patch release of the ``community.mysql`` collection. +Besides a bugfix, it contains an important upcoming breaking-change information. + +Breaking Changes / Porting Guide +-------------------------------- + +- mysql_user - the ``user`` alias of the ``name`` argument has been deprecated and will be removed in collection version 5.0.0. Use the ``name`` argument instead. + +Bugfixes +-------- + +- mysql_user - module makes changes when is executed with ``plugin_auth_string`` parameter and check mode. + v3.10.0 ======= diff --git a/changelogs/changelog.yaml b/changelogs/changelog.yaml index 8c18264..1b8048a 100644 --- a/changelogs/changelog.yaml +++ b/changelogs/changelog.yaml @@ -156,6 +156,23 @@ releases: - mysql_user_tls_requires.yml - supports_mysql_change_replication_source_to.yml release_date: '2024-08-22' + 3.10.1: + changes: + breaking_changes: + - mysql_user - the ``user`` alias of the ``name`` argument has been deprecated + and will be removed in collection version 5.0.0. Use the ``name`` argument + instead. + bugfixes: + - mysql_user - module makes changes when is executed with ``plugin_auth_string`` + parameter and check mode. + release_summary: 'This is a patch release of the ``community.mysql`` collection. + + Besides a bugfix, it contains an important upcoming breaking-change information.' + fragments: + - 0-mysql_user.yml + - 3.10.1.yml + - 596-fix-check-changes.yaml + release_date: '2024-09-04' 3.2.0: changes: bugfixes: diff --git a/changelogs/fragments/0-mysql_user.yml b/changelogs/fragments/0-mysql_user.yml deleted file mode 100644 index b75533f..0000000 --- a/changelogs/fragments/0-mysql_user.yml +++ /dev/null @@ -1,2 +0,0 @@ -breaking_changes: -- mysql_user - the ``user`` alias of the ``name`` argument has been deprecated and will be removed in collection version 5.0.0. Use the ``name`` argument instead. diff --git a/changelogs/fragments/596-fix-check-changes.yaml b/changelogs/fragments/596-fix-check-changes.yaml deleted file mode 100644 index e7c24f1..0000000 --- a/changelogs/fragments/596-fix-check-changes.yaml +++ /dev/null @@ -1,2 +0,0 @@ -bugfixes: - - mysql_user - module makes changes when is executed with ``plugin_auth_string`` parameter and check mode. diff --git a/galaxy.yml b/galaxy.yml index 353a6f8..ffcb55b 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -1,7 +1,7 @@ --- namespace: community name: mysql -version: 3.10.0 +version: 3.10.1 readme: README.md authors: - Ansible community From 3425fdb839615203e50a84b3e2ee07f5c2da4b67 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Thu, 5 Sep 2024 12:19:33 +0200 Subject: [PATCH 197/217] mysql_user: add correct ed25519 plugin handling when creating a user (#674) --- changelogs/fragments/0-mysql_user.yml | 2 ++ plugins/module_utils/user.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 changelogs/fragments/0-mysql_user.yml diff --git a/changelogs/fragments/0-mysql_user.yml b/changelogs/fragments/0-mysql_user.yml new file mode 100644 index 0000000..61a9a01 --- /dev/null +++ b/changelogs/fragments/0-mysql_user.yml @@ -0,0 +1,2 @@ +bugfixes: +- mysql_user - add correct ``ed25519`` auth plugin handling when creating a user (https://github.com/ansible-collections/community.mysql/issues/672). diff --git a/plugins/module_utils/user.py b/plugins/module_utils/user.py index 7d7d304..58ed607 100644 --- a/plugins/module_utils/user.py +++ b/plugins/module_utils/user.py @@ -212,7 +212,7 @@ def user_add(cursor, user, host, host_all, password, encrypted, query_with_args = "CREATE USER %s@%s IDENTIFIED WITH %s AS %s", (user, host, plugin, plugin_hash_string) elif plugin and plugin_auth_string: # Mysql and MariaDB differ in naming pam plugin and Syntax to set it - if plugin == 'pam': # Used by MariaDB which requires the USING keyword, not BY + if plugin in ('pam', 'ed25519'): # Used by MariaDB which requires the USING keyword, not BY query_with_args = "CREATE USER %s@%s IDENTIFIED WITH %s USING %s", (user, host, plugin, plugin_auth_string) elif salt: if plugin in ['caching_sha2_password', 'sha256_password']: From 7188bea0c827fab6e190984c4d6fd3acb3668e35 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Fri, 6 Sep 2024 08:21:45 +0200 Subject: [PATCH 198/217] Release 3.10.2 commit (#675) --- CHANGELOG.rst | 15 +++++++++++++++ changelogs/changelog.yaml | 14 ++++++++++++++ changelogs/fragments/0-mysql_user.yml | 2 -- galaxy.yml | 2 +- 4 files changed, 30 insertions(+), 3 deletions(-) delete mode 100644 changelogs/fragments/0-mysql_user.yml diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 19b018b..55e08f2 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,21 @@ Community MySQL and MariaDB Collection Release Notes This changelog describes changes after version 2.0.0. +v3.10.2 +======= + +Release Summary +--------------- + +This is a bugfix release of the ``community.mysql`` collection. +This changelog contains all changes to the modules and plugins in this +collection that have been made after the previous release. + +Bugfixes +-------- + +- mysql_user - add correct ``ed25519`` auth plugin handling when creating a user (https://github.com/ansible-collections/community.mysql/issues/672). + v3.10.1 ======= diff --git a/changelogs/changelog.yaml b/changelogs/changelog.yaml index 1b8048a..56b9a53 100644 --- a/changelogs/changelog.yaml +++ b/changelogs/changelog.yaml @@ -173,6 +173,20 @@ releases: - 3.10.1.yml - 596-fix-check-changes.yaml release_date: '2024-09-04' + 3.10.2: + changes: + bugfixes: + - mysql_user - add correct ``ed25519`` auth plugin handling when creating a + user (https://github.com/ansible-collections/community.mysql/issues/672). + release_summary: 'This is a bugfix release of the ``community.mysql`` collection. + + This changelog contains all changes to the modules and plugins in this + + collection that have been made after the previous release.' + fragments: + - 0-mysql_user.yml + - 3.10.2.yml + release_date: '2024-09-06' 3.2.0: changes: bugfixes: diff --git a/changelogs/fragments/0-mysql_user.yml b/changelogs/fragments/0-mysql_user.yml deleted file mode 100644 index 61a9a01..0000000 --- a/changelogs/fragments/0-mysql_user.yml +++ /dev/null @@ -1,2 +0,0 @@ -bugfixes: -- mysql_user - add correct ``ed25519`` auth plugin handling when creating a user (https://github.com/ansible-collections/community.mysql/issues/672). diff --git a/galaxy.yml b/galaxy.yml index ffcb55b..99a5a39 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -1,7 +1,7 @@ --- namespace: community name: mysql -version: 3.10.1 +version: 3.10.2 readme: README.md authors: - Ansible community From eec6e7091f5dd1ecb7fbb114be7b8c71e94d909e Mon Sep 17 00:00:00 2001 From: hubiongithub <79990207+hubiongithub@users.noreply.github.com> Date: Mon, 9 Sep 2024 15:01:26 +0200 Subject: [PATCH 199/217] Update user.py (#676) * Update user.py Added correct syntax to ed25519 password plugin. on create user on update user This only accepts cleartext passwords (PASSWORD(%s)) not pregenerated ed25519 hashes. * Update plugins/module_utils/user.py Co-authored-by: Andrew Klychkov * Update plugins/module_utils/user.py Co-authored-by: Andrew Klychkov * Update plugins/module_utils/user.py Co-authored-by: Andrew Klychkov * Update plugins/module_utils/user.py Co-authored-by: Andrew Klychkov * Update plugins/module_utils/user.py * Update plugins/module_utils/user.py --------- Co-authored-by: Andrew Klychkov --- plugins/module_utils/user.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/plugins/module_utils/user.py b/plugins/module_utils/user.py index 58ed607..7b6914f 100644 --- a/plugins/module_utils/user.py +++ b/plugins/module_utils/user.py @@ -212,8 +212,10 @@ def user_add(cursor, user, host, host_all, password, encrypted, query_with_args = "CREATE USER %s@%s IDENTIFIED WITH %s AS %s", (user, host, plugin, plugin_hash_string) elif plugin and plugin_auth_string: # Mysql and MariaDB differ in naming pam plugin and Syntax to set it - if plugin in ('pam', 'ed25519'): # Used by MariaDB which requires the USING keyword, not BY + if plugin == 'pam': # Used by MariaDB which requires the USING keyword, not BY query_with_args = "CREATE USER %s@%s IDENTIFIED WITH %s USING %s", (user, host, plugin, plugin_auth_string) + elif plugin == 'ed25519': # Used by MariaDB which requires the USING keyword, not BY + query_with_args = "CREATE USER %s@%s IDENTIFIED WITH %s USING PASSWORD(%s)", (user, host, plugin, plugin_auth_string) elif salt: if plugin in ['caching_sha2_password', 'sha256_password']: generated_hash_string = mysql_sha256_password_hash_hex(password=plugin_auth_string, salt=salt) @@ -398,8 +400,10 @@ def user_mod(cursor, user, host, host_all, password, encrypted, query_with_args = "ALTER USER %s@%s IDENTIFIED WITH %s AS %s", (user, host, plugin, plugin_hash_string) elif plugin_auth_string: # Mysql and MariaDB differ in naming pam plugin and syntax to set it - if plugin in ('pam', 'ed25519'): + if plugin == 'pam': query_with_args = "ALTER USER %s@%s IDENTIFIED WITH %s USING %s", (user, host, plugin, plugin_auth_string) + elif plugin == 'ed25519': + query_with_args = "ALTER USER %s@%s IDENTIFIED WITH %s USING PASSWORD(%s)", (user, host, plugin, plugin_auth_string) elif salt: if plugin in ['caching_sha2_password', 'sha256_password']: generated_hash_string = mysql_sha256_password_hash_hex(password=plugin_auth_string, salt=salt) From a75d71a7ff9d6929a08616b90ee4b50d2b15b841 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Mon, 9 Sep 2024 15:05:25 +0200 Subject: [PATCH 200/217] Release 3.10.3 commit (#678) --- CHANGELOG.rst | 15 +++++++++++++++ changelogs/changelog.yaml | 14 ++++++++++++++ galaxy.yml | 2 +- 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 55e08f2..76d83fe 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,21 @@ Community MySQL and MariaDB Collection Release Notes This changelog describes changes after version 2.0.0. +v3.10.3 +======= + +Release Summary +--------------- + +This is a bugfix release of the ``community.mysql`` collection. +This changelog contains all changes to the modules and plugins in this +collection that have been made after the previous release. + +Bugfixes +-------- + +- mysql_user - add correct ``ed25519`` auth plugin handling when creating a user (https://github.com/ansible-collections/community.mysql/pull/676). + v3.10.2 ======= diff --git a/changelogs/changelog.yaml b/changelogs/changelog.yaml index 56b9a53..ea7c09f 100644 --- a/changelogs/changelog.yaml +++ b/changelogs/changelog.yaml @@ -187,6 +187,20 @@ releases: - 0-mysql_user.yml - 3.10.2.yml release_date: '2024-09-06' + 3.10.3: + changes: + bugfixes: + - mysql_user - add correct ``ed25519`` auth plugin handling when creating a + user (https://github.com/ansible-collections/community.mysql/pull/676). + release_summary: 'This is a bugfix release of the ``community.mysql`` collection. + + This changelog contains all changes to the modules and plugins in this + + collection that have been made after the previous release.' + fragments: + - 0-mysql_user.yml + - 3.10.3.yml + release_date: '2024-09-09' 3.2.0: changes: bugfixes: diff --git a/galaxy.yml b/galaxy.yml index 99a5a39..0046b5a 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -1,7 +1,7 @@ --- namespace: community name: mysql -version: 3.10.2 +version: 3.10.3 readme: README.md authors: - Ansible community From 28bf7093be36e0bd47866e28aefff1a38cc5b2b0 Mon Sep 17 00:00:00 2001 From: Maxwell G Date: Wed, 11 Sep 2024 07:35:02 -0500 Subject: [PATCH 201/217] changelogs: categorize deprecations under deprecated_features (#679) These should be put under deprecated_features so they show up properly in the generated changelog. --- CHANGELOG.rst | 8 ++++---- changelogs/changelog.yaml | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 76d83fe..cf1162f 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -45,8 +45,8 @@ Release Summary This is a patch release of the ``community.mysql`` collection. Besides a bugfix, it contains an important upcoming breaking-change information. -Breaking Changes / Porting Guide --------------------------------- +Deprecated Features +------------------- - mysql_user - the ``user`` alias of the ``name`` argument has been deprecated and will be removed in collection version 5.0.0. Use the ``name`` argument instead. @@ -75,8 +75,8 @@ Minor Changes - mysql_replication - Improve detection of IsReplica and IsPrimary by inspecting the dictionary returned from the SQL query instead of relying on variable types. This ensures compatibility with changes in the connector or the output of SHOW REPLICA STATUS and SHOW MASTER STATUS, allowing for easier maintenance if these change in the future. - mysql_user - Add salt parameter to generate static hash for `caching_sha2_password` and `sha256_password` plugins. -Breaking Changes / Porting Guide --------------------------------- +Deprecated Features +------------------- - collection - support of mysqlclient connector is deprecated - use PyMySQL connector instead! We will stop testing against it in collection version 4.0.0 and remove the related code in 5.0.0 (https://github.com/ansible-collections/community.mysql/issues/654). - mysql_info - The ``users_info`` filter returned variable ``plugin_auth_string`` contains the hashed password and it's misleading, it will be removed from community.mysql 4.0.0. Use the `plugin_hash_string` return value instead (https://github.com/ansible-collections/community.mysql/pull/629). diff --git a/changelogs/changelog.yaml b/changelogs/changelog.yaml index ea7c09f..27ae315 100644 --- a/changelogs/changelog.yaml +++ b/changelogs/changelog.yaml @@ -99,7 +99,7 @@ releases: release_date: '2022-04-26' 3.10.0: changes: - breaking_changes: + deprecated_features: - collection - support of mysqlclient connector is deprecated - use PyMySQL connector instead! We will stop testing against it in collection version 4.0.0 and remove the related code in 5.0.0 (https://github.com/ansible-collections/community.mysql/issues/654). @@ -158,7 +158,7 @@ releases: release_date: '2024-08-22' 3.10.1: changes: - breaking_changes: + deprecated_features: - mysql_user - the ``user`` alias of the ``name`` argument has been deprecated and will be removed in collection version 5.0.0. Use the ``name`` argument instead. From a5afa1a375ebd7dc676ff6ab6f7323ce0b88b299 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Thu, 26 Sep 2024 14:31:08 +0200 Subject: [PATCH 202/217] CI: add stable-2.18, fix README (#681) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * CI: add stable-2.18, fix README * Update .github/workflows/ansible-test-plugins.yml Co-authored-by: Laurent Indermühle * Update .github/workflows/ansible-test-plugins.yml Co-authored-by: Laurent Indermühle * Update .github/workflows/ansible-test-plugins.yml Co-authored-by: Laurent Indermühle * Update README.md Co-authored-by: Laurent Indermühle --------- Co-authored-by: Laurent Indermühle --- .github/workflows/ansible-test-plugins.yml | 6 +++--- README.md | 2 +- tests/sanity/ignore-2.19.txt | 3 +++ 3 files changed, 7 insertions(+), 4 deletions(-) create mode 100644 tests/sanity/ignore-2.19.txt diff --git a/.github/workflows/ansible-test-plugins.yml b/.github/workflows/ansible-test-plugins.yml index efc1537..ad8c4b5 100644 --- a/.github/workflows/ansible-test-plugins.yml +++ b/.github/workflows/ansible-test-plugins.yml @@ -22,9 +22,9 @@ jobs: strategy: matrix: ansible: - - stable-2.15 - stable-2.16 - stable-2.17 + - stable-2.18 - devel steps: # https://github.com/ansible-community/ansible-test-gh-action @@ -44,9 +44,9 @@ jobs: fail-fast: false matrix: ansible: - - stable-2.15 - stable-2.16 - stable-2.17 + - stable-2.18 - devel db_engine_name: - mysql @@ -282,9 +282,9 @@ jobs: fail-fast: true matrix: ansible: - - stable-2.15 - stable-2.16 - stable-2.17 + - stable-2.18 - devel python: - '3.8' diff --git a/README.md b/README.md index 1f5b47a..5db2f05 100644 --- a/README.md +++ b/README.md @@ -90,9 +90,9 @@ Here is the table for the support timeline: ### ansible-core -- stable-2.15 - stable-2.16 - stable-2.17 +- stable-2.18 - current development version ### Python diff --git a/tests/sanity/ignore-2.19.txt b/tests/sanity/ignore-2.19.txt new file mode 100644 index 0000000..152162d --- /dev/null +++ b/tests/sanity/ignore-2.19.txt @@ -0,0 +1,3 @@ +plugins/modules/mysql_db.py validate-modules:use-run-command-not-popen +plugins/module_utils/mysql.py pylint:unused-import +plugins/module_utils/version.py pylint:unused-import From 93cd1850d93b8b9356a8310e461dfb6bd6f989b7 Mon Sep 17 00:00:00 2001 From: JS <26802713+rujschafer@users.noreply.github.com> Date: Wed, 23 Oct 2024 04:31:40 -0400 Subject: [PATCH 203/217] Update mysql_user.py - table/privilege spacing update (#687) * Update mysql_user.py - table/privilege spacing update Add note for no spacing between the table and the privilege as this will make the task not idempotent in check mode but still make it idempotent when in normal mode. * Update plugins/modules/mysql_user.py Co-authored-by: Andrew Klychkov --------- Co-authored-by: Andrew Klychkov --- plugins/modules/mysql_user.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/modules/mysql_user.py b/plugins/modules/mysql_user.py index 78f11a9..cf210a3 100644 --- a/plugins/modules/mysql_user.py +++ b/plugins/modules/mysql_user.py @@ -46,6 +46,7 @@ options: priv: description: - "MySQL privileges string in the format: C(db.table:priv1,priv2)." + - Additionally, there must be no spaces between the table and the privilege as this will yield a non-idempotent check mode. - "Multiple privileges can be specified by separating each one using a forward slash: C(db.table1:priv/db.table2:priv)." - The format is based on MySQL C(GRANT) statement. From 90bd0b0a75e2dd8b893058cf98b5bc98ca0ac5d6 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Thu, 24 Oct 2024 10:57:36 +0200 Subject: [PATCH 204/217] Update contributor's email (#684) --- plugins/modules/mysql_info.py | 2 +- plugins/modules/mysql_query.py | 2 +- plugins/modules/mysql_replication.py | 2 +- plugins/modules/mysql_role.py | 2 +- tests/integration/old_mariadb_replication/tasks/main.yml | 2 +- .../old_mariadb_replication/tasks/mariadb_master_use_gtid.yml | 2 +- .../tasks/mariadb_replication_connection_name.yml | 2 +- .../tasks/mariadb_replication_initial.yml | 2 +- tests/integration/targets/test_mysql_info/tasks/main.yml | 2 +- .../targets/test_mysql_query/tasks/mysql_query_initial.yml | 2 +- tests/integration/targets/test_mysql_replication/tasks/main.yml | 2 +- .../test_mysql_replication/tasks/mysql_replication_channel.yml | 2 +- .../test_mysql_replication/tasks/mysql_replication_initial.yml | 2 +- .../tasks/mysql_replication_primary_delay.yml | 2 +- .../tasks/mysql_replication_resetprimary_mode.yml | 2 +- tests/unit/plugins/module_utils/test_mariadb_replication.py | 2 +- tests/unit/plugins/module_utils/test_mysql_replication.py | 2 +- 17 files changed, 17 insertions(+), 17 deletions(-) diff --git a/plugins/modules/mysql_info.py b/plugins/modules/mysql_info.py index 2d1fe94..3a30597 100644 --- a/plugins/modules/mysql_info.py +++ b/plugins/modules/mysql_info.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) +# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) # 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 diff --git a/plugins/modules/mysql_query.py b/plugins/modules/mysql_query.py index 13a07de..2cdf096 100644 --- a/plugins/modules/mysql_query.py +++ b/plugins/modules/mysql_query.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright: (c) 2020, Andrew Klychkov (@Andersson007) +# Copyright: (c) 2020, Andrew Klychkov (@Andersson007) # 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) diff --git a/plugins/modules/mysql_replication.py b/plugins/modules/mysql_replication.py index 723fc35..35659d3 100644 --- a/plugins/modules/mysql_replication.py +++ b/plugins/modules/mysql_replication.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- # Copyright: (c) 2013, Balazs Pocze -# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) +# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) # Certain parts are taken from Mark Theunissen's mysqldb module # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) diff --git a/plugins/modules/mysql_role.py b/plugins/modules/mysql_role.py index 032b41e..c88392b 100644 --- a/plugins/modules/mysql_role.py +++ b/plugins/modules/mysql_role.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright: (c) 2021, Andrew Klychkov +# Copyright: (c) 2021, Andrew Klychkov # 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 diff --git a/tests/integration/old_mariadb_replication/tasks/main.yml b/tests/integration/old_mariadb_replication/tasks/main.yml index 4ea76a9..321ba4d 100644 --- a/tests/integration/old_mariadb_replication/tasks/main.yml +++ b/tests/integration/old_mariadb_replication/tasks/main.yml @@ -1,4 +1,4 @@ -# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) +# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # Initial CI tests of mysql_replication module diff --git a/tests/integration/old_mariadb_replication/tasks/mariadb_master_use_gtid.yml b/tests/integration/old_mariadb_replication/tasks/mariadb_master_use_gtid.yml index 699b61f..8977c10 100644 --- a/tests/integration/old_mariadb_replication/tasks/mariadb_master_use_gtid.yml +++ b/tests/integration/old_mariadb_replication/tasks/mariadb_master_use_gtid.yml @@ -1,4 +1,4 @@ -# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) +# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # Tests for master_use_gtid parameter. diff --git a/tests/integration/old_mariadb_replication/tasks/mariadb_replication_connection_name.yml b/tests/integration/old_mariadb_replication/tasks/mariadb_replication_connection_name.yml index 3928c78..337a839 100644 --- a/tests/integration/old_mariadb_replication/tasks/mariadb_replication_connection_name.yml +++ b/tests/integration/old_mariadb_replication/tasks/mariadb_replication_connection_name.yml @@ -1,4 +1,4 @@ -# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) +# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # Needs for further tests: diff --git a/tests/integration/old_mariadb_replication/tasks/mariadb_replication_initial.yml b/tests/integration/old_mariadb_replication/tasks/mariadb_replication_initial.yml index f65d090..1a95a55 100644 --- a/tests/integration/old_mariadb_replication/tasks/mariadb_replication_initial.yml +++ b/tests/integration/old_mariadb_replication/tasks/mariadb_replication_initial.yml @@ -1,4 +1,4 @@ -# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) +# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # Preparation: diff --git a/tests/integration/targets/test_mysql_info/tasks/main.yml b/tests/integration/targets/test_mysql_info/tasks/main.yml index 93570f2..42350c6 100644 --- a/tests/integration/targets/test_mysql_info/tasks/main.yml +++ b/tests/integration/targets/test_mysql_info/tasks/main.yml @@ -5,7 +5,7 @@ #################################################################### # Test code for mysql_info module -# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) +# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) ################### diff --git a/tests/integration/targets/test_mysql_query/tasks/mysql_query_initial.yml b/tests/integration/targets/test_mysql_query/tasks/mysql_query_initial.yml index 82665af..fbf5ca8 100644 --- a/tests/integration/targets/test_mysql_query/tasks/mysql_query_initial.yml +++ b/tests/integration/targets/test_mysql_query/tasks/mysql_query_initial.yml @@ -1,6 +1,6 @@ --- # Test code for mysql_query module -# Copyright: (c) 2020, Andrew Klychkov (@Andersson007) +# Copyright: (c) 2020, Andrew Klychkov (@Andersson007) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - vars: mysql_parameters: &mysql_params diff --git a/tests/integration/targets/test_mysql_replication/tasks/main.yml b/tests/integration/targets/test_mysql_replication/tasks/main.yml index a65cabd..32ce553 100644 --- a/tests/integration/targets/test_mysql_replication/tasks/main.yml +++ b/tests/integration/targets/test_mysql_replication/tasks/main.yml @@ -4,7 +4,7 @@ # and should not be used as examples of how to write Ansible roles # #################################################################### -# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) +# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # Initial CI tests of mysql_replication module: diff --git a/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_channel.yml b/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_channel.yml index 802865c..0bcc6e6 100644 --- a/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_channel.yml +++ b/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_channel.yml @@ -1,5 +1,5 @@ --- -# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) +# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - vars: 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 30cd99f..00699c1 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 @@ -1,5 +1,5 @@ --- -# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) +# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - vars: diff --git a/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_primary_delay.yml b/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_primary_delay.yml index 3ae4339..2093b70 100644 --- a/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_primary_delay.yml +++ b/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_primary_delay.yml @@ -1,4 +1,4 @@ -# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) +# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - vars: diff --git a/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_resetprimary_mode.yml b/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_resetprimary_mode.yml index 8968049..cdd5fa7 100644 --- a/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_resetprimary_mode.yml +++ b/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_resetprimary_mode.yml @@ -1,5 +1,5 @@ --- -# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) +# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - vars: diff --git a/tests/unit/plugins/module_utils/test_mariadb_replication.py b/tests/unit/plugins/module_utils/test_mariadb_replication.py index deb3099..513d8cf 100644 --- a/tests/unit/plugins/module_utils/test_mariadb_replication.py +++ b/tests/unit/plugins/module_utils/test_mariadb_replication.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright: (c) 2020, Andrew Klychkov (@Andersson007) +# Copyright: (c) 2020, Andrew Klychkov (@Andersson007) from __future__ import (absolute_import, division, print_function) __metaclass__ = type diff --git a/tests/unit/plugins/module_utils/test_mysql_replication.py b/tests/unit/plugins/module_utils/test_mysql_replication.py index 96d4d9a..c4126a5 100644 --- a/tests/unit/plugins/module_utils/test_mysql_replication.py +++ b/tests/unit/plugins/module_utils/test_mysql_replication.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright: (c) 2020, Andrew Klychkov (@Andersson007) +# Copyright: (c) 2020, Andrew Klychkov (@Andersson007) from __future__ import (absolute_import, division, print_function) __metaclass__ = type From ebb37ae7a3b126603cfe4066aa69e3e9c7cc93e7 Mon Sep 17 00:00:00 2001 From: Soledad208 Date: Thu, 7 Nov 2024 15:56:31 +0700 Subject: [PATCH 205/217] sql_mode can be set in session, therefore we should look for ANSI_QUOTES in session variable instead of global variable (#677) * issue-671: get ASNI_QUOTES from session sql_mode instead of GLOBAL sql_mode --- .../fragments/671-modules_util_user.yml | 12 ++ plugins/module_utils/user.py | 2 +- .../test_mysql_user/tasks/issue-671.yaml | 112 ++++++++++++++++++ .../targets/test_mysql_user/tasks/main.yml | 6 + 4 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 changelogs/fragments/671-modules_util_user.yml create mode 100644 tests/integration/targets/test_mysql_user/tasks/issue-671.yaml diff --git a/changelogs/fragments/671-modules_util_user.yml b/changelogs/fragments/671-modules_util_user.yml new file mode 100644 index 0000000..a913651 --- /dev/null +++ b/changelogs/fragments/671-modules_util_user.yml @@ -0,0 +1,12 @@ +bugfixes: + - mysql_user,mysql_role - The sql_mode ANSI_QUOTES affects how the modules mysql_user + and mysql_role compare the existing privileges with the configured privileges, + as well as decide whether double quotes or backticks should be used in the GRANT + statements. Pointing out in issue 671, the modules mysql_user and mysql_role allow + users to enable/disable ANSI_QUOTES in session variable (within a DB session, the + session variable always overwrites the global one). But due to the issue, the modules + do not check for ANSI_MODE in the session variable, instead, they only check in the + GLOBAL one.That behavior is not only limiting the users' flexibility, but also not + allowing users to explicitly disable ANSI_MODE to work around such bugs like + https://bugs.mysql.com/bug.php?id=115953. + (https://github.com/ansible-collections/community.mysql/issues/671) \ No newline at end of file diff --git a/plugins/module_utils/user.py b/plugins/module_utils/user.py index 7b6914f..307ef6e 100644 --- a/plugins/module_utils/user.py +++ b/plugins/module_utils/user.py @@ -32,7 +32,7 @@ class InvalidPrivsError(Exception): def get_mode(cursor): - cursor.execute('SELECT @@GLOBAL.sql_mode') + cursor.execute('SELECT @@sql_mode') result = cursor.fetchone() mode_str = result[0] if 'ANSI' in mode_str: diff --git a/tests/integration/targets/test_mysql_user/tasks/issue-671.yaml b/tests/integration/targets/test_mysql_user/tasks/issue-671.yaml new file mode 100644 index 0000000..3696cf0 --- /dev/null +++ b/tests/integration/targets/test_mysql_user/tasks/issue-671.yaml @@ -0,0 +1,112 @@ +--- +# Due to https://bugs.mysql.com/bug.php?id=115953, in Mysql 8, if ANSI_QUOTES is enabled, +# backticks will be used instead of double quotes to quote functions or procedures name. +# As a consequence, mysql_user and mysql_roles will always report "changed" for functions +# and procedures no matter the privileges are granted or not. +# Workaround for the mysql bug 116953 is removing ANSI_QUOTES from the module's session +# sql_mode. But because issue 671, ANSI_QUOTES is always got from GLOBAL sql_mode, thus +# this workaround can't work. Even without the Mysql bug, because sql_mode in session +# precedes GLOBAL sql_mode. we should check for sql_mode in session variable instead of +# the GLOBAL one. +- vars: + mysql_parameters: &mysql_params + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: '{{ mysql_host }}' + login_port: '{{ mysql_primary_port }}' + + block: + - name: Issue-671| test setup | drop database + community.mysql.mysql_db: + <<: *mysql_params + name: "{{ item }}" + state: absent + loop: + - foo + - bar + + - name: Issue-671| test setup | create database + community.mysql.mysql_db: + <<: *mysql_params + name: "{{ item }}" + state: present + loop: + - foo + - bar + + - name: Issue-671| test setup | get value of GLOBAL.sql_mode + community.mysql.mysql_query: + <<: *mysql_params + query: 'select @@GLOBAL.sql_mode AS sql_mode' + register: sql_mode_orig + + - name: Issue-671| Assert sql_mode_orig + ansible.builtin.assert: + that: + - sql_mode_orig.query_result[0][0].sql_mode != None + + - name: Issue-671| enable sql_mode ANSI_QUOTES + community.mysql.mysql_variables: + <<: *mysql_params + variable: sql_mode + value: '{{ sql_mode_orig.query_result[0][0].sql_mode }},ANSI_QUOTES' + mode: "{% if db_engine == 'mariadb' %}global{% else %}persist{% endif %}" + + - name: Issue-671| Copy SQL scripts to remote + ansible.builtin.copy: + src: "{{ item }}" + dest: "{{ remote_tmp_dir }}/{{ item | basename }}" + loop: + - create-function.sql + - create-procedure.sql + + - name: Issue-671| Create function for test + ansible.builtin.shell: + cmd: "{{ mysql_command }} < {{ remote_tmp_dir }}/create-function.sql" + + - name: Issue-671| Create procedure for test + ansible.builtin.shell: + cmd: "{{ mysql_command }} < {{ remote_tmp_dir }}/create-procedure.sql" + + - name: Issue-671| Create user with FUNCTION and PROCEDURE privileges + community.mysql.mysql_user: + <<: *mysql_params + name: '{{ user_name_2 }}' + password: '{{ user_password_2 }}' + state: present + priv: 'FUNCTION foo.function:EXECUTE/foo.*:SELECT/PROCEDURE bar.procedure:EXECUTE' + + - name: Issue-671| Grant the privileges again, remove ANSI_QUOTES from the session variable + community.mysql.mysql_user: + <<: *mysql_params + session_vars: + sql_mode: "" + name: '{{ user_name_2 }}' + password: '{{ user_password_2 }}' + state: present + priv: 'FUNCTION foo.function:EXECUTE/foo.*:SELECT/PROCEDURE bar.procedure:EXECUTE' + register: result + failed_when: + - result is failed or result is changed + + - name: Issue-671| Test teardown | cleanup databases + community.mysql.mysql_db: + <<: *mysql_params + name: "{{ item }}" + state: absent + loop: + - foo + - bar + + - name: Issue-671| set sql_mode back to original value + community.mysql.mysql_variables: + <<: *mysql_params + variable: sql_mode + value: '{{ sql_mode_orig.query_result[0][0].sql_mode }}' + mode: "{% if db_engine == 'mariadb' %}global{% else %}persist{% endif %}" + + - name: Issue-671| Teardown user_name_2 + ansible.builtin.include_tasks: + file: utils/remove_user.yml + vars: + user_name: "{{ user_name_2 }}" \ No newline at end of file diff --git a/tests/integration/targets/test_mysql_user/tasks/main.yml b/tests/integration/targets/test_mysql_user/tasks/main.yml index e77c443..9244570 100644 --- a/tests/integration/targets/test_mysql_user/tasks/main.yml +++ b/tests/integration/targets/test_mysql_user/tasks/main.yml @@ -282,6 +282,12 @@ - import_tasks: issue-64560.yaml tags: - issue-64560 + + - name: Test ANSI_QUOTES + ansible.builtin.import_tasks: + file: issue-671.yaml + tags: + - issue-671 # Test that mysql_user still works with force_context enabled (database set to "mysql") # (https://github.com/ansible-collections/community.mysql/issues/265) From 7d787eb238738e158f6ad8626d65b61a0a94b902 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20Inderm=C3=BChle?= Date: Thu, 7 Nov 2024 10:37:10 +0100 Subject: [PATCH 206/217] Add contributors from last 10 PR pages (#688) I've applied a sort on the whole file. This Patch is hard to read, sorry. I've remove nobody! Only move! --- CONTRIBUTORS | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 06fb579..6d946cc 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -17,9 +17,11 @@ amitk79 amree Andersson007 andrewhowdencom +aneustroev ansibot anthonyxpalermo antonioribeiro +Aohzan apollo13 aquach arcmop @@ -33,6 +35,8 @@ baldpale banyek BarbzYHOOL Berbe +betanummeric +bigo8525 bizmate bjne bmalynovytch @@ -46,6 +50,7 @@ candeira caphrim007 cdalbergue checkphi +chriscroome chrismeyersfsu ChristopherGAndrews cmodijk @@ -56,13 +61,14 @@ CormacBracken cosmix cptMikky crashes +d-lee +d-rupp dagwieers damianmoore Davidffry denisemauldin +dennisurtubia diclophis -d-lee -d-rupp dmp1ce dnelson dramaley @@ -72,9 +78,11 @@ DSpeichert dungdm93 dwagelaar dylanjbarth -einarc E-M +einarc +elpavel eowin +eRadical Ernest0x esamattis Everspace @@ -82,24 +90,30 @@ F21 faitno felixfontein flatrocks +FlorianPerrot fourjay fraff +francescsanjuanmrf g00fy- geerlingguy georgeOsdDev ghjm ghost +GhostLyrics giacmir giorgio-v gkoller +gotmax23 gottwald gstorme gundalow hansbaer hchargois hluaces +hubiongithub hwali hyperfocus1338 +IBims1NicerTobi igormukhingmailcom imjoseangel infigoKriti @@ -164,8 +178,8 @@ markdorison markotitel marktheunissen markuman -mattclay matt-horwood-mayden +mattclay mavimo maxamillion maxbube @@ -184,11 +198,15 @@ mkrizek mmoya mohag mohsenSy +moledzki mpdehaan +MRMegaNova MRwangyd +mstinsky mverwijs mvgrimes mysqlbox +n-cc netmonk nhojpatrick nicolas-g @@ -202,7 +220,9 @@ organman91 p53 pakal paulbadcock +paulcampbell-ayroc pennycoders +perlun petoju petracvv pgrenaud @@ -223,12 +243,14 @@ richlv riupie rndmh3ro robertdebock +robertsilen robpblake rokka-n Roxyrob roysmith rsicart rthouvenin +rujschafer ruudk samccann samdoran @@ -242,6 +264,7 @@ shrikeh sivel skalfyfan skoriy88 +SoledaD208 sperantus spoyd steverweber @@ -262,19 +285,22 @@ time-palominodb timorunge Tomasthanes tomdymond +tompal3 Tronde tuhoanganh tvlooy tyll UncertaintyP unnecessary-username +v-zhuravlev vamshi8 vanne vdboor vmahadev -v-zhuravlev +webknjaz webmat wedi +wfelipew whysthatso willthames windowsansiblernew From d613fa19938d24ce6adccf792040d2f849ca3083 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20Inderm=C3=BChle?= Date: Mon, 18 Nov 2024 15:44:39 +0100 Subject: [PATCH 207/217] Fix wrong documentation assertion (#690) --- plugins/modules/mysql_db.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/mysql_db.py b/plugins/modules/mysql_db.py index 4a2c954..e1d1a7a 100644 --- a/plugins/modules/mysql_db.py +++ b/plugins/modules/mysql_db.py @@ -159,7 +159,7 @@ options: pipefail: description: - Use C(bash) instead of C(sh) and add C(-o pipefail) to catch errors from the - mysql_dump command when I(state=import) and compression is used. + mysql_dump command when I(state=dump) and compression is used. - The default is C(no) to prevent issues on systems without bash as a default interpreter. - The default will change to C(yes) in community.mysql 4.0.0. type: bool From 9057637844d81cc84ac7f0d9a80bfa1df2de3275 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20Inderm=C3=BChle?= Date: Tue, 19 Nov 2024 08:51:03 +0100 Subject: [PATCH 208/217] mysql_info - add table count to the databases returned values (#691) * Add tables count per database * Add integrations tests * Deduplicate tests between main and new task file --- .../591-mysql_info-db_tables_count.yml | 3 + plugins/modules/mysql_info.py | 65 +++---- .../tasks/filter_databases.yml | 161 ++++++++++++++++++ .../targets/test_mysql_info/tasks/main.yml | 89 +--------- 4 files changed, 202 insertions(+), 116 deletions(-) create mode 100644 changelogs/fragments/591-mysql_info-db_tables_count.yml create mode 100644 tests/integration/targets/test_mysql_info/tasks/filter_databases.yml diff --git a/changelogs/fragments/591-mysql_info-db_tables_count.yml b/changelogs/fragments/591-mysql_info-db_tables_count.yml new file mode 100644 index 0000000..abbc1cb --- /dev/null +++ b/changelogs/fragments/591-mysql_info-db_tables_count.yml @@ -0,0 +1,3 @@ +--- +minor_changes: + - mysql_info - adds the count of tables for each database to the returned values. It is possible to exclude this new field using the ``db_table_count`` exclusion filter. (https://github.com/ansible-collections/community.mysql/pull/691) diff --git a/plugins/modules/mysql_info.py b/plugins/modules/mysql_info.py index 3a30597..8c3845d 100644 --- a/plugins/modules/mysql_info.py +++ b/plugins/modules/mysql_info.py @@ -35,7 +35,7 @@ options: exclude_fields: description: - List of fields which are not needed to collect. - - "Supports elements: C(db_size). Unsupported elements will be ignored." + - "Supports elements: C(db_size), C(db_table_count). Unsupported elements will be ignored." type: list elements: str version_added: '0.1.0' @@ -204,13 +204,19 @@ databases: returned: if not excluded by filter type: dict sample: - - { "mysql": { "size": 656594 }, "information_schema": { "size": 73728 } } + - { "mysql": { "size": 656594, "tables": 31 }, "information_schema": { "size": 73728, "tables": 79 } } contains: size: description: Database size in bytes. returned: if not excluded by filter type: dict sample: { 'size': 656594 } + tables: + description: Count of tables and views in that database. + returned: if not excluded by filter + type: dict + sample: { 'tables': 12 } + version_added: '3.11.0' settings: description: Global settings (variables) information. returned: if not excluded by filter @@ -656,40 +662,39 @@ class MySQL_Info(object): def __get_databases(self, exclude_fields, return_empty_dbs): """Get info about databases.""" - if not exclude_fields: - query = ('SELECT table_schema AS "name", ' - 'SUM(data_length + index_length) AS "size" ' - 'FROM information_schema.TABLES GROUP BY table_schema') - else: - if 'db_size' in exclude_fields: - query = ('SELECT table_schema AS "name" ' - 'FROM information_schema.TABLES GROUP BY table_schema') - res = self.__exec_sql(query) + def is_field_included(field_name): + return not exclude_fields or 'db_{}'.format(field_name) not in exclude_fields - if res: - for db in res: - self.info['databases'][db['name']] = {} + def create_db_info(db_data): + info = {} + if is_field_included('size'): + info['size'] = int(db_data.get('size', 0) or 0) + if is_field_included('table_count'): + info['tables'] = int(db_data.get('tables', 0) or 0) + return info - if not exclude_fields or 'db_size' not in exclude_fields: - if db['size'] is None: - db['size'] = 0 + # Build the main query + query_parts = ['SELECT table_schema AS "name"'] + if is_field_included('size'): + query_parts.append('SUM(data_length + index_length) AS "size"') + if is_field_included('table_count'): + query_parts.append('COUNT(table_name) as "tables"') - self.info['databases'][db['name']]['size'] = int(db['size']) + query = "{} FROM information_schema.TABLES GROUP BY table_schema".format(", ".join(query_parts)) - # If empty dbs are not needed in the returned dict, exit from the method - if not return_empty_dbs: - return None + # Get and process databases with tables + databases = self.__exec_sql(query) or [] + for db in databases: + self.info['databases'][db['name']] = create_db_info(db) - # Add info about empty databases (issue #65727): - res = self.__exec_sql('SHOW DATABASES') - if res: - for db in res: - if db['Database'] not in self.info['databases']: - self.info['databases'][db['Database']] = {} - - if not exclude_fields or 'db_size' not in exclude_fields: - self.info['databases'][db['Database']]['size'] = 0 + # Handle empty databases if requested + if return_empty_dbs: + empty_databases = self.__exec_sql('SHOW DATABASES') or [] + for db in empty_databases: + db_name = db['Database'] + if db_name not in self.info['databases']: + self.info['databases'][db_name] = create_db_info({}) def __exec_sql(self, query, ddl=False): """Execute SQL. diff --git a/tests/integration/targets/test_mysql_info/tasks/filter_databases.yml b/tests/integration/targets/test_mysql_info/tasks/filter_databases.yml new file mode 100644 index 0000000..da1058b --- /dev/null +++ b/tests/integration/targets/test_mysql_info/tasks/filter_databases.yml @@ -0,0 +1,161 @@ +--- + +- module_defaults: + community.mysql.mysql_db: &mysql_defaults + login_user: "{{ mysql_user }}" + login_password: "{{ mysql_password }}" + login_host: "{{ mysql_host }}" + login_port: "{{ mysql_primary_port }}" + community.mysql.mysql_query: *mysql_defaults + community.mysql.mysql_info: *mysql_defaults + community.mysql.mysql_user: *mysql_defaults + + block: + + # ================================ Prepare ============================== + - name: Mysql_info databases | Prepare | Create databases + community.mysql.mysql_db: + name: + - db_tables_count_empty + - db_tables_count_1 + - db_tables_count_2 + - db_only_views # https://github.com/ansible-Getions/community.mysql/issues/204 + state: present + + - name: Mysql_info databases | Prepare | Create tables + community.mysql.mysql_query: + query: + - >- + CREATE TABLE IF NOT EXISTS db_tables_count_1.t1 + (id int, name varchar(9)) + - >- + CREATE TABLE IF NOT EXISTS db_tables_count_2.t1 + (id int, name1 varchar(9)) + - >- + CREATE TABLE IF NOT EXISTS db_tables_count_2.t2 + (id int, name1 varchar(9)) + - >- + CREATE VIEW db_only_views.v_today (today) AS SELECT CURRENT_DATE + + # ================================== Tests ============================== + + - name: Mysql_info databases | Get all non-empty databases fields + community.mysql.mysql_info: + filter: + - databases + register: result + failed_when: + - > + result.databases['db_tables_count_1'].size != 16384 or + result.databases['db_tables_count_1'].tables != 1 or + result.databases['db_tables_count_2'].size != 32768 or + result.databases['db_tables_count_2'].tables != 2 or + result.databases['db_only_views'].size != 0 or + result.databases['db_only_views'].tables != 1 or + 'db_tables_count_empty' in result.databases | dict2items + | map(attribute='key') + + - name: Mysql_info databases | Get all dbs fields except db_size + community.mysql.mysql_info: + filter: + - databases + exclude_fields: + - db_size + register: result + failed_when: + - > + result.databases['db_tables_count_1'].size is defined or + result.databases['db_tables_count_1'].tables != 1 or + result.databases['db_tables_count_2'].size is defined or + result.databases['db_tables_count_2'].tables != 2 or + result.databases['db_only_views'].size is defined or + result.databases['db_only_views'].tables != 1 or + 'db_tables_count_empty' in result.databases | dict2items + | map(attribute='key') + + # 'unsupported' element is passed to check that an unsupported value + # won't break anything (will be ignored regarding to the module's + # documentation). + - name: Mysql_info databases | Get all dbs fields with unsupported value + community.mysql.mysql_info: + filter: + - databases + exclude_fields: + - db_size + - unsupported + register: result + failed_when: + - > + result.databases['db_tables_count_1'].size is defined or + result.databases['db_tables_count_1'].tables != 1 or + result.databases['db_tables_count_2'].size is defined or + result.databases['db_tables_count_2'].tables != 2 or + result.databases['db_only_views'].size is defined or + result.databases['db_only_views'].tables != 1 or + 'db_tables_count_empty' in result.databases | dict2items + | map(attribute='key') + + - name: Mysql_info databases | Get all dbs fields except tables + community.mysql.mysql_info: + filter: + - databases + exclude_fields: + - db_table_count + register: result + failed_when: + - > + result.databases['db_tables_count_1'].size != 16384 or + result.databases['db_tables_count_1'].tables is defined or + result.databases['db_tables_count_2'].size != 32768 or + result.databases['db_tables_count_2'].tables is defined or + result.databases['db_only_views'].size != 0 or + result.databases['db_only_views'].tables is defined or + 'db_tables_count_empty' in result.databases | dict2items + | map(attribute='key') + + - name: Mysql_info databases | Get all dbs even empty ones + community.mysql.mysql_info: + filter: + - databases + return_empty_dbs: true + register: result + failed_when: + - > + result.databases['db_tables_count_1'].size != 16384 or + result.databases['db_tables_count_1'].tables != 1 or + result.databases['db_tables_count_2'].size != 32768 or + result.databases['db_tables_count_2'].tables != 2 or + result.databases['db_only_views'].size != 0 or + result.databases['db_only_views'].tables != 1 or + result.databases['db_tables_count_empty'].size != 0 or + result.databases['db_tables_count_empty'].tables != 0 + + - name: Mysql_info databases | Get all dbs even empty ones without size + community.mysql.mysql_info: + filter: + - databases + exclude_fields: + - db_size + return_empty_dbs: true + register: result + failed_when: + - > + result.databases['db_tables_count_1'].size is defined or + result.databases['db_tables_count_1'].tables != 1 or + result.databases['db_tables_count_2'].size is defined or + result.databases['db_tables_count_2'].tables != 2 or + result.databases['db_only_views'].size is defined or + result.databases['db_only_views'].tables != 1 or + result.databases['db_tables_count_empty'].size is defined or + result.databases['db_tables_count_empty'].tables != 0 + + # ================================== Cleanup ============================ + + - name: Mysql_info databases | Cleanup databases + community.mysql.mysql_db: + name: + - db_tables_count_empty + - db_tables_count_1 + - db_tables_count_2 + - db_only_views + 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 42350c6..61f238f 100644 --- a/tests/integration/targets/test_mysql_info/tasks/main.yml +++ b/tests/integration/targets/test_mysql_info/tasks/main.yml @@ -132,94 +132,11 @@ - result.global_status is not defined - result.users is not defined - # Test exclude_fields: db_size - # 'unsupported' element is passed to check that an unsupported value - # won't break anything (will be ignored regarding to the module's documentation). - - name: Collect info about databases excluding their sizes - mysql_info: - <<: *mysql_params - filter: - - databases - exclude_fields: - - db_size - - unsupported - register: result - - - assert: - that: - - result is not changed - - result.databases != {} - - result.databases.mysql == {} - - ######################################################## - # Issue #65727, empty databases must be in returned dict - # - - name: Create empty database acme - mysql_db: - <<: *mysql_params - name: acme - - - name: Collect info about databases - mysql_info: - <<: *mysql_params - filter: - - databases - return_empty_dbs: true - register: result - - # Check acme is in returned dict - - assert: - that: - - result is not changed - - result.databases.acme.size == 0 - - result.databases.mysql != {} - - - name: Collect info about databases excluding their sizes - mysql_info: - <<: *mysql_params - filter: - - databases - exclude_fields: - - db_size - return_empty_dbs: true - register: result - - # Check acme is in returned dict - - assert: - that: - - result is not changed - - result.databases.acme == {} - - result.databases.mysql == {} - - - name: Remove acme database - mysql_db: - <<: *mysql_params - name: acme - state: absent - - include_tasks: issue-28.yml - # https://github.com/ansible-collections/community.mysql/issues/204 - - name: Create database containing only views - mysql_db: - <<: *mysql_params - name: allviews - - - name: Create view - mysql_query: - <<: *mysql_params - login_db: allviews - query: 'CREATE VIEW v_today (today) AS SELECT CURRENT_DATE' - - - name: Fetch info - mysql_info: - <<: *mysql_params - register: result - - - name: Check - assert: - that: - - result.databases.allviews.size == 0 + - name: Import tasks file to tests tables count in database filter + ansible.builtin.import_tasks: + file: filter_databases.yml - name: Import tasks file to tests users_info filter ansible.builtin.import_tasks: From e437d562c1fec1979906c639bc579a69072a38ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20Inderm=C3=BChle?= Date: Tue, 19 Nov 2024 10:51:58 +0100 Subject: [PATCH 209/217] Release 3.11.0 commit (#692) --- CHANGELOG.rst | 20 ++++++++ changelogs/changelog.yaml | 49 +++++++++++++++---- .../591-mysql_info-db_tables_count.yml | 3 -- .../fragments/671-modules_util_user.yml | 12 ----- galaxy.yml | 2 +- 5 files changed, 60 insertions(+), 26 deletions(-) delete mode 100644 changelogs/fragments/591-mysql_info-db_tables_count.yml delete mode 100644 changelogs/fragments/671-modules_util_user.yml diff --git a/CHANGELOG.rst b/CHANGELOG.rst index cf1162f..a6ada35 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,26 @@ Community MySQL and MariaDB Collection Release Notes This changelog describes changes after version 2.0.0. +v3.11.0 +======= + +Release Summary +--------------- + +This is a minor release of the ``community.mysql`` collection. +This changelog contains all changes to the modules and plugins in this +collection that have been made after the previous release. + +Minor Changes +------------- + +- mysql_info - adds the count of tables for each database to the returned values. It is possible to exclude this new field using the ``db_table_count`` exclusion filter. (https://github.com/ansible-collections/community.mysql/pull/691) + +Bugfixes +-------- + +- mysql_user,mysql_role - The sql_mode ANSI_QUOTES affects how the modules mysql_user and mysql_role compare the existing privileges with the configured privileges, as well as decide whether double quotes or backticks should be used in the GRANT statements. Pointing out in issue 671, the modules mysql_user and mysql_role allow users to enable/disable ANSI_QUOTES in session variable (within a DB session, the session variable always overwrites the global one). But due to the issue, the modules do not check for ANSI_MODE in the session variable, instead, they only check in the GLOBAL one.That behavior is not only limiting the users' flexibility, but also not allowing users to explicitly disable ANSI_MODE to work around such bugs like https://bugs.mysql.com/bug.php?id=115953. (https://github.com/ansible-collections/community.mysql/issues/671) + v3.10.3 ======= diff --git a/changelogs/changelog.yaml b/changelogs/changelog.yaml index 27ae315..8e5aeaf 100644 --- a/changelogs/changelog.yaml +++ b/changelogs/changelog.yaml @@ -99,13 +99,6 @@ releases: release_date: '2022-04-26' 3.10.0: changes: - deprecated_features: - - collection - support of mysqlclient connector is deprecated - use PyMySQL - connector instead! We will stop testing against it in collection version 4.0.0 - and remove the related code in 5.0.0 (https://github.com/ansible-collections/community.mysql/issues/654). - - mysql_info - The ``users_info`` filter returned variable ``plugin_auth_string`` - contains the hashed password and it's misleading, it will be removed from - community.mysql 4.0.0. Use the `plugin_hash_string` return value instead (https://github.com/ansible-collections/community.mysql/pull/629). bugfixes: - mysql_info - Add ``plugin_hash_string`` to ``users_info`` filter's output. The existing ``plugin_auth_string`` contained the hashed password and thus @@ -122,6 +115,13 @@ releases: avoid versions 3.8.0 to 3.9.0 (https://github.com/ansible-collections/community.mysql/pull/642). - mysql_user - add correct ``ed25519`` auth plugin handling (https://github.com/ansible-collections/community.mysql/issues/6). - mysql_variables - fix the module always changes on boolean values (https://github.com/ansible-collections/community.mysql/issues/652). + deprecated_features: + - collection - support of mysqlclient connector is deprecated - use PyMySQL + connector instead! We will stop testing against it in collection version 4.0.0 + and remove the related code in 5.0.0 (https://github.com/ansible-collections/community.mysql/issues/654). + - mysql_info - The ``users_info`` filter returned variable ``plugin_auth_string`` + contains the hashed password and it's misleading, it will be removed from + community.mysql 4.0.0. Use the `plugin_hash_string` return value instead (https://github.com/ansible-collections/community.mysql/pull/629). minor_changes: - mysql_info - Add ``tls_requires`` returned value for the ``users_info`` filter (https://github.com/ansible-collections/community.mysql/pull/628). @@ -158,13 +158,13 @@ releases: release_date: '2024-08-22' 3.10.1: changes: + bugfixes: + - mysql_user - module makes changes when is executed with ``plugin_auth_string`` + parameter and check mode. deprecated_features: - mysql_user - the ``user`` alias of the ``name`` argument has been deprecated and will be removed in collection version 5.0.0. Use the ``name`` argument instead. - bugfixes: - - mysql_user - module makes changes when is executed with ``plugin_auth_string`` - parameter and check mode. release_summary: 'This is a patch release of the ``community.mysql`` collection. Besides a bugfix, it contains an important upcoming breaking-change information.' @@ -201,6 +201,35 @@ releases: - 0-mysql_user.yml - 3.10.3.yml release_date: '2024-09-09' + 3.11.0: + changes: + bugfixes: + - mysql_user,mysql_role - The sql_mode ANSI_QUOTES affects how the modules mysql_user + and mysql_role compare the existing privileges with the configured privileges, + as well as decide whether double quotes or backticks should be used in the + GRANT statements. Pointing out in issue 671, the modules mysql_user and mysql_role + allow users to enable/disable ANSI_QUOTES in session variable (within a DB + session, the session variable always overwrites the global one). But due to + the issue, the modules do not check for ANSI_MODE in the session variable, + instead, they only check in the GLOBAL one.That behavior is not only limiting + the users' flexibility, but also not allowing users to explicitly disable + ANSI_MODE to work around such bugs like https://bugs.mysql.com/bug.php?id=115953. + (https://github.com/ansible-collections/community.mysql/issues/671) + minor_changes: + - mysql_info - adds the count of tables for each database to the returned values. + It is possible to exclude this new field using the ``db_table_count`` exclusion + filter. (https://github.com/ansible-collections/community.mysql/pull/691) + release_summary: 'This is a minor release of the ``community.mysql`` collection. + + + This changelog contains all changes to the modules and plugins in this + + collection that have been made after the previous release.' + fragments: + - 3.11.0.yml + - 591-mysql_info-db_tables_count.yml + - 671-modules_util_user.yml + release_date: '2024-11-19' 3.2.0: changes: bugfixes: diff --git a/changelogs/fragments/591-mysql_info-db_tables_count.yml b/changelogs/fragments/591-mysql_info-db_tables_count.yml deleted file mode 100644 index abbc1cb..0000000 --- a/changelogs/fragments/591-mysql_info-db_tables_count.yml +++ /dev/null @@ -1,3 +0,0 @@ ---- -minor_changes: - - mysql_info - adds the count of tables for each database to the returned values. It is possible to exclude this new field using the ``db_table_count`` exclusion filter. (https://github.com/ansible-collections/community.mysql/pull/691) diff --git a/changelogs/fragments/671-modules_util_user.yml b/changelogs/fragments/671-modules_util_user.yml deleted file mode 100644 index a913651..0000000 --- a/changelogs/fragments/671-modules_util_user.yml +++ /dev/null @@ -1,12 +0,0 @@ -bugfixes: - - mysql_user,mysql_role - The sql_mode ANSI_QUOTES affects how the modules mysql_user - and mysql_role compare the existing privileges with the configured privileges, - as well as decide whether double quotes or backticks should be used in the GRANT - statements. Pointing out in issue 671, the modules mysql_user and mysql_role allow - users to enable/disable ANSI_QUOTES in session variable (within a DB session, the - session variable always overwrites the global one). But due to the issue, the modules - do not check for ANSI_MODE in the session variable, instead, they only check in the - GLOBAL one.That behavior is not only limiting the users' flexibility, but also not - allowing users to explicitly disable ANSI_MODE to work around such bugs like - https://bugs.mysql.com/bug.php?id=115953. - (https://github.com/ansible-collections/community.mysql/issues/671) \ No newline at end of file diff --git a/galaxy.yml b/galaxy.yml index 0046b5a..1ecd6f2 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -1,7 +1,7 @@ --- namespace: community name: mysql -version: 3.10.3 +version: 3.11.0 readme: README.md authors: - Ansible community From 3d3f115574adf10a6c8552b5d811a45aef2597ba Mon Sep 17 00:00:00 2001 From: Laurent Indermuehle Date: Tue, 19 Nov 2024 10:56:37 +0100 Subject: [PATCH 210/217] Add next expected version --- galaxy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/galaxy.yml b/galaxy.yml index 1ecd6f2..4830311 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -1,7 +1,7 @@ --- namespace: community name: mysql -version: 3.11.0 +version: 3.11.1 readme: README.md authors: - Ansible community From 022ed60906c36beb9082b7d39ba1aa4602199306 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Fri, 13 Dec 2024 09:21:06 +0100 Subject: [PATCH 211/217] Fix linting issues (#693) --- plugins/modules/mysql_replication.py | 1 - plugins/modules/mysql_user.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/modules/mysql_replication.py b/plugins/modules/mysql_replication.py index 35659d3..b902da0 100644 --- a/plugins/modules/mysql_replication.py +++ b/plugins/modules/mysql_replication.py @@ -284,7 +284,6 @@ EXAMPLES = r''' community.mysql.mysql_replication: mode: changeprimary fail_on_error: true - ''' RETURN = r''' diff --git a/plugins/modules/mysql_user.py b/plugins/modules/mysql_user.py index cf210a3..499f2a0 100644 --- a/plugins/modules/mysql_user.py +++ b/plugins/modules/mysql_user.py @@ -269,7 +269,7 @@ EXAMPLES = r''' priv: '*.*:ALL,GRANT' state: present session_vars: - wsrep_on: off + wsrep_on: 'off' - name: Create user with password, all database privileges and 'WITH GRANT OPTION' in db1 and db2 community.mysql.mysql_user: From a45a0d006d5654da57ea6a0f6692fba238646113 Mon Sep 17 00:00:00 2001 From: Sergio <45396489+Sergio-IME@users.noreply.github.com> Date: Thu, 16 Jan 2025 09:35:04 +0100 Subject: [PATCH 212/217] mysql_db: added `zstd` support (#696) --- changelogs/fragments/696-mysql-db-add-zstd-support.yml | 3 +++ plugins/modules/mysql_db.py | 8 ++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 changelogs/fragments/696-mysql-db-add-zstd-support.yml diff --git a/changelogs/fragments/696-mysql-db-add-zstd-support.yml b/changelogs/fragments/696-mysql-db-add-zstd-support.yml new file mode 100644 index 0000000..537fc6e --- /dev/null +++ b/changelogs/fragments/696-mysql-db-add-zstd-support.yml @@ -0,0 +1,3 @@ +minor_changes: +- mysql_db - added ``zstd`` (de)compression support for ``import``/``dump`` states + (https://github.com/ansible-collections/community.mysql/issues/696). diff --git a/plugins/modules/mysql_db.py b/plugins/modules/mysql_db.py index e1d1a7a..e108054 100644 --- a/plugins/modules/mysql_db.py +++ b/plugins/modules/mysql_db.py @@ -46,8 +46,8 @@ options: target: description: - Location, on the remote host, of the dump file to read from or write to. - - Uncompressed SQL files (C(.sql)) as well as bzip2 (C(.bz2)), gzip (C(.gz)) and - xz (Added in 2.0) compressed files are supported. + - Uncompressed SQL files (C(.sql)) as well as bzip2 (C(.bz2)), gzip (C(.gz)), + xz (Added in 2.0) and zstd (C(.zst)) (Added in 3.12.0) compressed files are supported. type: path single_transaction: description: @@ -455,6 +455,8 @@ def db_dump(module, host, user, password, db_name, target, all_databases, port, path = module.get_bin_path('bzip2', True) elif os.path.splitext(target)[-1] == '.xz': path = module.get_bin_path('xz', True) + elif os.path.splitext(target)[-1] == '.zst': + path = module.get_bin_path('zstd', True) if path: cmd = '%s | %s > %s' % (cmd, path, shlex_quote(target)) @@ -526,6 +528,8 @@ def db_import(module, host, user, password, db_name, target, all_databases, port comp_prog_path = module.get_bin_path('bzip2', required=True) elif os.path.splitext(target)[-1] == '.xz': comp_prog_path = module.get_bin_path('xz', required=True) + elif os.path.splitext(target)[-1] == '.zst': + comp_prog_path = module.get_bin_path('zstd', required=True) if comp_prog_path: # The line below is for returned data only: executed_commands.append('%s -dc %s | %s' % (comp_prog_path, target, cmd)) From 960ac32adffac3ff91c1c307ca04c62667a11b2b Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Thu, 16 Jan 2025 15:49:53 +0100 Subject: [PATCH 213/217] mysql_query: returns execution_time_ms list containing execution time per query (#697) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * mysql_query: returns execution_time_ms list containing execution time per query * Update changelogs/fragments/0-mysql_query-returns-exec-time-ms.yml Co-authored-by: Laurent Indermühle --- .../0-mysql_query-returns-exec-time-ms.yml | 2 ++ plugins/modules/mysql_query.py | 28 +++++++++++++++++-- .../tasks/mysql_query_initial.yml | 3 ++ 3 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 changelogs/fragments/0-mysql_query-returns-exec-time-ms.yml diff --git a/changelogs/fragments/0-mysql_query-returns-exec-time-ms.yml b/changelogs/fragments/0-mysql_query-returns-exec-time-ms.yml new file mode 100644 index 0000000..d17628c --- /dev/null +++ b/changelogs/fragments/0-mysql_query-returns-exec-time-ms.yml @@ -0,0 +1,2 @@ +minor_changes: +- mysql_query - returns the ``execution_time_ms`` list containing execution time per query in milliseconds. diff --git a/plugins/modules/mysql_query.py b/plugins/modules/mysql_query.py index 2cdf096..35beeb3 100644 --- a/plugins/modules/mysql_query.py +++ b/plugins/modules/mysql_query.py @@ -62,7 +62,6 @@ author: - Andrew Klychkov (@Andersson007) extends_documentation_fragment: - community.mysql.mysql - ''' EXAMPLES = r''' @@ -117,8 +116,18 @@ rowcount: returned: changed type: list sample: [5, 1] +execution_time_ms: + description: + - A list containing execution time per query in milliseconds. + - The measurements are done right before and after passing + the query to the driver for execution. + returned: success + type: list + sample: [7104, 85] + version_added: '3.12.0' ''' +import time import warnings from ansible.module_utils.basic import AnsibleModule @@ -139,6 +148,18 @@ DDL_QUERY_KEYWORDS = ('CREATE', 'DROP', 'ALTER', 'RENAME', 'TRUNCATE') # Module execution. # + +def execute_and_return_time(cursor, query, args): + # Measure query execution time in milliseconds + start_time = time.perf_counter() + + cursor.execute(query, args) + + # Calculate the execution time rounding it to 4 decimal places + exec_time_ms = round((time.perf_counter() - start_time) * 1000, 4) + return cursor, exec_time_ms + + def main(): argument_spec = mysql_common_argument_spec() argument_spec.update( @@ -213,6 +234,7 @@ def main(): query_result = [] executed_queries = [] rowcount = [] + execution_time_ms = [] already_exists = False for q in query: @@ -223,7 +245,8 @@ def main(): category=mysql_driver.Warning) try: - cursor.execute(q, arguments) + cursor, exec_time_ms = execute_and_return_time(cursor, q, arguments) + execution_time_ms.append(exec_time_ms) except mysql_driver.Warning: # When something is run with IF NOT EXISTS # and there's "already exists" MySQL warning, @@ -280,6 +303,7 @@ def main(): 'executed_queries': executed_queries, 'query_result': query_result, 'rowcount': rowcount, + 'execution_time_ms': execution_time_ms, } # Exit: diff --git a/tests/integration/targets/test_mysql_query/tasks/mysql_query_initial.yml b/tests/integration/targets/test_mysql_query/tasks/mysql_query_initial.yml index fbf5ca8..310f925 100644 --- a/tests/integration/targets/test_mysql_query/tasks/mysql_query_initial.yml +++ b/tests/integration/targets/test_mysql_query/tasks/mysql_query_initial.yml @@ -35,6 +35,7 @@ that: - result is changed - result.executed_queries == ['CREATE TABLE {{ test_table1 }} (id int)'] + - result.execution_time_ms[0] > 0 - name: Insert test data mysql_query: @@ -52,6 +53,8 @@ - result is changed - result.rowcount == [2, 1] - result.executed_queries == ['INSERT INTO {{ test_table1 }} VALUES (1), (2)', 'INSERT INTO {{ test_table1 }} VALUES (3)'] + - result.execution_time_ms[0] > 0 + - result.execution_time_ms[1] > 0 - name: Check data in {{ test_table1 }} mysql_query: From e9845b0a1caba4344aab9e957865ac74ab17fc7f Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Fri, 17 Jan 2025 10:11:27 +0100 Subject: [PATCH 214/217] Release 3.12.0 commit (#698) --- CHANGELOG.rst | 17 +++++++++++++++++ changelogs/changelog.yaml | 17 +++++++++++++++++ .../0-mysql_query-returns-exec-time-ms.yml | 2 -- .../fragments/696-mysql-db-add-zstd-support.yml | 3 --- galaxy.yml | 2 +- 5 files changed, 35 insertions(+), 6 deletions(-) delete mode 100644 changelogs/fragments/0-mysql_query-returns-exec-time-ms.yml delete mode 100644 changelogs/fragments/696-mysql-db-add-zstd-support.yml diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a6ada35..ba19887 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,22 @@ Community MySQL and MariaDB Collection Release Notes This changelog describes changes after version 2.0.0. +v3.12.0 +======= + +Release Summary +--------------- + +This is a minor release of the ``community.mysql`` collection. +This changelog contains all changes to the modules and plugins in this +collection that have been made after the previous release. + +Minor Changes +------------- + +- mysql_db - added ``zstd`` (de)compression support for ``import``/``dump`` states (https://github.com/ansible-collections/community.mysql/issues/696). +- mysql_query - returns the ``execution_time_ms`` list containing execution time per query in milliseconds. + v3.11.0 ======= @@ -13,6 +29,7 @@ Release Summary --------------- This is a minor release of the ``community.mysql`` collection. + This changelog contains all changes to the modules and plugins in this collection that have been made after the previous release. diff --git a/changelogs/changelog.yaml b/changelogs/changelog.yaml index 8e5aeaf..fa08150 100644 --- a/changelogs/changelog.yaml +++ b/changelogs/changelog.yaml @@ -230,6 +230,23 @@ releases: - 591-mysql_info-db_tables_count.yml - 671-modules_util_user.yml release_date: '2024-11-19' + 3.12.0: + changes: + minor_changes: + - mysql_db - added ``zstd`` (de)compression support for ``import``/``dump`` + states (https://github.com/ansible-collections/community.mysql/issues/696). + - mysql_query - returns the ``execution_time_ms`` list containing execution + time per query in milliseconds. + release_summary: 'This is a minor release of the ``community.mysql`` collection. + + This changelog contains all changes to the modules and plugins in this + + collection that have been made after the previous release.' + fragments: + - 0-mysql_query-returns-exec-time-ms.yml + - 3.12.0.yml + - 696-mysql-db-add-zstd-support.yml + release_date: '2025-01-17' 3.2.0: changes: bugfixes: diff --git a/changelogs/fragments/0-mysql_query-returns-exec-time-ms.yml b/changelogs/fragments/0-mysql_query-returns-exec-time-ms.yml deleted file mode 100644 index d17628c..0000000 --- a/changelogs/fragments/0-mysql_query-returns-exec-time-ms.yml +++ /dev/null @@ -1,2 +0,0 @@ -minor_changes: -- mysql_query - returns the ``execution_time_ms`` list containing execution time per query in milliseconds. diff --git a/changelogs/fragments/696-mysql-db-add-zstd-support.yml b/changelogs/fragments/696-mysql-db-add-zstd-support.yml deleted file mode 100644 index 537fc6e..0000000 --- a/changelogs/fragments/696-mysql-db-add-zstd-support.yml +++ /dev/null @@ -1,3 +0,0 @@ -minor_changes: -- mysql_db - added ``zstd`` (de)compression support for ``import``/``dump`` states - (https://github.com/ansible-collections/community.mysql/issues/696). diff --git a/galaxy.yml b/galaxy.yml index 4830311..cf87c64 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -1,7 +1,7 @@ --- namespace: community name: mysql -version: 3.11.1 +version: 3.12.0 readme: README.md authors: - Ansible community From dd7e297d509d833dac5bd721d1e48a170079748e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20Inderm=C3=BChle?= Date: Mon, 10 Mar 2025 18:55:42 +0100 Subject: [PATCH 215/217] Add support for MariaDB 11.4 (#703) * fix missing symlink to mysql binaries for MariaDB 11+ * update tested version of MariaDB 11.4 instead of 10.5 * add changelog fragment * [CI] add way to trigger workflow manually Useful in the case we don't modifiy any files in the paths: sections of the push event. * add version check for mariadb < 10.4.6 without mariadb* binaries * Use same concatenation method between functions to avoid future confusion I didn't notice that db_dump and db_import were different, thus I introduced a bug with the initialization of the variable cmd. This commit fixes that. --- .github/workflows/ansible-test-plugins.yml | 20 +++--- Makefile | 23 +++++-- README.md | 4 +- TESTING.md | 4 +- changelogs/fragments/tests_mariadb_11_4.yml | 5 ++ plugins/modules/mysql_db.py | 76 +++++++++++++-------- plugins/modules/mysql_info.py | 1 + 7 files changed, 84 insertions(+), 49 deletions(-) create mode 100644 changelogs/fragments/tests_mariadb_11_4.yml diff --git a/.github/workflows/ansible-test-plugins.yml b/.github/workflows/ansible-test-plugins.yml index ad8c4b5..0b6c184 100644 --- a/.github/workflows/ansible-test-plugins.yml +++ b/.github/workflows/ansible-test-plugins.yml @@ -13,7 +13,7 @@ on: # yamllint disable-line rule:truthy - '.github/workflows/ansible-test-plugins.yml' schedule: - cron: '0 6 * * *' - + workflow_dispatch: jobs: sanity: @@ -54,8 +54,8 @@ jobs: db_engine_version: - '8.0.38' - '8.4.1' - - '10.5.25' - '10.11.8' + - '11.4.5' connector_name: - pymysql - mysqlclient @@ -87,10 +87,10 @@ jobs: exclude: - db_engine_name: mysql - db_engine_version: '10.5.25' + db_engine_version: '10.11.8' - db_engine_name: mysql - db_engine_version: '10.11.8' + db_engine_version: '11.4.5' - db_engine_name: mariadb db_engine_version: '8.0.38' @@ -119,13 +119,13 @@ jobs: - db_engine_version: '8.0.38' ansible: stable-2.17 - - db_engine_version: '10.5.25' + - db_engine_version: '10.11.8' ansible: stable-2.17 - db_engine_version: '8.0.38' ansible: devel - - db_engine_version: '10.5.25' + - db_engine_version: '10.11.8' ansible: devel - db_engine_version: '8.4.1' @@ -162,7 +162,7 @@ jobs: db_engine_version: '8.0.38' - connector_version: '1.1.1' - db_engine_version: '10.5.25' + db_engine_version: '10.11.8' services: db_primary: @@ -175,7 +175,7 @@ jobs: # We write our own health-cmd because the mariadb container does not # provide a healthcheck options: >- - --health-cmd "mysqladmin ping -P 3306 -pmsandbox |grep alive || exit 1" + --health-cmd "${{ matrix.db_engine_name == 'mysql' && 'mysqladmin' || 'mariadb-admin' }} ping -P 3306 -pmsandbox |grep alive || exit 1" --health-start-period 10s --health-interval 10s --health-timeout 5s @@ -189,7 +189,7 @@ jobs: ports: - 3308:3306 options: >- - --health-cmd "mysqladmin ping -P 3306 -pmsandbox |grep alive || exit 1" + --health-cmd "${{ matrix.db_engine_name == 'mysql' && 'mysqladmin' || 'mariadb-admin' }} ping -P 3306 -pmsandbox |grep alive || exit 1" --health-start-period 10s --health-interval 10s --health-timeout 5s @@ -203,7 +203,7 @@ jobs: ports: - 3309:3306 options: >- - --health-cmd "mysqladmin ping -P 3306 -pmsandbox |grep alive || exit 1" + --health-cmd "${{ matrix.db_engine_name == 'mysql' && 'mysqladmin' || 'mariadb-admin' }} ping -P 3306 -pmsandbox |grep alive || exit 1" --health-start-period 10s --health-interval 10s --health-timeout 5s diff --git a/Makefile b/Makefile index 5a11d1b..b503e2f 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,17 @@ ifdef continue_on_errors _continue_on_errors = --continue-on-error endif +# Set command variables based on database engine +# Required for MariaDB 11+ which no longer includes mysql named compatible +# executable symlinks +ifeq ($(db_engine_name),mysql) + _command = mysqld + _health_cmd = mysqladmin +else + _command = mariadbd + _health_cmd = mariadb-admin +endif + .PHONY: test-integration test-integration: @echo -n $(db_engine_name) > tests/integration/db_engine_name @@ -29,9 +40,9 @@ test-integration: --env MYSQL_ROOT_PASSWORD=msandbox \ --network podman \ --publish 3307:3306 \ - --health-cmd 'mysqladmin ping -P 3306 -pmsandbox | grep alive || exit 1' \ + --health-cmd '$(_health_cmd) ping -P 3306 -pmsandbox | grep alive || exit 1' \ docker.io/library/$(db_engine_name):$(db_engine_version) \ - mysqld + $(_command) podman run \ --detach \ --replace \ @@ -40,9 +51,9 @@ test-integration: --env MYSQL_ROOT_PASSWORD=msandbox \ --network podman \ --publish 3308:3306 \ - --health-cmd 'mysqladmin ping -P 3306 -pmsandbox | grep alive || exit 1' \ + --health-cmd '$(_health_cmd) ping -P 3306 -pmsandbox | grep alive || exit 1' \ docker.io/library/$(db_engine_name):$(db_engine_version) \ - mysqld + $(_command) podman run \ --detach \ --replace \ @@ -51,9 +62,9 @@ test-integration: --env MYSQL_ROOT_PASSWORD=msandbox \ --network podman \ --publish 3309:3306 \ - --health-cmd 'mysqladmin ping -P 3306 -pmsandbox | grep alive || exit 1' \ + --health-cmd '$(_health_cmd) ping -P 3306 -pmsandbox | grep alive || exit 1' \ docker.io/library/$(db_engine_name):$(db_engine_version) \ - mysqld + $(_command) # Setup replication and restart containers using the same subshell to keep variables alive db_ver=$(db_engine_version); \ maj="$${db_ver%.*.*}"; \ diff --git a/README.md b/README.md index 5db2f05..df2404f 100644 --- a/README.md +++ b/README.md @@ -112,10 +112,10 @@ For MariaDB, only Long Term releases are tested. When multiple LTS are available - mariadb:10.3.34 (collection version < 3.5.1) - mariadb:10.4.24 (collection version >= 3.5.2, < 3.10.0) - mariadb:10.5.18 (collection version >= 3.5.2, < 3.10.0) -- mariadb:10.5.25 (collection version >= 3.10.0) +- mariadb:10.5.25 (collection version >= 3.10.0, <3.13.0) - mariadb:10.6.11 (collection version >= 3.5.2, < 3.10.0) - mariadb:10.11.8 (collection version >= 3.10.0) - +- mariadb:11.4.5 (collection version >= 3.13.0) ### Database connectors diff --git a/TESTING.md b/TESTING.md index 1a22832..45e6bba 100644 --- a/TESTING.md +++ b/TESTING.md @@ -65,8 +65,8 @@ The Makefile accept the following options - Choices: - "8.0.38" <- mysql - "8.4.1" <- mysql (NOT WORKING YET, ansible-test uses Ubuntu 20.04 which is too old to install mysql-community-client 8.4) - - "10.5.25" <- mariadb - "10.11.8" <- mariadb + - "11.4.5" <- mariadb - Description: The tag of the container to use for the service containers that will host a primary database and two replicas. Do not use short version, like `mysql:8` (don't do that) because our tests expect a full version to filter tests precisely. For instance: `when: db_version is version ('8.0.22', '>')`. You can use any tag available on [hub.docker.com/_/mysql](https://hub.docker.com/_/mysql) and [hub.docker.com/_/mariadb](https://hub.docker.com/_/mariadb) but GitHub Action will only use the versions listed above. - `connector_name` @@ -121,7 +121,7 @@ make ansible="stable-2.16" db_engine_name="mysql" db_engine_version="8.0.31" con make ansible="stable-2.17" db_engine_name="mysql" db_engine_version="8.0.31" connector_name="mysqlclient" connector_version="2.0.3" target="test_mysql_query" keep_containers_alive=1 continue_on_errors=1 # If your system has an usupported version of Python: -make local_python_version="3.10" ansible="stable-2.17" db_engine_name="mariadb" db_engine_version="10.6.11" connector_name="pymysql" connector_version="1.0.2" +make local_python_version="3.10" ansible="stable-2.17" db_engine_name="mariadb" db_engine_version="11.4.5" connector_name="pymysql" connector_version="1.0.2" ``` diff --git a/changelogs/fragments/tests_mariadb_11_4.yml b/changelogs/fragments/tests_mariadb_11_4.yml new file mode 100644 index 0000000..46927bf --- /dev/null +++ b/changelogs/fragments/tests_mariadb_11_4.yml @@ -0,0 +1,5 @@ +--- +minor_changes: + - Integration tests for MariaDB 11.4 have replaced those for 10.5. The previous version is now 10.11. +bugfixes: + - mysql_db - fix dump and import to find MariaDB binaries (mariadb and mariadb-dump) when MariaDB 11+ is used and symbolic links to MySQL binaries are absent. diff --git a/plugins/modules/mysql_db.py b/plugins/modules/mysql_db.py index e108054..6ef578c 100644 --- a/plugins/modules/mysql_db.py +++ b/plugins/modules/mysql_db.py @@ -386,67 +386,75 @@ def db_dump(module, host, user, password, db_name, target, all_databases, port, encoding=None, force=False, master_data=0, skip_lock_tables=False, dump_extra_args=None, unsafe_password=False, restrict_config_file=False, check_implicit_admin=False, pipefail=False): - cmd = module.get_bin_path('mysqldump', True) + + cmd_str = 'mysqldump' + if server_implementation == 'mariadb' and LooseVersion(server_version) >= LooseVersion("10.4.6"): + cmd_str = 'mariadb-dump' + try: + cmd = [module.get_bin_path(cmd_str, True)] + except Exception as e: + return 1, "", "Error determining dump command: %s" % str(e) + # If defined, mysqldump demands --defaults-extra-file be the first option if config_file: if restrict_config_file: - cmd += " --defaults-file=%s" % shlex_quote(config_file) + cmd.append("--defaults-file=%s" % shlex_quote(config_file)) else: - cmd += " --defaults-extra-file=%s" % shlex_quote(config_file) + cmd.append("--defaults-extra-file=%s" % shlex_quote(config_file)) if check_implicit_admin: - cmd += " --user=root --password=''" + cmd.append("--user=root --password=''") else: if user is not None: - cmd += " --user=%s" % shlex_quote(user) + cmd.append("--user=%s" % shlex_quote(user)) if password is not None: if not unsafe_password: - cmd += " --password=%s" % shlex_quote(password) + cmd.append("--password=%s" % shlex_quote(password)) else: - cmd += " --password=%s" % password + cmd.append("--password=%s" % password) if ssl_cert is not None: - cmd += " --ssl-cert=%s" % shlex_quote(ssl_cert) + cmd.append("--ssl-cert=%s" % shlex_quote(ssl_cert)) if ssl_key is not None: - cmd += " --ssl-key=%s" % shlex_quote(ssl_key) + cmd.append("--ssl-key=%s" % shlex_quote(ssl_key)) if ssl_ca is not None: - cmd += " --ssl-ca=%s" % shlex_quote(ssl_ca) + cmd.append("--ssl-ca=%s" % shlex_quote(ssl_ca)) if force: - cmd += " --force" + cmd.append("--force") if socket is not None: - cmd += " --socket=%s" % shlex_quote(socket) + cmd.append("--socket=%s" % shlex_quote(socket)) else: - cmd += " --host=%s --port=%i" % (shlex_quote(host), port) + cmd.append("--host=%s --port=%i" % (shlex_quote(host), port)) if all_databases: - cmd += " --all-databases" + cmd.append("--all-databases") elif len(db_name) > 1: - cmd += " --databases {0}".format(' '.join(db_name)) + cmd.append("--databases {0}".format(' '.join(db_name))) else: - cmd += " %s" % shlex_quote(' '.join(db_name)) + cmd.append("%s" % shlex_quote(' '.join(db_name))) if skip_lock_tables: - cmd += " --skip-lock-tables" + cmd.append("--skip-lock-tables") if (encoding is not None) and (encoding != ""): - cmd += " --default-character-set=%s" % shlex_quote(encoding) + cmd.append("--default-character-set=%s" % shlex_quote(encoding)) if single_transaction: - cmd += " --single-transaction=true" + cmd.append("--single-transaction=true") if quick: - cmd += " --quick" + cmd.append("--quick") if ignore_tables: for an_ignored_table in ignore_tables: - cmd += " --ignore-table={0}".format(an_ignored_table) + cmd.append("--ignore-table={0}".format(an_ignored_table)) if hex_blob: - cmd += " --hex-blob" + cmd.append("--hex-blob") if master_data: if (server_implementation == 'mysql' and LooseVersion(server_version) >= LooseVersion("8.2.0")): - cmd += " --source-data=%s" % master_data + cmd.append("--source-data=%s" % master_data) else: - cmd += " --master-data=%s" % master_data + cmd.append("--master-data=%s" % master_data) if dump_extra_args is not None: - cmd += " " + dump_extra_args + cmd.append(dump_extra_args) path = None if os.path.splitext(target)[-1] == '.gz': @@ -458,6 +466,8 @@ def db_dump(module, host, user, password, db_name, target, all_databases, port, elif os.path.splitext(target)[-1] == '.zst': path = module.get_bin_path('zstd', True) + cmd = ' '.join(cmd) + if path: cmd = '%s | %s > %s' % (cmd, path, shlex_quote(target)) if pipefail: @@ -476,13 +486,21 @@ def db_dump(module, host, user, password, db_name, target, all_databases, port, def db_import(module, host, user, password, db_name, target, all_databases, port, config_file, - socket=None, ssl_cert=None, ssl_key=None, ssl_ca=None, encoding=None, force=False, + server_implementation, server_version, socket=None, ssl_cert=None, ssl_key=None, ssl_ca=None, + encoding=None, force=False, use_shell=False, unsafe_password=False, restrict_config_file=False, check_implicit_admin=False): if not os.path.exists(target): return module.fail_json(msg="target %s does not exist on the host" % target) - cmd = [module.get_bin_path('mysql', True)] + cmd_str = 'mysql' + if server_implementation == 'mariadb' and LooseVersion(server_version) >= LooseVersion("10.4.6"): + cmd_str = 'mariadb' + try: + cmd = [module.get_bin_path(cmd_str, True)] + except Exception as e: + return 1, "", "Error determining mysql/mariadb command: %s" % str(e) + # --defaults-file must go first, or errors out if config_file: if restrict_config_file: @@ -772,8 +790,8 @@ def main(): rc, stdout, stderr = db_import(module, login_host, login_user, login_password, db, target, all_databases, - login_port, config_file, - socket, ssl_cert, ssl_key, ssl_ca, + login_port, config_file, server_implementation, + server_version, socket, ssl_cert, ssl_key, ssl_ca, encoding, force, use_shell, unsafe_login_password, restrict_config_file, check_implicit_admin) if rc != 0: diff --git a/plugins/modules/mysql_info.py b/plugins/modules/mysql_info.py index 8c3845d..9bf89ae 100644 --- a/plugins/modules/mysql_info.py +++ b/plugins/modules/mysql_info.py @@ -4,6 +4,7 @@ # Copyright: (c) 2019, Andrew Klychkov (@Andersson007) # 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 __metaclass__ = type From 45a29408ad41fb42271b05617ca6e44c3c384208 Mon Sep 17 00:00:00 2001 From: Keeper-of-the-Keys Date: Wed, 19 Mar 2025 15:40:59 +0200 Subject: [PATCH 216/217] User locking (#702) * function to check if a user is locked already Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys * Add the location and logic of where I think user locking would happen. Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys * Fix missing parameters for execute() Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys * Add the locked attribute Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys * Initial user locking integration tests Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys * Add attribute documentation Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys * More descriptive names in the integration tests Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys * - Changes requested/suggested by @Andersson007 - Example usage - Changelog fragment Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys * Fix user_is_locked and remove host_all option. Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys * Fix host of user (was % should have been localhost after deleting `host:` earlier) Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys * Switch locked to named instead of positional. Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys * Add check_mode support. Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys * Add check_mode: true test cases Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys * Fix names that included `check_mode: true` Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys * Add idempotence checks Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys * Switch calls to user_mod with sequences of None positional arguments to full named arguments Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys * locked check should not run for roles. Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys * check_mode is set at the task level and not the module level Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys * Add user locking to info module and test. Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys * Handle DictCursor Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys * Add check_mode feedback Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys * Add another builtin account to the exclusion list Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys * Initial switch to default=None for locked, will need to add a test for it. Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys * Add check that missing locked argument does not unlock a user Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys --------- Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys --- changelogs/fragments/702-user_locking.yaml | 2 + plugins/module_utils/user.py | 42 +++- plugins/modules/mysql_info.py | 5 +- plugins/modules/mysql_role.py | 11 +- plugins/modules/mysql_user.py | 33 ++- .../tasks/filter_users_info.yml | 2 + .../targets/test_mysql_user/tasks/main.yml | 4 + .../tasks/test_user_locking.yml | 200 ++++++++++++++++++ 8 files changed, 285 insertions(+), 14 deletions(-) create mode 100644 changelogs/fragments/702-user_locking.yaml create mode 100644 tests/integration/targets/test_mysql_user/tasks/test_user_locking.yml diff --git a/changelogs/fragments/702-user_locking.yaml b/changelogs/fragments/702-user_locking.yaml new file mode 100644 index 0000000..1378793 --- /dev/null +++ b/changelogs/fragments/702-user_locking.yaml @@ -0,0 +1,2 @@ +minor_changes: +- mysql_user - add ``locked`` option to lock/unlock users, this is mainly used to have users that will act as definers on stored procedures. diff --git a/plugins/module_utils/user.py b/plugins/module_utils/user.py index 307ef6e..9de1c6d 100644 --- a/plugins/module_utils/user.py +++ b/plugins/module_utils/user.py @@ -52,6 +52,25 @@ def user_exists(cursor, user, host, host_all): return count[0] > 0 +def user_is_locked(cursor, user, host): + cursor.execute("SHOW CREATE USER %s@%s", (user, host)) + + # Per discussions on irc:libera.chat:#maria the query may return up to 2 rows but "ACCOUNT LOCK" should always be in the first row. + result = cursor.fetchone() + + # ACCOUNT LOCK does not have to be the last option in the CREATE USER query. + # Need to handle both DictCursor and non-DictCursor + if isinstance(result, tuple): + if result[0].find('ACCOUNT LOCK') > 0: + return True + elif isinstance(result, dict): + for res in result.values(): + if res.find('ACCOUNT LOCK') > 0: + return True + + return False + + def sanitize_requires(tls_requires): sanitized_requires = {} if tls_requires: @@ -160,7 +179,7 @@ def get_existing_authentication(cursor, user, host=None): def user_add(cursor, user, host, host_all, password, encrypted, plugin, plugin_hash_string, plugin_auth_string, salt, new_priv, attributes, tls_requires, reuse_existing_password, module, - password_expire, password_expire_interval): + password_expire, password_expire_interval, locked=False): # If attributes are set, perform a sanity check to ensure server supports user attributes before creating user if attributes and not get_attribute_support(cursor): module.fail_json(msg="user attributes were specified but the server does not support user attributes") @@ -250,6 +269,9 @@ def user_add(cursor, user, host, host_all, password, encrypted, cursor.execute("ALTER USER %s@%s ATTRIBUTE %s", (user, host, json.dumps(attributes))) final_attributes = attributes_get(cursor, user, host) + if locked: + cursor.execute("ALTER USER %s@%s ACCOUNT LOCK", (user, host)) + return {'changed': True, 'password_changed': not used_existing_password, 'attributes': final_attributes} @@ -264,7 +286,7 @@ def is_hash(password): def user_mod(cursor, user, host, host_all, password, encrypted, plugin, plugin_hash_string, plugin_auth_string, salt, new_priv, append_privs, subtract_privs, attributes, tls_requires, module, - password_expire, password_expire_interval, role=False, maria_role=False): + password_expire, password_expire_interval, locked=None, role=False, maria_role=False): changed = False msg = "User unchanged" grant_option = False @@ -536,6 +558,22 @@ def user_mod(cursor, user, host, host_all, password, encrypted, if attribute_support: final_attributes = attributes_get(cursor, user, host) + if not role and locked is not None and user_is_locked(cursor, user, host) != locked: + if not module.check_mode: + if locked: + cursor.execute("ALTER USER %s@%s ACCOUNT LOCK", (user, host)) + msg = 'User locked' + else: + cursor.execute("ALTER USER %s@%s ACCOUNT UNLOCK", (user, host)) + msg = 'User unlocked' + else: + if locked: + msg = 'User will be locked' + else: + msg = 'User will be unlocked' + + changed = True + if role: continue diff --git a/plugins/modules/mysql_info.py b/plugins/modules/mysql_info.py index 9bf89ae..2360d01 100644 --- a/plugins/modules/mysql_info.py +++ b/plugins/modules/mysql_info.py @@ -319,6 +319,7 @@ from ansible_collections.community.mysql.plugins.module_utils.user import ( get_resource_limits, get_existing_authentication, get_user_implementation, + user_is_locked, ) from ansible.module_utils.six import iteritems from ansible.module_utils._text import to_native @@ -653,8 +654,10 @@ class MySQL_Info(object): if authentications: output_dict.update(authentications[0]) + if line.get('is_role') and line['is_role'] == 'N': + output_dict['locked'] = user_is_locked(self.cursor, user, host) + # TODO password_option - # TODO lock_option # but both are not supported by mysql_user atm. So no point yet. output.append(output_dict) diff --git a/plugins/modules/mysql_role.py b/plugins/modules/mysql_role.py index c88392b..382445c 100644 --- a/plugins/modules/mysql_role.py +++ b/plugins/modules/mysql_role.py @@ -930,11 +930,12 @@ class Role(): set_default_role_all=set_default_role_all) if privs: - result = user_mod(self.cursor, self.name, self.host, - None, None, None, None, None, None, None, - privs, append_privs, subtract_privs, None, None, - self.module, None, None, role=True, - maria_role=self.is_mariadb) + result = user_mod(cursor=self.cursor, user=self.name, host=self.host, + host_all=None, password=None, encrypted=None, plugin=None, + plugin_auth_string=None, plugin_hash_string=None, salt=None, + new_priv=privs, append_privs=append_privs, subtract_privs=subtract_privs, + attributes=None, tls_requires=None, module=self.module, password_expire=None, + password_expire_interval=None, role=True, maria_role=self.is_mariadb) changed = result['changed'] if admin: diff --git a/plugins/modules/mysql_user.py b/plugins/modules/mysql_user.py index 499f2a0..2a5855c 100644 --- a/plugins/modules/mysql_user.py +++ b/plugins/modules/mysql_user.py @@ -189,6 +189,15 @@ options: fields names in privileges. type: bool version_added: '3.8.0' + + locked: + description: + - Lock account to prevent connections using it. + - This is primarily used for creating a user that will act as a DEFINER on stored procedures. + - If not specified leaves the lock state as is (for a new user creates unlocked). + type: bool + version_added: '3.13.0' + attributes: description: - "Create, update, or delete user attributes (arbitrary 'key: value' comments) for the user." @@ -225,6 +234,7 @@ author: - Lukasz Tomaszkiewicz (@tomaszkiewicz) - kmarse (@kmarse) - Laurent Indermühle (@laurent-indermuehle) +- E.S. Rosenberg (@Keeper-of-the-Keys) extends_documentation_fragment: - community.mysql.mysql @@ -400,6 +410,13 @@ EXAMPLES = r''' priv: 'db1.*': DELETE +- name: Create locked user to act as a definer on procedures + community.mysql.mysql_user: + name: readonly_procedures_locked + locked: true + priv: + db1.*: SELECT + # Example .my.cnf file for setting the root password # [client] # user=root @@ -470,6 +487,7 @@ def main(): column_case_sensitive=dict(type='bool', default=None), # TODO 4.0.0 add default=True password_expire=dict(type='str', choices=['now', 'never', 'default', 'interval'], no_log=True), password_expire_interval=dict(type='int', required_if=[('password_expire', 'interval', True)], no_log=True), + locked=dict(type='bool'), ) module = AnsibleModule( argument_spec=argument_spec, @@ -510,6 +528,7 @@ def main(): column_case_sensitive = module.params["column_case_sensitive"] password_expire = module.params["password_expire"] password_expire_interval = module.params["password_expire_interval"] + locked = module.boolean(module.params['locked']) 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)) @@ -577,13 +596,15 @@ def main(): result = user_mod(cursor, user, host, host_all, password, encrypted, plugin, plugin_hash_string, plugin_auth_string, salt, priv, append_privs, subtract_privs, attributes, tls_requires, module, - password_expire, password_expire_interval) + password_expire, password_expire_interval, locked=locked) else: - result = user_mod(cursor, user, host, host_all, None, encrypted, - None, None, None, None, - priv, append_privs, subtract_privs, attributes, tls_requires, module, - password_expire, password_expire_interval) + result = user_mod(cursor=cursor, user=user, host=host, host_all=host_all, password=None, + encrypted=encrypted, plugin=None, plugin_hash_string=None, plugin_auth_string=None, + salt=None, new_priv=priv, append_privs=append_privs, subtract_privs=subtract_privs, + attributes=attributes, tls_requires=tls_requires, module=module, + password_expire=password_expire, password_expire_interval=password_expire_interval, + locked=locked) changed = result['changed'] msg = result['msg'] password_changed = result['password_changed'] @@ -601,7 +622,7 @@ def main(): result = user_add(cursor, user, host, host_all, password, encrypted, plugin, plugin_hash_string, plugin_auth_string, salt, priv, attributes, tls_requires, reuse_existing_password, module, - password_expire, password_expire_interval) + password_expire, password_expire_interval, locked=locked) changed = result['changed'] password_changed = result['password_changed'] final_attributes = result['attributes'] diff --git a/tests/integration/targets/test_mysql_info/tasks/filter_users_info.yml b/tests/integration/targets/test_mysql_info/tasks/filter_users_info.yml index 36508f3..558d309 100644 --- a/tests/integration/targets/test_mysql_info/tasks/filter_users_info.yml +++ b/tests/integration/targets/test_mysql_info/tasks/filter_users_info.yml @@ -261,6 +261,7 @@ resource_limits: "{{ item.resource_limits | default(omit) }}" column_case_sensitive: true state: present + locked: "{{ item.locked | default(omit) }}" loop: "{{ result.users_info }}" loop_control: label: "{{ item.name }}@{{ item.host }}" @@ -275,6 +276,7 @@ - item.name != 'mariadb.sys' - item.name != 'mysql.sys' - item.name != 'mysql.infoschema' + - item.name != 'mysql.session' # ================================== Cleanup ============================ diff --git a/tests/integration/targets/test_mysql_user/tasks/main.yml b/tests/integration/targets/test_mysql_user/tasks/main.yml index 9244570..7212886 100644 --- a/tests/integration/targets/test_mysql_user/tasks/main.yml +++ b/tests/integration/targets/test_mysql_user/tasks/main.yml @@ -305,3 +305,7 @@ - name: Mysql_user - test update_password ansible.builtin.import_tasks: file: test_update_password.yml + + - name: Mysql_user - test user_locking + ansible.builtin.import_tasks: + file: test_user_locking.yml diff --git a/tests/integration/targets/test_mysql_user/tasks/test_user_locking.yml b/tests/integration/targets/test_mysql_user/tasks/test_user_locking.yml new file mode 100644 index 0000000..3990610 --- /dev/null +++ b/tests/integration/targets/test_mysql_user/tasks/test_user_locking.yml @@ -0,0 +1,200 @@ +--- + +- vars: + mysql_parameters: &mysql_params + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: '{{ mysql_host }}' + login_port: '{{ mysql_primary_port }}' + + block: + + # ========================= Prepare ======================================= + - name: Mysql_user Lock user | Create a test database + community.mysql.mysql_db: + <<: *mysql_params + name: mysql_lock_user_test + state: present + + # ========================== Tests ======================================== + + - name: Mysql_user Lock user | create locked | Create test user + community.mysql.mysql_user: + <<: *mysql_params + name: mysql_locked_user + password: 'msandbox' + locked: true + priv: + 'mysql_lock_user_test.*': 'SELECT' + + - name: Mysql_user Lock user | create locked | Assert that test user is locked + community.mysql.mysql_query: + <<: *mysql_params + query: + - SHOW CREATE USER 'mysql_locked_user'@'localhost' + register: locked_user_creation + failed_when: + - locked_user_creation.query_result[0][0] is not search('ACCOUNT LOCK') + + - name: 'Mysql_user Lock user | create locked | Idempotence check' + check_mode: true + community.mysql.mysql_user: + <<: *mysql_params + name: mysql_locked_user + locked: true + priv: + 'mysql_lock_user_test.*': 'SELECT' + register: idempotence_check + failed_when: idempotence_check is changed + + - name: 'Mysql_user Lock user | create locked | Check that absense of locked does not unlock the user' + check_mode: true + community.mysql.mysql_user: + <<: *mysql_params + name: mysql_locked_user + priv: + 'mysql_lock_user_test.*': 'SELECT' + register: idempotence_check + failed_when: idempotence_check is changed + + - name: 'Mysql_user Lock user | create locked | Unlock test user check_mode: true' + check_mode: true + community.mysql.mysql_user: + <<: *mysql_params + name: mysql_locked_user + locked: false + priv: + 'mysql_lock_user_test.*': 'SELECT' + + - name: Mysql_user Lock user | create locked | Assert that test user is locked + community.mysql.mysql_query: + <<: *mysql_params + query: + - SHOW CREATE USER 'mysql_locked_user'@'localhost' + register: locked_user_creation + failed_when: + - locked_user_creation.query_result[0][0] is not search('ACCOUNT LOCK') + + - name: Mysql_user Lock user | create locked | Unlock test user + community.mysql.mysql_user: + <<: *mysql_params + name: mysql_locked_user + locked: false + priv: + 'mysql_lock_user_test.*': 'SELECT' + + - name: Mysql_user Lock user | create locked | Assert that test user is not locked + community.mysql.mysql_query: + <<: *mysql_params + query: + - SHOW CREATE USER 'mysql_locked_user'@'localhost' + register: locked_user_creation + failed_when: + - locked_user_creation.query_result[0][0] is search('ACCOUNT LOCK') + + - name: Mysql_user Lock user | create locked | Remove test user + community.mysql.mysql_user: + <<: *mysql_params + name: mysql_locked_user + state: absent + + - name: Mysql_user Lock user | create unlocked | Create test user + community.mysql.mysql_user: + <<: *mysql_params + name: mysql_locked_user + password: 'msandbox' + locked: false + priv: + 'mysql_lock_user_test.*': 'SELECT' + + - name: Mysql_user Lock user | create unlocked | Assert that test user is not locked + community.mysql.mysql_query: + <<: *mysql_params + query: + - SHOW CREATE USER 'mysql_locked_user'@'localhost' + register: locked_user_creation + failed_when: + - locked_user_creation.query_result[0][0] is search('ACCOUNT LOCK') + + - name: 'Mysql_user Lock user | create unlocked | Idempotence check' + check_mode: true + community.mysql.mysql_user: + <<: *mysql_params + name: mysql_locked_user + locked: false + priv: + 'mysql_lock_user_test.*': 'SELECT' + register: idempotence_check + failed_when: idempotence_check is changed + + - name: 'Mysql_user Lock user | create unlocked | Lock test user check_mode: true' + check_mode: true + community.mysql.mysql_user: + <<: *mysql_params + name: mysql_locked_user + locked: true + priv: + 'mysql_lock_user_test.*': 'SELECT' + + - name: Mysql_user Lock user | create unlocked | Assert that test user is not locked + community.mysql.mysql_query: + <<: *mysql_params + query: + - SHOW CREATE USER 'mysql_locked_user'@'localhost' + register: locked_user_creation + failed_when: + - locked_user_creation.query_result[0][0] is search('ACCOUNT LOCK') + + - name: Mysql_user Lock user | create unlocked | Lock test user + community.mysql.mysql_user: + <<: *mysql_params + name: mysql_locked_user + locked: true + priv: + 'mysql_lock_user_test.*': 'SELECT' + + - name: Mysql_user Lock user | create unlocked | Assert that test user is locked + community.mysql.mysql_query: + <<: *mysql_params + query: + - SHOW CREATE USER 'mysql_locked_user'@'localhost' + register: locked_user_creation + failed_when: + - locked_user_creation.query_result[0][0] is not search('ACCOUNT LOCK') + + - name: Mysql_user Lock user | create unlocked | Remove test user + community.mysql.mysql_user: + <<: *mysql_params + name: mysql_locked_user + state: absent + + - name: Mysql_user Lock user | create default | Create test user + community.mysql.mysql_user: + <<: *mysql_params + name: mysql_locked_user + password: 'msandbox' + priv: + 'mysql_lock_user_test.*': 'SELECT' + + - name: Mysql_user Lock user | create default | Assert that test user is not locked + community.mysql.mysql_query: + <<: *mysql_params + query: + - SHOW CREATE USER 'mysql_locked_user'@'localhost' + register: locked_user_creation + failed_when: + - locked_user_creation.query_result[0][0] is search('ACCOUNT LOCK') + + - name: Mysql_user Lock user | create default | Remove test user + community.mysql.mysql_user: + <<: *mysql_params + name: mysql_locked_user + state: absent + + # ========================= Teardown ====================================== + + - name: Mysql_user Lock user | Delete test database + community.mysql.mysql_db: + <<: *mysql_params + name: mysql_lock_user_test + state: absent From b26235b7d7f571895245cf5d1137096951e44294 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Fri, 21 Mar 2025 07:02:43 +0100 Subject: [PATCH 217/217] Release 3.13.0 commit (#705) --- CHANGELOG.rst | 21 +++++++++++++++++++++ changelogs/changelog.yaml | 20 ++++++++++++++++++++ changelogs/fragments/702-user_locking.yaml | 2 -- changelogs/fragments/tests_mariadb_11_4.yml | 5 ----- galaxy.yml | 2 +- 5 files changed, 42 insertions(+), 8 deletions(-) delete mode 100644 changelogs/fragments/702-user_locking.yaml delete mode 100644 changelogs/fragments/tests_mariadb_11_4.yml diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ba19887..b318076 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,27 @@ Community MySQL and MariaDB Collection Release Notes This changelog describes changes after version 2.0.0. +v3.13.0 +======= + +Release Summary +--------------- + +This is a minor release of the ``community.mysql`` collection. +This changelog contains all changes to the modules and plugins in this +collection that have been made after the previous release. + +Minor Changes +------------- + +- Integration tests for MariaDB 11.4 have replaced those for 10.5. The previous version is now 10.11. +- mysql_user - add ``locked`` option to lock/unlock users, this is mainly used to have users that will act as definers on stored procedures. + +Bugfixes +-------- + +- mysql_db - fix dump and import to find MariaDB binaries (mariadb and mariadb-dump) when MariaDB 11+ is used and symbolic links to MySQL binaries are absent. + v3.12.0 ======= diff --git a/changelogs/changelog.yaml b/changelogs/changelog.yaml index fa08150..5ec7dc9 100644 --- a/changelogs/changelog.yaml +++ b/changelogs/changelog.yaml @@ -247,6 +247,26 @@ releases: - 3.12.0.yml - 696-mysql-db-add-zstd-support.yml release_date: '2025-01-17' + 3.13.0: + changes: + bugfixes: + - mysql_db - fix dump and import to find MariaDB binaries (mariadb and mariadb-dump) + when MariaDB 11+ is used and symbolic links to MySQL binaries are absent. + minor_changes: + - Integration tests for MariaDB 11.4 have replaced those for 10.5. The previous + version is now 10.11. + - mysql_user - add ``locked`` option to lock/unlock users, this is mainly used + to have users that will act as definers on stored procedures. + release_summary: 'This is a minor release of the ``community.mysql`` collection. + + This changelog contains all changes to the modules and plugins in this + + collection that have been made after the previous release.' + fragments: + - 3.13.0.yml + - 702-user_locking.yaml + - tests_mariadb_11_4.yml + release_date: '2025-03-21' 3.2.0: changes: bugfixes: diff --git a/changelogs/fragments/702-user_locking.yaml b/changelogs/fragments/702-user_locking.yaml deleted file mode 100644 index 1378793..0000000 --- a/changelogs/fragments/702-user_locking.yaml +++ /dev/null @@ -1,2 +0,0 @@ -minor_changes: -- mysql_user - add ``locked`` option to lock/unlock users, this is mainly used to have users that will act as definers on stored procedures. diff --git a/changelogs/fragments/tests_mariadb_11_4.yml b/changelogs/fragments/tests_mariadb_11_4.yml deleted file mode 100644 index 46927bf..0000000 --- a/changelogs/fragments/tests_mariadb_11_4.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -minor_changes: - - Integration tests for MariaDB 11.4 have replaced those for 10.5. The previous version is now 10.11. -bugfixes: - - mysql_db - fix dump and import to find MariaDB binaries (mariadb and mariadb-dump) when MariaDB 11+ is used and symbolic links to MySQL binaries are absent. diff --git a/galaxy.yml b/galaxy.yml index cf87c64..624c7d6 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -1,7 +1,7 @@ --- namespace: community name: mysql -version: 3.12.0 +version: 3.13.0 readme: README.md authors: - Ansible community