From 28bf7093be36e0bd47866e28aefff1a38cc5b2b0 Mon Sep 17 00:00:00 2001 From: Maxwell G Date: Wed, 11 Sep 2024 07:35:02 -0500 Subject: [PATCH 01/25] 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 02/25] 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 03/25] 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 04/25] 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 05/25] 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 06/25] 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 07/25] 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 08/25] 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 09/25] 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 10/25] 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 11/25] 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 12/25] 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 13/25] 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 14/25] 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 15/25] 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 16/25] 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 17/25] 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 From da2dc9ab5d0cc0c3d444ef5b5694fde421eab1ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20Inderm=C3=BChle?= Date: Thu, 24 Apr 2025 13:44:50 +0200 Subject: [PATCH 18/25] Doc: locked returned in users info (#706) * doc: users_info returns a new field "locked" * doc: fix hash syntax Co-authored-by: Andrew Klychkov --------- Co-authored-by: Andrew Klychkov --- plugins/modules/mysql_info.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/modules/mysql_info.py b/plugins/modules/mysql_info.py index 2360d01..a630f18 100644 --- a/plugins/modules/mysql_info.py +++ b/plugins/modules/mysql_info.py @@ -151,6 +151,7 @@ EXAMPLES = r''' tls_requires: "{{ item.tls_requires | default(omit) }}" priv: "{{ item.priv | default(omit) }}" resource_limits: "{{ item.resource_limits | default(omit) }}" + locked: "{{ item.locked | default(omit) }}" column_case_sensitive: true state: present loop: "{{ result.users_info }}" @@ -246,6 +247,7 @@ users_info: 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. + - The "locked" field was aded in ``community.mysql`` 3.13. returned: if not excluded by filter type: dict sample: @@ -255,7 +257,8 @@ users_info: "plugin": "mysql_native_password", "priv": "db1.*:SELECT/db2.*:SELECT", "resource_limits": { "MAX_USER_CONNECTIONS": 100 }, - "tls_requires": { "SSL": null } } + "tls_requires": { "SSL": null }, + "locked": false } version_added: '3.8.0' engines: description: Information about the server's storage engines. From d44f8f20396b5963f6a8fb03bf20fde68837dcb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20Inderm=C3=BChle?= Date: Mon, 28 Apr 2025 13:52:40 +0200 Subject: [PATCH 19/25] CI: fix string templating for ansible devel (core 2.19, 12 beta) in tests (#713) * fix string templating for ansible devel (core 2.19, 12 beta) * fix order to prevent [ERROR]: Task failed: Module failed: 'Server_id' * cut jinja templates from conditionals * fix 'item' already in use --- .../tasks/multi_db_create_delete.yml | 120 +++++++++--------- .../test_mysql_db/tasks/state_dump_import.yml | 16 ++- .../tasks/state_present_absent.yml | 36 ++++-- .../tasks/mysql_query_initial.yml | 63 ++++++--- .../tasks/mysql_replication_channel.yml | 2 +- .../tasks/mysql_replication_initial.yml | 3 +- .../tasks/mysql_replication_primary_delay.yml | 2 +- .../test_mysql_user/tasks/test_privs.yml | 4 +- .../tasks/test_user_plugin_auth.yml | 2 +- .../tasks/utils/assert_user.yml | 4 +- .../test_mysql_variables/tasks/assert_var.yml | 6 +- .../tasks/assert_var_output.yml | 6 +- .../tasks/mysql_variables.yml | 22 +++- 13 files changed, 173 insertions(+), 113 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 0bd7d58..d65b422 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 @@ -33,9 +33,9 @@ - name: assert that databases does not exist assert: that: - - "'{{ db1_name }}' not in mysql_result.stdout" - - "'{{ db2_name }}' not in mysql_result.stdout" - - "'{{ db3_name }}' not in mysql_result.stdout" + - db1_name not in mysql_result.stdout + - db2_name not in mysql_result.stdout + - db3_name not in mysql_result.stdout # ========================================================================== # Create multiple databases that does not exists (check mode) @@ -65,9 +65,9 @@ - name: assert that databases does not exist (since created via check mode) assert: that: - - "'{{ db1_name }}' not in mysql_result.stdout" - - "'{{ db2_name }}' not in mysql_result.stdout" - - "'{{ db3_name }}' not in mysql_result.stdout" + - db1_name not in mysql_result.stdout + - db2_name not in mysql_result.stdout + - db3_name not in mysql_result.stdout # ========================================================================== # Create multiple databases @@ -88,7 +88,7 @@ assert: that: - result is changed - - result.db_list == ['{{ db1_name }}', '{{ db2_name }}', '{{ db3_name }}'] + - result.db_list == [db1_name, db2_name, db3_name] - name: run command to list databases like specified database name command: "{{ mysql_command }} \"-e show databases like 'database%'\"" @@ -97,9 +97,9 @@ - name: assert that databases exist after creation assert: that: - - "'{{ db1_name }}' in mysql_result.stdout" - - "'{{ db2_name }}' in mysql_result.stdout" - - "'{{ db3_name }}' in mysql_result.stdout" + - db1_name in mysql_result.stdout + - db2_name in mysql_result.stdout + - db3_name in mysql_result.stdout # ========================================================================= # Recreate already existing databases (check mode) @@ -129,9 +129,9 @@ - name: assert that databases exist (since performed recreation of existing databases via check mode) assert: that: - - "'{{ db1_name }}' in mysql_result.stdout" - - "'{{ db2_name }}' in mysql_result.stdout" - - "'{{ db3_name }}' in mysql_result.stdout" + - db1_name in mysql_result.stdout + - db2_name in mysql_result.stdout + - db3_name in mysql_result.stdout # ========================================================================== # Recreate same databases @@ -160,9 +160,9 @@ - name: assert that databases does priorly exist assert: that: - - "'{{ db1_name }}' in mysql_result.stdout" - - "'{{ db2_name }}' in mysql_result.stdout" - - "'{{ db3_name }}' in mysql_result.stdout" + - db1_name in mysql_result.stdout + - db2_name in mysql_result.stdout + - db3_name in mysql_result.stdout # ========================================================================== # Delete one of the databases (db2 here) @@ -189,9 +189,9 @@ - name: assert that only db2 database does not exist assert: that: - - "'{{ db1_name }}' in mysql_result.stdout" - - "'{{ db2_name }}' not in mysql_result.stdout" - - "'{{ db3_name }}' in mysql_result.stdout" + - db1_name in mysql_result.stdout + - db2_name not in mysql_result.stdout + - db3_name in mysql_result.stdout # ========================================================================= # Recreate multiple databases in which few databases does not exists (check mode) @@ -221,9 +221,9 @@ - name: assert that recreated non existing databases does not exist (since created via check mode) assert: that: - - "'{{ db1_name }}' in mysql_result.stdout" - - "'{{ db2_name }}' not in mysql_result.stdout" - - "'{{ db3_name }}' in mysql_result.stdout" + - db1_name in mysql_result.stdout + - db2_name not in mysql_result.stdout + - db3_name in mysql_result.stdout # ========================================================================== # Create multiple databases @@ -252,9 +252,9 @@ - name: assert that databases exist assert: that: - - "'{{ db1_name }}' in mysql_result.stdout" - - "'{{ db2_name }}' in mysql_result.stdout" - - "'{{ db3_name }}' in mysql_result.stdout" + - db1_name in mysql_result.stdout + - db2_name in mysql_result.stdout + - db3_name in mysql_result.stdout # ============================== DUMP TEST ================================= # @@ -293,9 +293,9 @@ - name: assert that databases exist (check mode) assert: that: - - "'{{ db1_name }}' in mysql_result.stdout" - - "'{{ db2_name }}' in mysql_result.stdout" - - "'{{ db3_name }}' in mysql_result.stdout" + - db1_name in mysql_result.stdout + - db2_name in mysql_result.stdout + - db3_name in mysql_result.stdout - name: state dump - file name should not exist (since dumped via check mode) file: @@ -332,10 +332,10 @@ - name: assert that databases exist (since check mode) assert: that: - - "'{{ db1_name }}' in mysql_result.stdout" - - "'{{ db2_name }}' in mysql_result.stdout" - - "'{{ db3_name }}' in mysql_result.stdout" - - "'{{ db4_name }}' not in mysql_result.stdout" + - db1_name in mysql_result.stdout + - db2_name in mysql_result.stdout + - db3_name in mysql_result.stdout + - db4_name not in mysql_result.stdout - name: state dump - file name should not exist (since prior dump operation performed via check mode) file: @@ -371,11 +371,11 @@ - name: assert that databases exist (since delete via check mode) assert: that: - - "'{{ db1_name }}' in mysql_result.stdout" - - "'{{ db2_name }}' in mysql_result.stdout" - - "'{{ db3_name }}' in mysql_result.stdout" - - "'{{ db4_name }}' not in mysql_result.stdout" - - "'{{ db5_name }}' not in mysql_result.stdout" + - db1_name in mysql_result.stdout + - db2_name in mysql_result.stdout + - db3_name in mysql_result.stdout + - db4_name not in mysql_result.stdout + - db5_name not in mysql_result.stdout - name: state dump - file name should not exist (since prior dump operation performed via check mode) file: @@ -403,7 +403,7 @@ assert: that: - dump_result is changed - - dump_result.db_list == ['{{ db1_name }}', '{{ db2_name }}', '{{ db3_name }}'] + - dump_result.db_list == [db1_name, db2_name, db3_name] - name: Run command to list databases like specified database name command: "{{ mysql_command }} \"-e show databases like 'database%'\"" @@ -412,9 +412,9 @@ - name: assert that databases exist assert: that: - - "'{{ db1_name }}' in mysql_result.stdout" - - "'{{ db2_name }}' in mysql_result.stdout" - - "'{{ db3_name }}' in mysql_result.stdout" + - db1_name in mysql_result.stdout + - db2_name in mysql_result.stdout + - db3_name in mysql_result.stdout - name: State dump - file name should exist (dump1_file) file: @@ -461,11 +461,11 @@ - name: assert that databases exist assert: that: - - "'{{ db1_name }}' in mysql_result.stdout" - - "'{{ db2_name }}' in mysql_result.stdout" - - "'{{ db3_name }}' in mysql_result.stdout" - - "'{{ db4_name }}' not in mysql_result.stdout" - - "'{{ db5_name }}' not in mysql_result.stdout" + - db1_name in mysql_result.stdout + - db2_name in mysql_result.stdout + - db3_name in mysql_result.stdout + - db4_name not in mysql_result.stdout + - db5_name not in mysql_result.stdout - name: state dump - file name should exist (dump2_file) file: @@ -501,8 +501,8 @@ - name: assert that databases exist even after deleting (since deleted via check mode) assert: that: - - "'{{ db2_name }}' in mysql_result.stdout" - - "'{{ db3_name }}' in mysql_result.stdout" + - db2_name in mysql_result.stdout + - db3_name in mysql_result.stdout # ========================================================================== # Delete multiple databases @@ -522,7 +522,7 @@ assert: that: - result is changed - - result.db_list == ['{{ db2_name }}', '{{ db3_name }}'] + - result.db_list == [db2_name, db3_name] - name: run command to list databases like specified database name command: "{{ mysql_command }} \"-e show databases like 'database%'\"" @@ -531,8 +531,8 @@ - name: assert that databases does not exist assert: that: - - "'{{ db2_name }}' not in mysql_result.stdout" - - "'{{ db3_name }}' not in mysql_result.stdout" + - db2_name not in mysql_result.stdout + - db3_name not in mysql_result.stdout # ========================================================================== # Delete non existing databases (check mode) @@ -561,8 +561,8 @@ - name: assert that databases does not exist since were deleted priorly (check mode) assert: that: - - "'{{ db2_name }}' not in mysql_result.stdout" - - "'{{ db4_name }}' not in mysql_result.stdout" + - db2_name not in mysql_result.stdout + - db4_name not in mysql_result.stdout # ========================================================================== # Delete already deleted databases @@ -590,8 +590,8 @@ - name: assert that databases does not exists assert: that: - - "'{{ db2_name }}' not in mysql_result.stdout" - - "'{{ db4_name }}' not in mysql_result.stdout" + - db2_name not in mysql_result.stdout + - db4_name not in mysql_result.stdout # ========================================================================== # Delete all databases @@ -622,11 +622,11 @@ - name: assert that specific databases does not exist assert: that: - - "'{{ db1_name }}' not in mysql_result.stdout" - - "'{{ db2_name }}' not in mysql_result.stdout" - - "'{{ db3_name }}' not in mysql_result.stdout" - - "'{{ db4_name }}' not in mysql_result.stdout" - - "'{{ db5_name }}' not in mysql_result.stdout" + - db1_name not in mysql_result.stdout + - db2_name not in mysql_result.stdout + - db3_name not in mysql_result.stdout + - db4_name not in mysql_result.stdout + - db5_name not in mysql_result.stdout - name: state dump - dump 1 file name should be removed file: 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 f8d2b4b..96e6371 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 @@ -283,7 +283,7 @@ - name: Dump and Import | Assert that db_name2 database does not exist assert: that: - - "'{{ db_name2 }}' not in mysql_result.stdout" + - db_name2 not in mysql_result.stdout - name: Dump and Import | Test state=import to restore a database from dumped file2 (check mode) mysql_db: @@ -309,7 +309,7 @@ - name: Dump and Import | Assert that db_name2 database does not exist (check mode) assert: that: - - "'{{ db_name2 }}' not in mysql_result.stdout" + - db_name2 not in mysql_result.stdout - name: Dump and Import | Test state=import to restore a database from multiple database dumped file2 mysql_db: @@ -326,7 +326,7 @@ assert: that: - import_result2 is changed - - import_result2.db_list == ['{{ db_name2 }}'] + - import_result2.db_list == [db_name2] - name: Dump and Import | Run command to list databases command: "{{ mysql_command }} \"-e show databases like 'data%'\"" @@ -335,7 +335,7 @@ - name: Dump and Import | Assert that db_name2 database does exist after import assert: that: - - "'{{ db_name2 }}' in mysql_result.stdout" + - db_name2 in mysql_result.stdout - name: Dump and Import | Test state=dump to backup the database of type {{ format_type }} (expect changed=true) mysql_db: @@ -487,18 +487,22 @@ login_password: '{{ mysql_password }}' login_host: '{{ mysql_host }}' login_port: '{{ mysql_primary_port }}' - name: '{{ item }}' + name: '{{ cleanup_db }}' state: absent loop: - '{{ db_name }}' - '{{ db_name2 }}' + loop_control: + loop_var: cleanup_db - name: Dump and Import | Clean up files file: - name: '{{ item }}' + name: '{{ cleanup_file }}' state: absent loop: - '{{ db_file_name }}' - '{{ wrong_sql_file }}' - '{{ dump_file1 }}' - '{{ dump_file2 }}' + loop_control: + loop_var: cleanup_file 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 12633f2..6b3c772 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 @@ -41,8 +41,10 @@ assert: that: - result is changed - - result.db == '{{ db_name }}' - - result.executed_commands == ["CREATE DATABASE `{{ db_name }}`"] + - result.db == db_name + - result.executed_commands == expected_commands + vars: + expected_commands: ["CREATE DATABASE `{{ db_name }}`"] - 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\") }}'\"" @@ -51,7 +53,7 @@ - name: State Present Absent | Assert database exist assert: that: - - "'{{ db_name }}' in result.stdout" + - db_name in result.stdout # ============================================================ - name: State Present Absent | Test state=absent for a database name (expect changed=true) @@ -68,8 +70,10 @@ assert: that: - result is changed - - result.db == '{{ db_name }}' - - result.executed_commands == ["DROP DATABASE `{{ db_name }}`"] + - result.db == db_name + - result.executed_commands == expected_commands + vars: + expected_commands: ["DROP DATABASE `{{ db_name }}`"] - 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\") }}'\"" @@ -78,7 +82,7 @@ - name: State Present Absent | Assert database does not exist assert: that: - - "'{{ db_name }}' not in result.stdout" + - db_name not in result.stdout # ============================================================ - name: State Present Absent | Test mysql_db encoding param not valid - issue 8075 @@ -116,7 +120,9 @@ assert: that: - result is changed - - result.executed_commands == ["CREATE DATABASE `en{{ db_name }}` CHARACTER SET 'utf8'"] + - result.executed_commands == expected_commands + vars: + expected_commands: ["CREATE DATABASE `en{{ db_name }}` CHARACTER SET 'utf8'"] - name: State Present Absent | Test database was created command: "{{ mysql_command }} -e \"SHOW CREATE DATABASE `en{{ db_name }}`\"" @@ -152,7 +158,9 @@ assert: that: - result is changed - - result.executed_commands == ["CREATE DATABASE `en{{ db_name }}` CHARACTER SET 'binary'"] + - result.executed_commands == expected_commands + vars: + expected_commands: ["CREATE DATABASE `en{{ db_name }}` CHARACTER SET 'binary'"] - name: State Present Absent | Run command to test database was created command: "{{ mysql_command }} -e \"SHOW CREATE DATABASE `en{{ db_name }}`\"" @@ -207,7 +215,7 @@ - name: State Present Absent | Assert database exist assert: that: - - "'{{ db_user1 }}' in result.stdout" + - db_user1 in result.stdout # ============================================================ - name: State Present Absent | Create user2 to access database with privilege select only @@ -245,7 +253,7 @@ - name: State Present Absent | Assert database does not exist assert: that: - - "'{{ db_user2 }}' not in result.stdout" + - db_user2 not in result.stdout # ============================================================ - name: State Present Absent | Delete database using user2 with no privilege to delete (expect failed=true) @@ -272,7 +280,7 @@ - name: State Present Absent | Assert database still exist assert: that: - - "'{{ db_user1 }}' in result.stdout" + - db_user1 in result.stdout # ============================================================ - name: State Present Absent | Delete database using user1 with all privilege to delete a database (expect changed=true) @@ -290,7 +298,9 @@ assert: that: - result is changed - - result.executed_commands == ["DROP DATABASE `{{ db_user1 }}`"] + - result.executed_commands == expected_commands + vars: + expected_commands: ["DROP DATABASE `{{ db_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\") }}'\"" @@ -299,4 +309,4 @@ - name: State Present Absent | Assert database does not exist assert: that: - - "'{{ db_user1 }}' not in result.stdout" + - db_user1 not in result.stdout 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 310f925..5545111 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 @@ -21,7 +21,9 @@ assert: that: - result is changed - - result.executed_queries == ['CREATE DATABASE {{ test_db }}'] + - result.executed_queries == expected_queries + vars: + expected_queries: ['CREATE DATABASE {{ test_db }}'] - name: Create {{ test_table1 }} mysql_query: @@ -34,8 +36,10 @@ assert: that: - result is changed - - result.executed_queries == ['CREATE TABLE {{ test_table1 }} (id int)'] + - result.executed_queries == expected_queries - result.execution_time_ms[0] > 0 + vars: + expected_queries: ['CREATE TABLE {{ test_table1 }} (id int)'] - name: Insert test data mysql_query: @@ -52,9 +56,14 @@ 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.executed_queries == expected_queries - result.execution_time_ms[0] > 0 - result.execution_time_ms[1] > 0 + vars: + expected_queries: [ + 'INSERT INTO {{ test_table1 }} VALUES (1), (2)', + 'INSERT INTO {{ test_table1 }} VALUES (3)', + ] - name: Check data in {{ test_table1 }} mysql_query: @@ -67,11 +76,13 @@ assert: that: - result is not changed - - result.executed_queries == ['SELECT * FROM {{ test_table1 }}'] + - result.executed_queries == expected_queries - result.rowcount == [3] - result.query_result[0][0].id == 1 - result.query_result[0][1].id == 2 - result.query_result[0][2].id == 3 + vars: + expected_queries: ['SELECT * FROM {{ test_table1 }}'] - name: Check data in {{ test_table1 }} using positional args mysql_query: @@ -86,9 +97,11 @@ assert: that: - result is not changed - - result.executed_queries == ["SELECT * FROM {{ test_table1 }} WHERE id = 1"] + - result.executed_queries == expected_queries - result.rowcount == [1] - result.query_result[0][0].id == 1 + vars: + expected_queries: ["SELECT * FROM {{ test_table1 }} WHERE id = 1"] - name: Check data in {{ test_table1 }} using named args mysql_query: @@ -103,9 +116,11 @@ assert: that: - result is not changed - - result.executed_queries == ["SELECT * FROM {{ test_table1 }} WHERE id = 1"] + - result.executed_queries == expected_queries - result.rowcount == [1] - result.query_result[0][0].id == 1 + vars: + expected_queries: ["SELECT * FROM {{ test_table1 }} WHERE id = 1"] - name: Update data in {{ test_table1 }} mysql_query: @@ -121,8 +136,10 @@ assert: that: - result is changed - - result.executed_queries == ['UPDATE {{ test_table1 }} SET id = 0 WHERE id = 1'] + - result.executed_queries == expected_queries - result.rowcount == [1] + vars: + expected_queries: ['UPDATE {{ test_table1 }} SET id = 0 WHERE id = 1'] - name: Check the prev update - row with value 1 does not exist anymore mysql_query: @@ -137,8 +154,10 @@ assert: that: - result is not changed - - result.executed_queries == ['SELECT * FROM {{ test_table1 }} WHERE id = 1'] + - result.executed_queries == expected_queries - result.rowcount == [0] + vars: + expected_queries: ['SELECT * FROM {{ test_table1 }} WHERE id = 1'] - name: Check the prev update - row with value - exist mysql_query: @@ -153,8 +172,10 @@ assert: that: - result is not changed - - result.executed_queries == ['SELECT * FROM {{ test_table1 }} WHERE id = 0'] + - result.executed_queries == expected_queries - result.rowcount == [1] + vars: + expected_queries: ['SELECT * FROM {{ test_table1 }} WHERE id = 0'] - name: Update data in {{ test_table1 }} again mysql_query: @@ -170,8 +191,10 @@ assert: that: - result is not changed - - result.executed_queries == ['UPDATE {{ test_table1 }} SET id = 0 WHERE id = 1'] + - result.executed_queries == expected_queries - result.rowcount == [0] + vars: + expected_queries: ['UPDATE {{ test_table1 }} SET id = 0 WHERE id = 1'] - name: Delete data from {{ test_table1 }} mysql_query: @@ -186,8 +209,10 @@ assert: that: - result is changed - - result.executed_queries == ['DELETE FROM {{ test_table1 }} WHERE id = 0', 'SELECT * FROM {{ test_table1 }} WHERE id = 0'] + - result.executed_queries == expected_queries - result.rowcount == [1, 0] + vars: + expected_queries: ['DELETE FROM {{ test_table1 }} WHERE id = 0', 'SELECT * FROM {{ test_table1 }} WHERE id = 0'] - name: Delete data from {{ test_table1 }} again mysql_query: @@ -200,8 +225,10 @@ assert: that: - result is not changed - - result.executed_queries == ['DELETE FROM {{ test_table1 }} WHERE id = 0'] + - result.executed_queries == expected_queries - result.rowcount == [0] + vars: + expected_queries: ['DELETE FROM {{ test_table1 }} WHERE id = 0'] - name: Truncate {{ test_table1 }} mysql_query: @@ -216,8 +243,10 @@ assert: that: - result is changed - - result.executed_queries == ['TRUNCATE {{ test_table1 }}', 'SELECT * FROM {{ test_table1 }}'] + - result.executed_queries == expected_queries - result.rowcount == [0, 0] + vars: + expected_queries: ['TRUNCATE {{ test_table1 }}', 'SELECT * FROM {{ test_table1 }}'] - name: Rename {{ test_table1 }} mysql_query: @@ -230,8 +259,10 @@ assert: that: - result is changed - - result.executed_queries == ['RENAME TABLE {{ test_table1 }} TO {{ test_table2 }}'] + - result.executed_queries == expected_queries - result.rowcount == [0] + vars: + expected_queries: ['RENAME TABLE {{ test_table1 }} TO {{ test_table2 }}'] - name: Check the prev rename mysql_query: @@ -398,7 +429,9 @@ assert: that: - result is changed - - result.executed_queries == ['DROP DATABASE {{ test_db }}'] + - result.executed_queries == expected_queries + vars: + expected_queries: ['DROP DATABASE {{ test_db }}'] always: 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 0bcc6e6..67d9261 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 @@ -75,7 +75,7 @@ - assert: that: - result is changed - - result.queries == result_query or result_query2 + - result.queries in [result_query, result_query2] vars: result_query: ["START SLAVE FOR CHANNEL '{{ test_channel }}'"] result_query2: ["START REPLICA 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 00699c1..182ea5c 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 @@ -337,5 +337,6 @@ - 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, changereplication, 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 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 2093b70..fe22db8 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 @@ -55,5 +55,5 @@ - assert: that: - - replica_status.SQL_Delay == {{ test_primary_delay }} + - replica_status.SQL_Delay == test_primary_delay - replica_status is not changed 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 95d44aa..cd50427 100644 --- a/tests/integration/targets/test_mysql_user/tasks/test_privs.yml +++ b/tests/integration/targets/test_mysql_user/tasks/test_privs.yml @@ -220,7 +220,7 @@ - name: Privs | Assert that 'GRANT' permission is present assert: that: - - mysql_info_about_users.users.localhost.{{ user_name_2 }}.Grant_priv == 'Y' + - mysql_info_about_users.users.localhost.db_user2.Grant_priv == 'Y' - name: Privs | Test idempotency (expect ok) mysql_user: @@ -246,7 +246,7 @@ - name: Privs | Assert that 'GRANT' permission is present (by host) assert: that: - - mysql_info_about_users.users.localhost.{{ user_name_2 }}.Grant_priv == 'Y' + - mysql_info_about_users.users.localhost.db_user2.Grant_priv == 'Y' # ============================================================ - name: Privs | 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 f6f3c2e..164fea7 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 @@ -45,7 +45,7 @@ - name: Plugin auth | Check that the expected plugin type is set (with hash string) ansible.builtin.assert: that: - - "'{{ test_plugin_type }}' in show_create_user.stdout" + - test_plugin_type in show_create_user.stdout when: db_engine == 'mysql' or (db_engine == 'mariadb' and db_version is version('10.3', '>=')) - ansible.builtin.include_tasks: utils/assert_user.yml 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 index e6bd23f..e03386e 100644 --- a/tests/integration/targets/test_mysql_user/tasks/utils/assert_user.yml +++ b/tests/integration/targets/test_mysql_user/tasks/utils/assert_user.yml @@ -17,5 +17,7 @@ - name: Utils | Assert user | Assert user has given privileges ansible.builtin.assert: that: - - "'GRANT {{ priv }} ON *.*' in result.stdout" + - expected_command in result.stdout when: priv is defined + vars: + expected_command: "GRANT {{ priv }} ON *.*" 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 e64c5a7..d72c7e8 100644 --- a/tests/integration/targets/test_mysql_variables/tasks/assert_var.yml +++ b/tests/integration/targets/test_mysql_variables/tasks/assert_var.yml @@ -23,7 +23,7 @@ - name: Assert output message changed value assert: that: - - "output.changed | bool == changed | bool" + - output.changed | bool == changed | bool - name: Run mysql command to show variable command: "{{ mysql_command }} \"-e show variables like '{{ var_name }}'\"" @@ -33,5 +33,5 @@ assert: that: - result is changed - - "'{{ var_name }}' in result.stdout" - - "'{{ var_value }}' in result.stdout" + - var_name | string in result.stdout + - var_value | string 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 6f26386..0e9874c 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 | bool == changed | bool" + - output.changed | bool == changed | bool - set_fact: key_name: "{{ var_name }}" @@ -36,5 +36,5 @@ assert: that: - result is changed - - "key_name in result.stdout" - - "key_value in result.stdout" + - key_name in result.stdout + - key_value in result.stdout 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 8194172..e2fe9ab 100644 --- a/tests/integration/targets/test_mysql_variables/tasks/mysql_variables.yml +++ b/tests/integration/targets/test_mysql_variables/tasks/mysql_variables.yml @@ -92,7 +92,9 @@ - assert: that: - - result.queries == ["SET GLOBAL `{{ set_name }}` = {{ set_value }}"] + - result.queries == expected_queries + vars: + expected_queries: ["SET GLOBAL `{{ set_name }}` = {{ set_value }}"] - include_tasks: assert_var.yml vars: @@ -291,9 +293,9 @@ # Bugfix https://github.com/ansible-collections/community.mysql/issues/652 - name: Get server version - register: result mysql_info: <<: *mysql_params + register: result - name: Set variable name when running on MySQL set_fact: @@ -316,7 +318,9 @@ 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"] + - result.msg == "Variable is already set to requested value." or result.queries == expected_queries + vars: + expected_queries: ["SET GLOBAL `{{ log_slow_statements }}` = ON"] - name: Set a boolean value again using ON mysql_variables: @@ -378,7 +382,9 @@ assert: that: - result is changed - - result.queries == ["SET GLOBAL `{{ log_slow_statements }}` = ON"] + - result.queries == expected_queries + vars: + expected_queries: ["SET GLOBAL `{{ log_slow_statements }}` = ON"] #============================================================ # Verify mysql_variable fails with an incorrect login_password parameter @@ -452,7 +458,9 @@ - assert: that: - - result.queries == ["SET PERSIST `{{ set_name }}` = {{ set_value }}"] + - result.queries == expected_queries + vars: + expected_queries: ["SET PERSIST `{{ set_name }}` = {{ set_value }}"] - include_tasks: assert_var.yml vars: @@ -494,7 +502,9 @@ - assert: that: - result is changed - - result.queries == ["SET PERSIST_ONLY `{{ set_name }}` = {{ set_value }}"] + - result.queries == expected_queries + vars: + expected_queries: ["SET PERSIST_ONLY `{{ set_name }}` = {{ set_value }}"] - name: try to update mysql variable value (expect changed=false) in persist_only mode again mysql_variables: From 74ea0438ce214931b6849de8043a3f9014049925 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20Inderm=C3=BChle?= Date: Mon, 28 Apr 2025 14:45:33 +0200 Subject: [PATCH 20/25] fix mysql_info crash if PUBLIC role is used on MariaDB 10.11+ (#712) * tests: grant a privilege to the PUBLIC role to force the module to fail * skip the PUBLIC role from the users migration example * Cut the 3rd matching group as it fails to match roles without quotes The group purpose was to match the closing quotes. We don't need the value after the TO. So we can ignore this entirely. * add MariaDB role detection * Add changelog fragment * doc: add notes that users_info doesn't support MariaDB roles. --- changelogs/fragments/grant_to_public.yml | 3 +++ plugins/module_utils/user.py | 2 +- plugins/modules/mysql_info.py | 6 +++++- .../test_mysql_info/tasks/filter_users_info.yml | 15 ++++++++++++++- 4 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 changelogs/fragments/grant_to_public.yml diff --git a/changelogs/fragments/grant_to_public.yml b/changelogs/fragments/grant_to_public.yml new file mode 100644 index 0000000..ea24da0 --- /dev/null +++ b/changelogs/fragments/grant_to_public.yml @@ -0,0 +1,3 @@ +--- +bugfixes: + - mysql_info - fix a crash (ERROR 1141, There is no such grant defined for user 'PUBLIC' on host '%') when using the ``users_info`` filter with a PUBLIC role present in MariaDB 10.11+. Do note that the fix doesn't change the fact that the module won't return the privileges from the PUBLIC role in the users privileges list. It can't do that because you have to login as the particular user and use `SHOW GRANTS FOR CURRENT_USER`. We considered using an aggregation with the `SHOW GRANTS FOR PUBLIC` command. However, this approach would make copying users from one server to another transform the privileges inherited from the role as if they were direct privileges on the user. diff --git a/plugins/module_utils/user.py b/plugins/module_utils/user.py index 9de1c6d..337cc67 100644 --- a/plugins/module_utils/user.py +++ b/plugins/module_utils/user.py @@ -662,7 +662,7 @@ def privileges_get(cursor, user, host, maria_role=False): 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]) + res = re.match("""GRANT (.+) ON (.+) TO .*""", grant[0]) if res is None: # If a user has roles assigned, we'll have one of priv tuples looking like diff --git a/plugins/modules/mysql_info.py b/plugins/modules/mysql_info.py index a630f18..0447743 100644 --- a/plugins/modules/mysql_info.py +++ b/plugins/modules/mysql_info.py @@ -50,6 +50,7 @@ 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). +- filters C(users_info) doesn't support MariaDB roles. attributes: check_mode: @@ -161,6 +162,7 @@ EXAMPLES = r''' - item.name != 'root' # In case you don't want to import admin accounts - item.name != 'mariadb.sys' - item.name != 'mysql' + - item.name != 'PUBLIC' # MariaDB roles are not supported ''' RETURN = r''' @@ -606,7 +608,9 @@ class MySQL_Info(object): user = line['User'] host = line['Host'] - user_priv = privileges_get(self.cursor, user, host) + # MariaDB roles have no host + is_role = self.server_implementation == 'mariadb' and not host + user_priv = privileges_get(self.cursor, user, host, maria_role=is_role) if not user_priv: self.module.warn("No privileges found for %s on host %s" % (user, host)) 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 558d309..102bf59 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 @@ -31,6 +31,19 @@ CREATE TABLE IF NOT EXISTS users_info_db.T_UPPER (id int, name1 varchar(9), NAME2 varchar(9), Name3 varchar(9)) + # No need for a specific test later. When the module will retrieve the + # users privileges, it will fail with an error "1141 - There is no such + # grant defined for user 'PUBLIC' on host'%'" if the PUBLIC role is not + # handled properly by our module. + - name: Mysql_info users_info | Grant to PUBLIC for MariaDB 10.11+ + community.mysql.mysql_query: + query: + - >- + GRANT SELECT,INSERT,UPDATE,DELETE on users_info_db.* TO PUBLIC + when: + - db_engine == 'mariadb' + - db_version is version('10.11.1', '>=') + # 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 @@ -277,7 +290,7 @@ - item.name != 'mysql.sys' - item.name != 'mysql.infoschema' - item.name != 'mysql.session' - + - item.name != 'PUBLIC' # MariaDB roles are not supported # ================================== Cleanup ============================ From 06e23c8ac3b96b0dbef31eb9020677560ec0dc65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20Inderm=C3=BChle?= Date: Tue, 6 May 2025 09:05:41 +0200 Subject: [PATCH 21/25] Add query to find command between python and python3 (#715) * Add query to find command between python and python3 * Cut extra $ --- tests/integration/targets/setup_controller/tasks/verify.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/integration/targets/setup_controller/tasks/verify.yml b/tests/integration/targets/setup_controller/tasks/verify.yml index b47e354..d3d615f 100644 --- a/tests/integration/targets/setup_controller/tasks/verify.yml +++ b/tests/integration/targets/setup_controller/tasks/verify.yml @@ -42,8 +42,8 @@ - connector_name == 'mysqlclient' - name: Get the python version in use - ansible.builtin.command: - cmd: python -V + ansible.builtin.shell: + cmd: echo $( $(command -v python || command -v python3) -V ) changed_when: false failed_when: false register: python_version_in_use @@ -52,7 +52,7 @@ ansible.builtin.debug: msg: > Python in use inside the test container: - ${{ python_version_in_use }} + {{ python_version_in_use.stdout }} when: - python_version_in_use is defined From 7307a51f200b2236854a193f2956db78daf9f2af Mon Sep 17 00:00:00 2001 From: Joakim Soderlund Date: Wed, 7 May 2025 14:32:55 +0200 Subject: [PATCH 22/25] Add action group containing all modules (#704) Helpful for setting module defaults. For example, when using the same credentials across multiple tasks while only declaring them once. This change creates the group `community.mysql.all` for that purpose. --- meta/runtime.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/meta/runtime.yml b/meta/runtime.yml index 2ee3c9f..0a472e8 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -1,2 +1,11 @@ --- requires_ansible: '>=2.9.10' +action_groups: + all: + - mysql_db + - mysql_info + - mysql_query + - mysql_replication + - mysql_role + - mysql_user + - mysql_variables From 49be739e89373503f3a38aa9192ce6e004c0ea0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20Inderm=C3=BChle?= Date: Fri, 9 May 2025 11:00:29 +0200 Subject: [PATCH 23/25] Fix ssl verification always enabled for replication even if set to false (#707) * Fix ssl verification always enabled for replication even if set to false * add changelog fragment * fix test when multiple replication channels are present * test: add check for changereplication * fix mismatch default value We use "None" as a default to know if the user provide this option or not. But a bool can't have a default of "None" for the sanity tests. So we must erase the line. * doc: add var name used by MySQL and MariaDB * Revert the change of default value * style * fix indentation * Revert "Revert the change of default value" This reverts commit db047fda9044a31fa9f3d47b3c2d60d58731fb12. * add changelog about changed default value * add link to issue and PR in changelog * doc: explain false had no effect prior to 3.14.0 --- .../707-source_ssl_verify_server_cert.yml | 6 ++ plugins/modules/mysql_replication.py | 21 ++++--- .../tasks/issue-689.yml | 62 +++++++++++++++++++ .../test_mysql_replication/tasks/main.yml | 4 ++ 4 files changed, 86 insertions(+), 7 deletions(-) create mode 100644 changelogs/fragments/707-source_ssl_verify_server_cert.yml create mode 100644 tests/integration/targets/test_mysql_replication/tasks/issue-689.yml diff --git a/changelogs/fragments/707-source_ssl_verify_server_cert.yml b/changelogs/fragments/707-source_ssl_verify_server_cert.yml new file mode 100644 index 0000000..592a831 --- /dev/null +++ b/changelogs/fragments/707-source_ssl_verify_server_cert.yml @@ -0,0 +1,6 @@ +--- +bugfixes: + - mysql_replication - fixed an issue where setting ``primary_ssl_verify_server_cert`` to false had no effect (https://github.com/ansible-collections/community.mysql/issues/689). + +minor_changes: + - mysql_replication - change default value for ``primary_ssl_verify_server_cert`` from False to None. This should not affect existing playbooks (https://github.com/ansible-collections/community.mysql/pull/707). diff --git a/plugins/modules/mysql_replication.py b/plugins/modules/mysql_replication.py index b902da0..a743b82 100644 --- a/plugins/modules/mysql_replication.py +++ b/plugins/modules/mysql_replication.py @@ -137,9 +137,10 @@ options: aliases: [master_ssl_cipher] primary_ssl_verify_server_cert: description: - - Same as mysql variable. + - Same as C(MASTER_SSL_VERIFY_SERVER_CERT) MySQL/MariaDB variable. + - The module switch automatically to C(SOURCE_SSL_VERIFY_SERVER_CERT) for MySQL 8.0.23 and later. + - Prior to community.mysql 3.14.0 C(false) had no effect. type: bool - default: false version_added: '3.5.0' primary_auto_position: description: @@ -493,7 +494,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_ssl_verify_server_cert=dict(type='bool'), primary_use_gtid=dict(type='str', choices=[ 'current_pos', 'replica_pos', 'disabled'], aliases=['master_use_gtid']), primary_delay=dict(type='int', aliases=['master_delay']), @@ -641,8 +642,11 @@ def main(): chm.append("%s='%s'" % (command_resolver.resolve_command('MASTER_SSL_KEY'), primary_ssl_key)) if primary_ssl_cipher is not None: chm.append("%s='%s'" % (command_resolver.resolve_command('MASTER_SSL_CIPHER'), primary_ssl_cipher)) - if primary_ssl_verify_server_cert: - chm.append("%s=1" % command_resolver.resolve_command('MASTER_SSL_VERIFY_SERVER_CERT')) + if primary_ssl_verify_server_cert is not None: + if primary_ssl_verify_server_cert: + chm.append("%s=1" % command_resolver.resolve_command('MASTER_SSL_VERIFY_SERVER_CERT')) + else: + chm.append("%s=0" % command_resolver.resolve_command('MASTER_SSL_VERIFY_SERVER_CERT')) if primary_auto_position: chm.append("%s=1" % command_resolver.resolve_command('MASTER_AUTO_POSITION')) if primary_use_gtid is not None: @@ -723,8 +727,11 @@ def main(): 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_ssl_verify_server_cert is not None: + if primary_ssl_verify_server_cert: + chm.append("%s=1" % command_resolver.resolve_command('MASTER_SSL_VERIFY_SERVER_CERT')) + else: + chm.append("%s=0" % command_resolver.resolve_command('MASTER_SSL_VERIFY_SERVER_CERT')) if primary_auto_position: chm.append("SOURCE_AUTO_POSITION=1") try: diff --git a/tests/integration/targets/test_mysql_replication/tasks/issue-689.yml b/tests/integration/targets/test_mysql_replication/tasks/issue-689.yml new file mode 100644 index 0000000..74fa1e0 --- /dev/null +++ b/tests/integration/targets/test_mysql_replication/tasks/issue-689.yml @@ -0,0 +1,62 @@ +--- + +- vars: + mysql_parameters: &mysql_params + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: '{{ mysql_host }}' + login_port: '{{ mysql_primary_port }}' + block: + + - name: Disable ssl verification + community.mysql.mysql_replication: + <<: *mysql_params + login_port: '{{ mysql_replica1_port }}' + mode: changeprimary + primary_ssl_verify_server_cert: false + register: result + + - 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_SSL_VERIFY_SERVER_CERT=0"] + + - 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_SSL_VERIFY_SERVER_CERT=0"] + + - name: Disable ssl verification for MySQL 8.0.23+ + community.mysql.mysql_replication: + <<: *mysql_params + login_port: '{{ mysql_replica1_port }}' + mode: changereplication + primary_ssl_verify_server_cert: false + register: result + when: + - db_engine == 'mysql' + - db_version is version('8.0.23', '>=') + + - name: Assert that changereplication 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_SSL_VERIFY_SERVER_CERT=0"] diff --git a/tests/integration/targets/test_mysql_replication/tasks/main.yml b/tests/integration/targets/test_mysql_replication/tasks/main.yml index 32ce553..e77af38 100644 --- a/tests/integration/targets/test_mysql_replication/tasks/main.yml +++ b/tests/integration/targets/test_mysql_replication/tasks/main.yml @@ -13,6 +13,10 @@ # Tests of replication filters and force_context - include_tasks: issue-265.yml +# primary_ssl_verify_server_cert +# Must run before mysql add channels in mysql_replication_channel.yml +- import_tasks: issue-689.yml + # Tests of primary_delay parameter: - import_tasks: mysql_replication_primary_delay.yml From fa3c72b2c0acd682a4be0ddcf8b3f69b0526b6df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20Inderm=C3=BChle?= Date: Fri, 23 May 2025 11:33:43 +0200 Subject: [PATCH 24/25] Release 3.14.0 commit (#717) --- CHANGELOG.rst | 21 +++++++++++++++ changelogs/changelog.yaml | 27 +++++++++++++++++++ .../707-source_ssl_verify_server_cert.yml | 6 ----- changelogs/fragments/grant_to_public.yml | 3 --- galaxy.yml | 2 +- 5 files changed, 49 insertions(+), 10 deletions(-) delete mode 100644 changelogs/fragments/707-source_ssl_verify_server_cert.yml delete mode 100644 changelogs/fragments/grant_to_public.yml diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b318076..114b359 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.14.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_replication - change default value for ``primary_ssl_verify_server_cert`` from False to None. This should not affect existing playbooks (https://github.com/ansible-collections/community.mysql/pull/707). + +Bugfixes +-------- + +- mysql_info - fix a crash (ERROR 1141, There is no such grant defined for user 'PUBLIC' on host '%') when using the ``users_info`` filter with a PUBLIC role present in MariaDB 10.11+. Do note that the fix doesn't change the fact that the module won't return the privileges from the PUBLIC role in the users privileges list. It can't do that because you have to login as the particular user and use `SHOW GRANTS FOR CURRENT_USER`. We considered using an aggregation with the `SHOW GRANTS FOR PUBLIC` command. However, this approach would make copying users from one server to another transform the privileges inherited from the role as if they were direct privileges on the user. +- mysql_replication - fixed an issue where setting ``primary_ssl_verify_server_cert`` to false had no effect (https://github.com/ansible-collections/community.mysql/issues/689). + v3.13.0 ======= diff --git a/changelogs/changelog.yaml b/changelogs/changelog.yaml index 5ec7dc9..79ee9cf 100644 --- a/changelogs/changelog.yaml +++ b/changelogs/changelog.yaml @@ -267,6 +267,33 @@ releases: - 702-user_locking.yaml - tests_mariadb_11_4.yml release_date: '2025-03-21' + 3.14.0: + changes: + bugfixes: + - mysql_info - fix a crash (ERROR 1141, There is no such grant defined for user + 'PUBLIC' on host '%') when using the ``users_info`` filter with a PUBLIC role + present in MariaDB 10.11+. Do note that the fix doesn't change the fact that + the module won't return the privileges from the PUBLIC role in the users privileges + list. It can't do that because you have to login as the particular user and + use `SHOW GRANTS FOR CURRENT_USER`. We considered using an aggregation with + the `SHOW GRANTS FOR PUBLIC` command. However, this approach would make copying + users from one server to another transform the privileges inherited from the + role as if they were direct privileges on the user. + - mysql_replication - fixed an issue where setting ``primary_ssl_verify_server_cert`` + to false had no effect (https://github.com/ansible-collections/community.mysql/issues/689). + minor_changes: + - mysql_replication - change default value for ``primary_ssl_verify_server_cert`` + from False to None. This should not affect existing playbooks (https://github.com/ansible-collections/community.mysql/pull/707). + 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: + - 707-source_ssl_verify_server_cert.yml + - grant_to_public.yml + - release_3_14_0.yml + release_date: '2025-05-23' 3.2.0: changes: bugfixes: diff --git a/changelogs/fragments/707-source_ssl_verify_server_cert.yml b/changelogs/fragments/707-source_ssl_verify_server_cert.yml deleted file mode 100644 index 592a831..0000000 --- a/changelogs/fragments/707-source_ssl_verify_server_cert.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -bugfixes: - - mysql_replication - fixed an issue where setting ``primary_ssl_verify_server_cert`` to false had no effect (https://github.com/ansible-collections/community.mysql/issues/689). - -minor_changes: - - mysql_replication - change default value for ``primary_ssl_verify_server_cert`` from False to None. This should not affect existing playbooks (https://github.com/ansible-collections/community.mysql/pull/707). diff --git a/changelogs/fragments/grant_to_public.yml b/changelogs/fragments/grant_to_public.yml deleted file mode 100644 index ea24da0..0000000 --- a/changelogs/fragments/grant_to_public.yml +++ /dev/null @@ -1,3 +0,0 @@ ---- -bugfixes: - - mysql_info - fix a crash (ERROR 1141, There is no such grant defined for user 'PUBLIC' on host '%') when using the ``users_info`` filter with a PUBLIC role present in MariaDB 10.11+. Do note that the fix doesn't change the fact that the module won't return the privileges from the PUBLIC role in the users privileges list. It can't do that because you have to login as the particular user and use `SHOW GRANTS FOR CURRENT_USER`. We considered using an aggregation with the `SHOW GRANTS FOR PUBLIC` command. However, this approach would make copying users from one server to another transform the privileges inherited from the role as if they were direct privileges on the user. diff --git a/galaxy.yml b/galaxy.yml index 624c7d6..01e50fc 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -1,7 +1,7 @@ --- namespace: community name: mysql -version: 3.13.0 +version: 3.14.0 readme: README.md authors: - Ansible community From 67f1460070e8a349a11f190d55bd0b5bd9c424b2 Mon Sep 17 00:00:00 2001 From: Laurent Indermuehle Date: Fri, 23 May 2025 11:38:52 +0200 Subject: [PATCH 25/25] Add next expected version --- galaxy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/galaxy.yml b/galaxy.yml index 01e50fc..e69e4e8 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -1,7 +1,7 @@ --- namespace: community name: mysql -version: 3.14.0 +version: 3.14.1 readme: README.md authors: - Ansible community