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] 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