Compare commits

..

No commits in common. "main" and "1.0.2" have entirely different histories.
main ... 1.0.2

176 changed files with 3637 additions and 17232 deletions

View file

@ -1,5 +0,0 @@
---
backport_branch_prefix: patchback/backports/
backport_label_prefix: backport-
target_branch_prefix: stable-
...

View file

@ -1,6 +1,5 @@
---
name: Plugins CI
on: # yamllint disable-line rule:truthy
on:
push:
paths:
- 'plugins/**'
@ -13,323 +12,81 @@ on: # yamllint disable-line rule:truthy
- '.github/workflows/ansible-test-plugins.yml'
schedule:
- cron: '0 6 * * *'
workflow_dispatch:
env:
mysql_version_file: "./ansible_collections/community/mysql/tests/integration/targets/setup_mysql/defaults/main.yml"
jobs:
sanity:
name: "Sanity (${{ matrix.ansible }})"
runs-on: ubuntu-22.04
name: "Sanity (Python: ${{ matrix.python }}, Ansible: ${{ matrix.ansible }})"
runs-on: ubuntu-latest
strategy:
matrix:
ansible:
- stable-2.16
- stable-2.17
- stable-2.18
- stable-2.9
- stable-2.10
- devel
steps:
# https://github.com/ansible-community/ansible-test-gh-action
- name: Perform sanity testing
uses: ansible-community/ansible-test-gh-action@release/v1
with:
ansible-core-version: ${{ matrix.ansible }}
testing-type: sanity
pull-request-change-detection: true
# Use this to chose which version of Python vs Ansible to test:
# https://docs.ansible.com/ansible/latest/reference_appendices/release_and_maintenance.html#ansible-core-control-node-python-support
- name: Check out code
uses: actions/checkout@v2
with:
path: ansible_collections/community/mysql
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.8
- name: Install ansible-base (${{ matrix.ansible }})
run: pip install https://github.com/ansible/ansible/archive/${{ matrix.ansible }}.tar.gz --disable-pip-version-check
- name: Run sanity tests
run: ansible-test sanity --docker -v --color
working-directory: ./ansible_collections/community/mysql
integration:
name: "Integration (Ⓐ${{ matrix.ansible }}, DB: ${{ matrix.db_engine_name }} ${{ matrix.db_engine_version }}, connector: ${{ matrix.connector_name }} ${{ matrix.connector_version }})"
runs-on: ubuntu-22.04
name: "Integration (Python: ${{ matrix.python }}, Ansible: ${{ matrix.ansible }}, MySQL: ${{ matrix.mysql }})"
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
mysql:
- 5.7.31
- 8.0.21
ansible:
- stable-2.16
- stable-2.17
- stable-2.18
- devel
db_engine_name:
- mysql
- mariadb
db_engine_version:
- '8.0.38'
- '8.4.1'
- '10.11.8'
- '11.4.5'
connector_name:
- pymysql
- mysqlclient
connector_version:
- '0.9.3'
- '1.0.2'
- '1.1.1'
- '2.0.1'
- '2.0.3'
- '2.1.1'
include:
# RHEL8 context
- connector_name: pymysql
connector_version: '0.10.1'
ansible: stable-2.16
db_engine_name: mariadb
db_engine_version: '10.11.8'
# RHEL9 context
# - connector_name: pymysql
# connector_version: '1.1.1'
# ansible: stable-2.17
# db_engine_name: mariadb
# db_engine_version: '10.11.8'
# This tests is already included in the matrix, no need repeating
exclude:
- db_engine_name: mysql
db_engine_version: '10.11.8'
- db_engine_name: mysql
db_engine_version: '11.4.5'
- db_engine_name: mariadb
db_engine_version: '8.0.38'
- db_engine_name: mariadb
db_engine_version: '8.4.1'
- connector_name: pymysql
connector_version: '2.0.1'
- connector_name: pymysql
connector_version: '2.0.3'
- connector_name: pymysql
connector_version: '2.1.1'
- connector_name: mysqlclient
connector_version: '0.9.3'
- connector_name: mysqlclient
connector_version: '1.0.2'
- connector_name: mysqlclient
connector_version: '1.1.1'
- db_engine_version: '8.0.38'
ansible: stable-2.17
- db_engine_version: '10.11.8'
ansible: stable-2.17
- db_engine_version: '8.0.38'
ansible: devel
- db_engine_version: '10.11.8'
ansible: devel
- db_engine_version: '8.4.1'
connector_version: '0.9.3'
- db_engine_version: '8.4.1'
connector_version: '1.0.2'
- db_engine_version: '8.4.1'
connector_version: '2.0.1'
- db_engine_version: '8.4.1'
connector_version: '2.0.3'
- db_engine_version: '10.11.8'
connector_version: '0.9.3'
- db_engine_version: '10.11.8'
connector_version: '1.0.2'
- db_engine_version: '10.11.8'
connector_version: '2.0.1'
- db_engine_version: '10.11.8'
connector_version: '2.0.1'
- db_engine_version: '10.11.8'
ansible: stable-2.15
- db_engine_version: '8.4.1'
ansible: stable-2.15
- connector_version: '1.1.1'
db_engine_version: '8.0.38'
- connector_version: '1.1.1'
db_engine_version: '10.11.8'
services:
db_primary:
image: docker.io/library/${{ matrix.db_engine_name }}:${{ matrix.db_engine_version }}
env:
MARIADB_ROOT_PASSWORD: msandbox
MYSQL_ROOT_PASSWORD: msandbox
ports:
- 3307:3306
# We write our own health-cmd because the mariadb container does not
# provide a healthcheck
options: >-
--health-cmd "${{ 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
--health-retries 6
db_replica1:
image: docker.io/library/${{ matrix.db_engine_name }}:${{ matrix.db_engine_version }}
env:
MARIADB_ROOT_PASSWORD: msandbox
MYSQL_ROOT_PASSWORD: msandbox
ports:
- 3308:3306
options: >-
--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
--health-retries 6
db_replica2:
image: docker.io/library/${{ matrix.db_engine_name }}:${{ matrix.db_engine_version }}
env:
MARIADB_ROOT_PASSWORD: msandbox
MYSQL_ROOT_PASSWORD: msandbox
ports:
- 3309:3306
options: >-
--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
--health-retries 6
steps:
# No need to check for service health. GitHub Action took care of it.
- name: Restart MySQL server with settings for replication
run: |
db_ver="${{ matrix.db_engine_version }}"
maj="${db_ver%.*.*}"
maj_min="${db_ver%.*}"
min="${maj_min#*.}"
if [[ "${{ matrix.db_engine_name }}" == "mysql" && "$maj" -eq 8 && "$min" -ge 2 ]]; then
prima_conf='[mysqld]\\nserver-id=1\\nlog-bin=/var/lib/mysql/primary-bin\\nmysql-native-password=1'
repl1_conf='[mysqld]\\nserver-id=2\\nlog-bin=/var/lib/mysql/replica1-bin\\nmysql-native-password=1'
repl2_conf='[mysqld]\\nserver-id=3\\nlog-bin=/var/lib/mysql/replica2-bin\\nmysql-native-password=1'
else
prima_conf='[mysqld]\\nserver-id=1\\nlog-bin=/var/lib/mysql/primary-bin'
repl1_conf='[mysqld]\\nserver-id=2\\nlog-bin=/var/lib/mysql/replica1-bin'
repl2_conf='[mysqld]\\nserver-id=3\\nlog-bin=/var/lib/mysql/replica2-bin'
fi
docker exec -e cnf=$prima_conf ${{ job.services.db_primary.id }} bash -c 'echo -e ${cnf//\\n/\n} > /etc/mysql/conf.d/replication.cnf'
docker exec -e cnf=$repl1_conf ${{ job.services.db_replica1.id }} bash -c 'echo -e ${cnf//\\n/\n} > /etc/mysql/conf.d/replication.cnf'
docker exec -e cnf=$repl2_conf ${{ job.services.db_replica2.id }} bash -c 'echo -e ${cnf//\\n/\n} > /etc/mysql/conf.d/replication.cnf'
docker restart -t 30 ${{ job.services.db_primary.id }}
docker restart -t 30 ${{ job.services.db_replica1.id }}
docker restart -t 30 ${{ job.services.db_replica2.id }}
- name: Wait for the primary to be healthy
run: >
while ! /usr/bin/docker inspect
--format="{{if .Config.Healthcheck}}{{print .State.Health.Status}}{{end}}"
${{ job.services.db_primary.id }}
| grep healthy && [[ "$SECONDS" -lt 120 ]]; do sleep 1; done
- name: >-
Perform integration testing against
Ansible version ${{ matrix.ansible }}
uses: ansible-community/ansible-test-gh-action@release/v1
with:
ansible-core-version: ${{ matrix.ansible }}
docker-image: ubuntu2204
pre-test-cmd: >-
echo Setting db_engine_name to "${{ matrix.db_engine_name }}"...;
echo -n "${{ matrix.db_engine_name }}"
> tests/integration/db_engine_name;
echo Setting db_engine_version to \
"${{ matrix.db_engine_version }}"...;
echo -n "${{ matrix.db_engine_version }}"
> tests/integration/db_engine_version;
echo Setting Connector name to "${{ matrix.connector_name }}"...;
echo -n "${{ matrix.connector_name }}"
> tests/integration/connector_name;
echo Setting Connector name to "${{ matrix.connector_version }}"...;
echo -n "${{ matrix.connector_version }}"
> tests/integration/connector_version;
echo Setting Ansible version to "${{ matrix.ansible }}"...;
echo -n "${{ matrix.ansible }}"
> tests/integration/ansible
testing-type: integration
integration-retry-on-error: false
units:
runs-on: ubuntu-22.04
name: Units (Ⓐ${{ matrix.ansible }}, Python${{ matrix.python }})
strategy:
# As soon as the first unit test fails,
# cancel the others to free up the CI queue
fail-fast: true
matrix:
ansible:
- stable-2.16
- stable-2.17
- stable-2.18
- stable-2.9
- stable-2.10
- devel
python:
- '3.8'
- '3.9'
- '3.10'
- '3.11'
exclude:
- python: '3.8'
ansible: stable-2.16
- python: '3.8'
ansible: stable-2.17
- python: '3.8'
ansible: devel
- python: '3.9'
ansible: stable-2.15
- python: '3.9'
ansible: stable-2.17
- python: '3.9'
ansible: devel
- python: '3.10'
ansible: stable-2.15
- python: '3.10'
ansible: stable-2.16
- python: '3.11'
ansible: stable-2.15
- python: '3.11'
ansible: stable-2.16
- 3.6
steps:
- name: >-
Perform unit testing against
Ansible version ${{ matrix.ansible }} and
python version ${{ matrix.python }}
uses: ansible-community/ansible-test-gh-action@release/v1
- name: Check out code
uses: actions/checkout@v2
with:
ansible-core-version: ${{ matrix.ansible }}
target-python-version: ${{ matrix.python }}
testing-type: units
pull-request-change-detection: true
path: ansible_collections/community/mysql
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.8
- name: Install ansible-base (${{ matrix.ansible }})
run: pip install https://github.com/ansible/ansible/archive/${{ matrix.ansible }}.tar.gz --disable-pip-version-check
- name: Set MySQL version (${{ matrix.mysql }})
run: "sed -i 's/^mysql_version:.*/mysql_version: \"${{ matrix.mysql }}\"/g' ${{ env.mysql_version_file }}"
- name: Run integration tests
run: ansible-test integration --docker -v --color --retry-on-error --continue-on-error --python ${{ matrix.python }} --diff --coverage
working-directory: ./ansible_collections/community/mysql
- name: Generate coverage report.
run: ansible-test coverage xml -v --requirements --group-by command --group-by version
working-directory: ./ansible_collections/community/mysql
- uses: codecov/codecov-action@v1
with:
fail_ci_if_error: false

View file

@ -1,6 +1,5 @@
---
name: Roles CI
on: # yamllint disable-line rule:truthy
on:
push:
paths:
- 'roles/**'
@ -15,7 +14,7 @@ on: # yamllint disable-line rule:truthy
jobs:
molecule:
name: "Molecule (Python: ${{ matrix.python }}, Ansible: ${{ matrix.ansible }}, MySQL: ${{ matrix.mysql }})"
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
env:
PY_COLORS: 1
ANSIBLE_FORCE_COLOR: 1
@ -24,40 +23,28 @@ jobs:
mysql:
- 2.0.12
ansible:
- stable-2.15
- stable-2.16
- stable-2.17
- devel
- stable-2.9
### it looks like there's errors for 2.10+ with ansible-lint (https://github.com/ansible/ansible-lint/pull/878)
### and molecule (_maybe_ relating to https://github.com/ansible-community/molecule/pull/2547)
# - stable-2.10
# - devel
python:
- '3.8'
- '3.9'
- '3.10'
exclude:
- python: 3.8
ansible: stable-2.17
- python: 3.9
ansible: stable-2.17
- python: 3.8
ansible: devel
- python: 3.9
ansible: devel
- 2.7
- 3.8
steps:
- name: Check out code
uses: actions/checkout@v3
uses: actions/checkout@v2
with:
path: ansible_collections/community/mysql
- name: Set up Python ${{ matrix.python }}
uses: actions/setup-python@v4
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python }}
- name: Install ansible-core (${{ matrix.ansible }})
- name: Install ansible-base (${{ matrix.ansible }})
run: pip install https://github.com/ansible/ansible/archive/${{ matrix.ansible }}.tar.gz --disable-pip-version-check
- name: Install molecule and related dependencies

5
.gitignore vendored
View file

@ -1,7 +1,5 @@
/tests/output/
/tests/integration/inventory
/changelogs/.plugin-cache.yaml
*.swp
# Byte-compiled / optimized / DLL files
__pycache__/
@ -135,6 +133,3 @@ dmypy.json
# MacOS
.DS_Store
# IntelliJ IDEA or PyCharm
.idea/

View file

@ -1,489 +0,0 @@
====================================================
Community MySQL and MariaDB Collection Release Notes
====================================================
.. contents:: Topics
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
=======
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
=======
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
=======
Release Summary
---------------
This is a bugfix release of the ``community.mysql`` collection.
This changelog contains all changes to the modules and plugins in this
collection that have been made after the previous release.
Bugfixes
--------
- mysql_user - add correct ``ed25519`` auth plugin handling when creating a user (https://github.com/ansible-collections/community.mysql/pull/676).
v3.10.2
=======
Release Summary
---------------
This is a bugfix release of the ``community.mysql`` collection.
This changelog contains all changes to the modules and plugins in this
collection that have been made after the previous release.
Bugfixes
--------
- mysql_user - add correct ``ed25519`` auth plugin handling when creating a user (https://github.com/ansible-collections/community.mysql/issues/672).
v3.10.1
=======
Release Summary
---------------
This is a patch release of the ``community.mysql`` collection.
Besides a bugfix, it contains an important upcoming breaking-change information.
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.
v3.10.0
=======
Release Summary
---------------
This is a minor release of the ``community.mysql`` collection.
This changelog contains all changes to the modules and plugins in this
collection that have been made after the previous release.
Minor Changes
-------------
- mysql_info - Add ``tls_requires`` returned value for the ``users_info`` filter (https://github.com/ansible-collections/community.mysql/pull/628).
- mysql_info - return a database server engine used (https://github.com/ansible-collections/community.mysql/issues/644).
- mysql_replication - Adds support for `CHANGE REPLICATION SOURCE TO` statement (https://github.com/ansible-collections/community.mysql/issues/635).
- mysql_replication - Adds support for `SHOW BINARY LOG STATUS` and `SHOW BINLOG STATUS` on getprimary mode.
- mysql_replication - Improve detection of IsReplica and IsPrimary by inspecting the dictionary returned from the SQL query instead of relying on variable types. This ensures compatibility with changes in the connector or the output of SHOW REPLICA STATUS and SHOW MASTER STATUS, allowing for easier maintenance if these change in the future.
- mysql_user - Add salt parameter to generate static hash for `caching_sha2_password` and `sha256_password` plugins.
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 is missleading, it will be removed from community.mysql 4.0.0. (https://github.com/ansible-collections/community.mysql/pull/629).
- mysql_user - Added a warning to update_password's on_new_username option if multiple accounts with the same username but different passwords exist (https://github.com/ansible-collections/community.mysql/pull/642).
- mysql_user - Fix ``tls_requires`` not removing ``SSL`` and ``X509`` when sets as empty (https://github.com/ansible-collections/community.mysql/pull/628).
- mysql_user - Fix idempotence when using variables from the ``users_info`` filter of ``mysql_info`` as an input (https://github.com/ansible-collections/community.mysql/pull/628).
- mysql_user - Fixed an IndexError in the update_password functionality introduced in PR https://github.com/ansible-collections/community.mysql/pull/580 and released in community.mysql 3.8.0. If you used this functionality, please avoid versions 3.8.0 to 3.9.0 (https://github.com/ansible-collections/community.mysql/pull/642).
- mysql_user - add correct ``ed25519`` auth plugin handling (https://github.com/ansible-collections/community.mysql/issues/6).
- mysql_variables - fix the module always changes on boolean values (https://github.com/ansible-collections/community.mysql/issues/652).
v3.9.0
======
Release Summary
---------------
This is a minor release of the ``community.mysql`` collection.
This changelog contains all changes to the modules and plugins in this
collection that have been made after the previous release.
Major Changes
-------------
- Collection version 2.*.* is EOL, no more bugfixes will be backported. Please consider upgrading to the latest version.
Minor Changes
-------------
- mysql_user - add the ``password_expire`` and ``password_expire_interval`` arguments to implement the password expiration management for mysql user (https://github.com/ansible-collections/community.mysql/pull/598).
- mysql_user - add user attribute support via the ``attributes`` parameter and return value (https://github.com/ansible-collections/community.mysql/pull/604).
Bugfixes
--------
- mysql_info - the ``slave_status`` filter was returning an empty list on MariaDB with multiple replication channels. It now returns all channels by running ``SHOW ALL SLAVES STATUS`` for MariaDB servers (https://github.com/ansible-collections/community.mysql/issues/603).
v3.8.0
======
Release Summary
---------------
This is the minor release of the ``community.mysql`` collection.
This changelog contains all changes to the modules and plugins in this
collection that have been made after the previous release.
Major Changes
-------------
- The community.mysql collection no longer supports ``ansible-core 2.12`` and ``ansible-core 2.13``. While we take no active measures to prevent usage and there are no plans to introduce incompatible code to the modules, we will stop testing those versions. Both are or will soon be End of Life and if you are still using them, you should consider upgrading to the ``latest Ansible / ansible-core 2.15 or later`` as soon as possible (https://github.com/ansible-collections/community.mysql/pull/574).
- mysql_role - the ``column_case_sensitive`` argument's default value will be changed to ``true`` in community.mysql 4.0.0. If your playbook expected the column to be automatically uppercased for your roles privileges, you should set this to false explicitly (https://github.com/ansible-collections/community.mysql/issues/578).
- mysql_user - the ``column_case_sensitive`` argument's default value will be changed to ``true`` in community.mysql 4.0.0. If your playbook expected the column to be automatically uppercased for your users privileges, you should set this to false explicitly (https://github.com/ansible-collections/community.mysql/issues/577).
Minor Changes
-------------
- mysql_info - add filter ``users_info`` (https://github.com/ansible-collections/community.mysql/pull/580).
- mysql_role - add ``column_case_sensitive`` option to prevent field names from being uppercased (https://github.com/ansible-collections/community.mysql/pull/569).
- mysql_user - add ``column_case_sensitive`` option to prevent field names from being uppercased (https://github.com/ansible-collections/community.mysql/pull/569).
v3.7.2
======
Release Summary
---------------
This is a patch release of the community.mysql collection.
This changelog contains all changes to the modules and plugins in this collection
that have been made after the previous release.
Bugfixes
--------
- mysql module utils - use the connection arguments ``db`` instead of ``database`` and ``passwd`` instead of ``password`` when running with MySQLdb < 2.0.0 (https://github.com/ansible-collections/community.mysql/pull/553).
v3.7.1
======
Release Summary
---------------
This is a patch release of the community.mysql collection.
This changelog contains all changes to the modules and plugins in this collection
that have been made after the previous release.
Bugfixes
--------
- mysql module utils - use the connection arguments ``db`` instead of ``database`` and ``passwd`` instead of ``password`` when running with older mysql drivers (MySQLdb < 2.1.0 or PyMySQL < 1.0.0) (https://github.com/ansible-collections/community.mysql/pull/551).
v3.7.0
======
Release Summary
---------------
This is the minor release of the ``community.mysql`` collection.
This changelog contains all changes to the modules and plugins in this collection
that have been made after the previous release.
Minor Changes
-------------
- mysql module utils - change deprecated connection parameters ``passwd`` and ``db`` to ``password`` and ``database`` (https://github.com/ansible-collections/community.mysql/pull/177).
- mysql_user - add ``MAX_STATEMENT_TIME`` support for mariadb to the ``resource_limits`` argument (https://github.com/ansible-collections/community.mysql/issues/211).
v3.6.0
======
Release Summary
---------------
This is the minor release of the ``community.mysql`` collection.
This changelog contains all changes to the modules and plugins in this collection
that have been made after the previous release.
Minor Changes
-------------
- mysql_info - add ``connector_name`` and ``connector_version`` to returned values (https://github.com/ansible-collections/community.mysql/pull/497).
- mysql_role - enable auto_commit to avoid MySQL metadata table lock (https://github.com/ansible-collections/community.mysql/issues/479).
- mysql_user - add plugin_auth_string as optional parameter to use a specific pam service if pam/auth_pam plugin is used (https://github.com/ansible-collections/community.mysql/pull/445).
- mysql_user - add the ``session_vars`` argument to set session variables at the beginning of module execution (https://github.com/ansible-collections/community.mysql/issues/478).
- mysql_user - display a more informative invalid privilege exception. Changes the exception handling of the granting permission logic to show the query executed , params and the exception message granting privileges fails` (https://github.com/ansible-collections/community.mysql/issues/465).
- mysql_user - enable auto_commit to avoid MySQL metadata table lock (https://github.com/ansible-collections/community.mysql/issues/479).
- setup_mysql - update MySQL tarball URL (https://github.com/ansible-collections/community.mysql/pull/491).
Bugfixes
--------
- mysql_user - when revoke privs consists only of ``GRANT``, a 2nd revoke query is executed with empty privs to revoke that ended in an SQL exception (https://github.com/ansible-collections/community.mysql/pull/503).
- mysql_variables - add uppercase character pattern to regex to allow GLOBAL variables containing uppercase characters. This recognizes variable names used in Galera, for example, ``wsrep_OSU_method``, which breaks the normal pattern of all lowercase characters (https://github.com/ansible-collections/community.mysql/pull/501).
v3.5.1
======
Release Summary
---------------
This is the patch release of the ``community.mysql`` collection.
This changelog contains all changes to the modules and plugins in this collection
that have been made after the previous release.
Bugfixes
--------
- mysql_user, mysql_role - mysql/mariadb recent versions translate 'ALL PRIVILEGES' to a list of specific privileges. That caused a change every time we modified user privileges. This fix compares privs before and after user modification to avoid this infinite change (https://github.com/ansible-collections/community.mysql/issues/77).
v3.5.0
======
Release Summary
---------------
This is the minor release of the ``community.mysql`` collection.
This changelog contains all changes to the modules in this collection
that have been added after the release of ``community.mysql`` 3.4.0.
Minor Changes
-------------
- mysql_replication - add a new option: ``primary_ssl_verify_server_cert`` (https://github.com//pull/435).
Bugfixes
--------
- mysql_user - grant option was revoked accidentally when modifying users. This fix revokes grant option only when privs are setup to do that (https://github.com/ansible-collections/community.mysql/issues/77#issuecomment-1209693807).
v3.4.0
======
Release Summary
---------------
This is the minor release of the ``community.mysql`` collection.
This changelog contains all changes to the modules in this collection
that have been added after the release of ``community.mysql`` 3.3.0.
Major Changes
-------------
- mysql_db - the ``pipefail`` argument's default value will be changed to ``true`` in community.mysql 4.0.0. If your target machines do not use ``bash`` as a default interpreter, set ``pipefail`` to ``false`` explicitly. However, we strongly recommend setting up ``bash`` as a default and ``pipefail=true`` as it will protect you from getting broken dumps you don't know about (https://github.com/ansible-collections/community.mysql/issues/407).
Minor Changes
-------------
- mysql_db - add the ``chdir`` argument to avoid failings when a dump file contains relative paths (https://github.com/ansible-collections/community.mysql/issues/395).
- mysql_db - add the ``pipefail`` argument to avoid broken dumps when ``state`` is ``dump`` and compression is used (https://github.com/ansible-collections/community.mysql/issues/256).
Bugfixes
--------
- Include ``simplified_bsd.txt`` license file for various module utils.
- mysql_db - Using compression masks errors messages from mysql_dump. By default the fix is inactive to ensure retro-compatibility with system without bash. To activate the fix, use the module option ``pipefail=true`` (https://github.com/ansible-collections/community.mysql/issues/256).
- mysql_replication - when the ``primary_ssl`` argument is set to ``no``, the module will turn off SSL (https://github.com/ansible-collections/community.mysql/issues/393).
v3.3.0
======
Release Summary
---------------
This is the minor release of the ``community.mysql`` collection.
This changelog contains all changes to the modules in this collection
that have been added after the release of ``community.mysql`` 3.2.1.
Minor Changes
-------------
- mysql_role - add the argument ``members_must_exist`` (boolean, default true). The assertion that the users supplied in the ``members`` argument exist is only executed when the new argument ``members_must_exist`` is ``true``, to allow opt-out (https://github.com/ansible-collections/community.mysql/pull/369).
- mysql_user - Add the option ``on_new_username`` to argument ``update_password`` to reuse the password (plugin and authentication_string) when creating a new user if some user with the same name already exists. If the existing user with the same name have varying passwords, the password from the arguments is used like with ``update_password: always`` (https://github.com/ansible-collections/community.mysql/pull/365).
- mysql_user - Add the result field ``password_changed`` (boolean). It is true, when the user got a new password. When the user was created with ``update_password: on_new_username`` and an existing password was reused, ``password_changed`` is false (https://github.com/ansible-collections/community.mysql/pull/365).
Bugfixes
--------
- mysql_query - fix false change reports when ``IF EXISTS/IF NOT EXISTS`` clause is used (https://github.com/ansible-collections/community.mysql/issues/268).
- mysql_role - don't add members to a role when creating the role and ``detach_members: true`` is set (https://github.com/ansible-collections/community.mysql/pull/367).
- mysql_role - in some cases (when "SHOW GRANTS" did not use backticks for quotes), no unwanted members were detached from the role (and redundant "GRANT" statements were executed for wanted members). This is fixed by querying the existing role members from the mysql.role_edges (MySQL) or mysql.roles_mapping (MariaDB) tables instead of parsing the "SHOW GRANTS" output (https://github.com/ansible-collections/community.mysql/pull/368).
- mysql_user - fix logic when ``update_password`` is set to ``on_create`` for users using ``plugin*`` arguments (https://github.com/ansible-collections/community.mysql/issues/334). The ``on_create`` sets ``password`` to None for old mysql_native_authentication but not for authentiation methods which uses the ``plugin*`` arguments. This PR changes this so ``on_create`` also exchange ``plugin``, ``plugin_hash_string``, ``plugin_auth_string`` to None in the list of arguments to change
v3.2.1
======
Release Summary
---------------
This is the patch release of the ``community.mysql`` collection.
This changelog contains all changes to the modules in this collection
that have been added after the release of ``community.mysql`` 3.2.0.
Bugfixes
--------
- Include ``PSF-license.txt`` file for ``plugins/module_utils/_version.py``.
v3.2.0
======
Release Summary
---------------
This is the minor release of the ``community.mysql`` collection.
This changelog contains all changes to the modules in this collection
that have been added after the release of ``community.mysql`` 3.1.3.
Major Changes
-------------
- The community.mysql collection no longer supports ``Ansible 2.9`` and ``ansible-base 2.10``. While we take no active measures to prevent usage and there are no plans to introduce incompatible code to the modules, we will stop testing against ``Ansible 2.9`` and ``ansible-base 2.10``. Both will very soon be End of Life and if you are still using them, you should consider upgrading to the ``latest Ansible / ansible-core 2.11 or later`` as soon as possible (https://github.com/ansible-collections/community.mysql/pull/343).
Minor Changes
-------------
- mysql_user and mysql_role: Add the argument ``subtract_privs`` (boolean, default false, mutually exclusive with ``append_privs``). If set, the privileges given in ``priv`` are revoked and existing privileges are kept (https://github.com/ansible-collections/community.mysql/pull/333).
Bugfixes
--------
- mysql_user - fix missing dynamic privileges after revoke and grant privileges to user (https://github.com/ansible-collections/community.mysql/issues/120).
- mysql_user - fix parsing privs when a user has roles assigned (https://github.com/ansible-collections/community.mysql/issues/231).
v3.1.3
======
Release Summary
---------------
This is the patch release of the ``community.mysql`` collection.
This changelog contains all changes to the modules in this collection
that have been added after the release of ``community.mysql`` 3.1.2.
Bugfixes
--------
- mysql_replication - fails when using the `primary_use_gtid` option with `slave_pos` or `replica_pos` (https://github.com/ansible-collections/community.mysql/issues/335).
- mysql_role - remove redundant connection closing (https://github.com/ansible-collections/community.mysql/pull/330).
- mysql_user - fix the possibility for a race condition that breaks certain (circular) replication configurations when ``DROP USER`` is executed on multiple nodes in the replica set. Adding ``IF EXISTS`` avoids the need to use ``sql_log_bin: no`` making the statement always replication safe (https://github.com/ansible-collections/community.mysql/pull/287).
v3.1.2
======
Release Summary
---------------
This is the patch release of the ``community.mysql`` collection.
This changelog contains all changes to the modules in this collection
that have been added after the release of ``community.mysql`` 3.1.1.
Bugfixes
--------
- Collection core functions - fixes related to the mysqlclient Python connector (https://github.com/ansible-collections/community.mysql/issues/292).
v3.1.1
======
Release Summary
---------------
This is the patch release of the ``community.mysql`` collection.
This changelog contains all changes to the modules in this collection
that have been added after the release of ``community.mysql`` 3.1.0.
Bugfixes
--------
- mysql_role - make the ``set_default_role_all`` parameter actually working (https://github.com/ansible-collections/community.mysql/pull/282).
v3.1.0
======
Release Summary
---------------
This is the minor release of the ``community.mysql`` collection.
This changelog contains all changes to the modules in this collection
that have been added after the release of ``community.mysql`` 3.0.0.
Minor Changes
-------------
- Added explicit description of the supported versions of databases and connectors. Changes to the collection are **NOT** tested against database versions older than `mysql 5.7.31` and `mariadb 10.2.37` or connector versions older than `pymysql 0.7.10` and `mysqlclient 2.0.1`. (https://github.com/ansible-collections/community.mysql/discussions/141)
- mysql_user - added the ``force_context`` boolean option to set the default database context for the queries to be the ``mysql`` database. This way replication/binlog filters can catch the statements (https://github.com/ansible-collections/community.mysql/issues/265).
Bugfixes
--------
- Collection core functions - use vendored version of ``distutils.version`` instead of the deprecated Python standard library ``distutils`` (https://github.com/ansible-collections/community.mysql/pull/269).
v3.0.0
======
Release Summary
---------------
This is the major release of the ``community.mysql`` collection.
This changelog contains all breaking changes to the modules in this collection
that have been added after the release of ``community.mysql`` 2.3.2.
Breaking Changes / Porting Guide
--------------------------------
- mysql_replication - remove ``Is_Slave`` and ``Is_Master`` return values (were replaced with ``Is_Primary`` and ``Is_Replica`` (https://github.com/ansible-collections/community.mysql/issues/145).
- mysql_replication - remove the mode options values containing ``master``/``slave`` and the master_use_gtid option ``slave_pos`` (were replaced with corresponding ``primary``/``replica`` values) (https://github.com/ansible-collections/community.mysql/issues/145).
- mysql_user - remove support for the `REQUIRESSL` special privilege as it has ben superseded by the `tls_requires` option (https://github.com/ansible-collections/community.mysql/discussions/121).
- mysql_user - validate privileges using database engine directly (https://github.com/ansible-collections/community.mysql/issues/234 https://github.com/ansible-collections/community.mysql/pull/243). Do not validate privileges in this module anymore.

View file

@ -1,80 +0,0 @@
# Contributing to this project
In this guide, you will find information relevant for code contributions, though any other kinds of contribution mentioned in the [Ansible Contributing guidelines](https://docs.ansible.com/ansible/devel/community/index.html) are equally appreciated and valuable.
If you have any questions after reading, please contact the community via one or more of the [available channels](https://github.com/ansible-collections/community.mysql#communication). Any feedback on this guide is very welcome.
## Reviewing open issue and pull requests
Refer to the [review checklist](https://docs.ansible.com/ansible/devel/community/collection_contributors/collection_reviewing.html) when triaging issues or reviewing pull requests (hereinafter PRs).
Most important things to pay attention to:
- Do not let major/breaking changes sneak into a minor/bugfix release! All such changes should be discussed in a dedicated issue, added to a corresponding milestone (which can be found or created in the project's Issues), and merged right before the major release. Take a look at similar issues to see what needs to be done and reflect on the steps you did/need to do in the issue.
- Every PR (except doc, refactoring, test-related, or a PR containing a new module/plugin) contains a [changelog fragment](https://docs.ansible.com/ansible/latest/community/development_process.html#creating-a-changelog-fragment). Let's give users a chance to know about the changes.
- Every new module `DOCUMENTATION` section contains the `version_added: 'x.y.z'` field. Besides the informative purpose, it is used by the changelog-generating tool to add a corresponding entry to the changelog. As the project follows SemVer, it is typically a next minor (x.y.0) version.
- Every new module argument contains the `version_added: 'x.y.z'` field. As the project follows SemVer, it is typically a next minor (x.y.0) version.
- Non-refactoring code changes (bugfixes, new features) are covered with, at least, integration tests! There can be exceptions but generally it is a requirement.
## Code contributions
If you want to submit a bugfix or new feature, refer to the [Quick-start development guide](https://docs.ansible.com/ansible/devel/community/create_pr_quick_start.html) first.
## Project-specific info
We assume you have read the [Quick-start development guide](https://docs.ansible.com/ansible/devel/community/create_pr_quick_start.html).
In order for any submitted PR to get merged, this project requires sanity, unit, and integration tests to pass.
Codecov job is there but not required.
We use the GitHub Actions platform to run the tests.
You can see the result in the bottom of every PR in the box listing the jobs and their results:
- Green checkmark: the test has been passed, no more action is needed.
- Red cross: the test has failed. You can see the reason by clicking the ``Details`` link. Fix them locally and push the commit.
Generally, all jobs must be green.
Sometimes, there can be failures unrelated to a PR, for example, when a test container is unavailable or there is another part of the code that does not satisfy recently introduced additional sanity checks.
If you think the failure does not relate to your changes, put a comment about it.
## CI testing
The jobs are launched automatically by GitHub Actions in every PR based on the [matrix](https://github.com/ansible-collections/community.mysql/blob/main/.github/workflows/ansible-test-plugins.yml).
As the project is included in `ansible` community package, it is a requirement for us to test against all supported `ansible-core` versions and corresponding Python versions.
To keep the matrix relevant, we are subscribed to the [news-for-maintainers](https://github.com/ansible-collections/news-for-maintainers) repository and the [Collection maintainers & contributors](https://forum.ansible.com/g/CollectionMaintainer) forum group to track announcements affecting CI.
If our matrix is permanently outdated, for example, when supported `ansible-core` versions are missed, the collections can get excluded from the package, so keep it updated!
Read more about our CI implementation in the [TESTING.md](https://github.com/ansible-collections/community.mysql/blob/main/TESTING.md) file.
## Adding tests
If you are new here, read the [Quick-start development guide](https://docs.ansible.com/ansible/devel/community/create_pr_quick_start.html) first.
When fixing a bug, first reproduce it by adding a task as reported to a suitable file under the ``tests/integration/targets/<module_name>/tasks/`` directory and run the integration tests as described below. The same is relevant for new features.
It is not necessary but if you want you can also add unit tests to a suitable file under the ``tests/units/`` directory and run them as described below.
## Checking your code locally
It will make your and other people's life a bit easier if you run the tests locally and fix all failures before pushing. If you're unable to run the tests locally, please create your PR as a **draft** to avoid reviewers being added automatically.
If you are new here, read the [Quick-start development guide](https://docs.ansible.com/ansible/devel/community/create_pr_quick_start.html) first.
We assume you [prepared your local environment](https://docs.ansible.com/ansible/devel/community/create_pr_quick_start.html#prepare-your-environment) as described in the guide before running the following commands. Otherwise, the command will fail.
### Sanity tests
``` console
$ ansible-test sanity path/to/changed_file.py --docker -v
```
### Integration tests
See the [TESTING.md](https://github.com/ansible-collections/community.mysql/blob/main/TESTING.md) file to learn how to run integration tests against different server/connector versions.
### Unit tests
``` console
$ ansible-test units tests/unit/plugins/unit_test_file.py --docker
```

View file

@ -1,312 +0,0 @@
116davinder
20
28
29
4
4n70w4
abadger
abondis
acozine
adamchainz
adq
Akasurde
Alexander198961
alustenberg
aminvakil
amitk79
amree
Andersson007
andrewhowdencom
aneustroev
ansibot
anthonyxpalermo
antonioribeiro
Aohzan
apollo13
aquach
arcmop
asad-at-srt
AshDevilRed
aurimasl
axelll
axisK
azielke
baldpale
banyek
BarbzYHOOL
Berbe
betanummeric
bigo8525
bizmate
bjne
bmalynovytch
bmildren
bmillemathias
boreal321
brutus
burner1024
calfonso
candeira
caphrim007
cdalbergue
checkphi
chriscroome
chrismeyersfsu
ChristopherGAndrews
cmodijk
codeaken
codebymikey
coreylane
CormacBracken
cosmix
cptMikky
crashes
d-lee
d-rupp
dagwieers
damianmoore
Davidffry
denisemauldin
dennisurtubia
diclophis
dmp1ce
dnelson
dramaley
drybjed
drzraf
DSpeichert
dungdm93
dwagelaar
dylanjbarth
E-M
einarc
elpavel
eowin
eRadical
Ernest0x
esamattis
Everspace
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
int32bit
ipergenitsa
iredmail
ivandigiusto
jadbaz
jaikdean
jamescassell
janosmiko
jarnold-timeout
JaSafieddine
jb-2197
jborean93
jctanner
jean-christophe-manciot
Jean-Daniel
jgornick
jhagg
jhoekx
jirib
jkleckner
jkordish
jlaska
Jmainguy
jochu
JoelFeiner
johnavp1989
jonatasbaldin
Jorge-Rodriguez
jpjaatin
jpmens
JSafieddine
jsmartin
juergenhoetzel
jw34
kalaisubbiah
kenichi-ogawa-1988
kkeane
klingac
kmarse
koleo
kotso
kuntalFreshBooks
kurtdavis
larsks
laurent-indermuehle
ldesgrange
leeadh
LeonB
leucos
lkthomas
loomsen
lorin
lowwalker
lperezs
makmanalp
manuelmorena
MarcinOrlowski
markdorison
markotitel
marktheunissen
markuman
matt-horwood-mayden
mattclay
mavimo
maxamillion
maxbube
mcgoldrickm
meanstrong
meersjo
megamisan
michaeldg
michalmedvecky
MikeiLL
milky-milk
milosz
mistaka0s
mklassen
mkrizek
mmoya
mohag
mohsenSy
moledzki
mpdehaan
MRMegaNova
MRwangyd
mstinsky
mverwijs
mvgrimes
mysqlbox
n-cc
netmonk
nhojpatrick
nicolas-g
NielsH
nitinkansal1984
nitzmahone
Ompragash
on
order
organman91
p53
pakal
paulbadcock
paulcampbell-ayroc
pennycoders
perlun
petoju
petracvv
pgrenaud
philfry
pileofrogs
pkaramol
platypus-geek
plumbeo
pratikgadiya12
pshanbhag
r0bj
rajsshah86
reduzent
relrod
resmo
ricco24
richlv
riupie
rndmh3ro
robertdebock
robertsilen
robpblake
rokka-n
Roxyrob
roysmith
rsicart
rthouvenin
rujschafer
ruudk
samccann
samdoran
sayap
scottbrown
seanorama
sedrubal
sergey-trukhin
Shaps
shrikeh
sivel
skalfyfan
skoriy88
SoledaD208
sperantus
spoyd
steverweber
steveteahan
stijnopheide
stintel
stoned
strixBE
SWADESNA
tapologo
tarunm97
tejatsk14
tersmitten
the
the02
thomasliddledba
time-palominodb
timorunge
Tomasthanes
tomdymond
tompal3
Tronde
tuhoanganh
tvlooy
tyll
UncertaintyP
unnecessary-username
v-zhuravlev
vamshi8
vanne
vdboor
vmahadev
webknjaz
webmat
wedi
wfelipew
whysthatso
willthames
windowsansiblernew
wrosario
xiata
Xyon
yangchao0512
ziegenberg
Zverik

View file

@ -1,3 +0,0 @@
betanummeric
laurent-indermuehle
Andersson007

View file

@ -1,3 +0,0 @@
# Maintaining this collection
Refer to the [Maintainer guidelines](https://github.com/ansible/community-docs/blob/main/maintaining.rst).

116
Makefile
View file

@ -1,116 +0,0 @@
SHELL := /bin/bash
# To tell ansible-test and Make to not kill the containers on failure or
# end of tests. Disabled by default.
ifdef keep_containers_alive
_keep_containers_alive = --docker-terminate never
endif
# This match what GitHub Action will do. Disabled by default.
ifdef continue_on_errors
_continue_on_errors = --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
@echo -n $(db_engine_version) > tests/integration/db_engine_version
@echo -n $(connector_name) > tests/integration/connector_name
@echo -n $(connector_version) > tests/integration/connector_version
@echo -n $(ansible) > tests/integration/ansible
# Create podman network for systems missing it. Error can be ignored
podman network create podman || true
podman run \
--detach \
--replace \
--name primary \
--env MARIADB_ROOT_PASSWORD=msandbox \
--env MYSQL_ROOT_PASSWORD=msandbox \
--network podman \
--publish 3307:3306 \
--health-cmd '$(_health_cmd) ping -P 3306 -pmsandbox | grep alive || exit 1' \
docker.io/library/$(db_engine_name):$(db_engine_version) \
$(_command)
podman run \
--detach \
--replace \
--name replica1 \
--env MARIADB_ROOT_PASSWORD=msandbox \
--env MYSQL_ROOT_PASSWORD=msandbox \
--network podman \
--publish 3308:3306 \
--health-cmd '$(_health_cmd) ping -P 3306 -pmsandbox | grep alive || exit 1' \
docker.io/library/$(db_engine_name):$(db_engine_version) \
$(_command)
podman run \
--detach \
--replace \
--name replica2 \
--env MARIADB_ROOT_PASSWORD=msandbox \
--env MYSQL_ROOT_PASSWORD=msandbox \
--network podman \
--publish 3309:3306 \
--health-cmd '$(_health_cmd) ping -P 3306 -pmsandbox | grep alive || exit 1' \
docker.io/library/$(db_engine_name):$(db_engine_version) \
$(_command)
# Setup replication and restart containers using the same subshell to keep variables alive
db_ver=$(db_engine_version); \
maj="$${db_ver%.*.*}"; \
maj_min="$${db_ver%.*}"; \
min="$${maj_min#*.}"; \
if [[ "$(db_engine_name)" == "mysql" && "$$maj" -eq 8 && "$$min" -ge 2 ]]; then \
prima_conf='[mysqld]\\nserver-id=1\\nlog-bin=/var/lib/mysql/primary-bin\\nmysql-native-password=1'; \
repl1_conf='[mysqld]\\nserver-id=2\\nlog-bin=/var/lib/mysql/replica1-bin\\nmysql-native-password=1'; \
repl2_conf='[mysqld]\\nserver-id=3\\nlog-bin=/var/lib/mysql/replica2-bin\\nmysql-native-password=1'; \
else \
prima_conf='[mysqld]\\nserver-id=1\\nlog-bin=/var/lib/mysql/primary-bin'; \
repl1_conf='[mysqld]\\nserver-id=2\\nlog-bin=/var/lib/mysql/replica1-bin'; \
repl2_conf='[mysqld]\\nserver-id=3\\nlog-bin=/var/lib/mysql/replica2-bin'; \
fi; \
podman exec -e cnf="$$prima_conf" primary bash -c 'echo -e "$${cnf//\\n/\n}" > /etc/mysql/conf.d/replication.cnf'; \
podman exec -e cnf="$$repl1_conf" replica1 bash -c 'echo -e "$${cnf//\\n/\n}" > /etc/mysql/conf.d/replication.cnf'; \
podman exec -e cnf="$$repl2_conf" replica2 bash -c 'echo -e "$${cnf//\\n/\n}" > /etc/mysql/conf.d/replication.cnf'
# Don't restart a container unless it is healthy
while ! podman healthcheck run primary && [[ "$$SECONDS" -lt 120 ]]; do sleep 1; done
podman restart -t 30 primary
while ! podman healthcheck run replica1 && [[ "$$SECONDS" -lt 120 ]]; do sleep 1; done
podman restart -t 30 replica1
while ! podman healthcheck run replica2 && [[ "$$SECONDS" -lt 120 ]]; do sleep 1; done
podman restart -t 30 replica2
while ! podman healthcheck run primary && [[ "$$SECONDS" -lt 120 ]]; do sleep 1; done
mkdir -p .venv/$(ansible)
python$(local_python_version) -m venv .venv/$(ansible)
# Start venv (use `; \` to keep the same shell)
source .venv/$(ansible)/bin/activate; \
python$(local_python_version) -m ensurepip; \
python$(local_python_version) -m pip install --disable-pip-version-check \
https://github.com/ansible/ansible/archive/$(ansible).tar.gz; \
set -x; \
ansible-test integration $(target) -v --color --coverage --diff \
--docker ubuntu2204 \
--docker-network podman $(_continue_on_errors) $(_keep_containers_alive); \
set +x
# End of venv
rm tests/integration/db_engine_name
rm tests/integration/db_engine_version
rm tests/integration/connector_name
rm tests/integration/connector_version
rm tests/integration/ansible
ifndef keep_containers_alive
podman stop --time 0 --ignore primary replica1 replica2
podman rm --ignore --volumes primary replica1 replica2
endif

View file

@ -1,48 +0,0 @@
PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
--------------------------------------------
1. This LICENSE AGREEMENT is between the Python Software Foundation
("PSF"), and the Individual or Organization ("Licensee") accessing and
otherwise using this software ("Python") in source or binary form and
its associated documentation.
2. Subject to the terms and conditions of this License Agreement, PSF hereby
grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
analyze, test, perform and/or display publicly, prepare derivative works,
distribute, and otherwise use Python alone or in any derivative version,
provided, however, that PSF's License Agreement and PSF's notice of copyright,
i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Python Software Foundation;
All Rights Reserved" are retained in Python alone or in any derivative version
prepared by Licensee.
3. In the event Licensee prepares a derivative work that is based on
or incorporates Python or any part thereof, and wants to make
the derivative work available to others as provided herein, then
Licensee hereby agrees to include in any such work a brief summary of
the changes made to Python.
4. PSF is making Python available to Licensee on an "AS IS"
basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
INFRINGE ANY THIRD PARTY RIGHTS.
5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
6. This License Agreement will automatically terminate upon a material
breach of its terms and conditions.
7. Nothing in this License Agreement shall be deemed to create any
relationship of agency, partnership, or joint venture between PSF and
Licensee. This License Agreement does not grant permission to use PSF
trademarks or trade name in a trademark sense to endorse or promote
products or services of Licensee, or any third party.
8. By copying, installing or otherwise using Python, Licensee
agrees to be bound by the terms and conditions of this License
Agreement.

151
README.md
View file

@ -1,135 +1,29 @@
# MySQL and MariaDB collection for Ansible
[![Plugins CI](https://github.com/ansible-collections/community.mysql/workflows/Plugins%20CI/badge.svg?event=push)](https://github.com/ansible-collections/community.mysql/actions?query=workflow%3A"Plugins+CI") [![Codecov](https://img.shields.io/codecov/c/github/ansible-collections/community.mysql)](https://codecov.io/gh/ansible-collections/community.mysql) [![Discuss on Matrix at #mysql:ansible.com](https://img.shields.io/matrix/mysql:ansible.com.svg?server_fqdn=ansible-accounts.ems.host&label=Discuss%20on%20Matrix%20at%20%23mysql:ansible.com&logo=matrix)](https://matrix.to/#/#mysql:ansible.com)
This collection is a part of the Ansible package.
## Our mission
The Ansible `community.mysql` collection goals are to produce and maintain simple,
flexible, and powerful open-source software for automating MySQL and MariaDB related tasks
providing good documentation for easy deployment and use.
## Code of Conduct
We follow the [Ansible Code of Conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html) in all our interactions within this project.
If you encounter abusive behavior violating the [Ansible Code of Conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html), please refer to the [policy violations](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html#policy-violations) section of the Code of Conduct for information on how to raise a complaint.
## Communication
* Join the Ansible forum:
* [Get Help](https://forum.ansible.com/c/help/6): get help or help others.
* [Posts tagged with 'mysql'](https://forum.ansible.com/tag/mysql): leverage tags to narrow the scope.
* [MySQL Team](https://forum.ansible.com/g/MySQLTeam): by joining the team you will automatically get subscribed to the posts tagged with [mysql](https://forum.ansible.com/tag/mysql).
* [Social Spaces](https://forum.ansible.com/c/chat/4): gather and interact with fellow enthusiasts.
* [News & Announcements](https://forum.ansible.com/c/news/5): track project-wide announcements including social events.
* The Ansible [Bullhorn newsletter](https://docs.ansible.com/ansible/devel/community/communication.html#the-bullhorn): used to announce releases and important changes.
* Matrix chat:
* [#mysql:ansible.com](https://matrix.to/#/#mysql:ansible.com) room: questions on how to contribute to this collection.
For more information about communication, see the [Ansible communication guide](https://docs.ansible.com/ansible/devel/community/communication.html).
## Contributing
The content of this collection is made by [people](https://github.com/ansible-collections/community.mysql/blob/main/CONTRIBUTORS) just like you, a community of individuals collaborating on making the world better through developing automation software.
We are actively accepting new contributors.
Any kind of contribution is very welcome.
You don't know how to start? Refer to our [contribution guide](https://github.com/ansible-collections/community.mysql/blob/main/CONTRIBUTING.md) or ask us in the [#mysql:ansible.com room](https://matrix.to/#/#mysql:ansible.com) on [Matrix](https://docs.ansible.com/ansible/devel/community/communication.html#ansible-community-on-matrix)!
## Collection maintenance
The current maintainers (contributors with `write` or higher access) are listed in the [MAINTAINERS](https://github.com/ansible-collections/community.mysql/blob/main/MAINTAINERS) file. If you have questions or need help, feel free to mention them in the proposals.
To learn how to maintain / become a maintainer of this collection, refer to the [Maintainer guidelines](https://github.com/ansible-collections/community.mysql/blob/main/MAINTAINING.md).
It is necessary for maintainers of this collection to be subscribed to:
* The collection itself (the `Watch` button -> `All Activity` in the upper right corner of the repository's homepage).
* The "Changes Impacting Collection Contributors and Maintainers" [issue](https://github.com/ansible-collections/overview/issues/45).
They also should be subscribed to Ansible's [The Bullhorn newsletter](https://docs.ansible.com/ansible/devel/community/communication.html#the-bullhorn).
## Governance
We, [the MySQL team](https://forum.ansible.com/g/MySQLTeam), use [the forum](https://forum.ansible.com/tag/mysql) posts tagged with `mysql` for general announcements and discussions.
The process of decision making in this collection is based on discussing and finding consensus among participants.
Every voice is important and every idea is valuable. If you have something on your mind, create an issue or dedicated forum [discussion](https://forum.ansible.com/new-topic?title=topic%20title&body=topic%20body&category=project&tags=mysql) and let's discuss it!
# MySQL collection for Ansible
[![Plugins CI](https://github.com/ansible-collections/community.mysql/workflows/Plugins%20CI/badge.svg?event=push)](https://github.com/ansible-collections/community.mysql/actions?query=workflow%3A"Plugins+CI") [![Roles CI](https://github.com/ansible-collections/community.mysql/workflows/Roles%20CI/badge.svg?event=push)](https://github.com/ansible-collections/community.mysql/actions?query=workflow%3A"Roles+CI") [![Codecov](https://img.shields.io/codecov/c/github/ansible-collections/community.mysql)](https://codecov.io/gh/ansible-collections/community.mysql)
## Included content
- **Modules**:
- [mysql_db](https://docs.ansible.com/ansible/latest/collections/community/mysql/mysql_db_module.html)
- [mysql_info](https://docs.ansible.com/ansible/latest/collections/community/mysql/mysql_info_module.html)
- [mysql_query](https://docs.ansible.com/ansible/latest/collections/community/mysql/mysql_query_module.html)
- [mysql_replication](https://docs.ansible.com/ansible/latest/collections/community/mysql/mysql_replication_module.html)
- [mysql_role](https://docs.ansible.com/ansible/latest/collections/community/mysql/mysql_role_module.html)
- [mysql_user](https://docs.ansible.com/ansible/latest/collections/community/mysql/mysql_user_module.html)
- [mysql_variables](https://docs.ansible.com/ansible/latest/collections/community/mysql/mysql_variables_module.html)
- [mysql_db](https://docs.ansible.com/ansible/latest/modules/mysql_db_module.html)
- [mysql_info](https://docs.ansible.com/ansible/latest/modules/mysql_info_module.html)
- [mysql_query](https://docs.ansible.com/ansible/latest/modules/mysql_query_module.html)
- [mysql_replication](https://docs.ansible.com/ansible/latest/modules/mysql_replication_module.html)
- [mysql_user](https://docs.ansible.com/ansible/latest/modules/mysql_user_module.html)
- [mysql_variables](https://docs.ansible.com/ansible/latest/modules/mysql_variables_module.html)
## Tested with Ansible
## Releases Support Timeline
We maintain each major release (1.x.y, 2.x.y, ...) for two years after the next major version is released.
Here is the table for the support timeline:
- 1.x.y: released 2020-08-17, EOL
- 2.x.y: released 2021-04-15, EOL
- 3.x.y: released 2021-12-01, current
- 4.x.y: To be released
## Tested with
### ansible-core
- stable-2.16
- stable-2.17
- stable-2.18
- current development version
### Python
- 3.8 (Unit tests only)
- 3.9 (Unit tests only)
- 3.10 (Sanity, Units and integrations tests)
- 3.11 (Unit tests only, collection version >= 3.10.0)
### Databases
For MariaDB, only Long Term releases are tested. When multiple LTS are available, we test the oldest and the newest only. Usually breaking changes introduced in the versions in between are also present in the latest version.
- mysql 5.7.40 (collection version < 3.10.0)
- mysql 8.0.31 (collection version < 3.10.0)
- mysql 8.4.1 (collection version >= 3.10.0) !!! FAILING, no support yet !!!
- mariadb:10.3.34 (collection version < 3.5.1)
- mariadb:10.4.24 (collection version >= 3.5.2, < 3.10.0)
- mariadb:10.5.18 (collection version >= 3.5.2, < 3.10.0)
- mariadb:10.5.25 (collection version >= 3.10.0, <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
- pymysql 0.7.11 (collection version < 3.10 and MySQL 5.7)
- pymysql 0.9.3
- pymysql 0.10.1 (for RHEL8 context)
- pymysql 1.0.2 (collection version >= 3.6.1)
- pymysql 1.1.1 (collection version >= 3.10.0)
- 2.9
- 2.10
- devel
## External requirements
The MySQL modules rely on a [PyMySQL](https://github.com/PyMySQL/PyMySQL) connector.
The MySQL modules rely on a MySQL connector. The list of supported drivers is below:
The `mysqlclient` connector support has been [deprecated](https://github.com/ansible-collections/community.mysql/issues/654) - use `PyMySQL` connector instead! We will stop testing against it in collection version 4.0.0 and remove the related code in 5.0.0.
- [PyMySQL](https://github.com/PyMySQL/PyMySQL)
- [MySQLdb](https://github.com/PyMySQL/mysqlclient-python)
- Support for other Python MySQL connectors may be added in a future release.
## Using this collection
@ -147,18 +41,7 @@ You can also include it in a `requirements.yml` file and install it via `ansible
---
collections:
- name: community.mysql
```
Note that if you install the collection from Ansible Galaxy, it will not be upgraded automatically if you upgrade the Ansible package. To upgrade the collection to the latest available version, run the following command:
```bash
ansible-galaxy collection install community.mysql --upgrade
```
You can also install a specific version of the collection, for example, if you need to downgrade when something is broken in the latest version (please report an issue in this repository). Use the following syntax:
```bash
ansible-galaxy collection install community.mysql:==2.0.0
version: 1.0.1
```
See [Ansible Using collections](https://docs.ansible.com/ansible/latest/user_guide/collections_using.html) for more details.

View file

@ -1,143 +0,0 @@
# Tests
This collection uses GitHub Actions to run ansible-test to validate its content. Three type of tests are used: Sanity, Integration and Units.
The tests covers plugins and roles (no role available yet, but tests are ready) and can be found here:
- Plugins: *.github/workflows/ansible-test-plugins.yml*
- Roles: *.github/workflows/ansible-test-roles.yml* (unused yet)
Everytime you push on your fork or you create a pull request, both workflows runs. You can see the output on the "Actions" tab.
## Integration tests
You can use GitHub to run ansible-test either on the community repo or your fork. But sometimes you want to quickly test a single version or a single target. To do that, you can use the Makefile present at the root of this repository.
For now, the makefile only supports Podman.
### Requirements
- python >= 3.8
- make
- podman
- Minimum 15GB of free space on the device storing containers images and volumes. You can use this command to check: `podman system info --format='{{.Store.GraphRoot}}'|xargs findmnt --noheadings --nofsroot --output SOURCE --target|xargs df -h --output=size,used,avail,pcent,target`
- Minimum 2GB of RAM
### ansible-test environment
Integration tests use the default container from ansible-test. Then required packages for the tests are installed from the `setup_controller` target located in the `tests/integration/targets` folder.
### Makefile options
The Makefile accept the following options
- `local_python_version`
- Mandatory: false
- Choices:
- "3.8"
- "3.9"
- "3.10"
- "3.11" (for stable-2.15+)
- Description: If `Python -V` shows an unsupported version, use this option to select a compatible Python version available on your system. Use `ls /usr/bin/python3*|grep -v config` to list the available versions (You may have to install one). Unsupported versions are those that are too recent for the Ansible version you are using. In such cases, you will see an error message similar to: 'This version of ansible-test cannot be executed with Python version 3.12.3. Supported Python versions are: 3.9, 3.10, 3.11'.
- `ansible`
- Mandatory: true
- Choices:
- "stable-2.15"
- "stable-2.16"
- "stable-2.17"
- "devel"
- Description: Version of ansible to install in a venv to run ansible-test
- `db_engine_name`
- Mandatory: true
- Choices:
- "mysql"
- "mariadb"
- Description: The name of the database engine to use for the service containers that will host a primary database and two replicas.
- `db_engine_version`
- Mandatory: true
- Choices:
- "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.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`
- Mandatory: true
- Choices:
- "pymysql"
- "mysqlclient"
- Description: The python package of the connector to use. In addition to selecting the test container, this value is also used for tests filtering: `when: connector_name == 'pymysql'`.
- `connector_version`
- Mandatory: true
- Choices:
- "0.9.3" <- pymysql
- "0.10.1" <- pymysql
- "1.0.2" <- pymysql
- "1.1.1" <- pymysql
- Description: The version of the python package of the connector to use. This value is used to filter tests meant for other connectors.
- `target`
- Mandatory: false
- Choices:
- "test_mysql_db"
- "test_mysql_info"
- "test_mysql_query"
- "test_mysql_replication"
- "test_mysql_role"
- "test_mysql_user"
- "test_mysql_variables"
- Description: If omitted, all test targets will run. But you can limit the tests to a single target to speed up your tests.
- `keep_containers_alive`
- Mandatory: false
- Description: This option keeps all tree databases containers and the ansible-test container alive at the end of tests or in case of failure. This is useful to enter one of the containers with `podman exec -it <container-name> bash` for debugging. Rerunning the
tests will overwrite the 3 databases containers so no need to kill them in advance. But nothing will kill the ansible-test container. You must do that using `podman stop` and `podman rm`. Add any value to activate this option: `keep_containers_alive=1`
- `continue_on_errors`
- Mandatory: false
- Description: Tells ansible-test to continue on errors. This is the way the GitHub Action's workflow runs the tests. This can be used to catch all errors in a single run, but you'll need to scroll up to find them. Add any value to activate this option: `continue_on_errors=1`
#### Makefile usage examples:
```sh
# Run all targets
make ansible="stable-2.16" db_engine_name="mysql" db_engine_version="8.0.31" connector_name="pymysql" connector_version="1.0.2"
# A single target
make ansible="stable-2.16" db_engine_name="mysql" db_engine_version="8.0.31" connector_name="pymysql" connector_version="1.0.2" target="test_mysql_info"
# Keep databases and ansible tests containers alives
# A single target and continue on errors
make ansible="stable-2.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="11.4.5" connector_name="pymysql" connector_version="1.0.2"
```
### Run all tests
GitHub Action offer a test matrix that run every combination of MySQL, MariaDB and Connector against each other. To reproduce this, this repo provides a script called *run_all_tests.py*.
Examples:
```sh
python run_all_tests.py
```
### Add a new Connector or Database version
New components version should be added to this file: [.github/workflows/ansible-test-plugins.yml](https://github.com/ansible-collections/community.mysql/tree/main/.github/workflows)
Be careful to not add too much tests. The matrix creates an exponential number of virtual machines!

98
changelogs/CHANGELOG.rst Normal file
View file

@ -0,0 +1,98 @@
========================================
Community MySQL Collection Release Notes
========================================
.. contents:: Topics
v1.0.2
======
Release Summary
---------------
This is the patch release of the ``community.mysql`` collection.
This changelog contains all changes to the modules in this collection that have been added after the release of ``community.mysql`` 1.0.1.
Bugfixes
--------
- mysql_user - fix module's crash when modifying a user with ``host_all`` (https://github.com/ansible-collections/community.mysql/issues/39).
v1.0.1
======
Release Summary
---------------
This is the patch release of the ``community.mysql`` collection.
This changelog contains all changes to the modules in this collection that have been added after the release of ``community.mysql`` 1.0.0.
Bugfixes
--------
- mysql_db - fix false warning related to ``unsafe_login_password`` option (https://github.com/ansible-collections/community.mysql/issues/33).
- mysql_user - added tests to verify that TLS requirements are removed with an empty ``tls_requires`` option (https://github.com/ansible-collections/community.mysql/issues/20).
- mysql_user - correct procedure to check existing TLS requirements (https://github.com/ansible-collections/community.mysql/pull/26).
- mysql_user - minor syntax fixes (https://github.com/ansible-collections/community.mysql/pull/26).
v1.0.0
======
Release Summary
---------------
This is the first proper release of the ``community.mysql`` collection.
This changelog contains all changes to the modules in this collection that were added after the release of Ansible 2.9.0.
Minor Changes
-------------
- mysql_db - add ``master_data`` parameter (https://github.com/ansible/ansible/pull/66048).
- mysql_db - add ``skip_lock_tables`` option (https://github.com/ansible/ansible/pull/66688).
- mysql_db - add the ``check_implicit_admin`` parameter (https://github.com/ansible/ansible/issues/24418).
- mysql_db - add the ``dump_extra_args`` parameter (https://github.com/ansible/ansible/pull/67747).
- mysql_db - add the ``executed_commands`` returned value (https://github.com/ansible/ansible/pull/65498).
- mysql_db - add the ``force`` parameter (https://github.com/ansible/ansible/pull/65547).
- mysql_db - add the ``restrict_config_file`` parameter (https://github.com/ansible/ansible/issues/34488).
- mysql_db - add the ``unsafe_login_password`` parameter (https://github.com/ansible/ansible/issues/63955).
- mysql_db - add the ``use_shell`` parameter (https://github.com/ansible/ansible/issues/20196).
- mysql_info - add ``exclude_fields`` parameter (https://github.com/ansible/ansible/issues/63319).
- mysql_info - add ``global_status`` filter parameter option and return (https://github.com/ansible/ansible/pull/63189).
- mysql_info - add ``return_empty_dbs`` parameter to list empty databases (https://github.com/ansible/ansible/issues/65727).
- mysql_replication - add ``channel`` parameter (https://github.com/ansible/ansible/issues/29311).
- mysql_replication - add ``connection_name`` parameter (https://github.com/ansible/ansible/issues/46243).
- mysql_replication - add ``fail_on_error`` parameter (https://github.com/ansible/ansible/pull/66252).
- mysql_replication - add ``master_delay`` parameter (https://github.com/ansible/ansible/issues/51326).
- mysql_replication - add ``master_use_gtid`` parameter (https://github.com/ansible/ansible/pull/62648).
- mysql_replication - add ``queries`` return value (https://github.com/ansible/ansible/pull/63036).
- mysql_replication - add support of ``resetmaster`` choice to ``mode`` parameter (https://github.com/ansible/ansible/issues/42870).
- mysql_user - ``priv`` parameter can be string or dictionary (https://github.com/ansible/ansible/issues/57533).
- mysql_user - add TLS REQUIRES parameters (https://github.com/ansible-collections/community.mysql/pull/9).
- mysql_user - add ``plugin_auth_string`` parameter (https://github.com/ansible/ansible/pull/44267).
- mysql_user - add ``plugin_hash_string`` parameter (https://github.com/ansible/ansible/pull/44267).
- mysql_user - add ``plugin`` parameter (https://github.com/ansible/ansible/pull/44267).
- mysql_user - add the resource_limits parameter (https://github.com/ansible-collections/community.general/issues/133).
- mysql_variables - add ``mode`` parameter (https://github.com/ansible/ansible/issues/60119).
Bugfixes
--------
- mysql - dont mask ``mysql_connect`` function errors from modules (https://github.com/ansible/ansible/issues/64560).
- mysql_db - fix Broken pipe error appearance when state is import and the target file is compressed (https://github.com/ansible/ansible/issues/20196).
- mysql_db - fix bug in the ``db_import`` function introduced by https://github.com/ansible/ansible/pull/56721 (https://github.com/ansible/ansible/issues/65351).
- mysql_info - add parameter for __collect to get only what are wanted (https://github.com/ansible-collections/community.general/pull/136).
- mysql_replication - allow to pass empty values to parameters (https://github.com/ansible/ansible/issues/23976).
- mysql_user - Fix idempotence when long grant lists are used (https://github.com/ansible/ansible/issues/68044)
- mysql_user - Remove false positive ``no_log`` warning for ``update_password`` option
- mysql_user - add ``INVOKE LAMBDA`` privilege support (https://github.com/ansible-collections/community.general/issues/283).
- mysql_user - add missed privileges to support (https://github.com/ansible-collections/community.general/issues/617).
- mysql_user - fix ``host_all`` arguments conversion string formatting error (https://github.com/ansible/ansible/issues/29644).
- mysql_user - fix overriding password to the same (https://github.com/ansible-collections/community.general/issues/543).
- mysql_user - fix support privileges with underscore (https://github.com/ansible/ansible/issues/66974).
- mysql_user - fix the error No database selected (https://github.com/ansible/ansible/issues/68070).
- mysql_user - make sure current_pass_hash is a string before using it in comparison (https://github.com/ansible/ansible/issues/60567).
- mysql_variable - fix the module doesn't support variables name with dot (https://github.com/ansible/ansible/issues/54239).

View file

@ -1,578 +1,135 @@
ancestor: 2.0.0
ancestor: null
releases:
3.0.0:
changes:
breaking_changes:
- mysql_replication - remove ``Is_Slave`` and ``Is_Master`` return values (were
replaced with ``Is_Primary`` and ``Is_Replica`` (https://github.com/ansible-collections/community.mysql/issues/145).
- mysql_replication - remove the mode options values containing ``master``/``slave``
and the master_use_gtid option ``slave_pos`` (were replaced with corresponding
``primary``/``replica`` values) (https://github.com/ansible-collections/community.mysql/issues/145).
- mysql_user - remove support for the `REQUIRESSL` special privilege as it has
ben superseded by the `tls_requires` option (https://github.com/ansible-collections/community.mysql/discussions/121).
- mysql_user - validate privileges using database engine directly (https://github.com/ansible-collections/community.mysql/issues/234
https://github.com/ansible-collections/community.mysql/pull/243). Do not validate
privileges in this module anymore.
release_summary: 'This is the major release of the ``community.mysql`` collection.
This changelog contains all breaking changes to the modules in this collection
that have been added after the release of ``community.mysql`` 2.3.2.'
fragments:
- 243-get-rid-of-privs-comparison.yml
- 244-remove-requiressl-privilege.yaml
- 3.0.0.yml
- 300-mysql_replication_remove_master_slave.yml
release_date: '2021-12-01'
3.1.0:
1.0.0:
changes:
bugfixes:
- Collection core functions - use vendored version of ``distutils.version``
instead of the deprecated Python standard library ``distutils`` (https://github.com/ansible-collections/community.mysql/pull/269).
- mysql - dont mask ``mysql_connect`` function errors from modules (https://github.com/ansible/ansible/issues/64560).
- mysql_db - fix Broken pipe error appearance when state is import and the target
file is compressed (https://github.com/ansible/ansible/issues/20196).
- mysql_db - fix bug in the ``db_import`` function introduced by https://github.com/ansible/ansible/pull/56721
(https://github.com/ansible/ansible/issues/65351).
- mysql_info - add parameter for __collect to get only what are wanted (https://github.com/ansible-collections/community.general/pull/136).
- mysql_replication - allow to pass empty values to parameters (https://github.com/ansible/ansible/issues/23976).
- mysql_user - Fix idempotence when long grant lists are used (https://github.com/ansible/ansible/issues/68044)
- mysql_user - Remove false positive ``no_log`` warning for ``update_password``
option
- mysql_user - add ``INVOKE LAMBDA`` privilege support (https://github.com/ansible-collections/community.general/issues/283).
- mysql_user - add missed privileges to support (https://github.com/ansible-collections/community.general/issues/617).
- mysql_user - fix ``host_all`` arguments conversion string formatting error
(https://github.com/ansible/ansible/issues/29644).
- mysql_user - fix overriding password to the same (https://github.com/ansible-collections/community.general/issues/543).
- mysql_user - fix support privileges with underscore (https://github.com/ansible/ansible/issues/66974).
- mysql_user - fix the error No database selected (https://github.com/ansible/ansible/issues/68070).
- mysql_user - make sure current_pass_hash is a string before using it in comparison
(https://github.com/ansible/ansible/issues/60567).
- mysql_variable - fix the module doesn't support variables name with dot (https://github.com/ansible/ansible/issues/54239).
minor_changes:
- Added explicit description of the supported versions of databases and connectors.
Changes to the collection are **NOT** tested against database versions older
than `mysql 5.7.31` and `mariadb 10.2.37` or connector versions older than
`pymysql 0.7.10` and `mysqlclient 2.0.1`. (https://github.com/ansible-collections/community.mysql/discussions/141)
- mysql_user - added the ``force_context`` boolean option to set the default
database context for the queries to be the ``mysql`` database. This way replication/binlog
filters can catch the statements (https://github.com/ansible-collections/community.mysql/issues/265).
release_summary: 'This is the minor release of the ``community.mysql`` collection.
- mysql_db - add ``master_data`` parameter (https://github.com/ansible/ansible/pull/66048).
- mysql_db - add ``skip_lock_tables`` option (https://github.com/ansible/ansible/pull/66688).
- mysql_db - add the ``check_implicit_admin`` parameter (https://github.com/ansible/ansible/issues/24418).
- mysql_db - add the ``dump_extra_args`` parameter (https://github.com/ansible/ansible/pull/67747).
- mysql_db - add the ``executed_commands`` returned value (https://github.com/ansible/ansible/pull/65498).
- mysql_db - add the ``force`` parameter (https://github.com/ansible/ansible/pull/65547).
- mysql_db - add the ``restrict_config_file`` parameter (https://github.com/ansible/ansible/issues/34488).
- mysql_db - add the ``unsafe_login_password`` parameter (https://github.com/ansible/ansible/issues/63955).
- mysql_db - add the ``use_shell`` parameter (https://github.com/ansible/ansible/issues/20196).
- mysql_info - add ``exclude_fields`` parameter (https://github.com/ansible/ansible/issues/63319).
- mysql_info - add ``global_status`` filter parameter option and return (https://github.com/ansible/ansible/pull/63189).
- mysql_info - add ``return_empty_dbs`` parameter to list empty databases (https://github.com/ansible/ansible/issues/65727).
- mysql_replication - add ``channel`` parameter (https://github.com/ansible/ansible/issues/29311).
- mysql_replication - add ``connection_name`` parameter (https://github.com/ansible/ansible/issues/46243).
- mysql_replication - add ``fail_on_error`` parameter (https://github.com/ansible/ansible/pull/66252).
- mysql_replication - add ``master_delay`` parameter (https://github.com/ansible/ansible/issues/51326).
- mysql_replication - add ``master_use_gtid`` parameter (https://github.com/ansible/ansible/pull/62648).
- mysql_replication - add ``queries`` return value (https://github.com/ansible/ansible/pull/63036).
- mysql_replication - add support of ``resetmaster`` choice to ``mode`` parameter
(https://github.com/ansible/ansible/issues/42870).
- mysql_user - ``priv`` parameter can be string or dictionary (https://github.com/ansible/ansible/issues/57533).
- mysql_user - add TLS REQUIRES parameters (https://github.com/ansible-collections/community.mysql/pull/9).
- mysql_user - add ``plugin_auth_string`` parameter (https://github.com/ansible/ansible/pull/44267).
- mysql_user - add ``plugin_hash_string`` parameter (https://github.com/ansible/ansible/pull/44267).
- mysql_user - add ``plugin`` parameter (https://github.com/ansible/ansible/pull/44267).
- mysql_user - add the resource_limits parameter (https://github.com/ansible-collections/community.general/issues/133).
- mysql_variables - add ``mode`` parameter (https://github.com/ansible/ansible/issues/60119).
release_summary: 'This is the first proper release of the ``community.mysql``
collection.
This changelog contains all changes to the modules in this collection
that have been added after the release of ``community.mysql`` 3.0.0.'
fragments:
- 141-supported-database-and-connector-versions.yaml
- 266-default-database-for-mysql-user.yml
- 267-prepare_for_distutils_be_removed.yml
- 3.1.0.yml
release_date: '2022-01-18'
3.1.1:
changes:
bugfixes:
- mysql_role - make the ``set_default_role_all`` parameter actually working
(https://github.com/ansible-collections/community.mysql/pull/282).
release_summary: 'This is the patch release of the ``community.mysql`` collection.
This changelog contains all changes to the modules in this collection
that have been added after the release of ``community.mysql`` 3.1.0.'
fragments:
- 282-mysql_role_fix_set_default_role_all_argument.yml
- 3.1.1.yml
release_date: '2022-02-16'
3.1.2:
changes:
bugfixes:
- Collection core functions - fixes related to the mysqlclient Python connector
(https://github.com/ansible-collections/community.mysql/issues/292).
release_summary: 'This is the patch release of the ``community.mysql`` collection.
This changelog contains all changes to the modules in this collection
that have been added after the release of ``community.mysql`` 3.1.1.'
fragments:
- 0-mysqlclient.yml
- 3.1.2.yml
release_date: '2022-03-14'
3.1.3:
changes:
bugfixes:
- mysql_replication - fails when using the `primary_use_gtid` option with `slave_pos`
or `replica_pos` (https://github.com/ansible-collections/community.mysql/issues/335).
- mysql_role - remove redundant connection closing (https://github.com/ansible-collections/community.mysql/pull/330).
- 'mysql_user - fix the possibility for a race condition that breaks certain
(circular) replication configurations when ``DROP USER`` is executed on multiple
nodes in the replica set. Adding ``IF EXISTS`` avoids the need to use ``sql_log_bin:
no`` making the statement always replication safe (https://github.com/ansible-collections/community.mysql/pull/287).'
release_summary: 'This is the patch release of the ``community.mysql`` collection.
This changelog contains all changes to the modules in this collection
that have been added after the release of ``community.mysql`` 3.1.2.'
fragments:
- 0-mysql_replication_replica_pos.yml
- 3.1.3.yml
- 307-mysql_user_add_if_exists_to_drop.yml
- 329-mysql_role-remove-redudant-connection-closing.yml
release_date: '2022-04-26'
3.10.0:
changes:
bugfixes:
- mysql_info - Add ``plugin_hash_string`` to ``users_info`` filter's output.
The existing ``plugin_auth_string`` contained the hashed password and thus
is missleading, it will be removed from community.mysql 4.0.0. (https://github.com/ansible-collections/community.mysql/pull/629).
- mysql_user - Added a warning to update_password's on_new_username option if
multiple accounts with the same username but different passwords exist (https://github.com/ansible-collections/community.mysql/pull/642).
- mysql_user - Fix ``tls_requires`` not removing ``SSL`` and ``X509`` when sets
as empty (https://github.com/ansible-collections/community.mysql/pull/628).
- mysql_user - Fix idempotence when using variables from the ``users_info``
filter of ``mysql_info`` as an input (https://github.com/ansible-collections/community.mysql/pull/628).
- mysql_user - Fixed an IndexError in the update_password functionality introduced
in PR https://github.com/ansible-collections/community.mysql/pull/580 and
released in community.mysql 3.8.0. If you used this functionality, please
avoid versions 3.8.0 to 3.9.0 (https://github.com/ansible-collections/community.mysql/pull/642).
- mysql_user - add correct ``ed25519`` auth plugin handling (https://github.com/ansible-collections/community.mysql/issues/6).
- mysql_variables - fix the module always changes on boolean values (https://github.com/ansible-collections/community.mysql/issues/652).
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).
- mysql_info - return a database server engine used (https://github.com/ansible-collections/community.mysql/issues/644).
- mysql_replication - Adds support for `CHANGE REPLICATION SOURCE TO` statement
(https://github.com/ansible-collections/community.mysql/issues/635).
- mysql_replication - Adds support for `SHOW BINARY LOG STATUS` and `SHOW BINLOG
STATUS` on getprimary mode.
- mysql_replication - Improve detection of IsReplica and IsPrimary by inspecting
the dictionary returned from the SQL query instead of relying on variable
types. This ensures compatibility with changes in the connector or the output
of SHOW REPLICA STATUS and SHOW MASTER STATUS, allowing for easier maintenance
if these change in the future.
- mysql_user - Add salt parameter to generate static hash for `caching_sha2_password`
and `sha256_password` plugins.
release_summary: 'This is a minor release of the ``community.mysql`` collection.
This changelog contains all changes to the modules and plugins in this
collection that have been made after the previous release.'
fragments:
- 0-mysql_user.yml
- 1-mysql_info.yml
- 2-mysql_variables.yml
- 3-deprecate_mysqlclient.yml
- 3.10.0.yml
- add_salt_param_to_gen_sha256_hash.yml
- get_primary_show_binary_log_status.yml
- improve_get_replica_primary_status.yml
- lie_fix_mysql_user_on_new_username.yml
- lie_fix_plugin_hash_string_return.yml
- mysql_user_tls_requires.yml
- supports_mysql_change_replication_source_to.yml
release_date: '2024-08-22'
3.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.
release_summary: 'This is a patch release of the ``community.mysql`` collection.
Besides a bugfix, it contains an important upcoming breaking-change information.'
fragments:
- 0-mysql_user.yml
- 3.10.1.yml
- 596-fix-check-changes.yaml
release_date: '2024-09-04'
3.10.2:
changes:
bugfixes:
- mysql_user - add correct ``ed25519`` auth plugin handling when creating a
user (https://github.com/ansible-collections/community.mysql/issues/672).
release_summary: 'This is a bugfix release of the ``community.mysql`` collection.
This changelog contains all changes to the modules and plugins in this
collection that have been made after the previous release.'
fragments:
- 0-mysql_user.yml
- 3.10.2.yml
release_date: '2024-09-06'
3.10.3:
changes:
bugfixes:
- mysql_user - add correct ``ed25519`` auth plugin handling when creating a
user (https://github.com/ansible-collections/community.mysql/pull/676).
release_summary: 'This is a bugfix release of the ``community.mysql`` collection.
This changelog contains all changes to the modules and plugins in this
collection that have been made after the previous release.'
fragments:
- 0-mysql_user.yml
- 3.10.3.yml
release_date: '2024-09-09'
3.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.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.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:
- mysql_user - fix missing dynamic privileges after revoke and grant privileges
to user (https://github.com/ansible-collections/community.mysql/issues/120).
- mysql_user - fix parsing privs when a user has roles assigned (https://github.com/ansible-collections/community.mysql/issues/231).
major_changes:
- The community.mysql collection no longer supports ``Ansible 2.9`` and ``ansible-base
2.10``. While we take no active measures to prevent usage and there are no
plans to introduce incompatible code to the modules, we will stop testing
against ``Ansible 2.9`` and ``ansible-base 2.10``. Both will very soon be
End of Life and if you are still using them, you should consider upgrading
to the ``latest Ansible / ansible-core 2.11 or later`` as soon as possible
(https://github.com/ansible-collections/community.mysql/pull/343).
minor_changes:
- 'mysql_user and mysql_role: Add the argument ``subtract_privs`` (boolean,
default false, mutually exclusive with ``append_privs``). If set, the privileges
given in ``priv`` are revoked and existing privileges are kept (https://github.com/ansible-collections/community.mysql/pull/333).'
release_summary: 'This is the minor release of the ``community.mysql`` collection.
This changelog contains all changes to the modules in this collection
that have been added after the release of ``community.mysql`` 3.1.3.'
fragments:
- 001-mysql_user_fix_pars_users_with_roles_assigned.yml
- 3.2.0.yml
- 333-mysql_user-mysql_role-add-subtract_privileges-argument.yml
- 338-mysql_user_fix_missing_dynamic_privileges.yml
- drop_support_of_2.9-2.10.yml
release_date: '2022-05-13'
3.2.1:
changes:
bugfixes:
- Include ``PSF-license.txt`` file for ``plugins/module_utils/_version.py``.
release_summary: 'This is the patch release of the ``community.mysql`` collection.
This changelog contains all changes to the modules in this collection
that have been added after the release of ``community.mysql`` 3.2.0.'
fragments:
- 3.2.1.yml
- psf-license.yml
release_date: '2022-05-17'
3.3.0:
changes:
bugfixes:
- mysql_query - fix false change reports when ``IF EXISTS/IF NOT EXISTS`` clause
is used (https://github.com/ansible-collections/community.mysql/issues/268).
- 'mysql_role - don''t add members to a role when creating the role and ``detach_members:
true`` is set (https://github.com/ansible-collections/community.mysql/pull/367).'
- 'mysql_role - in some cases (when "SHOW GRANTS" did not use backticks for
quotes), no unwanted members were detached from the role (and redundant "GRANT"
statements were executed for wanted members). This is fixed by querying the
existing role members from the mysql.role_edges (MySQL) or mysql.roles_mapping
(MariaDB) tables instead of parsing the "SHOW GRANTS" output (https://github.com/ansible-collections/community.mysql/pull/368).
This changelog contains all changes to the modules in this collection that
were added after the release of Ansible 2.9.0.
'
- mysql_user - fix logic when ``update_password`` is set to ``on_create`` for
users using ``plugin*`` arguments (https://github.com/ansible-collections/community.mysql/issues/334).
The ``on_create`` sets ``password`` to None for old mysql_native_authentication
but not for authentiation methods which uses the ``plugin*`` arguments. This
PR changes this so ``on_create`` also exchange ``plugin``, ``plugin_hash_string``,
``plugin_auth_string`` to None in the list of arguments to change
minor_changes:
- 'mysql_role - add the argument ``members_must_exist`` (boolean, default true).
The assertion that the users supplied in the ``members`` argument exist is
only executed when the new argument ``members_must_exist`` is ``true``, to
allow opt-out (https://github.com/ansible-collections/community.mysql/pull/369).
'
- 'mysql_user - Add the option ``on_new_username`` to argument ``update_password``
to reuse the password (plugin and authentication_string) when creating a new
user if some user with the same name already exists. If the existing user
with the same name have varying passwords, the password from the arguments
is used like with ``update_password: always`` (https://github.com/ansible-collections/community.mysql/pull/365).
'
- 'mysql_user - Add the result field ``password_changed`` (boolean). It is true,
when the user got a new password. When the user was created with ``update_password:
on_new_username`` and an existing password was reused, ``password_changed``
is false (https://github.com/ansible-collections/community.mysql/pull/365).
'
release_summary: 'This is the minor release of the ``community.mysql`` collection.
This changelog contains all changes to the modules in this collection
that have been added after the release of ``community.mysql`` 3.2.1.'
fragments:
- 3.3.0.yml
- 322-mysql_query_fix_false_change_report.yml
- 334-mysql_user_fix_logic_on_oncreate.yml
- 365-mysql_user-add-on_new_username-and-password_changed.yml
- 367-mysql_role-fix-deatch-members.yml
- 368-mysql_role-fix-member-detection.yml
- 369_mysql_role-add-members_must_exist.yml
release_date: '2022-06-02'
3.4.0:
- 1.0.0.yml
- 142-mysql_user_add_resource_limit_parameter.yml
- 151-mysql_db_add_use_shell_parameter.yml
- 18-mysql_user-update_password-no_log.yml
- 225-mysql_user_fix_no_database_selected.yml
- 285-mysql_user_invoke_lambda_support.yml
- 369-mysql_user_add_tls_requires.yml
- 428-mysql_db_add_unsafe_login_password_param.yml
- 468-mysql_db_add_restrict_config_file_param.yml
- 486-mysql_db_add_check_implicit_admin_parameter.yml
- 490-mysql_user_fix_cursor_errors.yml
- 609-mysql_user_fix_overriding_password_to_the_same.yml
- 618-mysql_user_add_missed_privileges.yml
- 62648-mysql_replication_add_master_use_gtid_param.yml
- 63036-mysql_replication_add_return_value.yml
- 63130-mysql_replication_add_master_delay_parameter.yml
- 63189-mysql_info-global-status.yml
- 63229-mysql_replication_add_connection_name_parameter.yml
- 63271-mysql_replication_add_channel_parameter.yml
- 63321-mysql_replication_add_resetmaster_to_mode.yml
- 63371-mysql_info_add_exclude_fields_parameter.yml
- 63546-mysql_replication_allow_to_pass_empty_values.yml
- 63547-mysql_variables_add_mode_param.yml
- 64059-mysql_user_fix_password_comparison.yaml
- 64585-mysql_dont_mask_mysql_connect_errors_from_modules.yml
- 65498-mysql_db_add_executed_commands_return_val.yml
- 65547-mysql_db_add_force_param.yml
- 65755-mysql_info_doesnt_list_empty_dbs.yml
- 65789-mysql_user_add_plugin_authentication_parameters.yml
- 66048-mysql_add_master_data_parameter.yml
- 66252-mysql_replication_fail_on_error.yml
- 66688-mysql_db_add_skip_lock_tables_option.yml
- 66801-mysql_user_priv_can_be_dict.yml
- 66806-mysql_variables_not_support_variables_with_dot.yml
- 66974-mysql_user_doesnt_support_privs_with_underscore.yml
- 67337-fix-proxysql-mysql-cursor.yaml
- 67747-mysql_db_add_dump_extra_args_param.yml
- 67767-mysql_db_fix_bug_introduced_by_56721.yml
- mysql_info_add_parameter.yml
- mysql_user_idempotency.yml
release_date: '2020-08-17'
1.0.1:
changes:
bugfixes:
- Include ``simplified_bsd.txt`` license file for various module utils.
- mysql_db - Using compression masks errors messages from mysql_dump. By default
the fix is inactive to ensure retro-compatibility with system without bash.
To activate the fix, use the module option ``pipefail=true`` (https://github.com/ansible-collections/community.mysql/issues/256).
- mysql_replication - when the ``primary_ssl`` argument is set to ``no``, the
module will turn off SSL (https://github.com/ansible-collections/community.mysql/issues/393).
major_changes:
- mysql_db - the ``pipefail`` argument's default value will be changed to ``true``
in community.mysql 4.0.0. If your target machines do not use ``bash`` as a
default interpreter, set ``pipefail`` to ``false`` explicitly. However, we
strongly recommend setting up ``bash`` as a default and ``pipefail=true``
as it will protect you from getting broken dumps you don't know about (https://github.com/ansible-collections/community.mysql/issues/407).
minor_changes:
- mysql_db - add the ``chdir`` argument to avoid failings when a dump file contains
relative paths (https://github.com/ansible-collections/community.mysql/issues/395).
- mysql_db - add the ``pipefail`` argument to avoid broken dumps when ``state``
is ``dump`` and compression is used (https://github.com/ansible-collections/community.mysql/issues/256).
release_summary: 'This is the minor release of the ``community.mysql`` collection.
This changelog contains all changes to the modules in this collection
that have been added after the release of ``community.mysql`` 3.3.0.'
fragments:
- 0-mysql_db_add_chdir_argument.yml
- 1-mysql_replication_can_disable_master_ssl.yml
- 2-mysql_db_announce.yml
- 3.4.0.yml
- fix-256-mysql_dump-errors.yml
- simplified-bsd-license.yml
release_date: '2022-08-02'
3.5.0:
changes:
bugfixes:
- mysql_user - grant option was revoked accidentally when modifying users. This
fix revokes grant option only when privs are setup to do that (https://github.com/ansible-collections/community.mysql/issues/77#issuecomment-1209693807).
minor_changes:
- 'mysql_replication - add a new option: ``primary_ssl_verify_server_cert``
(https://github.com//pull/435).'
release_summary: 'This is the minor release of the ``community.mysql`` collection.
This changelog contains all changes to the modules in this collection
that have been added after the release of ``community.mysql`` 3.4.0.'
fragments:
- 3.5.0.yml
- 434-do-not-revoke-grant-option-always.yaml
- 435-mysql_replication_verify_server_cert.yml
release_date: '2022-09-05'
3.5.1:
changes:
bugfixes:
- mysql_user, mysql_role - mysql/mariadb recent versions translate 'ALL PRIVILEGES'
to a list of specific privileges. That caused a change every time we modified
user privileges. This fix compares privs before and after user modification
to avoid this infinite change (https://github.com/ansible-collections/community.mysql/issues/77).
- mysql_db - fix false warning related to ``unsafe_login_password`` option (https://github.com/ansible-collections/community.mysql/issues/33).
- mysql_user - added tests to verify that TLS requirements are removed with
an empty ``tls_requires`` option (https://github.com/ansible-collections/community.mysql/issues/20).
- mysql_user - correct procedure to check existing TLS requirements (https://github.com/ansible-collections/community.mysql/pull/26).
- mysql_user - minor syntax fixes (https://github.com/ansible-collections/community.mysql/pull/26).
release_summary: 'This is the patch release of the ``community.mysql`` collection.
This changelog contains all changes to the modules and plugins in this collection
This changelog contains all changes to the modules in this collection that
have been added after the release of ``community.mysql`` 1.0.0.
that have been made after the previous release.'
'
fragments:
- 3.5.1.yml
- 438-fix-privilege-changing-everytime.yml
release_date: '2022-09-09'
3.6.0:
- 1.0.1.yml
- 26-remove_tls_requirements.yml
- 34-mysql_db_fix_false_warning.yml
release_date: '2020-09-29'
1.0.2:
changes:
bugfixes:
- mysql_user - when revoke privs consists only of ``GRANT``, a 2nd revoke query
is executed with empty privs to revoke that ended in an SQL exception (https://github.com/ansible-collections/community.mysql/pull/503).
- mysql_variables - add uppercase character pattern to regex to allow GLOBAL
variables containing uppercase characters. This recognizes variable names
used in Galera, for example, ``wsrep_OSU_method``, which breaks the normal
pattern of all lowercase characters (https://github.com/ansible-collections/community.mysql/pull/501).
minor_changes:
- mysql_info - add ``connector_name`` and ``connector_version`` to returned
values (https://github.com/ansible-collections/community.mysql/pull/497).
- mysql_role - enable auto_commit to avoid MySQL metadata table lock (https://github.com/ansible-collections/community.mysql/issues/479).
- mysql_user - add plugin_auth_string as optional parameter to use a specific
pam service if pam/auth_pam plugin is used (https://github.com/ansible-collections/community.mysql/pull/445).
- mysql_user - add the ``session_vars`` argument to set session variables at
the beginning of module execution (https://github.com/ansible-collections/community.mysql/issues/478).
- mysql_user - display a more informative invalid privilege exception. Changes
the exception handling of the granting permission logic to show the query
executed , params and the exception message granting privileges fails` (https://github.com/ansible-collections/community.mysql/issues/465).
- mysql_user - enable auto_commit to avoid MySQL metadata table lock (https://github.com/ansible-collections/community.mysql/issues/479).
- setup_mysql - update MySQL tarball URL (https://github.com/ansible-collections/community.mysql/pull/491).
release_summary: 'This is the minor release of the ``community.mysql`` collection.
- mysql_user - fix module's crash when modifying a user with ``host_all`` (https://github.com/ansible-collections/community.mysql/issues/39).
release_summary: 'This is the patch release of the ``community.mysql`` collection.
This changelog contains all changes to the modules and plugins in this collection
This changelog contains all changes to the modules in this collection that
have been added after the release of ``community.mysql`` 1.0.1.
that have been made after the previous release.'
'
fragments:
- 0_mysql_user_session_vars.yml
- 3.6.0.yml
- 445_add_service_name_to_plugin_pam_auth_pam_usage.yml
- 465-display_more_informative_invalid_priv_exceptiion.yml
- 479_enable_auto_commit.yml
- 479_enable_auto_commit_part2.yml
- 491_fix_download_url.yaml
- 497_mysql_info_returns_connector_name_and_version.yml
- 503-fix-revoke-grant-only.yml
- mysql_variables_allow_uppercase_identifiers.yml
release_date: '2023-02-08'
3.7.0:
changes:
minor_changes:
- mysql module utils - change deprecated connection parameters ``passwd`` and
``db`` to ``password`` and ``database`` (https://github.com/ansible-collections/community.mysql/pull/177).
- mysql_user - add ``MAX_STATEMENT_TIME`` support for mariadb to the ``resource_limits``
argument (https://github.com/ansible-collections/community.mysql/issues/211).
release_summary: 'This is the minor release of the ``community.mysql`` collection.
This changelog contains all changes to the modules and plugins in this collection
that have been made after the previous release.'
fragments:
- 177-change_deprecated_connection_parameters.yml
- 3.7.0.yml
- 523-add-max_statement_time_resource-limit.yml
release_date: '2023-05-05'
3.7.1:
changes:
bugfixes:
- mysql module utils - use the connection arguments ``db`` instead of ``database``
and ``passwd`` instead of ``password`` when running with older mysql drivers
(MySQLdb < 2.1.0 or PyMySQL < 1.0.0) (https://github.com/ansible-collections/community.mysql/pull/551).
release_summary: 'This is a patch release of the community.mysql collection.
This changelog contains all changes to the modules and plugins in this collection
that have been made after the previous release.'
fragments:
- 3.7.1.yml
- 551-fix_connection_arguments_driver_compatability.yaml
release_date: '2023-05-22'
3.7.2:
changes:
bugfixes:
- mysql module utils - use the connection arguments ``db`` instead of ``database``
and ``passwd`` instead of ``password`` when running with MySQLdb < 2.0.0 (https://github.com/ansible-collections/community.mysql/pull/553).
release_summary: 'This is a patch release of the community.mysql collection.
This changelog contains all changes to the modules and plugins in this collection
that have been made after the previous release.'
fragments:
- 3.7.2.yml
- 553_fix_connection_arguemnts_for_old_mysqldb_driver.yaml
release_date: '2023-05-25'
3.8.0:
changes:
major_changes:
- The community.mysql collection no longer supports ``ansible-core 2.12`` and
``ansible-core 2.13``. While we take no active measures to prevent usage and
there are no plans to introduce incompatible code to the modules, we will
stop testing those versions. Both are or will soon be End of Life and if you
are still using them, you should consider upgrading to the ``latest Ansible
/ ansible-core 2.15 or later`` as soon as possible (https://github.com/ansible-collections/community.mysql/pull/574).
- mysql_role - the ``column_case_sensitive`` argument's default value will be
changed to ``true`` in community.mysql 4.0.0. If your playbook expected the
column to be automatically uppercased for your roles privileges, you should
set this to false explicitly (https://github.com/ansible-collections/community.mysql/issues/578).
- mysql_user - the ``column_case_sensitive`` argument's default value will be
changed to ``true`` in community.mysql 4.0.0. If your playbook expected the
column to be automatically uppercased for your users privileges, you should
set this to false explicitly (https://github.com/ansible-collections/community.mysql/issues/577).
minor_changes:
- mysql_info - add filter ``users_info`` (https://github.com/ansible-collections/community.mysql/pull/580).
- mysql_role - add ``column_case_sensitive`` option to prevent field names from
being uppercased (https://github.com/ansible-collections/community.mysql/pull/569).
- mysql_user - add ``column_case_sensitive`` option to prevent field names from
being uppercased (https://github.com/ansible-collections/community.mysql/pull/569).
release_summary: 'This is the minor release of the ``community.mysql`` collection.
This changelog contains all changes to the modules and plugins in this
collection that have been made after the previous release.'
fragments:
- 3.8.0.yml
- 569_fix_column_uppercasing.yml
- drop_ansible_core_2_12_and_2_13.yml
- lie_mysql_info_users_info.yml
release_date: '2023-10-25'
3.9.0:
changes:
bugfixes:
- mysql_info - the ``slave_status`` filter was returning an empty list on MariaDB
with multiple replication channels. It now returns all channels by running
``SHOW ALL SLAVES STATUS`` for MariaDB servers (https://github.com/ansible-collections/community.mysql/issues/603).
major_changes:
- Collection version 2.*.* is EOL, no more bugfixes will be backported. Please
consider upgrading to the latest version.
minor_changes:
- mysql_user - add the ``password_expire`` and ``password_expire_interval``
arguments to implement the password expiration management for mysql user (https://github.com/ansible-collections/community.mysql/pull/598).
- mysql_user - add user attribute support via the ``attributes`` parameter and
return value (https://github.com/ansible-collections/community.mysql/pull/604).
release_summary: 'This is a minor release of the ``community.mysql`` collection.
This changelog contains all changes to the modules and plugins in this
collection that have been made after the previous release.'
fragments:
- 0-stable-2-eol.yml
- 3.9.0.yml
- 598-password_expire-support-for-mysql_user.yml
- 602-show-all-slaves-status.yaml
- 604-user-attributes.yaml
release_date: '2024-02-22'
- 1.0.2.yml
- 40-mysql_user_fix_error_when_host_all_used.yml
release_date: '2020-10-01'

View file

@ -1,4 +1,4 @@
changelog_filename_template: ../CHANGELOG.rst
changelog_filename_template: CHANGELOG.rst
changelog_filename_version_depth: 0
changes_file: changelog.yaml
changes_format: combined
@ -25,5 +25,5 @@ sections:
- Bugfixes
- - known_issues
- Known Issues
title: Community MySQL and MariaDB Collection
title: Community MySQL Collection
trivial_section_name: trivial

View file

@ -1,11 +1,10 @@
---
namespace: community
name: mysql
version: 3.13.0
version: 1.0.2
readme: README.md
authors:
- Ansible community
description: MySQL and MariaDB collection for Ansible
- MySQL Working Group (https://github.com/ansible-collections/community.mysql/wiki/MySQL-Working-Group)
description: MySQL collection for Ansible
license_file: COPYING
tags:
- database

View file

@ -28,4 +28,4 @@ Here is an example directory of the majority of plugins currently supported by A
└── vars
```
A full list of plugin types can be found at [Working With Plugins](https://docs.ansible.com/ansible/latest/plugins/plugins.html).
A full list of plugin types can be found at [Working With Plugins](https://docs.ansible.com/ansible/2.9/plugins/plugins.html).

View file

@ -35,7 +35,6 @@ options:
login_unix_socket:
description:
- The path to a Unix domain socket for local connections.
- Use this parameter to avoid the C(Please explicitly state intended protocol) error.
type: str
connect_timeout:
description:
@ -45,9 +44,6 @@ options:
config_file:
description:
- Specify a config file from which user and password are to be read.
- The default config file, C(~/.my.cnf), if it exists, will be read, even if I(config_file) is not specified.
- The default config file, C(~/.my.cnf), if it exists, must contain a C([client]) section as a MySQL connector requirement.
- To prevent the default config file from being read, set I(config_file) to be an empty string.
type: path
default: '~/.my.cnf'
ca_cert:
@ -66,27 +62,18 @@ options:
- The path to the client private key.
type: path
aliases: [ ssl_key ]
check_hostname:
description:
- Whether to validate the server host name when an SSL connection is required. Corresponds to MySQL CLIs C(--ssl) switch.
- Setting this to C(false) disables hostname verification. Use with caution.
- Requires pymysql >= 0.7.11.
type: bool
version_added: '1.1.0'
requirements:
- PyMySQL (Python 2.7 and Python 3.x)
- PyMySQL (Python 2.7 and Python 3.X), or
- MySQLdb (Python 2.x)
notes:
- Requires the PyMySQL (Python 2.7 and Python 3.X) package installed on the remote host.
- Requires the PyMySQL (Python 2.7 and Python 3.X) or MySQL-python (Python 2.X) package installed on the remote host.
The Python package may be installed with apt-get install python-pymysql (Ubuntu; see M(ansible.builtin.apt)) or
yum install python2-PyMySQL (RHEL/CentOS/Fedora; see M(ansible.builtin.yum)). You can also use dnf install python2-PyMySQL
for newer versions of Fedora; see M(ansible.builtin.dnf).
- Be sure you have PyMySQL library installed on the target machine
for the Python interpreter Ansible discovers. For example if ansible discovers and uses Python 3, you need to install
the Python 3 version of PyMySQL. If ansible discovers and uses Python 2, you need to install the Python 2
version of PyMySQL.
- If you have trouble, it may help to force Ansible to use the Python interpreter you need by specifying
C(ansible_python_interpreter). For more information, see
U(https://docs.ansible.com/ansible/latest/reference_appendices/interpreter_discovery.html).
- Be sure you have PyMySQL or MySQLdb library installed on the target machine
for the Python interpreter Ansible uses, for example, if it is Python 3,
you must install the library for Python 3. You can also change the interpreter.
For more information, see U(https://docs.ansible.com/ansible/latest/reference_appendices/interpreter_discovery.html).
- Both C(login_password) and C(login_user) are required when you are
passing credentials. If none are present, the module will attempt to read
the credentials from C(~/.my.cnf), and finally fall back to using the MySQL
@ -96,15 +83,4 @@ notes:
and later uses the unix_socket authentication plugin by default that
without using I(login_unix_socket=/var/run/mysqld/mysqld.sock) (the default path)
causes the error ``Host '127.0.0.1' is not allowed to connect to this MariaDB server``.
- "If credentials from the config file (for example, C(/root/.my.cnf)) are not needed to connect to a database server, but
the file exists and does not contain a C([client]) section, before any other valid directives, it will be read and this
will cause the connection to fail, to prevent this set it to an empty string, (for example C(config_file: ''))."
- "To avoid the C(Please explicitly state intended protocol) error, use the I(login_unix_socket) argument,
for example, C(login_unix_socket: /run/mysqld/mysqld.sock)."
- Alternatively, to avoid using I(login_unix_socket) argument on each invocation you can specify the socket path
using the `socket` option in your MySQL config file (usually C(~/.my.cnf)) on the destination host, for
example C(socket=/var/lib/mysql/mysql.sock).
attributes:
check_mode:
description: Can run in check_mode and return changed status prediction without modifying target.
'''

View file

@ -1,343 +0,0 @@
# Vendored copy of distutils/version.py from CPython 3.9.5
#
# Implements multiple version numbering conventions for the
# Python Module Distribution Utilities.
#
# PSF License (see PSF-license.txt or https://opensource.org/licenses/Python-2.0)
#
"""Provides classes to represent module version numbers (one class for
each style of version numbering). There are currently two such classes
implemented: StrictVersion and LooseVersion.
Every version number class implements the following interface:
* the 'parse' method takes a string and parses it to some internal
representation; if the string is an invalid version number,
'parse' raises a ValueError exception
* the class constructor takes an optional string argument which,
if supplied, is passed to 'parse'
* __str__ reconstructs the string that was passed to 'parse' (or
an equivalent string -- ie. one that will generate an equivalent
version number instance)
* __repr__ generates Python code to recreate the version number instance
* _cmp compares the current instance with either another instance
of the same class or a string (which will be parsed to an instance
of the same class, thus must follow the same rules)
"""
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import re
try:
RE_FLAGS = re.VERBOSE | re.ASCII
except AttributeError:
RE_FLAGS = re.VERBOSE
class Version:
"""Abstract base class for version numbering classes. Just provides
constructor (__init__) and reproducer (__repr__), because those
seem to be the same for all version numbering classes; and route
rich comparisons to _cmp.
"""
def __init__(self, vstring=None):
if vstring:
self.parse(vstring)
def __repr__(self):
return "%s ('%s')" % (self.__class__.__name__, str(self))
def __eq__(self, other):
c = self._cmp(other)
if c is NotImplemented:
return c
return c == 0
def __lt__(self, other):
c = self._cmp(other)
if c is NotImplemented:
return c
return c < 0
def __le__(self, other):
c = self._cmp(other)
if c is NotImplemented:
return c
return c <= 0
def __gt__(self, other):
c = self._cmp(other)
if c is NotImplemented:
return c
return c > 0
def __ge__(self, other):
c = self._cmp(other)
if c is NotImplemented:
return c
return c >= 0
# Interface for version-number classes -- must be implemented
# by the following classes (the concrete ones -- Version should
# be treated as an abstract class).
# __init__ (string) - create and take same action as 'parse'
# (string parameter is optional)
# parse (string) - convert a string representation to whatever
# internal representation is appropriate for
# this style of version numbering
# __str__ (self) - convert back to a string; should be very similar
# (if not identical to) the string supplied to parse
# __repr__ (self) - generate Python code to recreate
# the instance
# _cmp (self, other) - compare two version numbers ('other' may
# be an unparsed version string, or another
# instance of your version class)
class StrictVersion(Version):
"""Version numbering for anal retentives and software idealists.
Implements the standard interface for version number classes as
described above. A version number consists of two or three
dot-separated numeric components, with an optional "pre-release" tag
on the end. The pre-release tag consists of the letter 'a' or 'b'
followed by a number. If the numeric components of two version
numbers are equal, then one with a pre-release tag will always
be deemed earlier (lesser) than one without.
The following are valid version numbers (shown in the order that
would be obtained by sorting according to the supplied cmp function):
0.4 0.4.0 (these two are equivalent)
0.4.1
0.5a1
0.5b3
0.5
0.9.6
1.0
1.0.4a3
1.0.4b1
1.0.4
The following are examples of invalid version numbers:
1
2.7.2.2
1.3.a4
1.3pl1
1.3c4
The rationale for this version numbering system will be explained
in the distutils documentation.
"""
version_re = re.compile(r'^(\d+) \. (\d+) (\. (\d+))? ([ab](\d+))?$',
RE_FLAGS)
def parse(self, vstring):
match = self.version_re.match(vstring)
if not match:
raise ValueError("invalid version number '%s'" % vstring)
(major, minor, patch, prerelease, prerelease_num) = \
match.group(1, 2, 4, 5, 6)
if patch:
self.version = tuple(map(int, [major, minor, patch]))
else:
self.version = tuple(map(int, [major, minor])) + (0,)
if prerelease:
self.prerelease = (prerelease[0], int(prerelease_num))
else:
self.prerelease = None
def __str__(self):
if self.version[2] == 0:
vstring = '.'.join(map(str, self.version[0:2]))
else:
vstring = '.'.join(map(str, self.version))
if self.prerelease:
vstring = vstring + self.prerelease[0] + str(self.prerelease[1])
return vstring
def _cmp(self, other):
if isinstance(other, str):
other = StrictVersion(other)
elif not isinstance(other, StrictVersion):
return NotImplemented
if self.version != other.version:
# numeric versions don't match
# prerelease stuff doesn't matter
if self.version < other.version:
return -1
else:
return 1
# have to compare prerelease
# case 1: neither has prerelease; they're equal
# case 2: self has prerelease, other doesn't; other is greater
# case 3: self doesn't have prerelease, other does: self is greater
# case 4: both have prerelease: must compare them!
if (not self.prerelease and not other.prerelease):
return 0
elif (self.prerelease and not other.prerelease):
return -1
elif (not self.prerelease and other.prerelease):
return 1
elif (self.prerelease and other.prerelease):
if self.prerelease == other.prerelease:
return 0
elif self.prerelease < other.prerelease:
return -1
else:
return 1
else:
raise AssertionError("never get here")
# end class StrictVersion
# The rules according to Greg Stein:
# 1) a version number has 1 or more numbers separated by a period or by
# sequences of letters. If only periods, then these are compared
# left-to-right to determine an ordering.
# 2) sequences of letters are part of the tuple for comparison and are
# compared lexicographically
# 3) recognize the numeric components may have leading zeroes
#
# The LooseVersion class below implements these rules: a version number
# string is split up into a tuple of integer and string components, and
# comparison is a simple tuple comparison. This means that version
# numbers behave in a predictable and obvious way, but a way that might
# not necessarily be how people *want* version numbers to behave. There
# wouldn't be a problem if people could stick to purely numeric version
# numbers: just split on period and compare the numbers as tuples.
# However, people insist on putting letters into their version numbers;
# the most common purpose seems to be:
# - indicating a "pre-release" version
# ('alpha', 'beta', 'a', 'b', 'pre', 'p')
# - indicating a post-release patch ('p', 'pl', 'patch')
# but of course this can't cover all version number schemes, and there's
# no way to know what a programmer means without asking him.
#
# The problem is what to do with letters (and other non-numeric
# characters) in a version number. The current implementation does the
# obvious and predictable thing: keep them as strings and compare
# lexically within a tuple comparison. This has the desired effect if
# an appended letter sequence implies something "post-release":
# eg. "0.99" < "0.99pl14" < "1.0", and "5.001" < "5.001m" < "5.002".
#
# However, if letters in a version number imply a pre-release version,
# the "obvious" thing isn't correct. Eg. you would expect that
# "1.5.1" < "1.5.2a2" < "1.5.2", but under the tuple/lexical comparison
# implemented here, this just isn't so.
#
# Two possible solutions come to mind. The first is to tie the
# comparison algorithm to a particular set of semantic rules, as has
# been done in the StrictVersion class above. This works great as long
# as everyone can go along with bondage and discipline. Hopefully a
# (large) subset of Python module programmers will agree that the
# particular flavour of bondage and discipline provided by StrictVersion
# provides enough benefit to be worth using, and will submit their
# version numbering scheme to its domination. The free-thinking
# anarchists in the lot will never give in, though, and something needs
# to be done to accommodate them.
#
# Perhaps a "moderately strict" version class could be implemented that
# lets almost anything slide (syntactically), and makes some heuristic
# assumptions about non-digits in version number strings. This could
# sink into special-case-hell, though; if I was as talented and
# idiosyncratic as Larry Wall, I'd go ahead and implement a class that
# somehow knows that "1.2.1" < "1.2.2a2" < "1.2.2" < "1.2.2pl3", and is
# just as happy dealing with things like "2g6" and "1.13++". I don't
# think I'm smart enough to do it right though.
#
# In any case, I've coded the test suite for this module (see
# ../test/test_version.py) specifically to fail on things like comparing
# "1.2a2" and "1.2". That's not because the *code* is doing anything
# wrong, it's because the simple, obvious design doesn't match my
# complicated, hairy expectations for real-world version numbers. It
# would be a snap to fix the test suite to say, "Yep, LooseVersion does
# the Right Thing" (ie. the code matches the conception). But I'd rather
# have a conception that matches common notions about version numbers.
class LooseVersion(Version):
"""Version numbering for anarchists and software realists.
Implements the standard interface for version number classes as
described above. A version number consists of a series of numbers,
separated by either periods or strings of letters. When comparing
version numbers, the numeric components will be compared
numerically, and the alphabetic components lexically. The following
are all valid version numbers, in no particular order:
1.5.1
1.5.2b2
161
3.10a
8.02
3.4j
1996.07.12
3.2.pl0
3.1.1.6
2g6
11g
0.960923
2.2beta29
1.13++
5.5.kw
2.0b1pl0
In fact, there is no such thing as an invalid version number under
this scheme; the rules for comparison are simple and predictable,
but may not always give the results you want (for some definition
of "want").
"""
component_re = re.compile(r'(\d+ | [a-z]+ | \.)', re.VERBOSE)
def __init__(self, vstring=None):
if vstring:
self.parse(vstring)
def parse(self, vstring):
# I've given up on thinking I can reconstruct the version string
# from the parsed tuple -- so I just store the string here for
# use by __str__
self.vstring = vstring
components = [x for x in self.component_re.split(vstring) if x and x != '.']
for i, obj in enumerate(components):
try:
components[i] = int(obj)
except ValueError:
pass
self.version = components
def __str__(self):
return self.vstring
def __repr__(self):
return "LooseVersion ('%s')" % str(self)
def _cmp(self, other):
if isinstance(other, str):
other = LooseVersion(other)
elif not isinstance(other, LooseVersion):
return NotImplemented
if self.version == other.version:
return 0
if self.version < other.version:
return -1
if self.version > other.version:
return 1
# end class LooseVersion

View file

@ -1,180 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import (absolute_import, division, print_function)
from ._version import LooseVersion
__metaclass__ = type
class CommandResolver():
def __init__(self, server_implementation, server_version):
self.server_implementation = server_implementation
self.server_version = LooseVersion(server_version)
def resolve_command(self, command):
"""
Resolves the appropriate SQL command based on the server implementation and version.
Parameters:
command (str): The base SQL command to be resolved (e.g., "SHOW SLAVE HOSTS").
Returns:
str: The resolved SQL command suitable for the given server implementation and version.
Raises:
ValueError: If the command is not supported or recognized.
Example:
Given a server implementation `mysql` and server version `8.0.23`, and a command `SHOW SLAVE HOSTS`,
the method will resolve the command based on the following table of versions:
Table:
[
("mysql", "default", "SHOW SLAVES HOSTS default"),
("mysql", "5.7.0", "SHOW SLAVES HOSTS"),
("mysql", "8.0.22", "SHOW REPLICAS"),
("mysql", "8.4.0", "SHOW REPLICAS 8.4"),
("mariadb", "10.5.1", "SHOW REPLICAS HOSTS"),
]
Example usage:
>>> resolver = CommandResolver("mysql", "8.0.23")
>>> resolver.resolve_command("SHOW SLAVE HOSTS")
'SHOW REPLICAS'
In this example, the resolver will:
- Filter and sort applicable versions: [
("8.4.0", "SHOW REPLICAS 8.4"),
("8.0.22", "HOW REPLICAS"),
("5.7.0", "SHOW SLAVES HOSTS")
]
- Iterate through the sorted list and find the first version less than or equal to 8.0.23,
which is 8.0.22, and return the corresponding command.
"""
# Convert the command to uppercase to ensure case-insensitive lookup
command = command.upper()
commands = {
"SHOW MASTER STATUS": {
("mysql", "default"): "SHOW MASTER STATUS",
("mariadb", "default"): "SHOW MASTER STATUS",
("mysql", "8.2.0"): "SHOW BINARY LOG STATUS",
("mariadb", "10.5.2"): "SHOW BINLOG STATUS",
},
"SHOW SLAVE STATUS": {
("mysql", "default"): "SHOW SLAVE STATUS",
("mariadb", "default"): "SHOW SLAVE STATUS",
("mysql", "8.0.22"): "SHOW REPLICA STATUS",
("mariadb", "10.5.1"): "SHOW REPLICA STATUS",
},
"SHOW SLAVE HOSTS": {
("mysql", "default"): "SHOW SLAVE HOSTS",
("mariadb", "default"): "SHOW SLAVE HOSTS",
("mysql", "8.0.22"): "SHOW REPLICAS",
("mariadb", "10.5.1"): "SHOW REPLICA HOSTS",
},
"CHANGE MASTER": {
("mysql", "default"): "CHANGE MASTER",
("mariadb", "default"): "CHANGE MASTER",
("mysql", "8.0.23"): "CHANGE REPLICATION SOURCE",
},
"MASTER_HOST": {
("mysql", "default"): "MASTER_HOST",
("mariadb", "default"): "MASTER_HOST",
("mysql", "8.0.23"): "SOURCE_HOST",
},
"MASTER_USER": {
("mysql", "default"): "MASTER_USER",
("mariadb", "default"): "MASTER_USER",
("mysql", "8.0.23"): "SOURCE_USER",
},
"MASTER_PASSWORD": {
("mysql", "default"): "MASTER_PASSWORD",
("mariadb", "default"): "MASTER_PASSWORD",
("mysql", "8.0.23"): "SOURCE_PASSWORD",
},
"MASTER_PORT": {
("mysql", "default"): "MASTER_PORT",
("mariadb", "default"): "MASTER_PORT",
("mysql", "8.0.23"): "SOURCE_PORT",
},
"MASTER_CONNECT_RETRY": {
("mysql", "default"): "MASTER_CONNECT_RETRY",
("mariadb", "default"): "MASTER_CONNECT_RETRY",
("mysql", "8.0.23"): "SOURCE_CONNECT_RETRY",
},
"MASTER_LOG_FILE": {
("mysql", "default"): "MASTER_LOG_FILE",
("mariadb", "default"): "MASTER_LOG_FILE",
("mysql", "8.0.23"): "SOURCE_LOG_FILE",
},
"MASTER_LOG_POS": {
("mysql", "default"): "MASTER_LOG_POS",
("mariadb", "default"): "MASTER_LOG_POS",
("mysql", "8.0.23"): "SOURCE_LOG_POS",
},
"MASTER_DELAY": {
("mysql", "default"): "MASTER_DELAY",
("mariadb", "default"): "MASTER_DELAY",
("mysql", "8.0.23"): "SOURCE_DELAY",
},
"MASTER_SSL": {
("mysql", "default"): "MASTER_SSL",
("mariadb", "default"): "MASTER_SSL",
("mysql", "8.0.23"): "SOURCE_SSL",
},
"MASTER_SSL_CA": {
("mysql", "default"): "MASTER_SSL_CA",
("mariadb", "default"): "MASTER_SSL_CA",
("mysql", "8.0.23"): "SOURCE_SSL_CA",
},
"MASTER_SSL_CAPATH": {
("mysql", "default"): "MASTER_SSL_CAPATH",
("mariadb", "default"): "MASTER_SSL_CAPATH",
("mysql", "8.0.23"): "SOURCE_SSL_CAPATH",
},
"MASTER_SSL_CERT": {
("mysql", "default"): "MASTER_SSL_CERT",
("mariadb", "default"): "MASTER_SSL_CERT",
("mysql", "8.0.23"): "SOURCE_SSL_CERT",
},
"MASTER_SSL_KEY": {
("mysql", "default"): "MASTER_SSL_KEY",
("mariadb", "default"): "MASTER_SSL_KEY",
("mysql", "8.0.23"): "SOURCE_SSL_KEY",
},
"MASTER_SSL_CIPHER": {
("mysql", "default"): "MASTER_SSL_CIPHER",
("mariadb", "default"): "MASTER_SSL_CIPHER",
("mysql", "8.0.23"): "SOURCE_SSL_CIPHER",
},
"MASTER_SSL_VERIFY_SERVER_CERT": {
("mysql", "default"): "MASTER_SSL_VERIFY_SERVER_CERT",
("mariadb", "default"): "MASTER_SSL_VERIFY_SERVER_CERT",
("mysql", "8.0.23"): "SOURCE_SSL_VERIFY_SERVER_CERT",
},
"MASTER_AUTO_POSITION": {
("mysql", "default"): "MASTER_AUTO_POSITION",
("mariadb", "default"): "MASTER_AUTO_POSITION",
("mysql", "8.0.23"): "SOURCE_AUTO_POSITION",
},
"RESET MASTER": {
("mysql", "default"): "RESET MASTER",
("mariadb", "default"): "RESET MASTER",
("mysql", "8.4.0"): "RESET BINARY LOGS AND GTIDS",
},
# Add more command mappings here
}
if command in commands:
cmd_syntaxes = commands[command]
applicable_versions = [(v, cmd) for (impl, v), cmd in cmd_syntaxes.items() if impl == self.server_implementation and v != 'default']
applicable_versions.sort(reverse=True, key=lambda x: LooseVersion(x[0]))
for version, cmd in applicable_versions:
if self.server_version >= LooseVersion(version):
return cmd
return cmd_syntaxes[(self.server_implementation, "default")]
raise ValueError("Unsupported command: %s" % command)

View file

@ -6,7 +6,7 @@
#
# Copyright (c) 2014, Toshio Kuratomi <tkuratomi@ansible.com>
#
# Simplified BSD License (see simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type

View file

@ -1,14 +0,0 @@
# -*- coding: utf-8 -*-
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from ansible_collections.community.mysql.plugins.module_utils.mysql import get_server_version
from ansible_collections.community.mysql.plugins.module_utils.version import LooseVersion
def uses_replica_terminology(cursor):
"""Checks if REPLICA must be used instead of SLAVE"""
return LooseVersion(get_server_version(cursor)) >= LooseVersion('10.5.1')

View file

@ -1,19 +0,0 @@
# -*- coding: utf-8 -*-
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from ansible_collections.community.mysql.plugins.module_utils.version import LooseVersion
from ansible_collections.community.mysql.plugins.module_utils.mysql import get_server_version
def supports_roles(cursor):
version = get_server_version(cursor)
return LooseVersion(version) >= LooseVersion('10.0.5')
def is_mariadb():
return True

View file

@ -1,76 +0,0 @@
# -*- coding: utf-8 -*-
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from ansible_collections.community.mysql.plugins.module_utils.version import LooseVersion
from ansible_collections.community.mysql.plugins.module_utils.mysql import get_server_version
def use_old_user_mgmt(cursor):
version = get_server_version(cursor)
return LooseVersion(version) < LooseVersion("10.2")
def supports_identified_by_password(cursor):
return True
def server_supports_alter_user(cursor):
version = get_server_version(cursor)
return LooseVersion(version) >= LooseVersion("10.2")
def server_supports_password_expire(cursor):
version = get_server_version(cursor)
return LooseVersion(version) >= LooseVersion("10.4.3")
def get_tls_requires(cursor, user, host):
"""Get user TLS requirements.
Reads directly from mysql.user table allowing for a more
readable code.
Args:
cursor (cursor): DB driver cursor object.
user (str): User name.
host (str): User host name.
Returns: Dictionary containing current TLS required
"""
tls_requires = dict()
query = ('SELECT ssl_type, ssl_cipher, x509_issuer, x509_subject '
'FROM mysql.user WHERE User = %s AND Host = %s')
cursor.execute(query, (user, host))
res = cursor.fetchone()
# Mysql_info use a DictCursor so we must convert back to a list
# otherwise we get KeyError 0
if isinstance(res, dict):
res = list(res.values())
# When user don't require SSL, res value is: ('', '', '', '')
if not any(res):
return None
if res[0] == 'ANY':
tls_requires['SSL'] = None
if res[0] == 'X509':
tls_requires['X509'] = None
if res[1]:
tls_requires['CIPHER'] = res[1]
if res[2]:
tls_requires['ISSUER'] = res[2]
if res[3]:
tls_requires['SUBJECT'] = res[3]
return tls_requires

View file

@ -1,125 +0,0 @@
"""
Generate MySQL sha256 compatible plugins hash for a given password and salt
based on
* https://www.akkadia.org/drepper/SHA-crypt.txt
* https://crypto.stackexchange.com/questions/77427/whats-the-algorithm-behind-mysqls-sha256-password-hashing-scheme/111174#111174
* https://github.com/hashcat/hashcat/blob/master/tools/test_modules/m07400.pm
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import hashlib
def _to64(v, n):
"""Convert a 32-bit integer to a base-64 string"""
i64 = (
[".", "/"]
+ [chr(x) for x in range(48, 58)]
+ [chr(x) for x in range(65, 91)]
+ [chr(x) for x in range(97, 123)]
)
result = ""
while n > 0:
n -= 1
result += i64[v & 0x3F]
v >>= 6
return result
def _hashlib_sha256(data):
"""Return SHA-256 digest from hashlib ."""
return hashlib.sha256(data).digest()
def _sha256_digest(key, salt, loops):
"""Return a SHA-256 digest of the concatenation of the key, the salt, and the key, repeated as necessary."""
# https://www.akkadia.org/drepper/SHA-crypt.txt
num_bytes = 32
bytes_key = key.encode()
bytes_salt = salt.encode()
digest_b = _hashlib_sha256(bytes_key + bytes_salt + bytes_key)
tmp = bytes_key + bytes_salt
for i in range(len(bytes_key), 0, -num_bytes):
tmp += digest_b if i > num_bytes else digest_b[:i]
i = len(bytes_key)
while i > 0:
tmp += digest_b if (i & 1) != 0 else bytes_key
i >>= 1
digest_a = _hashlib_sha256(tmp)
tmp = b""
for i in range(len(bytes_key)):
tmp += bytes_key
digest_dp = _hashlib_sha256(tmp)
byte_sequence_p = b""
for i in range(len(bytes_key), 0, -num_bytes):
byte_sequence_p += digest_dp if i > num_bytes else digest_dp[:i]
tmp = b""
til = 16 + digest_a[0]
for i in range(til):
tmp += bytes_salt
digest_ds = _hashlib_sha256(tmp)
byte_sequence_s = b""
for i in range(len(bytes_salt), 0, -num_bytes):
byte_sequence_s += digest_ds if i > num_bytes else digest_ds[:i]
digest_c = digest_a
for i in range(loops):
tmp = byte_sequence_p if (i & 1) else digest_c
if i % 3:
tmp += byte_sequence_s
if i % 7:
tmp += byte_sequence_p
tmp += digest_c if (i & 1) else byte_sequence_p
digest_c = _hashlib_sha256(tmp)
inc1, inc2, mod, end = (10, 21, 30, 0)
i = 0
tmp = ""
while True:
tmp += _to64(
(digest_c[i] << 16)
| (digest_c[(i + inc1) % mod] << 8)
| digest_c[(i + inc1 * 2) % mod],
4,
)
i = (i + inc2) % mod
if i == end:
break
tmp += _to64((digest_c[31] << 8) | digest_c[30], 3)
return tmp
def mysql_sha256_password_hash(password, salt):
"""Return a MySQL compatible caching_sha2_password hash in raw format."""
if len(salt) != 20:
raise ValueError("Salt must be 20 characters long.")
count = 5
iteration = 1000 * count
digest = _sha256_digest(password, salt, iteration)
return "$A${0:>03}${1}{2}".format(count, salt, digest)
def mysql_sha256_password_hash_hex(password, salt):
"""Return a MySQL compatible caching_sha2_password hash in hex format."""
return mysql_sha256_password_hash(password, salt).encode().hex().upper()

View file

@ -1,14 +0,0 @@
# -*- coding: utf-8 -*-
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from ansible_collections.community.mysql.plugins.module_utils.mysql import get_server_version
from ansible_collections.community.mysql.plugins.module_utils.version import LooseVersion
def uses_replica_terminology(cursor):
"""Checks if REPLICA must be used instead of SLAVE"""
return LooseVersion(get_server_version(cursor)) >= LooseVersion('8.0.22')

View file

@ -1,19 +0,0 @@
# -*- coding: utf-8 -*-
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from ansible_collections.community.mysql.plugins.module_utils.version import LooseVersion
from ansible_collections.community.mysql.plugins.module_utils.mysql import get_server_version
def supports_roles(cursor):
version = get_server_version(cursor)
return LooseVersion(version) >= LooseVersion('8')
def is_mariadb():
return False

View file

@ -1,78 +0,0 @@
# -*- coding: utf-8 -*-
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from ansible_collections.community.mysql.plugins.module_utils.version import LooseVersion
from ansible_collections.community.mysql.plugins.module_utils.mysql import get_server_version
import re
import shlex
def use_old_user_mgmt(cursor):
version = get_server_version(cursor)
return LooseVersion(version) < LooseVersion("5.7")
def supports_identified_by_password(cursor):
version = get_server_version(cursor)
return LooseVersion(version) < LooseVersion("8")
def server_supports_alter_user(cursor):
version = get_server_version(cursor)
return LooseVersion(version) >= LooseVersion("5.6")
def server_supports_password_expire(cursor):
version = get_server_version(cursor)
return LooseVersion(version) >= LooseVersion("5.7")
def get_tls_requires(cursor, user, host):
"""Get user TLS requirements.
We must use SHOW GRANTS because some tls fileds are encoded.
Args:
cursor (cursor): DB driver cursor object.
user (str): User name.
host (str): User host name.
Returns: Dictionary containing current TLS required
"""
if not use_old_user_mgmt(cursor):
query = "SHOW CREATE USER '%s'@'%s'" % (user, host)
else:
query = "SHOW GRANTS for '%s'@'%s'" % (user, host)
cursor.execute(query)
grants = cursor.fetchone()
# Mysql_info use a DictCursor so we must convert back to a list
# otherwise we get KeyError 0
if isinstance(grants, dict):
grants = list(grants.values())
grants_str = ''.join(grants)
pattern = r"(?<=\bREQUIRE\b)(.*?)(?=(?:\bPASSWORD\b|$))"
requires_match = re.search(pattern, grants_str)
requires = requires_match.group().strip() if requires_match else ""
if requires.startswith('NONE'):
return None
if requires.startswith('SSL'):
return {'SSL': None}
if requires.startswith('X509'):
return {'X509': None}
items = iter(shlex.split(requires))
requires = dict(zip(items, items))
return requires or None

View file

@ -7,108 +7,54 @@
# Copyright (c), Jonathan Mainguy <jon@soh.re>, 2015
# Most of this was originally added by Sven Schliesing @muffl0n in the mysql_user.py module
#
# Simplified BSD License (see simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
from __future__ import (absolute_import, division, print_function)
from functools import reduce
__metaclass__ = type
import os
from ansible.module_utils.six.moves import configparser
from ansible.module_utils._text import to_native
try:
import pymysql as mysql_driver
_mysql_cursor_param = 'cursor'
except ImportError:
try:
# mysqlclient is called MySQLdb
import MySQLdb as mysql_driver
import MySQLdb.cursors
_mysql_cursor_param = 'cursorclass'
except ImportError:
mysql_driver = None
mysql_driver_fail_msg = ('A MySQL module is required: for Python 2.7 either PyMySQL, or '
'MySQL-python, or for Python 3.X mysqlclient or PyMySQL. '
'Consider setting ansible_python_interpreter to use '
'the intended Python version.')
from ansible_collections.community.mysql.plugins.module_utils.database import mysql_quote_identifier
def get_connector_name(connector):
""" (class) -> str
Return the name of the connector (pymysql or mysqlclient (MySQLdb))
or 'Unknown' if not pymysql or MySQLdb. When adding a
connector here, also modify get_connector_version.
"""
if connector is None or not hasattr(connector, '__name__'):
return 'Unknown'
return connector.__name__
def get_connector_version(connector):
""" (class) -> str
Return the version of pymysql or mysqlclient (MySQLdb).
Return 'Unknown' if the connector name is unknown.
"""
if connector is None:
return 'Unknown'
connector_name = get_connector_name(connector)
if connector_name == 'pymysql':
# pymysql has two methods:
# - __version__ that returns the string: 0.7.11.None
# - VERSION that returns the tuple (0, 7, 11, None)
v = connector.VERSION[:3]
return '.'.join(map(str, v))
elif connector_name == 'MySQLdb':
# version_info returns the tuple (2, 1, 1, 'final', 0)
v = connector.version_info[:3]
return '.'.join(map(str, v))
else:
return 'Unknown'
mysql_driver_fail_msg = 'The PyMySQL (Python 2.7 and Python 3.X) or MySQL-python (Python 2.X) module is required.'
def parse_from_mysql_config_file(cnf):
# Default values of comment_prefix is '#' and ';'.
# '!' added to prevent a parsing error
# when a config file contains !includedir parameter.
cp = configparser.ConfigParser(comment_prefixes=('#', ';', '!'))
cp = configparser.ConfigParser()
cp.read(cnf)
return cp
def mysql_connect(module, login_user=None, login_password=None, config_file='', ssl_cert=None,
ssl_key=None, ssl_ca=None, db=None, cursor_class=None, connect_timeout=30,
autocommit=False, config_overrides_defaults=False, check_hostname=None):
ssl_key=None, ssl_ca=None, db=None, cursor_class=None,
connect_timeout=30, autocommit=False, config_overrides_defaults=False):
config = {}
if config_file and os.path.exists(config_file):
config['read_default_file'] = config_file
if config_overrides_defaults:
cp = parse_from_mysql_config_file(config_file)
# Override some commond defaults with values from config file if needed
if cp and cp.has_section('client') and config_overrides_defaults:
try:
cp = parse_from_mysql_config_file(config_file)
module.params['login_host'] = cp.get('client', 'host', fallback=module.params['login_host'])
module.params['login_port'] = cp.getint('client', 'port', fallback=module.params['login_port'])
except Exception as e:
module.fail_json(msg="Failed to parse %s: %s" % (config_file, to_native(e)))
if "got an unexpected keyword argument 'fallback'" in e.message:
module.fail_json('To use config_overrides_defaults, '
'it needs Python 3.5+ as the default interpreter on a target host')
# Override some commond defaults with values from config file if needed
if cp and cp.has_section('client'):
try:
module.params['login_host'] = cp.get('client', 'host', fallback=module.params['login_host'])
module.params['login_port'] = cp.getint('client', 'port', fallback=module.params['login_port'])
except Exception as e:
if "got an unexpected keyword argument 'fallback'" in e.message:
module.fail_json(msg='To use config_overrides_defaults, '
'it needs Python 3.5+ as the default interpreter on a target host')
if ssl_ca is not None or ssl_key is not None or ssl_cert is not None or check_hostname is not None:
if ssl_ca is not None or ssl_key is not None or ssl_cert is not None:
config['ssl'] = {}
if module.params['login_unix_socket']:
@ -122,7 +68,7 @@ def mysql_connect(module, login_user=None, login_password=None, config_file='',
if login_user is not None:
config['user'] = login_user
if login_password is not None:
config['password'] = login_password
config['passwd'] = login_password
if ssl_cert is not None:
config['ssl']['cert'] = ssl_cert
if ssl_key is not None:
@ -130,55 +76,19 @@ def mysql_connect(module, login_user=None, login_password=None, config_file='',
if ssl_ca is not None:
config['ssl']['ca'] = ssl_ca
if db is not None:
config['database'] = db
config['db'] = db
if connect_timeout is not None:
config['connect_timeout'] = connect_timeout
if check_hostname is not None:
if get_connector_name(mysql_driver) == 'pymysql':
version_tuple = (n for n in mysql_driver.__version__.split('.') if n != 'None')
if reduce(lambda x, y: int(x) * 100 + int(y), version_tuple) >= 711:
config['ssl']['check_hostname'] = check_hostname
else:
module.fail_json(msg='To use check_hostname, pymysql >= 0.7.11 is required on the target host')
if get_connector_name(mysql_driver) == 'pymysql':
if _mysql_cursor_param == 'cursor':
# In case of PyMySQL driver:
if mysql_driver.version_info[0] < 1:
# for PyMySQL < 1.0.0, use 'db' instead of 'database' and 'passwd' instead of 'password'
if 'database' in config:
config['db'] = config['database']
del config['database']
if 'password' in config:
config['passwd'] = config['password']
del config['password']
db_connection = mysql_driver.connect(autocommit=autocommit, **config)
else:
# In case of MySQLdb driver
# Will be deprecated and dropped
# https://github.com/ansible-collections/community.mysql/issues/654
module.warn('Support of mysqlcline/MySQLdb connector is deprecated. '
'We\'ll stop testing against it in collection version 4.0.0 '
'and remove the related code in 5.0.0. Use PyMySQL connector instead.')
if mysql_driver.version_info[0] < 2 or (mysql_driver.version_info[0] == 2 and mysql_driver.version_info[1] < 1):
# for MySQLdb < 2.1.0, use 'db' instead of 'database' and 'passwd' instead of 'password'
if 'database' in config:
config['db'] = config['database']
del config['database']
if 'password' in config:
config['passwd'] = config['password']
del config['password']
db_connection = mysql_driver.connect(**config)
if autocommit:
db_connection.autocommit(True)
# Monkey patch the Connection class to close the connection when garbage collected
def _conn_patch(conn_self):
conn_self.close()
db_connection.__class__.__del__ = _conn_patch
# Patched
if cursor_class == 'DictCursor':
return db_connection.cursor(**{_mysql_cursor_param: mysql_driver.cursors.DictCursor}), db_connection
else:
@ -197,35 +107,4 @@ def mysql_common_argument_spec():
client_cert=dict(type='path', aliases=['ssl_cert']),
client_key=dict(type='path', aliases=['ssl_key']),
ca_cert=dict(type='path', aliases=['ssl_ca']),
check_hostname=dict(type='bool', default=None),
)
def get_server_version(cursor):
"""Returns a string representation of the server version."""
cursor.execute("SELECT VERSION() AS version")
result = cursor.fetchone()
if isinstance(result, dict):
version_str = result['version']
else:
version_str = result[0]
return version_str
def get_server_implementation(cursor):
if 'mariadb' in get_server_version(cursor).lower():
return "mariadb"
else:
return "mysql"
def set_session_vars(module, cursor, session_vars):
"""Set session vars."""
for var, value in session_vars.items():
query = "SET SESSION %s = " % mysql_quote_identifier(var, 'vars')
try:
cursor.execute(query + "%s", (value,))
except Exception as e:
module.fail_json(msg='Failed to execute %s%s: %s' % (query, value, e))

File diff suppressed because it is too large Load diff

View file

@ -1,16 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2021, Felix Fontein <felix@fontein.de>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""Provide version object to compare version numbers."""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
# Once we drop support for ansible-core 2.11, we can
# remove the _version.py file, and replace the following import by
#
# from ansible.module_utils.compat.version import LooseVersion
from ._version import LooseVersion

View file

@ -11,9 +11,9 @@ __metaclass__ = type
DOCUMENTATION = r'''
---
module: mysql_db
short_description: Add or remove MySQL or MariaDB databases from a remote host
short_description: Add or remove MySQL databases from a remote host
description:
- Add or remove MySQL or MariaDB databases from a remote host.
- Add or remove MySQL databases from a remote host.
options:
name:
description:
@ -27,7 +27,7 @@ options:
aliases: [db]
state:
description:
- The database state.
- The database state
type: str
default: present
choices: ['absent', 'dump', 'import', 'present']
@ -46,38 +46,41 @@ 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)),
xz (Added in 2.0) and zstd (C(.zst)) (Added in 3.12.0) compressed files are supported.
- Uncompressed SQL files (C(.sql)) as well as bzip2 (C(.bz2)), gzip (C(.gz)) and
xz (Added in 2.0) compressed files are supported.
type: path
single_transaction:
description:
- Execute the dump in a single transaction.
type: bool
default: false
default: no
quick:
description:
- Option used for dumping large tables.
type: bool
default: true
default: yes
ignore_tables:
description:
- A list of table names that will be ignored in the dump
of the form database_name.table_name.
type: list
elements: str
required: no
default: []
hex_blob:
description:
- Dump binary columns using hexadecimal notation.
required: no
default: no
type: bool
default: false
version_added: '0.1.0'
force:
description:
- Continue dump or import even if we get an SQL error.
- Used only when I(state) is C(dump) or C(import).
required: no
type: bool
default: false
default: no
version_added: '0.1.0'
master_data:
description:
@ -88,6 +91,7 @@ options:
required on the slave to start the replication process.
- C(2) to generate a commented 'CHANGE MASTER TO'.
- Can be used when I(state=dump).
required: no
type: int
choices: [0, 1, 2]
default: 0
@ -95,13 +99,15 @@ options:
skip_lock_tables:
description:
- Skip locking tables for read. Used when I(state=dump), ignored otherwise.
required: no
type: bool
default: false
default: no
version_added: '0.1.0'
dump_extra_args:
description:
- Provide additional arguments for mysqldump.
Used when I(state=dump) only, ignored otherwise.
required: no
type: str
version_added: '0.1.0'
use_shell:
@ -109,37 +115,34 @@ options:
- Used to prevent C(Broken pipe) errors when the imported I(target) file is compressed.
- If C(yes), the module will internally execute commands via a shell.
- Used when I(state=import), ignored otherwise.
required: no
type: bool
default: false
default: no
version_added: '0.1.0'
unsafe_login_password:
description:
- If C(no), the module will safely use a shell-escaped
version of the I(login_password) value.
- It makes sense to use C(yes) only if there are special
symbols in the value and errors C(Access denied) occur.
- Used only when I(state) is C(import) or C(dump) and
I(login_password) is passed, ignored otherwise.
- If C(no), the module will safely use a shell-escaped version of the I(login_password) value.
- It makes sense to use C(yes) only if there are special symbols in the value and errors C(Access denied) occur.
- Used only when I(state) is C(import) or C(dump) and I(login_password) is passed, ignored otherwise.
type: bool
default: false
default: no
version_added: '0.1.0'
restrict_config_file:
description:
- Read only passed I(config_file).
- When I(state) is C(dump) or C(import),
by default the module passes I(config_file) parameter
- When I(state) is C(dump) or C(import), by default the module passes I(config_file) parameter
using C(--defaults-extra-file) command-line argument to C(mysql/mysqldump) utilities
under the hood that read named option file in addition to usual option files.
- If this behavior is undesirable, use C(yes) to read only named option file.
type: bool
default: false
default: no
version_added: '0.1.0'
check_implicit_admin:
description:
- Check if mysql allows login as root/nopassword before trying supplied credentials.
- If success, passed I(login_user)/I(login_password) will be ignored.
type: bool
default: false
default: no
version_added: '0.1.0'
config_overrides_defaults:
description:
@ -148,23 +151,8 @@ options:
- Used when I(stat) is C(present) or C(absent), ignored otherwise.
- It needs Python 3.5+ as the default interpreter on a target host.
type: bool
default: false
default: no
version_added: '0.1.0'
chdir:
description:
- Changes the current working directory.
- Can be useful, for example, when I(state=import) and a dump file contains relative paths.
type: path
version_added: '3.4.0'
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=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
default: false
version_added: '3.4.0'
seealso:
- module: community.mysql.mysql_info
@ -188,25 +176,19 @@ requirements:
- mysql (command line binary)
- mysqldump (command line binary)
notes:
- Compatible with MariaDB or MySQL.
- Requires the mysql and mysqldump binaries on the remote host.
- This module is B(not idempotent) when I(state) is C(import),
and will import the dump file each time if run more than once.
attributes:
check_mode:
support: full
extends_documentation_fragment:
- community.mysql.mysql
'''
EXAMPLES = r'''
# If you encounter the "Please explicitly state intended protocol" error,
# use the login_unix_socket argument
- name: Create a new database with name 'bobdata'
community.mysql.mysql_db:
name: bobdata
state: present
login_unix_socket: /run/mysqld/mysqld.sock
- name: Create new databases with names 'foo' and 'bar'
community.mysql.mysql_db:
@ -232,7 +214,7 @@ EXAMPLES = r'''
name: my_db
state: import
target: /tmp/dump.sql.bz2
force: true
force: yes
- name: Dump multiple databases
community.mysql.mysql_db:
@ -304,18 +286,11 @@ EXAMPLES = r'''
- name: Try to create database as root/nopassword first. If not allowed, pass the credentials
community.mysql.mysql_db:
check_implicit_admin: true
check_implicit_admin: yes
login_user: bob
login_password: 123456
name: bobdata
state: present
- name: Dump a database with compression and catch errors from mysqldump with bash pipefail
community.mysql.mysql_db:
state: dump
name: foo
target: /tmp/dump.sql.gz
pipefail: true
'''
RETURN = r'''
@ -343,15 +318,7 @@ import traceback
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.mysql.plugins.module_utils.database import mysql_quote_identifier
from ansible_collections.community.mysql.plugins.module_utils.mysql import (
mysql_connect,
mysql_driver,
mysql_driver_fail_msg,
mysql_common_argument_spec,
get_server_implementation,
get_server_version,
)
from ansible_collections.community.mysql.plugins.module_utils.version import LooseVersion
from ansible_collections.community.mysql.plugins.module_utils.mysql import mysql_connect, mysql_driver, mysql_driver_fail_msg
from ansible.module_utils.six.moves import shlex_quote
from ansible.module_utils._text import to_native
@ -365,7 +332,7 @@ executed_commands = []
def db_exists(cursor, db):
res = 0
for each_db in db:
res += cursor.execute("SELECT SCHEMA_NAME FROM information_schema.SCHEMATA WHERE SCHEMA_NAME = %s", (each_db,))
res += cursor.execute("SHOW DATABASES LIKE %s", (each_db.replace("_", r"\_"),))
return res == len(db)
@ -380,81 +347,68 @@ def db_delete(cursor, db):
def db_dump(module, host, user, password, db_name, target, all_databases, port,
config_file, server_implementation, server_version, socket=None,
ssl_cert=None, ssl_key=None, ssl_ca=None,
config_file, socket=None, ssl_cert=None, ssl_key=None, ssl_ca=None,
single_transaction=None, quick=None, ignore_tables=None, hex_blob=None,
encoding=None, force=False, master_data=0, skip_lock_tables=False,
dump_extra_args=None, unsafe_password=False, restrict_config_file=False,
check_implicit_admin=False, pipefail=False):
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)
check_implicit_admin=False):
cmd = module.get_bin_path('mysqldump', True)
# If defined, mysqldump demands --defaults-extra-file be the first option
if config_file:
if restrict_config_file:
cmd.append("--defaults-file=%s" % shlex_quote(config_file))
cmd += " --defaults-file=%s" % shlex_quote(config_file)
else:
cmd.append("--defaults-extra-file=%s" % shlex_quote(config_file))
cmd += " --defaults-extra-file=%s" % shlex_quote(config_file)
if check_implicit_admin:
cmd.append("--user=root --password=''")
cmd += " --user=root --password=''"
else:
if user is not None:
cmd.append("--user=%s" % shlex_quote(user))
cmd += " --user=%s" % shlex_quote(user)
if password is not None:
if not unsafe_password:
cmd.append("--password=%s" % shlex_quote(password))
cmd += " --password=%s" % shlex_quote(password)
else:
cmd.append("--password=%s" % password)
cmd += " --password=%s" % password
if ssl_cert is not None:
cmd.append("--ssl-cert=%s" % shlex_quote(ssl_cert))
cmd += " --ssl-cert=%s" % shlex_quote(ssl_cert)
if ssl_key is not None:
cmd.append("--ssl-key=%s" % shlex_quote(ssl_key))
cmd += " --ssl-key=%s" % shlex_quote(ssl_key)
if ssl_ca is not None:
cmd.append("--ssl-ca=%s" % shlex_quote(ssl_ca))
cmd += " --ssl-ca=%s" % shlex_quote(ssl_ca)
if force:
cmd.append("--force")
cmd += " --force"
if socket is not None:
cmd.append("--socket=%s" % shlex_quote(socket))
cmd += " --socket=%s" % shlex_quote(socket)
else:
cmd.append("--host=%s --port=%i" % (shlex_quote(host), port))
cmd += " --host=%s --port=%i" % (shlex_quote(host), port)
if all_databases:
cmd.append("--all-databases")
cmd += " --all-databases"
elif len(db_name) > 1:
cmd.append("--databases {0}".format(' '.join(db_name)))
cmd += " --databases {0}".format(' '.join(db_name))
else:
cmd.append("%s" % shlex_quote(' '.join(db_name)))
cmd += " %s" % shlex_quote(' '.join(db_name))
if skip_lock_tables:
cmd.append("--skip-lock-tables")
cmd += " --skip-lock-tables"
if (encoding is not None) and (encoding != ""):
cmd.append("--default-character-set=%s" % shlex_quote(encoding))
cmd += " --default-character-set=%s" % shlex_quote(encoding)
if single_transaction:
cmd.append("--single-transaction=true")
cmd += " --single-transaction=true"
if quick:
cmd.append("--quick")
cmd += " --quick"
if ignore_tables:
for an_ignored_table in ignore_tables:
cmd.append("--ignore-table={0}".format(an_ignored_table))
cmd += " --ignore-table={0}".format(an_ignored_table)
if hex_blob:
cmd.append("--hex-blob")
cmd += " --hex-blob"
if master_data:
if (server_implementation == 'mysql' and
LooseVersion(server_version) >= LooseVersion("8.2.0")):
cmd.append("--source-data=%s" % master_data)
else:
cmd.append("--master-data=%s" % master_data)
cmd += " --master-data=%s" % master_data
if dump_extra_args is not None:
cmd.append(dump_extra_args)
cmd += " " + dump_extra_args
path = None
if os.path.splitext(target)[-1] == '.gz':
@ -463,44 +417,25 @@ 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)
cmd = ' '.join(cmd)
if path:
cmd = '%s | %s > %s' % (cmd, path, shlex_quote(target))
if pipefail:
cmd = 'set -o pipefail && ' + cmd
else:
cmd += " > %s" % shlex_quote(target)
executed_commands.append(cmd)
if pipefail:
rc, stdout, stderr = module.run_command(cmd, use_unsafe_shell=True, executable='bash')
else:
rc, stdout, stderr = module.run_command(cmd, use_unsafe_shell=True)
rc, stdout, stderr = module.run_command(cmd, use_unsafe_shell=True)
return rc, stdout, stderr
def db_import(module, host, user, password, db_name, target, all_databases, port, config_file,
server_implementation, server_version, socket=None, ssl_cert=None, ssl_key=None, ssl_ca=None,
encoding=None, force=False,
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_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)
cmd = [module.get_bin_path('mysql', True)]
# --defaults-file must go first, or errors out
if config_file:
if restrict_config_file:
@ -509,7 +444,7 @@ def db_import(module, host, user, password, db_name, target, all_databases, port
cmd.append("--defaults-extra-file=%s" % shlex_quote(config_file))
if check_implicit_admin:
cmd.append("--user=root --password=''")
cmd += " --user=root --password=''"
else:
if user:
cmd.append("--user=%s" % shlex_quote(user))
@ -546,8 +481,6 @@ 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))
@ -588,8 +521,7 @@ def db_create(cursor, db, encoding, collation):
query_params = dict(enc=encoding, collate=collation)
res = 0
for each_db in db:
# Escape '%' since mysql cursor.execute() uses a format string
query = ['CREATE DATABASE %s' % mysql_quote_identifier(each_db, 'database').replace('%', '%%')]
query = ['CREATE DATABASE %s' % mysql_quote_identifier(each_db, 'database')]
if encoding:
query.append("CHARACTER SET %(enc)s")
if collation:
@ -611,32 +543,37 @@ def db_create(cursor, db, encoding, collation):
def main():
argument_spec = mysql_common_argument_spec()
argument_spec.update(
name=dict(type='list', elements='str', required=True, aliases=['db']),
encoding=dict(type='str', default=''),
collation=dict(type='str', default=''),
target=dict(type='path'),
state=dict(type='str', default='present', choices=['absent', 'dump', 'import', 'present']),
single_transaction=dict(type='bool', default=False),
quick=dict(type='bool', default=True),
ignore_tables=dict(type='list', elements='str', default=[]),
hex_blob=dict(default=False, type='bool'),
force=dict(type='bool', default=False),
master_data=dict(type='int', default=0, choices=[0, 1, 2]),
skip_lock_tables=dict(type='bool', default=False),
dump_extra_args=dict(type='str'),
use_shell=dict(type='bool', default=False),
unsafe_login_password=dict(type='bool', default=False, no_log=True),
restrict_config_file=dict(type='bool', default=False),
check_implicit_admin=dict(type='bool', default=False),
config_overrides_defaults=dict(type='bool', default=False),
chdir=dict(type='path'),
pipefail=dict(type='bool', default=False),
)
module = AnsibleModule(
argument_spec=argument_spec,
argument_spec=dict(
login_user=dict(type='str'),
login_password=dict(type='str', no_log=True),
login_host=dict(type='str', default='localhost'),
login_port=dict(type='int', default=3306),
login_unix_socket=dict(type='str'),
name=dict(type='list', required=True, aliases=['db']),
encoding=dict(type='str', default=''),
collation=dict(type='str', default=''),
target=dict(type='path'),
state=dict(type='str', default='present', choices=['absent', 'dump', 'import', 'present']),
client_cert=dict(type='path', aliases=['ssl_cert']),
client_key=dict(type='path', aliases=['ssl_key']),
ca_cert=dict(type='path', aliases=['ssl_ca']),
connect_timeout=dict(type='int', default=30),
config_file=dict(type='path', default='~/.my.cnf'),
single_transaction=dict(type='bool', default=False),
quick=dict(type='bool', default=True),
ignore_tables=dict(type='list', default=[]),
hex_blob=dict(default=False, type='bool'),
force=dict(type='bool', default=False),
master_data=dict(type='int', default=0, choices=[0, 1, 2]),
skip_lock_tables=dict(type='bool', default=False),
dump_extra_args=dict(type='str'),
use_shell=dict(type='bool', default=False),
unsafe_login_password=dict(type='bool', default=False, no_log=True),
restrict_config_file=dict(type='bool', default=False),
check_implicit_admin=dict(type='bool', default=False),
config_overrides_defaults=dict(type='bool', default=False),
),
supports_check_mode=True,
)
@ -659,7 +596,6 @@ def main():
ssl_cert = module.params["client_cert"]
ssl_key = module.params["client_key"]
ssl_ca = module.params["ca_cert"]
check_hostname = module.params["check_hostname"]
connect_timeout = module.params['connect_timeout']
config_file = module.params['config_file']
login_password = module.params["login_password"]
@ -681,14 +617,6 @@ def main():
restrict_config_file = module.params["restrict_config_file"]
check_implicit_admin = module.params['check_implicit_admin']
config_overrides_defaults = module.params['config_overrides_defaults']
chdir = module.params['chdir']
pipefail = module.params['pipefail']
if chdir:
try:
os.chdir(chdir)
except Exception as e:
module.fail_json("Cannot change the current directory to %s: %s" % (chdir, e))
if len(db) > 1 and state == 'import':
module.fail_json(msg="Multiple databases are not supported with state=import")
@ -708,7 +636,7 @@ def main():
if check_implicit_admin:
try:
cursor, db_conn = mysql_connect(module, 'root', '', config_file, ssl_cert, ssl_key, ssl_ca,
connect_timeout=connect_timeout, check_hostname=check_hostname,
connect_timeout=connect_timeout,
config_overrides_defaults=config_overrides_defaults)
except Exception as e:
check_implicit_admin = False
@ -716,8 +644,7 @@ def main():
if not cursor:
cursor, db_conn = mysql_connect(module, login_user, login_password, config_file, ssl_cert, ssl_key, ssl_ca,
connect_timeout=connect_timeout, config_overrides_defaults=config_overrides_defaults,
check_hostname=check_hostname)
connect_timeout=connect_timeout, config_overrides_defaults=config_overrides_defaults)
except Exception as e:
if os.path.exists(config_file):
module.fail_json(msg="unable to connect to database, check login_user and login_password are correct or %s has the credentials. "
@ -725,9 +652,6 @@ def main():
else:
module.fail_json(msg="unable to find %s. Exception message: %s" % (config_file, to_native(e)))
server_implementation = get_server_implementation(cursor)
server_version = get_server_version(cursor)
changed = False
if not os.path.exists(config_file):
config_file = None
@ -768,12 +692,11 @@ def main():
module.exit_json(changed=True, db=db_name, db_list=db)
rc, stdout, stderr = db_dump(module, login_host, login_user,
login_password, db, target, all_databases,
login_port, config_file, server_implementation, server_version,
socket, ssl_cert, ssl_key,
login_port, config_file, socket, ssl_cert, ssl_key,
ssl_ca, single_transaction, quick, ignore_tables,
hex_blob, encoding, force, master_data, skip_lock_tables,
dump_extra_args, unsafe_login_password, restrict_config_file,
check_implicit_admin, pipefail)
check_implicit_admin)
if rc != 0:
module.fail_json(msg="%s" % stderr)
module.exit_json(changed=True, db=db_name, db_list=db, msg=stdout,
@ -790,8 +713,8 @@ def main():
rc, stdout, stderr = db_import(module, login_host, login_user,
login_password, db, target,
all_databases,
login_port, config_file, server_implementation,
server_version, socket, ssl_cert, ssl_key, ssl_ca,
login_port, config_file,
socket, ssl_cert, ssl_key, ssl_ca,
encoding, force, use_shell, unsafe_login_password,
restrict_config_file, check_implicit_admin)
if rc != 0:

View file

@ -1,27 +1,25 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) <andrew.a.klychkov@gmail.com>
# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) <aaklychkov@mail.ru>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r'''
---
module: mysql_info
short_description: Gather information about MySQL or MariaDB servers
short_description: Gather information about MySQL servers
description:
- Gathers information about MySQL or MariaDB servers.
- Gathers information about MySQL servers.
options:
filter:
description:
- Limit the collected information by comma separated string or YAML list.
- Allowable values are C(version), C(databases), C(settings), C(global_status),
C(users), C(users_info), C(engines), C(master_status), C(slave_status), C(slave_hosts).
C(users), C(engines), C(master_status), C(slave_status), C(slave_hosts).
- By default, collects all subsets.
- You can use '!' before value (for example, C(!settings)) to exclude it from the information.
- If you pass including and excluding values to the filter, for example, I(filter=!settings,version),
@ -36,7 +34,7 @@ options:
exclude_fields:
description:
- List of fields which are not needed to collect.
- "Supports elements: C(db_size), C(db_table_count). Unsupported elements will be ignored."
- "Supports elements: C(db_size). Unsupported elements will be ignored"
type: list
elements: str
version_added: '0.1.0'
@ -44,17 +42,12 @@ options:
description:
- Includes names of empty databases to returned dictionary.
type: bool
default: false
default: no
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).
attributes:
check_mode:
support: full
seealso:
- module: community.mysql.mysql_variables
- module: community.mysql.mysql_db
@ -63,11 +56,10 @@ seealso:
author:
- Andrew Klychkov (@Andersson007)
- Sebastian Gumprich (@rndmh3ro)
- Laurent Indermühle (@laurent-indermuehle)
extends_documentation_fragment:
- community.mysql.mysql
'''
EXAMPLES = r'''
@ -77,21 +69,15 @@ EXAMPLES = r'''
# Display only databases and users info:
# ansible mysql-hosts -m mysql_info -a 'filter=databases,users'
# Display all users privileges:
# ansible mysql-hosts -m mysql_info -a 'filter=users_info'
# Display only slave status:
# ansible standby -m mysql_info -a 'filter=slave_status'
# Display all info from databases group except settings:
# ansible databases -m mysql_info -a 'filter=!settings'
# If you encounter the "Please explicitly state intended protocol" error,
# use the login_unix_socket argument
- name: Collect all possible information using passwordless root access
community.mysql.mysql_info:
login_user: root
login_unix_socket: /run/mysqld/mysqld.sock
- name: Get MySQL version with non-default credentials
community.mysql.mysql_info:
@ -106,14 +92,14 @@ EXAMPLES = r'''
filter: "!settings,!users"
- name: Collect info about databases and version using ~/.my.cnf as a credential file
become: true
become: yes
community.mysql.mysql_info:
filter:
- databases
- version
- name: Collect info about databases and version using ~alice/.my.cnf as a credential file
become: true
become: yes
community.mysql.mysql_info:
config_file: /home/alice/.my.cnf
filter:
@ -121,59 +107,21 @@ EXAMPLES = r'''
- version
- name: Collect info about databases including empty and excluding their sizes
become: true
become: yes
community.mysql.mysql_info:
config_file: /home/alice/.my.cnf
filter:
- databases
exclude_fields: db_size
return_empty_dbs: true
- name: Clone users from one server to another
block:
# Step 1
- name: Fetch information from a source server
delegate_to: server_source
community.mysql.mysql_info:
filter:
- users_info
register: result
# Step 2
# Don't work with sha256_password and cache_sha2_password
- name: Clone users fetched in a previous task to a target server
community.mysql.mysql_user:
name: "{{ item.name }}"
host: "{{ item.host }}"
plugin: "{{ item.plugin | default(omit) }}"
plugin_auth_string: "{{ item.plugin_auth_string | default(omit) }}"
plugin_hash_string: "{{ item.plugin_hash_string | default(omit) }}"
tls_requires: "{{ item.tls_requires | default(omit) }}"
priv: "{{ item.priv | default(omit) }}"
resource_limits: "{{ item.resource_limits | default(omit) }}"
column_case_sensitive: true
state: present
loop: "{{ result.users_info }}"
loop_control:
label: "{{ item.name }}@{{ item.host }}"
when:
- item.name != 'root' # In case you don't want to import admin accounts
- item.name != 'mariadb.sys'
- item.name != 'mysql'
return_empty_dbs: yes
'''
RETURN = r'''
server_engine:
description: Database server engine.
returned: if not excluded by filter
type: str
sample: 'MariaDB'
version_added: '3.10.0'
version:
description: Database server version.
returned: if not excluded by filter
type: dict
sample: { "version": { "major": 5, "minor": 5, "release": 60, "suffix": "MariaDB", "full": "5.5.60-MariaDB" } }
sample: { "version": { "major": 5, "minor": 5, "release": 60 } }
contains:
major:
description: Major server version.
@ -190,34 +138,18 @@ version:
returned: if not excluded by filter
type: int
sample: 60
suffix:
description: Server suffix, for example MySQL, MariaDB, other or none.
returned: if not excluded by filter
type: str
sample: "MariaDB"
full:
description: Full server version.
returned: if not excluded by filter
type: str
sample: "5.5.60-MariaDB"
databases:
description: Information about databases.
returned: if not excluded by filter
type: dict
sample:
- { "mysql": { "size": 656594, "tables": 31 }, "information_schema": { "size": 73728, "tables": 79 } }
- { "mysql": { "size": 656594 }, "information_schema": { "size": 73728 } }
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
@ -231,32 +163,11 @@ global_status:
sample:
- { "Innodb_buffer_pool_read_requests": 123, "Innodb_buffer_pool_reads": 32 }
users:
description: Return a dictionnary of users grouped by host and with global privileges only.
description: Users information.
returned: if not excluded by filter
type: dict
sample:
- { "localhost": { "root": { "Alter_priv": "Y", "Alter_routine_priv": "Y" } } }
users_info:
description:
- Information about users accounts.
- The output can be used as an input of the M(community.mysql.mysql_user) plugin.
- Useful when migrating accounts to another server or to create an inventory.
- Does not support proxy privileges. If an account has proxy privileges, they won't appear in the output.
- Causes issues with authentications plugins C(sha256_password) and C(caching_sha2_password).
If the output is fed to M(community.mysql.mysql_user), the
``plugin_auth_string`` will most likely be unreadable due to non-binary
characters.
returned: if not excluded by filter
type: dict
sample:
- { "plugin_auth_string": '*1234567',
"name": "user1",
"host": "host.com",
"plugin": "mysql_native_password",
"priv": "db1.*:SELECT/db2.*:SELECT",
"resource_limits": { "MAX_USER_CONNECTIONS": 100 },
"tls_requires": { "SSL": null } }
version_added: '3.8.0'
engines:
description: Information about the server's storage engines.
returned: if not excluded by filter
@ -281,45 +192,16 @@ slave_hosts:
type: dict
sample:
- { "2": { "Host": "", "Master_id": 1, "Port": 3306 } }
connector_name:
description: Name of the python connector used by the module. When the connector is not identified, returns C(Unknown).
returned: always
type: str
sample:
- "pymysql"
version_added: '3.6.0'
connector_version:
description: Version of the python connector used by the module. When the connector is not identified, returns C(Unknown).
returned: always
type: str
sample:
- "1.0.2"
version_added: '3.6.0'
'''
from decimal import Decimal
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.mysql.plugins.module_utils.command_resolver import (
CommandResolver
)
from ansible_collections.community.mysql.plugins.module_utils.mysql import (
mysql_connect,
mysql_common_argument_spec,
mysql_driver,
mysql_driver_fail_msg,
get_connector_name,
get_connector_version,
get_server_implementation,
get_server_version,
)
from ansible_collections.community.mysql.plugins.module_utils.user import (
privileges_get,
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
@ -347,13 +229,9 @@ class MySQL_Info(object):
5. add info about the new subset with an example to RETURN block
"""
def __init__(self, module, cursor, server_implementation, server_version, user_implementation):
def __init__(self, module, cursor):
self.module = module
self.cursor = cursor
self.server_implementation = server_implementation
self.server_version = server_version
self.user_implementation = user_implementation
self.command_resolver = CommandResolver(self.server_implementation, self.server_version)
self.info = {
'version': {},
'databases': {},
@ -361,7 +239,6 @@ class MySQL_Info(object):
'global_status': {},
'engines': {},
'users': {},
'users_info': {},
'master_status': {},
'slave_hosts': {},
'slave_status': {},
@ -430,9 +307,6 @@ class MySQL_Info(object):
if 'users' in wanted:
self.__get_users()
if 'users_info' in wanted:
self.__get_users_info()
if 'master_status' in wanted:
self.__get_master_status()
@ -479,30 +353,13 @@ class MySQL_Info(object):
for var in res:
self.info['settings'][var['Variable_name']] = self.__convert(var['Value'])
# version = ["5", "5," "60-MariaDB]
version = self.info['settings']['version'].split('.')
# full_version = "5.5.60-MariaDB"
full = self.info['settings']['version']
# release = "60"
release = version[2].split('-')[0]
# check if a suffix exists by counting the length
if len(version[2].split('-')) > 1:
# suffix = "MariaDB"
suffix = version[2].split('-', 1)[1]
else:
suffix = ""
ver = self.info['settings']['version'].split('.')
release = ver[2].split('-')[0]
self.info['version'] = dict(
# major = "5"
major=int(version[0]),
# minor = "5"
minor=int(version[1]),
major=int(ver[0]),
minor=int(ver[1]),
release=int(release),
suffix=str(suffix),
full=str(full),
)
def __get_global_status(self):
@ -515,8 +372,7 @@ class MySQL_Info(object):
def __get_master_status(self):
"""Get master status if the instance is a master."""
query = self.command_resolver.resolve_command("SHOW MASTER STATUS")
res = self.__exec_sql(query)
res = self.__exec_sql('SHOW MASTER STATUS')
if res:
for line in res:
for vname, val in iteritems(line):
@ -524,8 +380,7 @@ class MySQL_Info(object):
def __get_slave_status(self):
"""Get slave status if the instance is a slave."""
query = self.command_resolver.resolve_command("SHOW SLAVE STATUS")
res = self.__exec_sql(query)
res = self.__exec_sql('SHOW SLAVE STATUS')
if res:
for line in res:
host = line['Master_Host']
@ -546,8 +401,7 @@ class MySQL_Info(object):
def __get_slaves(self):
"""Get slave hosts info if the instance is a master."""
query = self.command_resolver.resolve_command("SHOW SLAVE HOSTS")
res = self.__exec_sql(query)
res = self.__exec_sql('SHOW SLAVE HOSTS')
if res:
for line in res:
srv_id = line['Server_id']
@ -574,131 +428,39 @@ class MySQL_Info(object):
if vname not in ('Host', 'User'):
self.info['users'][host][user][vname] = self.__convert(val)
def __get_users_info(self):
"""Get user privileges, passwords, resources_limits, ...
Query the server to get all the users and return a string
of privileges that can be used by the mysql_user plugin.
For instance:
"users_info": [
{
"host": "users_info.com",
"priv": "*.*: ALL,GRANT",
"name": "users_info_adm"
},
{
"host": "users_info.com",
"priv": "`mysql`.*: SELECT/`users_info_db`.*: SELECT",
"name": "users_info_multi"
}
]
"""
res = self.__exec_sql('SELECT * FROM mysql.user')
if not res:
return None
output = list()
for line in res:
user = line['User']
host = line['Host']
user_priv = privileges_get(self.cursor, user, host)
if not user_priv:
self.module.warn("No privileges found for %s on host %s" % (user, host))
continue
priv_string = list()
for db_table, priv in user_priv.items():
# Proxy privileges are hard to work with because of different quotes or
# backticks like ''@'', ''@'%' or even ``@``. In addition, MySQL will
# forbid you to grant a proxy privileges through TCP.
if set(priv) == {'PROXY', 'GRANT'} or set(priv) == {'PROXY'}:
continue
unquote_db_table = db_table.replace('`', '').replace("'", '')
priv_string.append('%s:%s' % (unquote_db_table, ','.join(priv)))
# Only keep *.* USAGE if it's the only user privilege given
if len(priv_string) > 1 and '*.*:USAGE' in priv_string:
priv_string.remove('*.*:USAGE')
resource_limits = get_resource_limits(self.cursor, user, host)
copy_ressource_limits = dict.copy(resource_limits)
tls_requires = self.user_implementation.get_tls_requires(
self.cursor, user, host)
output_dict = {
'name': user,
'host': host,
'priv': '/'.join(priv_string),
'resource_limits': copy_ressource_limits,
'tls_requires': tls_requires,
}
# Prevent returning a resource limit if empty
if resource_limits:
for key, value in resource_limits.items():
if value == 0:
del output_dict['resource_limits'][key]
if len(output_dict['resource_limits']) == 0:
del output_dict['resource_limits']
# Prevent returning tls_require if empty
if not tls_requires:
del output_dict['tls_requires']
authentications = get_existing_authentication(self.cursor, user, host)
if authentications:
output_dict.update(authentications[0])
if line.get('is_role') and line['is_role'] == 'N':
output_dict['locked'] = user_is_locked(self.cursor, user, host)
# TODO password_option
# but both are not supported by mysql_user atm. So no point yet.
output.append(output_dict)
self.info['users_info'] = output
def __get_databases(self, exclude_fields, return_empty_dbs):
"""Get info about databases."""
if not exclude_fields:
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')
def is_field_included(field_name):
return not exclude_fields or 'db_{}'.format(field_name) not in exclude_fields
res = self.__exec_sql(query)
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 res:
for db in res:
self.info['databases'][db['name']] = {}
# 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"')
if not exclude_fields or 'db_size' not in exclude_fields:
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']] = {}
# 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({})
if not exclude_fields or 'db_size' not in exclude_fields:
self.info['databases'][db['Database']]['size'] = 0
def __exec_sql(self, query, ddl=False):
"""Execute SQL.
@ -730,8 +492,8 @@ def main():
argument_spec = mysql_common_argument_spec()
argument_spec.update(
login_db=dict(type='str'),
filter=dict(type='list', elements='str'),
exclude_fields=dict(type='list', elements='str'),
filter=dict(type='list'),
exclude_fields=dict(type='list'),
return_empty_dbs=dict(type='bool', default=False),
)
@ -747,7 +509,6 @@ def main():
ssl_cert = module.params['client_cert']
ssl_key = module.params['client_key']
ssl_ca = module.params['ca_cert']
check_hostname = module.params['check_hostname']
config_file = module.params['config_file']
filter_ = module.params['filter']
exclude_fields = module.params['exclude_fields']
@ -762,34 +523,20 @@ def main():
if mysql_driver is None:
module.fail_json(msg=mysql_driver_fail_msg)
connector_name = get_connector_name(mysql_driver)
connector_version = get_connector_version(mysql_driver)
try:
cursor, db_conn = mysql_connect(module, login_user, login_password,
config_file, ssl_cert, ssl_key, ssl_ca, db,
check_hostname=check_hostname,
connect_timeout=connect_timeout, cursor_class='DictCursor')
except Exception as e:
msg = ('unable to connect to database using %s %s, check login_user '
'and login_password are correct or %s has the credentials. '
'Exception message: %s' % (connector_name, connector_version, config_file, to_native(e)))
module.fail_json(msg)
server_implementation = get_server_implementation(cursor)
server_version = get_server_version(cursor)
user_implementation = get_user_implementation(cursor)
module.fail_json(msg="unable to connect to database, check login_user and login_password are correct or %s has the credentials. "
"Exception message: %s" % (config_file, to_native(e)))
###############################
# Create object and do main job
mysql = MySQL_Info(module, cursor, server_implementation, server_version, user_implementation)
mysql = MySQL_Info(module, cursor)
module.exit_json(changed=False,
server_engine='MariaDB' if server_implementation == 'mariadb' else 'MySQL',
connector_name=connector_name,
connector_version=connector_version,
**mysql.get_info(filter_, exclude_fields, return_empty_dbs))
module.exit_json(changed=False, **mysql.get_info(filter_, exclude_fields, return_empty_dbs))
if __name__ == '__main__':

View file

@ -1,7 +1,7 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2020, Andrew Klychkov (@Andersson007) <andrew.a.klychkov@gmail.com>
# Copyright: (c) 2020, Andrew Klychkov (@Andersson007) <aaklychkov@mail.ru>
# 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)
@ -10,33 +10,24 @@ __metaclass__ = type
DOCUMENTATION = r'''
---
module: mysql_query
short_description: Run MySQL or MariaDB queries
short_description: Run MySQL queries
description:
- Runs arbitrary MySQL or MariaDB queries.
- Runs arbitrary MySQL queries.
- Pay attention, the module does not support check mode!
All queries will be executed in autocommit mode.
- To run SQL queries from a file, use M(community.mysql.mysql_db) module.
version_added: '0.1.0'
options:
query:
description:
- SQL query to run. Multiple queries can be passed using YAML list syntax.
- Must be a string or YAML list containing strings.
- If you use I(named_args) or I(positional_args) any C(%) will be interpreted
as a formatting character. All literal C(%) characters in the query should be
escaped as C(%%).
- Note that if you use the C(IF EXISTS/IF NOT EXISTS) clauses in your query
and C(mysqlclient) or C(PyMySQL 0.10.0+) connectors, the module will report
that the state has been changed even if it has not. If it is important in your
workflow, use the C(PyMySQL 0.9.3) connector instead.
type: raw
required: true
type: list
elements: str
required: yes
positional_args:
description:
- List of values to be passed as positional arguments to the query.
- Mutually exclusive with I(named_args).
type: list
elements: raw
named_args:
description:
- Dictionary of key-value arguments to pass to the query.
@ -50,28 +41,21 @@ options:
description:
- Where passed queries run in a single transaction (C(yes)) or commit them one-by-one (C(no)).
type: bool
default: false
attributes:
check_mode:
support: none
seealso:
- module: community.mysql.mysql_db
default: no
notes:
- Compatible with MariaDB or MySQL.
- To pass a query containing commas, use YAML list notation with hyphen (see EXAMPLES block).
author:
- Andrew Klychkov (@Andersson007)
extends_documentation_fragment:
- community.mysql.mysql
'''
EXAMPLES = r'''
# If you encounter the "Please explicitly state intended protocol" error,
# use the login_unix_socket argument
- name: Simple select query to acme db
community.mysql.mysql_query:
login_db: acme
query: SELECT * FROM orders
login_unix_socket: /run/mysqld/mysqld.sock
- name: Select query to db acme with positional arguments
community.mysql.mysql_query:
@ -95,7 +79,7 @@ EXAMPLES = r'''
query:
- INSERT INTO articles (id, story) VALUES (2, 'my_long_story')
- INSERT INTO prices (id, price) VALUES (123, '100.00')
single_transaction: true
single_transaction: yes
'''
RETURN = r'''
@ -116,20 +100,8 @@ 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
from ansible_collections.community.mysql.plugins.module_utils.mysql import (
mysql_connect,
@ -139,7 +111,7 @@ from ansible_collections.community.mysql.plugins.module_utils.mysql import (
)
from ansible.module_utils._text import to_native
DML_QUERY_KEYWORDS = ('INSERT', 'UPDATE', 'DELETE', 'REPLACE')
DML_QUERY_KEYWORDS = ('INSERT', 'UPDATE', 'DELETE')
# TRUNCATE is not DDL query but it also returns 0 rows affected:
DDL_QUERY_KEYWORDS = ('CREATE', 'DROP', 'ALTER', 'RENAME', 'TRUNCATE')
@ -148,24 +120,12 @@ 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(
query=dict(type='raw', required=True),
query=dict(type='list', elements='str', required=True),
login_db=dict(type='str'),
positional_args=dict(type='list', elements='raw'),
positional_args=dict(type='list'),
named_args=dict(type='dict'),
single_transaction=dict(type='bool', default=False),
)
@ -184,20 +144,8 @@ def main():
ssl_cert = module.params['client_cert']
ssl_key = module.params['client_key']
ssl_ca = module.params['ca_cert']
check_hostname = module.params['check_hostname']
config_file = module.params['config_file']
query = module.params["query"]
if not isinstance(query, (str, list)):
module.fail_json(msg="the query option value must be a string or list, passed %s" % type(query))
if isinstance(query, str):
query = [query]
for elem in query:
if not isinstance(elem, str):
module.fail_json(msg="the elements in query list must be strings, passed '%s' %s" % (elem, type(elem)))
if module.params["single_transaction"]:
autocommit = False
else:
@ -217,14 +165,12 @@ def main():
try:
cursor, db_connection = mysql_connect(module, login_user, login_password,
config_file, ssl_cert, ssl_key, ssl_ca, db,
check_hostname=check_hostname,
connect_timeout=connect_timeout,
cursor_class='DictCursor', autocommit=autocommit)
except Exception as e:
module.fail_json(msg="unable to connect to database, check login_user and "
"login_password are correct or %s has the credentials. "
"Exception message: %s" % (config_file, to_native(e)))
# Set defaults:
changed = False
@ -234,26 +180,9 @@ def main():
query_result = []
executed_queries = []
rowcount = []
execution_time_ms = []
already_exists = False
for q in query:
try:
with warnings.catch_warnings():
warnings.filterwarnings(action='error',
message='.*already exists*',
category=mysql_driver.Warning)
try:
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,
# set the flag as True.
# PyMySQL < 0.10.0 throws the warning, mysqlclient
# and PyMySQL 0.10.0+ does NOT.
already_exists = True
cursor.execute(q, arguments)
except Exception as e:
if not autocommit:
@ -263,8 +192,7 @@ def main():
module.fail_json(msg="Cannot execute SQL '%s' args [%s]: %s" % (q, arguments, to_native(e)))
try:
if not already_exists:
query_result.append([dict(row) for row in cursor.fetchall()])
query_result.append([dict(row) for row in cursor.fetchall()])
except Exception as e:
if not autocommit:
@ -280,17 +208,9 @@ def main():
for keyword in DDL_QUERY_KEYWORDS:
if keyword in q:
if already_exists:
# Indicates the entity already exists
changed = False
already_exists = False # Reset flag
else:
changed = True
try:
executed_queries.append(cursor._last_executed)
except AttributeError:
# MySQLdb removed cursor._last_executed as a duplicate of cursor._executed
executed_queries.append(cursor._executed)
changed = True
executed_queries.append(cursor._last_executed)
rowcount.append(cursor.rowcount)
# When the module run with the single_transaction == True:
@ -303,7 +223,6 @@ def main():
'executed_queries': executed_queries,
'query_result': query_result,
'rowcount': rowcount,
'execution_time_ms': execution_time_ms,
}
# Exit:

View file

@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2013, Balazs Pocze <banyek@gawker.com>
# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) <andrew.a.klychkov@gmail.com>
# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) <aaklychkov@mail.ru>
# 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)
@ -13,74 +13,63 @@ __metaclass__ = type
DOCUMENTATION = r'''
---
module: mysql_replication
short_description: Manage MySQL or MariaDB replication
short_description: Manage MySQL replication
description:
- Manages MySQL or MariaDB server replication, replica, primary status, get and change primary host.
- Manages MySQL server replication, slave, master status, get and change master host.
author:
- Balazs Pocze (@banyek)
- Andrew Klychkov (@Andersson007)
- Dennis Urtubia (@dennisurtubia)
- Laurent Indermühle (@laurent-indermuehle)
options:
mode:
description:
- Module operating mode. Could be
C(changeprimary) (CHANGE MASTER TO) - also works for MySQL 8.0.23 and later since community.mysql 3.10.0,
C(changereplication) (CHANGE REPLICATION SOURCE TO) - only supported in MySQL 8.0.23 and later,
C(getprimary) (SHOW MASTER STATUS),
C(getreplica) (SHOW REPLICA STATUS),
C(startreplica) (START REPLICA),
C(stopreplica) (STOP REPLICA),
C(resetprimary) (RESET MASTER) - supported since community.mysql 0.1.0,
C(resetreplica) (RESET REPLICA),
C(resetreplicaall) (RESET REPLICA ALL).
C(changemaster) (CHANGE MASTER TO),
C(getmaster) (SHOW MASTER STATUS),
C(getslave) (SHOW SLAVE STATUS),
C(startslave) (START SLAVE),
C(stopslave) (STOP SLAVE),
C(resetmaster) (RESET MASTER) - supported since community.mysql 0.1.0,
C(resetslave) (RESET SLAVE),
C(resetslaveall) (RESET SLAVE ALL).
type: str
choices:
- changeprimary
- changereplication
- getprimary
- getreplica
- startreplica
- stopreplica
- resetprimary
- resetreplica
- resetreplicaall
default: getreplica
primary_host:
- changemaster
- getmaster
- getslave
- startslave
- stopslave
- resetmaster
- resetslave
- resetslaveall
default: getslave
master_host:
description:
- Same as the C(MASTER_HOST) mysql variable.
- Same as mysql variable.
type: str
aliases: [master_host]
primary_user:
master_user:
description:
- Same as the C(MASTER_USER) mysql variable.
- Same as mysql variable.
type: str
aliases: [master_user]
primary_password:
master_password:
description:
- Same as the C(MASTER_PASSWORD) mysql variable.
- Same as mysql variable.
type: str
aliases: [master_password]
primary_port:
master_port:
description:
- Same as the C(MASTER_PORT) mysql variable.
- Same as mysql variable.
type: int
aliases: [master_port]
primary_connect_retry:
master_connect_retry:
description:
- Same as the C(MASTER_CONNECT_RETRY) mysql variable.
- Same as mysql variable.
type: int
aliases: [master_connect_retry]
primary_log_file:
master_log_file:
description:
- Same as the C(MASTER_LOG_FILE) mysql variable.
- Same as mysql variable.
type: str
aliases: [master_log_file]
primary_log_pos:
master_log_pos:
description:
- Same as the C(MASTER_LOG_POS) mysql variable.
- Same as mysql variable.
type: int
aliases: [master_log_pos]
relay_log_file:
description:
- Same as mysql variable.
@ -89,88 +78,54 @@ options:
description:
- Same as mysql variable.
type: int
primary_ssl:
description:
- Same as the C(MASTER_SSL) mysql variable.
- When setting it to C(yes), the connection attempt only succeeds
if an encrypted connection can be established.
- For details, refer to
L(MySQL encrypted replication documentation,https://dev.mysql.com/doc/refman/8.0/en/replication-solutions-encrypted-connections.html).
- The default is C(false).
type: bool
aliases: [master_ssl]
primary_ssl_ca:
description:
- Same as the C(MASTER_SSL_CA) mysql variable.
- For details, refer to
L(MySQL encrypted replication documentation,https://dev.mysql.com/doc/refman/8.0/en/replication-solutions-encrypted-connections.html).
type: str
aliases: [master_ssl_ca]
primary_ssl_capath:
description:
- Same as the C(MASTER_SSL_CAPATH) mysql variable.
- For details, refer to
L(MySQL encrypted replication documentation,https://dev.mysql.com/doc/refman/8.0/en/replication-solutions-encrypted-connections.html).
type: str
aliases: [master_ssl_capath]
primary_ssl_cert:
description:
- Same as the C(MASTER_SSL_CERT) mysql variable.
- For details, refer to
L(MySQL encrypted replication documentation,https://dev.mysql.com/doc/refman/8.0/en/replication-solutions-encrypted-connections.html).
type: str
aliases: [master_ssl_cert]
primary_ssl_key:
description:
- Same as the C(MASTER_SSL_KEY) mysql variable.
- For details, refer to
L(MySQL encrypted replication documentation,https://dev.mysql.com/doc/refman/8.0/en/replication-solutions-encrypted-connections.html).
type: str
aliases: [master_ssl_key]
primary_ssl_cipher:
description:
- Same as the C(MASTER_SSL_CIPHER) mysql variable.
- Specifies a colon-separated list of one or more ciphers permitted by the replica for the replication connection.
- For details, refer to
L(MySQL encrypted replication documentation,https://dev.mysql.com/doc/refman/8.0/en/replication-solutions-encrypted-connections.html).
type: str
aliases: [master_ssl_cipher]
primary_ssl_verify_server_cert:
master_ssl:
description:
- Same as mysql variable.
type: bool
default: false
version_added: '3.5.0'
primary_auto_position:
master_ssl_ca:
description:
- Same as mysql variable.
type: str
master_ssl_capath:
description:
- Same as mysql variable.
type: str
master_ssl_cert:
description:
- Same as mysql variable.
type: str
master_ssl_key:
description:
- Same as mysql variable.
type: str
master_ssl_cipher:
description:
- Same as mysql variable.
type: str
master_auto_position:
description:
- Whether the host uses GTID based replication or not.
- Same as the C(MASTER_AUTO_POSITION) mysql variable.
type: bool
default: false
aliases: [master_auto_position]
primary_use_gtid:
master_use_gtid:
description:
- Configures the replica to use the MariaDB Global Transaction ID.
- Configures the slave to use the MariaDB Global Transaction ID.
- C(disabled) equals MASTER_USE_GTID=no command.
- To find information about available values see
U(https://mariadb.com/kb/en/library/change-master-to/#master_use_gtid).
- Available since MariaDB 10.0.2.
choices: [current_pos, replica_pos, disabled]
choices: [current_pos, slave_pos, disabled]
type: str
version_added: '0.1.0'
aliases: [master_use_gtid]
primary_delay:
master_delay:
description:
- Time lag behind the primary's state (in seconds).
- Same as the C(MASTER_DELAY) mysql variable.
- Time lag behind the master's state (in seconds).
- Available from MySQL 5.6.
- For more information see U(https://dev.mysql.com/doc/refman/8.0/en/replication-delayed.html).
type: int
version_added: '0.1.0'
aliases: [master_delay]
connection_name:
description:
- Name of the primary connection.
- Name of the master connection.
- Supported from MariaDB 10.0.1.
- Mutually exclusive with I(channel).
- For more information see U(https://mariadb.com/kb/en/library/multi-source-replication/).
@ -188,102 +143,86 @@ options:
description:
- Fails on error when calling mysql.
type: bool
default: false
default: False
version_added: '0.1.0'
notes:
- Compatible with MariaDB or MySQL.
- If an empty value for the parameter of string type is needed, use an empty string.
attributes:
check_mode:
support: none
- If an empty value for the parameter of string type is needed, use an empty string.
extends_documentation_fragment:
- community.mysql.mysql
seealso:
- module: community.mysql.mysql_info
- name: MySQL replication reference
description: Complete reference of the MySQL replication documentation.
link: https://dev.mysql.com/doc/refman/8.0/en/replication.html
- name: MySQL encrypted replication reference.
description: Setting up MySQL replication to use encrypted connection.
link: https://dev.mysql.com/doc/refman/8.0/en/replication-solutions-encrypted-connections.html
- name: MariaDB replication reference
description: Complete reference of the MariaDB replication documentation.
link: https://mariadb.com/kb/en/library/setting-up-replication/
'''
EXAMPLES = r'''
# If you encounter the "Please explicitly state intended protocol" error,
# use the login_unix_socket argument
- name: Stop mysql replica thread
- name: Stop mysql slave thread
community.mysql.mysql_replication:
mode: stopreplica
login_unix_socket: /run/mysqld/mysqld.sock
mode: stopslave
- name: Get primary binlog file name and binlog position
- name: Get master binlog file name and binlog position
community.mysql.mysql_replication:
mode: getprimary
mode: getmaster
- name: Change primary to primary server 192.0.2.1 and use binary log 'mysql-bin.000009' with position 4578
- name: Change master to master server 192.0.2.1 and use binary log 'mysql-bin.000009' with position 4578
community.mysql.mysql_replication:
mode: changeprimary
primary_host: 192.0.2.1
primary_log_file: mysql-bin.000009
primary_log_pos: 4578
mode: changemaster
master_host: 192.0.2.1
master_log_file: mysql-bin.000009
master_log_pos: 4578
- name: Change replication source to replica server 192.0.2.1 and use binary log 'mysql-bin.000009' with position 4578
- name: Check slave status using port 3308
community.mysql.mysql_replication:
mode: changereplication
primary_host: 192.0.2.1
primary_log_file: mysql-bin.000009
primary_log_pos: 4578
- name: Check replica status using port 3308
community.mysql.mysql_replication:
mode: getreplica
mode: getslave
login_host: ansible.example.com
login_port: 3308
- name: On MariaDB change primary to use GTID current_pos
- name: On MariaDB change master to use GTID current_pos
community.mysql.mysql_replication:
mode: changeprimary
primary_use_gtid: current_pos
mode: changemaster
master_use_gtid: current_pos
- name: Change primary to use replication delay 3600 seconds
- name: Change master to use replication delay 3600 seconds
community.mysql.mysql_replication:
mode: changeprimary
primary_host: 192.0.2.1
primary_delay: 3600
mode: changemaster
master_host: 192.0.2.1
master_delay: 3600
- name: Start MariaDB replica with connection name primary-1
- name: Start MariaDB standby with connection name master-1
community.mysql.mysql_replication:
mode: startreplica
connection_name: primary-1
mode: startslave
connection_name: master-1
- name: Stop replication in channel primary-1
- name: Stop replication in channel master-1
community.mysql.mysql_replication:
mode: stopreplica
channel: primary-1
mode: stopslave
channel: master-1
- name: >
Run RESET MASTER command which will delete all existing binary log files
and reset the binary log index file on the primary
and reset the binary log index file on the master
community.mysql.mysql_replication:
mode: resetprimary
mode: resetmaster
- name: Run start replica and fail the task on errors
- name: Run start slave and fail the task on errors
community.mysql.mysql_replication:
mode: startreplica
connection_name: primary-1
fail_on_error: true
mode: startslave
connection_name: master-1
fail_on_error: yes
- name: Change primary and fail on error (like when replica thread is running)
- name: Change master and fail on error (like when slave thread is running)
community.mysql.mysql_replication:
mode: changeprimary
fail_on_error: true
mode: changemaster
fail_on_error: yes
'''
RETURN = r'''
@ -291,7 +230,7 @@ queries:
description: List of executed queries which modified DB's state.
returned: always
type: list
sample: ["CHANGE MASTER TO MASTER_HOST='primary2.example.com',MASTER_PORT=3306"]
sample: ["CHANGE MASTER TO MASTER_HOST='master2.example.com',MASTER_PORT=3306"]
version_added: '0.1.0'
'''
@ -299,49 +238,37 @@ import os
import warnings
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.mysql.plugins.module_utils.command_resolver import (
CommandResolver
)
from ansible_collections.community.mysql.plugins.module_utils.mysql import (
get_server_version,
get_server_implementation,
mysql_connect,
mysql_driver,
mysql_driver_fail_msg,
mysql_common_argument_spec,
)
from ansible_collections.community.mysql.plugins.module_utils.mysql import mysql_connect, mysql_driver, mysql_driver_fail_msg
from ansible.module_utils._text import to_native
executed_queries = []
def get_primary_status(cursor, command_resolver):
query = command_resolver.resolve_command("SHOW MASTER STATUS")
cursor.execute(query)
primarystatus = cursor.fetchone()
return primarystatus
def get_master_status(cursor):
cursor.execute("SHOW MASTER STATUS")
masterstatus = cursor.fetchone()
return masterstatus
def get_replica_status(cursor, connection_name='', channel='', term='REPLICA'):
def get_slave_status(cursor, connection_name='', channel=''):
if connection_name:
query = "SHOW %s '%s' STATUS" % (term, connection_name)
query = "SHOW SLAVE '%s' STATUS" % connection_name
else:
query = "SHOW %s STATUS" % term
query = "SHOW SLAVE STATUS"
if channel:
query += " FOR CHANNEL '%s'" % channel
cursor.execute(query)
replica_status = cursor.fetchone()
return replica_status
slavestatus = cursor.fetchone()
return slavestatus
def stop_replica(module, cursor, connection_name='', channel='', fail_on_error=False, term='REPLICA'):
def stop_slave(module, cursor, connection_name='', channel='', fail_on_error=False):
if connection_name:
query = "STOP %s '%s'" % (term, connection_name)
query = "STOP SLAVE '%s'" % connection_name
else:
query = 'STOP %s' % term
query = 'STOP SLAVE'
if channel:
query += " FOR CHANNEL '%s'" % channel
@ -354,16 +281,16 @@ def stop_replica(module, cursor, connection_name='', channel='', fail_on_error=F
stopped = False
except Exception as e:
if fail_on_error:
module.fail_json(msg="STOP REPLICA failed: %s" % to_native(e))
module.fail_json(msg="STOP SLAVE failed: %s" % to_native(e))
stopped = False
return stopped
def reset_replica(module, cursor, connection_name='', channel='', fail_on_error=False, term='REPLICA'):
def reset_slave(module, cursor, connection_name='', channel='', fail_on_error=False):
if connection_name:
query = "RESET %s '%s'" % (term, connection_name)
query = "RESET SLAVE '%s'" % connection_name
else:
query = 'RESET %s' % term
query = 'RESET SLAVE'
if channel:
query += " FOR CHANNEL '%s'" % channel
@ -376,16 +303,16 @@ def reset_replica(module, cursor, connection_name='', channel='', fail_on_error=
reset = False
except Exception as e:
if fail_on_error:
module.fail_json(msg="RESET REPLICA failed: %s" % to_native(e))
module.fail_json(msg="RESET SLAVE failed: %s" % to_native(e))
reset = False
return reset
def reset_replica_all(module, cursor, connection_name='', channel='', fail_on_error=False, term='REPLICA'):
def reset_slave_all(module, cursor, connection_name='', channel='', fail_on_error=False):
if connection_name:
query = "RESET %s '%s' ALL" % (term, connection_name)
query = "RESET SLAVE '%s' ALL" % connection_name
else:
query = 'RESET %s ALL' % term
query = 'RESET SLAVE ALL'
if channel:
query += " FOR CHANNEL '%s'" % channel
@ -398,13 +325,13 @@ def reset_replica_all(module, cursor, connection_name='', channel='', fail_on_er
reset = False
except Exception as e:
if fail_on_error:
module.fail_json(msg="RESET REPLICA ALL failed: %s" % to_native(e))
module.fail_json(msg="RESET SLAVE ALL failed: %s" % to_native(e))
reset = False
return reset
def reset_primary(module, cursor, command_resolver, fail_on_error=False):
query = command_resolver.resolve_command('RESET MASTER')
def reset_master(module, cursor, fail_on_error=False):
query = 'RESET MASTER'
try:
executed_queries.append(query)
cursor.execute(query)
@ -413,16 +340,16 @@ def reset_primary(module, cursor, command_resolver, fail_on_error=False):
reset = False
except Exception as e:
if fail_on_error:
module.fail_json(msg="%s failed: %s" % (command_resolver.resolve_command('RESET MASTER'), to_native(e)))
module.fail_json(msg="RESET MASTER failed: %s" % to_native(e))
reset = False
return reset
def start_replica(module, cursor, connection_name='', channel='', fail_on_error=False, term='REPLICA'):
def start_slave(module, cursor, connection_name='', channel='', fail_on_error=False):
if connection_name:
query = "START %s '%s'" % (term, connection_name)
query = "START SLAVE '%s'" % connection_name
else:
query = 'START %s' % term
query = 'START SLAVE'
if channel:
query += " FOR CHANNEL '%s'" % channel
@ -435,27 +362,16 @@ def start_replica(module, cursor, connection_name='', channel='', fail_on_error=
started = False
except Exception as e:
if fail_on_error:
module.fail_json(msg="START REPLICA failed: %s" % to_native(e))
module.fail_json(msg="START SLAVE failed: %s" % to_native(e))
started = False
return started
def changeprimary(cursor, command_resolver, chm, connection_name='', channel=''):
query_head = command_resolver.resolve_command("CHANGE MASTER")
def changemaster(cursor, chm, connection_name='', channel=''):
if connection_name:
query = "%s '%s' TO %s" % (query_head, connection_name, ','.join(chm))
query = "CHANGE MASTER '%s' TO %s" % (connection_name, ','.join(chm))
else:
query = '%s TO %s' % (query_head, ','.join(chm))
if channel:
query += " FOR CHANNEL '%s'" % channel
executed_queries.append(query)
cursor.execute(query)
def changereplication(cursor, chm, channel=''):
query = 'CHANGE REPLICATION SOURCE TO %s' % ','.join(chm)
query = 'CHANGE MASTER TO %s' % ','.join(chm)
if channel:
query += " FOR CHANNEL '%s'" % channel
@ -465,77 +381,74 @@ def changereplication(cursor, chm, channel=''):
def main():
argument_spec = mysql_common_argument_spec()
argument_spec.update(
mode=dict(type='str', default='getreplica', choices=[
'getprimary',
'getreplica',
'changeprimary',
'stopreplica',
'startreplica',
'resetprimary',
'resetreplica',
'resetreplicaall',
'changereplication']),
primary_auto_position=dict(type='bool', default=False, aliases=['master_auto_position']),
primary_host=dict(type='str', aliases=['master_host']),
primary_user=dict(type='str', aliases=['master_user']),
primary_password=dict(type='str', no_log=True, aliases=['master_password']),
primary_port=dict(type='int', aliases=['master_port']),
primary_connect_retry=dict(type='int', aliases=['master_connect_retry']),
primary_log_file=dict(type='str', aliases=['master_log_file']),
primary_log_pos=dict(type='int', aliases=['master_log_pos']),
relay_log_file=dict(type='str'),
relay_log_pos=dict(type='int'),
primary_ssl=dict(type='bool', aliases=['master_ssl']),
primary_ssl_ca=dict(type='str', aliases=['master_ssl_ca']),
primary_ssl_capath=dict(type='str', aliases=['master_ssl_capath']),
primary_ssl_cert=dict(type='str', aliases=['master_ssl_cert']),
primary_ssl_key=dict(type='str', no_log=False, aliases=['master_ssl_key']),
primary_ssl_cipher=dict(type='str', aliases=['master_ssl_cipher']),
primary_ssl_verify_server_cert=dict(type='bool', default=False),
primary_use_gtid=dict(type='str', choices=[
'current_pos', 'replica_pos', 'disabled'], aliases=['master_use_gtid']),
primary_delay=dict(type='int', aliases=['master_delay']),
connection_name=dict(type='str'),
channel=dict(type='str'),
fail_on_error=dict(type='bool', default=False),
)
module = AnsibleModule(
argument_spec=argument_spec,
argument_spec=dict(
login_user=dict(type='str'),
login_password=dict(type='str', no_log=True),
login_host=dict(type='str', default='localhost'),
login_port=dict(type='int', default=3306),
login_unix_socket=dict(type='str'),
mode=dict(type='str', default='getslave', choices=[
'getmaster', 'getslave', 'changemaster', 'stopslave',
'startslave', 'resetmaster', 'resetslave', 'resetslaveall']),
master_auto_position=dict(type='bool', default=False),
master_host=dict(type='str'),
master_user=dict(type='str'),
master_password=dict(type='str', no_log=True),
master_port=dict(type='int'),
master_connect_retry=dict(type='int'),
master_log_file=dict(type='str'),
master_log_pos=dict(type='int'),
relay_log_file=dict(type='str'),
relay_log_pos=dict(type='int'),
master_ssl=dict(type='bool', default=False),
master_ssl_ca=dict(type='str'),
master_ssl_capath=dict(type='str'),
master_ssl_cert=dict(type='str'),
master_ssl_key=dict(type='str'),
master_ssl_cipher=dict(type='str'),
connect_timeout=dict(type='int', default=30),
config_file=dict(type='path', default='~/.my.cnf'),
client_cert=dict(type='path', aliases=['ssl_cert']),
client_key=dict(type='path', aliases=['ssl_key']),
ca_cert=dict(type='path', aliases=['ssl_ca']),
master_use_gtid=dict(type='str', choices=['current_pos', 'slave_pos', 'disabled']),
master_delay=dict(type='int'),
connection_name=dict(type='str'),
channel=dict(type='str'),
fail_on_error=dict(type='bool', default=False),
),
mutually_exclusive=[
['connection_name', 'channel']
],
)
mode = module.params["mode"]
primary_host = module.params["primary_host"]
primary_user = module.params["primary_user"]
primary_password = module.params["primary_password"]
primary_port = module.params["primary_port"]
primary_connect_retry = module.params["primary_connect_retry"]
primary_log_file = module.params["primary_log_file"]
primary_log_pos = module.params["primary_log_pos"]
master_host = module.params["master_host"]
master_user = module.params["master_user"]
master_password = module.params["master_password"]
master_port = module.params["master_port"]
master_connect_retry = module.params["master_connect_retry"]
master_log_file = module.params["master_log_file"]
master_log_pos = module.params["master_log_pos"]
relay_log_file = module.params["relay_log_file"]
relay_log_pos = module.params["relay_log_pos"]
primary_ssl = module.params["primary_ssl"]
primary_ssl_ca = module.params["primary_ssl_ca"]
primary_ssl_capath = module.params["primary_ssl_capath"]
primary_ssl_cert = module.params["primary_ssl_cert"]
primary_ssl_key = module.params["primary_ssl_key"]
primary_ssl_cipher = module.params["primary_ssl_cipher"]
primary_ssl_verify_server_cert = module.params["primary_ssl_verify_server_cert"]
primary_auto_position = module.params["primary_auto_position"]
master_ssl = module.params["master_ssl"]
master_ssl_ca = module.params["master_ssl_ca"]
master_ssl_capath = module.params["master_ssl_capath"]
master_ssl_cert = module.params["master_ssl_cert"]
master_ssl_key = module.params["master_ssl_key"]
master_ssl_cipher = module.params["master_ssl_cipher"]
master_auto_position = module.params["master_auto_position"]
ssl_cert = module.params["client_cert"]
ssl_key = module.params["client_key"]
ssl_ca = module.params["ca_cert"]
check_hostname = module.params["check_hostname"]
connect_timeout = module.params['connect_timeout']
config_file = module.params['config_file']
primary_delay = module.params['primary_delay']
if module.params.get("primary_use_gtid") == 'disabled':
primary_use_gtid = 'no'
master_delay = module.params['master_delay']
if module.params.get("master_use_gtid") == 'disabled':
master_use_gtid = 'no'
else:
primary_use_gtid = module.params["primary_use_gtid"]
master_use_gtid = module.params["master_use_gtid"]
connection_name = module.params["connection_name"]
channel = module.params['channel']
fail_on_error = module.params['fail_on_error']
@ -551,190 +464,107 @@ def main():
try:
cursor, db_conn = mysql_connect(module, login_user, login_password, config_file,
ssl_cert, ssl_key, ssl_ca, None, cursor_class='DictCursor',
connect_timeout=connect_timeout, check_hostname=check_hostname)
connect_timeout=connect_timeout)
except Exception as e:
if os.path.exists(config_file):
module.fail_json(msg="unable to connect to database, check login_user and "
"login_password are correct or %s has the credentials. "
module.fail_json(msg="unable to connect to database, check login_user and login_password are correct or %s has the credentials. "
"Exception message: %s" % (config_file, to_native(e)))
else:
module.fail_json(msg="unable to find %s. Exception message: %s" % (config_file, to_native(e)))
server_version = get_server_version(cursor)
server_implementation = get_server_implementation(cursor)
command_resolver = CommandResolver(server_implementation, server_version)
cursor.execute("SELECT VERSION()")
if server_implementation == 'mariadb':
from ansible_collections.community.mysql.plugins.module_utils.implementations.mariadb import replication as impl
else:
from ansible_collections.community.mysql.plugins.module_utils.implementations.mysql import replication as impl
# Since MySQL 8.0.22 and MariaDB 10.5.1,
# "REPLICA" must be used instead of "SLAVE"
if impl.uses_replica_terminology(cursor):
replica_term = 'REPLICA'
else:
replica_term = 'SLAVE'
if primary_use_gtid == 'replica_pos':
primary_use_gtid = 'slave_pos'
if mode == 'getprimary':
status = get_primary_status(cursor, command_resolver)
if status and "File" in status and "Position" in status:
status['Is_Primary'] = True
if mode in "getmaster":
status = get_master_status(cursor)
if not isinstance(status, dict):
status = dict(Is_Master=False, msg="Server is not configured as mysql master")
else:
status = dict(
Is_Primary=False,
msg="Server is not configured as mysql primary. "
"Meaning: Binary logs are disabled")
status['Is_Master'] = True
module.exit_json(queries=executed_queries, **status)
elif mode == "getreplica":
status = get_replica_status(cursor, connection_name, channel, replica_term)
# MySQL 8.0 uses Replica_...
# MariaDB 10.6 uses Slave_...
if status and (
"Slave_IO_Running" in status or
"Replica_IO_Running" in status):
status['Is_Replica'] = True
elif mode in "getslave":
status = get_slave_status(cursor, connection_name, channel)
if not isinstance(status, dict):
status = dict(Is_Slave=False, msg="Server is not configured as mysql slave")
else:
status = dict(Is_Replica=False, msg="Server is not configured as mysql replica")
status['Is_Slave'] = True
module.exit_json(queries=executed_queries, **status)
elif mode == 'changeprimary':
elif mode in "changemaster":
chm = []
result = {}
if primary_host is not None:
chm.append("%s='%s'" % (command_resolver.resolve_command('MASTER_HOST'), primary_host))
if primary_user is not None:
chm.append("%s='%s'" % (command_resolver.resolve_command('MASTER_USER'), primary_user))
if primary_password is not None:
chm.append("%s='%s'" % (command_resolver.resolve_command('MASTER_PASSWORD'), primary_password))
if primary_port is not None:
chm.append("%s=%s" % (command_resolver.resolve_command('MASTER_PORT'), primary_port))
if primary_connect_retry is not None:
chm.append("%s=%s" % (command_resolver.resolve_command('MASTER_CONNECT_RETRY'), primary_connect_retry))
if primary_log_file is not None:
chm.append("%s='%s'" % (command_resolver.resolve_command('MASTER_LOG_FILE'), primary_log_file))
if primary_log_pos is not None:
chm.append("%s=%s" % (command_resolver.resolve_command('MASTER_LOG_POS'), primary_log_pos))
if primary_delay is not None:
chm.append("%s=%s" % (command_resolver.resolve_command('MASTER_DELAY'), primary_delay))
if master_host is not None:
chm.append("MASTER_HOST='%s'" % master_host)
if master_user is not None:
chm.append("MASTER_USER='%s'" % master_user)
if master_password is not None:
chm.append("MASTER_PASSWORD='%s'" % master_password)
if master_port is not None:
chm.append("MASTER_PORT=%s" % master_port)
if master_connect_retry is not None:
chm.append("MASTER_CONNECT_RETRY=%s" % master_connect_retry)
if master_log_file is not None:
chm.append("MASTER_LOG_FILE='%s'" % master_log_file)
if master_log_pos is not None:
chm.append("MASTER_LOG_POS=%s" % master_log_pos)
if master_delay is not None:
chm.append("MASTER_DELAY=%s" % master_delay)
if relay_log_file is not None:
chm.append("RELAY_LOG_FILE='%s'" % relay_log_file)
if relay_log_pos is not None:
chm.append("RELAY_LOG_POS=%s" % relay_log_pos)
if primary_ssl is not None:
if primary_ssl:
chm.append("%s=1" % command_resolver.resolve_command('MASTER_SSL'))
else:
chm.append("%s=0" % command_resolver.resolve_command('MASTER_SSL'))
if primary_ssl_ca is not None:
chm.append("%s='%s'" % (command_resolver.resolve_command('MASTER_SSL_CA'), primary_ssl_ca))
if primary_ssl_capath is not None:
chm.append("%s='%s'" % (command_resolver.resolve_command('MASTER_SSL_CAPATH'), primary_ssl_capath))
if primary_ssl_cert is not None:
chm.append("%s='%s'" % (command_resolver.resolve_command('MASTER_SSL_CERT'), primary_ssl_cert))
if primary_ssl_key is not None:
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_auto_position:
chm.append("%s=1" % command_resolver.resolve_command('MASTER_AUTO_POSITION'))
if primary_use_gtid is not None:
chm.append("MASTER_USE_GTID=%s" % primary_use_gtid) # MariaDB only
if master_ssl:
chm.append("MASTER_SSL=1")
if master_ssl_ca is not None:
chm.append("MASTER_SSL_CA='%s'" % master_ssl_ca)
if master_ssl_capath is not None:
chm.append("MASTER_SSL_CAPATH='%s'" % master_ssl_capath)
if master_ssl_cert is not None:
chm.append("MASTER_SSL_CERT='%s'" % master_ssl_cert)
if master_ssl_key is not None:
chm.append("MASTER_SSL_KEY='%s'" % master_ssl_key)
if master_ssl_cipher is not None:
chm.append("MASTER_SSL_CIPHER='%s'" % master_ssl_cipher)
if master_auto_position:
chm.append("MASTER_AUTO_POSITION=1")
if master_use_gtid is not None:
chm.append("MASTER_USE_GTID=%s" % master_use_gtid)
try:
changeprimary(cursor, command_resolver, chm, connection_name, channel)
changemaster(cursor, chm, connection_name, channel)
except mysql_driver.Warning as e:
result['warning'] = to_native(e)
except Exception as e:
module.fail_json(msg='%s. Query == %s TO %s' % (to_native(e), command_resolver.resolve_command('CHANGE MASTER'), chm))
module.fail_json(msg='%s. Query == CHANGE MASTER TO %s' % (to_native(e), chm))
result['changed'] = True
module.exit_json(queries=executed_queries, **result)
elif mode == "startreplica":
started = start_replica(module, cursor, connection_name, channel, fail_on_error, replica_term)
elif mode in "startslave":
started = start_slave(module, cursor, connection_name, channel, fail_on_error)
if started is True:
module.exit_json(msg="Replica started ", changed=True, queries=executed_queries)
module.exit_json(msg="Slave started ", changed=True, queries=executed_queries)
else:
module.exit_json(msg="Replica already started (Or cannot be started)", changed=False, queries=executed_queries)
elif mode == "stopreplica":
stopped = stop_replica(module, cursor, connection_name, channel, fail_on_error, replica_term)
module.exit_json(msg="Slave already started (Or cannot be started)", changed=False, queries=executed_queries)
elif mode in "stopslave":
stopped = stop_slave(module, cursor, connection_name, channel, fail_on_error)
if stopped is True:
module.exit_json(msg="Replica stopped", changed=True, queries=executed_queries)
module.exit_json(msg="Slave stopped", changed=True, queries=executed_queries)
else:
module.exit_json(msg="Replica already stopped", changed=False, queries=executed_queries)
elif mode == 'resetprimary':
reset = reset_primary(module, cursor, command_resolver, fail_on_error)
module.exit_json(msg="Slave already stopped", changed=False, queries=executed_queries)
elif mode in "resetmaster":
reset = reset_master(module, cursor, fail_on_error)
if reset is True:
module.exit_json(msg="Primary reset", changed=True, queries=executed_queries)
module.exit_json(msg="Master reset", changed=True, queries=executed_queries)
else:
module.exit_json(msg="Primary already reset", changed=False, queries=executed_queries)
elif mode == "resetreplica":
reset = reset_replica(module, cursor, connection_name, channel, fail_on_error, replica_term)
module.exit_json(msg="Master already reset", changed=False, queries=executed_queries)
elif mode in "resetslave":
reset = reset_slave(module, cursor, connection_name, channel, fail_on_error)
if reset is True:
module.exit_json(msg="Replica reset", changed=True, queries=executed_queries)
module.exit_json(msg="Slave reset", changed=True, queries=executed_queries)
else:
module.exit_json(msg="Replica already reset", changed=False, queries=executed_queries)
elif mode == "resetreplicaall":
reset = reset_replica_all(module, cursor, connection_name, channel, fail_on_error, replica_term)
module.exit_json(msg="Slave already reset", changed=False, queries=executed_queries)
elif mode in "resetslaveall":
reset = reset_slave_all(module, cursor, connection_name, channel, fail_on_error)
if reset is True:
module.exit_json(msg="Replica reset", changed=True, queries=executed_queries)
module.exit_json(msg="Slave reset", changed=True, queries=executed_queries)
else:
module.exit_json(msg="Replica already reset", changed=False, queries=executed_queries)
elif mode == 'changereplication':
chm = []
result = {}
if primary_host is not None:
chm.append("SOURCE_HOST='%s'" % primary_host)
if primary_user is not None:
chm.append("SOURCE_USER='%s'" % primary_user)
if primary_password is not None:
chm.append("SOURCE_PASSWORD='%s'" % primary_password)
if primary_port is not None:
chm.append("SOURCE_PORT=%s" % primary_port)
if primary_connect_retry is not None:
chm.append("SOURCE_CONNECT_RETRY=%s" % primary_connect_retry)
if primary_log_file is not None:
chm.append("SOURCE_LOG_FILE='%s'" % primary_log_file)
if primary_log_pos is not None:
chm.append("SOURCE_LOG_POS=%s" % primary_log_pos)
if primary_delay is not None:
chm.append("SOURCE_DELAY=%s" % primary_delay)
if relay_log_file is not None:
chm.append("RELAY_LOG_FILE='%s'" % relay_log_file)
if relay_log_pos is not None:
chm.append("RELAY_LOG_POS=%s" % relay_log_pos)
if primary_ssl is not None:
if primary_ssl:
chm.append("SOURCE_SSL=1")
else:
chm.append("SOURCE_SSL=0")
if primary_ssl_ca is not None:
chm.append("SOURCE_SSL_CA='%s'" % primary_ssl_ca)
if primary_ssl_capath is not None:
chm.append("SOURCE_SSL_CAPATH='%s'" % primary_ssl_capath)
if primary_ssl_cert is not None:
chm.append("SOURCE_SSL_CERT='%s'" % primary_ssl_cert)
if primary_ssl_key is not None:
chm.append("SOURCE_SSL_KEY='%s'" % primary_ssl_key)
if primary_ssl_cipher is not None:
chm.append("SOURCE_SSL_CIPHER='%s'" % primary_ssl_cipher)
if primary_ssl_verify_server_cert:
chm.append("SOURCE_SSL_VERIFY_SERVER_CERT=1")
if primary_auto_position:
chm.append("SOURCE_AUTO_POSITION=1")
try:
changereplication(cursor, chm, channel)
except mysql_driver.Warning as e:
result['warning'] = to_native(e)
except Exception as e:
module.fail_json(msg='%s. Query == CHANGE REPLICATION SOURCE TO %s' % (to_native(e), chm))
result['changed'] = True
module.exit_json(queries=executed_queries, **result)
module.exit_json(msg="Slave already reset", changed=False, queries=executed_queries)
warnings.simplefilter("ignore")

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -12,21 +12,20 @@ DOCUMENTATION = r'''
---
module: mysql_variables
short_description: Manage MySQL or MariaDB global variables
short_description: Manage MySQL global variables
description:
- Query / Set MySQL or MariaDB variables.
- Query / Set MySQL variables.
author:
- Balazs Pocze (@banyek)
options:
variable:
description:
- Variable name to operate.
- Variable name to operate
type: str
required: true
required: yes
value:
description:
- If set, then sets variable value to this.
- With boolean values, use C(0)|C(1) or quoted C("ON")|C("OFF").
- If set, then sets variable value to this
type: str
mode:
description:
@ -45,41 +44,27 @@ options:
default: global
version_added: '0.1.0'
attributes:
check_mode:
support: none
seealso:
- module: community.mysql.mysql_info
- name: MySQL SET command reference
description: Complete reference of the MySQL SET command documentation.
link: https://dev.mysql.com/doc/refman/8.0/en/set-statement.html
notes:
- Compatible with MariaDB or MySQL.
extends_documentation_fragment:
- community.mysql.mysql
'''
EXAMPLES = r'''
# If you encounter the "Please explicitly state intended protocol" error,
# use the login_unix_socket argument
- name: Check for sync_binlog setting
community.mysql.mysql_variables:
variable: sync_binlog
login_unix_socket: /run/mysqld/mysqld.sock
- name: Set read_only variable to 1 persistently
community.mysql.mysql_variables:
variable: read_only
value: 1
mode: persist
- name: Set a boolean using ON/OFF notation
mysql_variables:
variable: log_slow_replica_statements
value: "ON" # Make sure it's quoted
'''
RETURN = r'''
@ -97,7 +82,7 @@ from re import match
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.mysql.plugins.module_utils.database import SQLParseError, mysql_quote_identifier
from ansible_collections.community.mysql.plugins.module_utils.mysql import mysql_connect, mysql_driver, mysql_driver_fail_msg, mysql_common_argument_spec
from ansible_collections.community.mysql.plugins.module_utils.mysql import mysql_connect, mysql_driver, mysql_driver_fail_msg
from ansible.module_utils._text import to_native
executed_queries = []
@ -182,28 +167,23 @@ def setvariable(cursor, mysqlvar, value, mode='global'):
return result
def convert_bool_setting_value_wanted(val):
"""Converts passed value from 0,1,on,off to ON/OFF
as it's represented in the server.
"""
if val in ('on', 1):
val = 'ON'
elif val in ('off', 0):
val = 'OFF'
return val
def main():
argument_spec = mysql_common_argument_spec()
argument_spec.update(
variable=dict(type='str', required=True),
value=dict(type='str'),
mode=dict(type='str', choices=['global', 'persist', 'persist_only'], default='global'),
)
module = AnsibleModule(
argument_spec=argument_spec
argument_spec=dict(
login_user=dict(type='str'),
login_password=dict(type='str', no_log=True),
login_host=dict(type='str', default='localhost'),
login_port=dict(type='int', default=3306),
login_unix_socket=dict(type='str'),
variable=dict(type='str'),
value=dict(type='str'),
client_cert=dict(type='path', aliases=['ssl_cert']),
client_key=dict(type='path', aliases=['ssl_key']),
ca_cert=dict(type='path', aliases=['ssl_ca']),
connect_timeout=dict(type='int', default=30),
config_file=dict(type='path', default='~/.my.cnf'),
mode=dict(type='str', choices=['global', 'persist', 'persist_only'], default='global'),
),
)
user = module.params["login_user"]
password = module.params["login_password"]
@ -211,7 +191,6 @@ def main():
ssl_cert = module.params["client_cert"]
ssl_key = module.params["client_key"]
ssl_ca = module.params["ca_cert"]
check_hostname = module.params["check_hostname"]
config_file = module.params['config_file']
db = 'mysql'
@ -221,7 +200,7 @@ def main():
if mysqlvar is None:
module.fail_json(msg="Cannot run without variable to operate with")
if match('^[0-9A-Za-z_.]+$', mysqlvar) is None:
if match('^[0-9a-z_.]+$', mysqlvar) is None:
module.fail_json(msg="invalid variable name \"%s\"" % mysqlvar)
if mysql_driver is None:
module.fail_json(msg=mysql_driver_fail_msg)
@ -230,7 +209,7 @@ def main():
try:
cursor, db_conn = mysql_connect(module, user, password, config_file, ssl_cert, ssl_key, ssl_ca, db,
connect_timeout=connect_timeout, check_hostname=check_hostname)
connect_timeout=connect_timeout)
except Exception as e:
if os.path.exists(config_file):
module.fail_json(msg=("unable to connect to database, check login_user and "
@ -261,9 +240,6 @@ def main():
# Type values before using them
value_wanted = typedvalue(value)
value_actual = typedvalue(mysqlvar_val)
if value_actual in ('ON', 'OFF') and value_wanted not in ('ON', 'OFF'):
value_wanted = convert_bool_setting_value_wanted(value_wanted)
value_in_auto_cnf = None
if var_in_mysqld_auto_cnf is not None:
value_in_auto_cnf = typedvalue(var_in_mysqld_auto_cnf)

View file

@ -1,115 +0,0 @@
#!/usr/bin/env python
import yaml
import os
github_workflow_file = '.github/workflows/ansible-test-plugins.yml'
def read_github_workflow_file():
with open(github_workflow_file, 'r') as gh_file:
try:
return yaml.safe_load(gh_file)
except yaml.YAMLError as exc:
print(exc)
def extract_value(target, dict_yaml):
for key, value in dict_yaml.items():
if key == target:
return value
def extract_matrix(workflow_yaml):
jobs = extract_value('jobs', workflow_yaml)
integration = extract_value('integration', jobs)
strategy = extract_value('strategy', integration)
matrix = extract_value('matrix', strategy)
return matrix
def is_exclude(exclude_list, test_suite):
test_is_excluded = False
for excl in exclude_list:
match = 0
if 'ansible' in excl:
if excl.get('ansible') == test_suite.get('ansible'):
match += 1
if 'db_engine_name' in excl:
if excl.get('db_engine_name') == test_suite.get('db_engine_name'):
match += 1
if 'db_engine_version' in excl:
if excl.get('db_engine_version') == test_suite.get('db_engine_version'):
match += 1
if 'python' in excl:
if excl.get('python') == test_suite.get('python'):
match += 1
if 'connector_name' in excl:
if excl.get('connector_name') == test_suite.get('connector_name'):
match += 1
if 'connector_version' in excl:
if excl.get('connector_version') == test_suite.get('connector_version'):
match += 1
if match > 1:
test_is_excluded = True
return test_is_excluded
return test_is_excluded
def main():
workflow_yaml = read_github_workflow_file()
tests_matrix_yaml = extract_matrix(workflow_yaml)
matrix = []
exclude_list = tests_matrix_yaml.get('exclude')
for ansible in tests_matrix_yaml.get('ansible'):
for db_engine_name in tests_matrix_yaml.get('db_engine_name'):
for db_engine_version in tests_matrix_yaml.get('db_engine_version'):
for python in tests_matrix_yaml.get('python'):
for connector_name in tests_matrix_yaml.get('connector_name'):
for connector_version in tests_matrix_yaml.get('connector_version'):
test_suite = {
'ansible': ansible,
'db_engine_name': db_engine_name,
'db_engine_version': db_engine_version,
'python': python,
'connector_name': connector_name,
'connector_version': connector_version
}
if not is_exclude(exclude_list, test_suite):
matrix.append(test_suite)
for tests in matrix:
a = tests.get('ansible')
dn = tests.get('db_engine_name')
dv = tests.get('db_engine_version')
p = tests.get('python')
cn = tests.get('connector_name')
cv = tests.get('connector_version')
make_cmd = (
f'make '
f'ansible="{a}" '
f'db_engine_name="{dn}" '
f'db_engine_version="{dv}" '
f'python="{p}" '
f'connector_name="{cn}" '
f'connector_version="{cv}" '
f'test-integration'
)
print(f'Run tests for: Ansible: {a}, DB: {dn} {dv}, Python: {p}, Connector: {cn} {cv}')
os.system(make_cmd)
# TODO, allow for CTRL+C to break the loop more easily
# TODO, store the failures from this iteration
# TODO, display a summary of failures from every iterations
if __name__ == '__main__':
main()

View file

@ -1,8 +0,0 @@
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -1,5 +1,3 @@
---
mysql_host: "{{ gateway_addr }}"
master_port: 3306
standby_port: 3307
test_db: test_db

View file

@ -1,4 +1,4 @@
# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) <andrew.a.klychkov@gmail.com>
# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) <aaklychkov@mail.ru>
# 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

View file

@ -1,4 +1,4 @@
# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) <andrew.a.klychkov@gmail.com>
# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) <aaklychkov@mail.ru>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# Tests for master_use_gtid parameter.
@ -11,7 +11,7 @@
# Auxiliary step:
- name: Get master status
mysql_replication:
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: "{{ primary_db.port }}"
mode: getmaster
register: primary_status
@ -19,10 +19,10 @@
# Set master_use_gtid disabled:
- name: Run replication
mysql_replication:
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: "{{ replica_db.port }}"
mode: changemaster
master_host: '{{ mysql_host }}'
master_host: 127.0.0.1
master_port: "{{ primary_db.port }}"
master_user: "{{ replication_user }}"
master_password: "{{ replication_pass }}"
@ -38,13 +38,13 @@
# Start standby for further tests:
- name: Start standby
mysql_replication:
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: "{{ primary_db.port }}"
mode: startslave
- name: Get standby status
mysql_replication:
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: "{{ replica_db.port }}"
mode: getslave
register: slave_status
@ -56,7 +56,7 @@
# Stop standby for further tests:
- name: Stop standby
mysql_replication:
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: "{{ replica_db.port }}"
mode: stopslave
@ -67,7 +67,7 @@
# Auxiliary step:
- name: Get master status
mysql_replication:
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: "{{ primary_db.port }}"
mode: getmaster
register: primary_status
@ -75,10 +75,10 @@
# Set master_use_gtid current_pos:
- name: Run replication
mysql_replication:
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: "{{ replica_db.port }}"
mode: changemaster
master_host: '{{ mysql_host }}'
master_host: 127.0.0.1
master_port: "{{ primary_db.port }}"
master_user: "{{ replication_user }}"
master_password: "{{ replication_pass }}"
@ -94,13 +94,13 @@
# Start standby for further tests:
- name: Start standby
mysql_replication:
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: "{{ primary_db.port }}"
mode: startslave
- name: Get standby status
mysql_replication:
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: "{{ replica_db.port }}"
mode: getslave
register: slave_status
@ -112,7 +112,7 @@
# Stop standby for further tests:
- name: Stop standby
mysql_replication:
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: "{{ replica_db.port }}"
mode: stopslave
@ -123,7 +123,7 @@
# Auxiliary step:
- name: Get master status
mysql_replication:
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: "{{ primary_db.port }}"
mode: getmaster
register: primary_status
@ -131,10 +131,10 @@
# Set master_use_gtid slave_pos:
- name: Run replication
mysql_replication:
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: "{{ replica_db.port }}"
mode: changemaster
master_host: '{{ mysql_host }}'
master_host: 127.0.0.1
master_port: "{{ primary_db.port }}"
master_user: "{{ replication_user }}"
master_password: "{{ replication_pass }}"
@ -150,13 +150,13 @@
# Start standby for further tests:
- name: Start standby
mysql_replication:
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: "{{ primary_db.port }}"
mode: startslave
- name: Get standby status
mysql_replication:
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: "{{ replica_db.port }}"
mode: getslave
register: slave_status
@ -168,6 +168,6 @@
# Stop standby for further tests:
- name: Stop standby
mysql_replication:
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: "{{ replica_db.port }}"
mode: stopslave

View file

@ -1,23 +1,23 @@
# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) <andrew.a.klychkov@gmail.com>
# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) <aaklychkov@mail.ru>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# Needs for further tests:
- name: Stop slave
mysql_replication:
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: "{{ replica_db.port }}"
mode: stopslave
- name: Reset slave all
mysql_replication:
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: "{{ replica_db.port }}"
mode: resetslaveall
# Get master log pos:
- name: Get master status
mysql_replication:
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: "{{ primary_db.port }}"
mode: getmaster
register: primary_status
@ -25,10 +25,10 @@
# Test changemaster mode:
- name: Run replication with connection_name
mysql_replication:
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: "{{ replica_db.port }}"
mode: changemaster
master_host: '{{ mysql_host }}'
master_host: 127.0.0.1
master_port: "{{ primary_db.port }}"
master_user: "{{ replication_user }}"
master_password: "{{ replication_pass }}"
@ -45,7 +45,7 @@
# Test startslave mode:
- name: Start slave with connection_name
mysql_replication:
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: "{{ replica_db.port }}"
mode: startslave
connection_name: "{{ conn_name }}"
@ -59,7 +59,7 @@
# Test getslave mode:
- name: Get standby statu with connection_name
mysql_replication:
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: "{{ replica_db.port }}"
mode: getslave
connection_name: "{{ conn_name }}"
@ -68,7 +68,7 @@
- assert:
that:
- slave_status.Is_Slave == true
- slave_status.Master_Host == ''{{ mysql_host }}''
- slave_status.Master_Host == '127.0.0.1'
- slave_status.Exec_Master_Log_Pos == primary_status.Position
- slave_status.Master_Port == {{ primary_db.port }}
- slave_status.Last_IO_Errno == 0
@ -78,7 +78,7 @@
# Test stopslave mode:
- name: Stop slave with connection_name
mysql_replication:
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: "{{ replica_db.port }}"
mode: stopslave
connection_name: "{{ conn_name }}"
@ -92,7 +92,7 @@
# Test reset
- name: Reset slave with connection_name
mysql_replication:
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: "{{ replica_db.port }}"
mode: resetslave
connection_name: "{{ conn_name }}"
@ -106,7 +106,7 @@
# Test reset all
- name: Reset slave all with connection_name
mysql_replication:
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: "{{ replica_db.port }}"
mode: resetslaveall
connection_name: "{{ conn_name }}"

View file

@ -1,13 +1,13 @@
# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) <andrew.a.klychkov@gmail.com>
# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) <aaklychkov@mail.ru>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# Preparation:
- name: Create user for replication
shell: "echo \"GRANT REPLICATION SLAVE ON *.* TO '{{ replication_user }}'@'localhost' IDENTIFIED BY '{{ replication_pass }}'; FLUSH PRIVILEGES;\" | mysql -P {{ primary_db.port }} -h '{{ mysql_host }}'"
shell: "echo \"GRANT REPLICATION SLAVE ON *.* TO '{{ replication_user }}'@'localhost' IDENTIFIED BY '{{ replication_pass }}'; FLUSH PRIVILEGES;\" | mysql -P {{ primary_db.port }} -h 127.0.0.1"
- name: Create test database
mysql_db:
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: '{{ primary_db.port }}'
state: present
name: '{{ test_db }}'
@ -16,12 +16,12 @@
shell: 'mysqldump -P {{ primary_db.port }} -h 127.0.01 --all-databases --master-data=2 > {{ dump_path }}'
- name: Restore the dump to the replica
shell: "mysql -P {{ replica_db.port }} -h '{{ mysql_host }}' < {{ dump_path }}"
shell: 'mysql -P {{ replica_db.port }} -h 127.0.0.1 < {{ dump_path }}'
# Test getmaster mode:
- name: Get master status
mysql_replication:
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: "{{ primary_db.port }}"
mode: getmaster
register: master_status
@ -35,10 +35,10 @@
# Test changemaster mode:
- name: Run replication
mysql_replication:
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: "{{ replica_db.port }}"
mode: changemaster
master_host: '{{ mysql_host }}'
master_host: 127.0.0.1
master_port: "{{ primary_db.port }}"
master_user: "{{ replication_user }}"
master_password: "{{ replication_pass }}"
@ -54,7 +54,7 @@
# Test startslave mode:
- name: Start slave
mysql_replication:
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: "{{ replica_db.port }}"
mode: startslave
register: result
@ -67,7 +67,7 @@
# Test getslave mode:
- name: Get replica status
mysql_replication:
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: "{{ replica_db.port }}"
mode: getslave
register: slave_status
@ -75,7 +75,7 @@
- assert:
that:
- slave_status.Is_Slave == true
- slave_status.Master_Host == ''{{ mysql_host }}''
- slave_status.Master_Host == '127.0.0.1'
- slave_status.Exec_Master_Log_Pos == master_status.Position
- slave_status.Master_Port == {{ primary_db.port }}
- slave_status.Last_IO_Errno == 0
@ -85,7 +85,7 @@
# Test stopslave mode:
- name: Stop slave
mysql_replication:
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: "{{ replica_db.port }}"
mode: stopslave
register: result

View file

@ -1,49 +0,0 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: SKS 1.1.6
Comment: Hostname: pgp.mit.edu
mQINBGU2rNoBEACSi5t0nL6/Hj3d0PwsbdnbY+SqLUIZ3uWZQm6tsNhvTnahvPPZBGdl99iW
YTt2KmXp0KeN2s9pmLKkGAbacQP1RqzMFnoHawSMf0qTUVjAvhnI4+qzMDjTNSBq9fa3nHmO
YxownnrRkpiQUM/yD7/JmVENgwWb6akZeGYrXch9jd4XV3t8OD6TGzTedTki0TDNr6YZYhC7
jUm9fK9Zs299pzOXSxRRNGd+3H9gbXizrBu4L/3lUrNf//rM7OvV9Ho7u9YYyAQ3L3+OABK9
FKHNhrpi8Q0cbhvWkD4oCKJ+YZ54XrOG0YTg/YUAs5/3//FATI1sWdtLjJ5pSb0onV3LIbar
RTN8lC4Le/5kd3lcot9J8b3EMXL5p9OGW7wBfmNVRSUI74Vmwt+v9gyp0Hd0keRCUn8lo/1V
0YD9i92KsE+/IqoYTjnya/5kX41jB8vr1ebkHFuJ404+G6ETd0owwxq64jLIcsp/GBZHGU0R
KKAo9DRLH7rpQ7PVlnw8TDNlOtWt5EJlBXFcPL+NgWbqkADAyA/XSNeWlqonvPlYfmasnAHA
pMd9NhPQhC7hJTjCiAwG8UyWpV8Dj07DHFQ5xBbkTnKH2OrJtguPqSNYtTASbsWz09S8ujoT
DXFT17NbFM2dMIiq0a4VQB3SzH13H2io9Cbg/TzJrJGmwgoXgwARAQABtDZNeVNRTCBSZWxl
YXNlIEVuZ2luZWVyaW5nIDxteXNxbC1idWlsZEBvc3Mub3JhY2xlLmNvbT6JAlQEEwEIAD4W
IQS8pDQXw7SF3RKOxtS3s7eIqNN4XAUCZTas2gIbAwUJA8JnAAULCQgHAgYVCgkICwIEFgID
AQIeAQIXgAAKCRC3s7eIqNN4XLzoD/9PlpWtfHlI8eQTHwGsGIwFA+fgipyDElapHw3MO+K9
VOEYRZCZSuBXHJe9kjGEVCGUDrfImvgTuNuqYmVUV+wyhP+w46W/cWVkqZKAW0hNp0TTvu3e
Dwap7gdk80VF24Y2Wo0bbiGkpPiPmB59oybGKaJ756JlKXIL4hTtK3/hjIPFnb64Ewe4YLZy
oJu0fQOyA8gXuBoalHhUQTbRpXI0XI3tpZiQemNbfBfJqXo6LP3/LgChAuOfHIQ8alvnhCwx
hNUSYGIRqx+BEbJw1X99Az8XvGcZ36VOQAZztkW7mEfH9NDPz7MXwoEvduc61xwlMvEsUIaS
fn6SGLFzWPClA98UMSJgF6sKb+JNoNbzKaZ8V5w13msLb/pq7hab72HH99XJbyKNliYj3+KA
3q0YLf+Hgt4Y4EhIJ8x2+g690Np7zJF4KXNFbi1BGloLGm78akY1rQlzpndKSpZq5KWw8FY/
1PEXORezg/BPD3Etp0AVKff4YdrDlOkNB7zoHRfFHAvEuuqti8aMBrbRnRSG0xunMUOEhbYS
/wOOTl0g3bF9NpAkfU1Fun57N96Us2T9gKo9AiOY5DxMe+IrBg4zaydEOovgqNi2wbU0MOBQ
b23Puhj7ZCIXcpILvcx9ygjkONr75w+XQrFDNeux4Znzay3ibXtAPqEykPMZHsZ2sbkCDQRl
NqzaARAAsdvBo8WRqZ5WVVk6lReD8b6Zx83eJUkV254YX9zn5t8KDRjYOySwS75mJIaZLsv0
YQjJk+5rt10tejyCrJIFo9CMvCmjUKtVbgmhfS5+fUDRrYCEZBBSa0Dvn68EBLiHugr+SPXF
6o1hXEUqdMCpB6oVp6X45JVQroCKIH5vsCtw2jU8S2/IjjV0V+E/zitGCiZaoZ1f6NG7ozyF
ep1CSAReZu/sssk0pCLlfCebRd9Rz3QjSrQhWYuJa+eJmiF4oahnpUGktxMD632I9aG+IMfj
tNJNtX32MbO+Se+cCtVc3cxSa/pR+89a3cb9IBA5tFF2Qoekhqo/1mmLi93Xn6uDUhl5tVxT
nB217dBT27tw+p0hjd9hXZRQbrIZUTyh3+8EMfmAjNSIeR+th86xRd9XFRr9EOqrydnALOUr
9cT7TfXWGEkFvn6ljQX7f4RvjJOTbc4jJgVFyu8K+VU6u1NnFJgDiNGsWvnYxAf7gDDbUSXE
uC2anhWvxPvpLGmsspngge4yl+3nv+UqZ9sm6LCebR/7UZ67tYz3p6xzAOVgYsYcxoIUuEZX
jHQtsYfTZZhrjUWBJ09jrMvlKUHLnS437SLbgoXVYZmcqwAWpVNOLZf+fFm4IE5aGBG5Dho2
CZ6ujngW9Zkn98T1d4N0MEwwXa2V6T1ijzcqD7GApZUAEQEAAYkCPAQYAQgAJhYhBLykNBfD
tIXdEo7G1Lezt4io03hcBQJlNqzaAhsMBQkDwmcAAAoJELezt4io03hcXqMP/01aPT3A3Sg7
oTQoHdCxj04ELkzrezNWGM+YwbSKrR2LoXR8zf2tBFzc2/Tl98V0+68f/eCvkvqCuOtq4392
Ps23j9W3r5XG+GDOwDsx0gl0E+Qkw07pwdJctA6efsmnRkjF2YVO0N9MiJA1tc8NbNXpEEHJ
Z7F8Ri5cpQrGUz/AY0eae2b7QefyP4rpUELpMZPjc8Px39Fe1DzRbT+5E19TZbrpbwlSYs1i
CzS5YGFmpCRyZcLKXo3zS6N22+82cnRBSPPipiO6WaQawcVMlQO1SX0giB+3/DryfN9VuIYd
1EWCGQa3O0MVu6o5KVHwPgl9R1P6xPZhurkDpAd0b1s4fFxin+MdxwmG7RslZA9CXRPpzo7/
fCMW8sYOH15DP+YfUckoEreBt+zezBxbIX2CGGWEV9v3UBXadRtwxYQ6sN9bqW4jm1b41vNA
17b6CVH6sVgtU3eN+5Y9an1e5jLD6kFYx+OIeqIIId/TEqwS61csY9aav4j4KLOZFCGNU0FV
ji7NQewSpepTcJwfJDOzmtiDP4vol1ApJGLRwZZZ9PB6wsOgDOoP6sr0YrDI/NNX2RyXXbgl
nQ1yJZVSH3/3eo6knG2qTthUKHCRDNKdy9Qqc1x4WWWtSRjh+zX8AvJK2q1rVLH2/3ilxe9w
cAZUlaj3id3TxquAlud4lWDz
=h5nH
-----END PGP PUBLIC KEY BLOCK-----

View file

@ -1,11 +0,0 @@
---
- name: "{{ role_name }} | Fake root | Ensure folder"
ansible.builtin.file:
path: "{{ playbook_dir }}/root"
state: directory
- name: "{{ role_name }} | Fake root | Ensure default file exists"
ansible.builtin.file:
path: "{{ playbook_dir }}/root/.my.cnf"
state: touch

View file

@ -1,21 +0,0 @@
---
####################################################################
# WARNING: These are designed specifically for Ansible tests #
# and should not be used as examples of how to write Ansible roles #
####################################################################
- name: "{{ role_name }} | Main | Prepare the fake root folder"
ansible.builtin.import_tasks:
file: fake_root.yml
- name: "{{ role_name }} | Main | Set variables"
ansible.builtin.import_tasks:
file: setvars.yml
- name: "{{ role_name }} | Main | Install requirements"
ansible.builtin.import_tasks:
file: requirements.yml
- name: "{{ role_name }} | Main | Verify all components version under test"
ansible.builtin.import_tasks:
file: verify.yml

View file

@ -1,54 +0,0 @@
---
# We use the ubuntu2204 image provided by ansible-test.
# The GPG key is imported in the files folder from:
# https://dev.mysql.com/doc/refman/8.4/en/checking-gpg-signature.html
# Downloading the key on each iteration of the tests is too slow.
- name: Install MySQL PGP public key
ansible.builtin.copy:
src: files/mysql.gpg
dest: /usr/share/keyrings/mysql.gpg
owner: root
group: root
mode: '0644'
when:
- db_engine == 'mysql'
- db_version is version('8.4', '>=')
- name: Add Apt signing key to keyring
ansible.builtin.apt_key:
id: A8D3785C
file: /usr/share/keyrings/mysql.gpg
state: present
when:
- db_engine == 'mysql'
- db_version is version('8.4', '>=')
- name: Add MySQL 8.4 repository
ansible.builtin.apt_repository:
repo: deb http://repo.mysql.com/apt/ubuntu/ jammy mysql-8.4-lts mysql-tools
state: present
filename: mysql
when:
- db_engine == 'mysql'
- db_version is version('8.4', '>=')
- name: "{{ role_name }} | Requirements | Install Linux packages"
ansible.builtin.package:
name:
- bzip2 # To test mysql_db dump compression
- "{{ db_engine }}-client"
# The command mysql-config must be present for mysqlclient python package.
# The package libmysqlclient-dev that provides this command have a
# different name between Ubuntu 20.04 and 22.04. Luckily, libmysql++ is
# available on both.
- "{{ 'libmysql++-dev' if db_engine == 'mysql' else 'libmariadb-dev' }}"
state: present
- name: "{{ role_name }} | Requirements | Install Python packages"
ansible.builtin.pip:
name:
- "{{ connector_name }}=={{ connector_version }}"
state: present

View file

@ -1,76 +0,0 @@
---
- name: "{{ role_name }} | Setvars | Install tools gather network facts"
ansible.builtin.package:
name:
- iproute2
state: present
- name: "{{ role_name }} | Setvars | Gather facts"
ansible.builtin.setup:
- name: "{{ role_name }} | Setvars | Set Fact"
ansible.builtin.set_fact:
gateway_addr: "{{ ansible_default_ipv4.gateway }}"
connector_name_lookup: >-
{{ lookup(
'file',
'/root/ansible_collections/community/mysql/tests/integration/connector_name'
) }}
connector_version_lookup: >-
{{ lookup(
'file',
'/root/ansible_collections/community/mysql/tests/integration/connector_version'
) }}
db_engine_name_lookup: >-
{{ lookup(
'file',
'/root/ansible_collections/community/mysql/tests/integration/db_engine_name'
) }}
db_engine_version_lookup: >-
{{ lookup(
'file',
'/root/ansible_collections/community/mysql/tests/integration/db_engine_version'
) }}
ansible_version_lookup: >-
{{ lookup(
'file',
'/root/ansible_collections/community/mysql/tests/integration/ansible'
) }}
- name: "{{ role_name }} | Setvars | Set Fact using above facts"
ansible.builtin.set_fact:
connector_name: "{{ connector_name_lookup.strip() }}"
connector_version: "{{ connector_version_lookup.strip() }}"
db_engine: "{{ db_engine_name_lookup.strip() }}"
db_version: "{{ db_engine_version_lookup.strip() }}"
test_ansible_version: >-
{%- if ansible_version_lookup == 'devel' -%}
{{ ansible_version_lookup }}
{%- else -%}
{{ ansible_version_lookup.split('-')[1].strip() }}
{%- endif -%}
mysql_command: >-
mysql
-h{{ gateway_addr }}
-P{{ mysql_primary_port }}
-u{{ mysql_user }}
-p{{ mysql_password }}
--protocol=tcp
mysql_command_wo_port: >-
mysql
-h{{ gateway_addr }}
-u{{ mysql_user }}
-p{{ mysql_password }}
--protocol=tcp
- name: "{{ role_name }} | Setvars | Output test informations"
vars:
msg: |-
connector_name: {{ connector_name }}
connector_version: {{ connector_version }}
db_engine: {{ db_engine }}
db_version: {{ db_version }}
test_ansible_version: {{ test_ansible_version }}
ansible.builtin.debug:
msg: "{{ msg.split('\n') }}"

View file

@ -1,67 +0,0 @@
---
- vars:
mysql_parameters: &mysql_params
login_user: root
login_password: msandbox
login_host: "{{ gateway_addr }}"
login_port: 3307
block:
- name: Query Primary container over TCP for MySQL/MariaDB version
mysql_info:
<<: *mysql_params
filter:
- version
register: primary_info
- name: Assert that test container runs the expected MySQL/MariaDB version
assert:
that:
- registred_db_version == db_version
vars:
registred_db_version:
"{{ primary_info.version.major }}.{{ primary_info.version.minor }}\
.{{ primary_info.version.release }}"
- name: Assert that mysql_info module used the expected version of pymysql
assert:
that:
- primary_info.connector_name == connector_name
- primary_info.connector_version == connector_version
when:
- connector_name == 'pymysql'
- name: Assert that mysql_info module used the expected version of mysqlclient
assert:
that:
- primary_info.connector_name == 'MySQLdb'
- primary_info.connector_version == connector_version
when:
- connector_name == 'mysqlclient'
- name: Get the python version in use
ansible.builtin.command:
cmd: python -V
changed_when: false
failed_when: false
register: python_version_in_use
- name: Display the python version in use
ansible.builtin.debug:
msg: >
Python in use inside the test container:
${{ python_version_in_use }}
when:
- python_version_in_use is defined
- name: Assert that we run the expected ansible version
assert:
that:
- ansible_running_version == test_ansible_version
vars:
ansible_running_version:
"{{ ansible_version.major }}.{{ ansible_version.minor }}"
when:
- test_ansible_version != 'devel' # Devel will change overtime

View file

@ -0,0 +1,13 @@
dbdeployer_version: 1.52.0
dbdeployer_home_dir: /opt/dbdeployer
home_dir: /root
percona_client_version: 5.7
mariadb_install: false
mysql_version: 8.0.21
mariadb_version: 10.5.4
mysql_base_port: 3306

View file

@ -0,0 +1,6 @@
---
- name: "{{ role_name }} | handler | create dbdeployer installed file"
template:
src: installed_file.j2
dest: "{{ dbdeployer_installed_file }}"
listen: create zookeeper installed file

View file

@ -0,0 +1,15 @@
---
- name: "{{ role_name }} | config | download mysql tarball"
get_url:
url: "{{ install_src }}"
dest: "{{ dbdeployer_sandbox_download_dir }}/{{ install_tarball }}"
- name: "{{ role_name }} | config | run unpack tarball"
shell:
cmd: "dbdeployer unpack {{ dbdeployer_sandbox_download_dir }}/{{ install_tarball }}"
creates: "{{ dbdeployer_sandbox_binary_dir }}/{{ install_version }}"
- name: "{{ role_name }} | config | setup replication topology"
shell:
cmd: "dbdeployer deploy multiple {{ install_version }} --base-port {{ mysql_base_port }} --my-cnf-options=\"master_info_repository='TABLE'\" --my-cnf-options=\"relay_log_info_repository='TABLE'\""
creates: "{{ dbdeployer_sandbox_home_dir }}/multi_msb_{{ install_version|replace('.','_') }}"

View file

@ -0,0 +1,11 @@
---
- name: "{{ role_name }} | dir | create dbdeployer directories"
file:
state: directory
path: "{{ item }}"
loop:
- "{{ dbdeployer_home_dir }}"
- "{{ dbdeployer_install_dir }}"
- "{{ dbdeployer_sandbox_download_dir }}"
- "{{ dbdeployer_sandbox_binary_dir }}"
- "{{ dbdeployer_sandbox_home_dir }}"

View file

@ -0,0 +1,52 @@
---
- name: "{{ role_name }} | install | add apt signing key for percona"
apt_key:
keyserver: keyserver.ubuntu.com
id: 4D1BB29D63D98E422B2113B19334A25F8507EFA5
state: present
- name: "{{ role_name }} | install | add percona repositories"
apt_repository:
repo: "{{ item }}"
state: present
loop: "{{ percona_mysql_repos }}"
- name: "{{ role_name }} | install | install packages required by percona"
apt:
name: "{{ percona_mysql_packages }}"
state: present
environment:
DEBIAN_FRONTEND: noninteractive
- name: "{{ role_name }} | install | install python packages"
pip:
name: "{{ python_packages }}"
- name: "{{ role_name }} | install | install packages required by mysql"
apt:
name: "{{ install_prereqs }}"
state: present
environment:
DEBIAN_FRONTEND: noninteractive
- name: "{{ role_name }} | install | download and unpack dbdeployer"
unarchive:
remote_src: true
src: "{{ dbdeployer_src }}"
dest: "{{ dbdeployer_install_dir }}"
creates: "{{ dbdeployer_installed_file }}"
register: dbdeployer_tarball_install
notify:
- create zookeeper installed file
until: dbdeployer_tarball_install is not failed
retries: 6
delay: 5
- name: "{{ role_name }} | install | create symlink"
file:
src: "{{ dbdeployer_install_dir }}/dbdeployer-{{ dbdeployer_version }}.linux"
dest: /usr/local/bin/dbdeployer
follow: false
state: link
- meta: flush_handlers

View file

@ -0,0 +1,11 @@
---
####################################################################
# WARNING: These are designed specifically for Ansible tests #
# and should not be used as examples of how to write Ansible roles #
####################################################################
- import_tasks: setvars.yml
- import_tasks: dir.yml
- import_tasks: install.yml
- import_tasks: config.yml
- import_tasks: verify.yml

View file

@ -0,0 +1,28 @@
---
- name: "{{ role_name }} | setvars | split mysql version in parts"
set_fact:
mysql_version_parts: "{{ mysql_version.split('.') }}"
- name: "{{ role_name }} | setvars | get mysql major version"
set_fact:
mysql_major_version: "{{ mysql_version_parts[0] + '.' + mysql_version_parts[1] }}"
- name: "{{ role_name }} | setvars | set the appropriate extension dependent on the mysql version"
set_fact:
mysql_compression_extension: "{{ mysql_version is version('8.0.0', '<') | ternary('gz', 'xz') }}"
- name: "{{ role_name }} | setvars | set the install type"
set_fact:
install_type: "{{ mariadb_install | ternary('mariadb', 'mysql') }}"
- name: "{{ role_name }} | setvars | set install_version"
set_fact:
install_version: "{{ lookup('vars', install_type + '_version') }}"
- name: "{{ role_name }} | setvars | set install_tarball"
set_fact:
install_tarball: "{{ lookup('vars', install_type + '_tarball') }}"
- name: "{{ role_name }} | setvars | set install_src"
set_fact:
install_src: "{{ lookup('vars', install_type + '_src') }}"

View file

@ -0,0 +1,27 @@
---
- name: "{{ role_name }} | verify | confirm primary is running and get the port"
shell: "{{ dbdeployer_sandbox_home_dir }}/multi_msb_{{ install_version|replace('.','_') }}/n1 -BNe'select @@port'"
register: primary_port
- name: "{{ role_name }} | verify | confirm replica1 is running and get the port"
shell: "{{ dbdeployer_sandbox_home_dir }}/multi_msb_{{ install_version|replace('.','_') }}/n2 -BNe'select @@port'"
register: replica1_port
- name: "{{ role_name }} | verify | confirm replica2 is running and get the port"
shell: "{{ dbdeployer_sandbox_home_dir }}/multi_msb_{{ install_version|replace('.','_') }}/n3 -BNe'select @@port'"
register: replica2_port
- name: "{{ role_name }} | verify | confirm primary is running on expected port"
assert:
that:
- primary_port.stdout|int == 3307
- name: "{{ role_name }} | verify | confirm replica1 is running on expected port"
assert:
that:
- replica1_port.stdout|int == 3308
- name: "{{ role_name }} | verify | confirm replica2 is running on expected port"
assert:
that:
- replica2_port.stdout|int == 3309

View file

@ -0,0 +1 @@
{{ dbdeployer_version }}

View file

@ -0,0 +1,27 @@
---
dbdeployer_install_dir: "{{ dbdeployer_home_dir }}/dbdeployer_{{ dbdeployer_version }}"
dbdeployer_src: "https://github.com/datacharmer/dbdeployer/releases/download/v{{ dbdeployer_version }}/dbdeployer-{{ dbdeployer_version }}.linux.tar.gz"
dbdeployer_installed_file: "{{ dbdeployer_home_dir }}/dbdeployer_installed"
dbdeployer_sandbox_download_dir: "{{ home_dir }}/downloads"
dbdeployer_sandbox_binary_dir: "{{ home_dir }}/opt/mysql"
dbdeployer_sandbox_home_dir: "{{ home_dir }}/sandboxes"
percona_mysql_repos:
- deb http://repo.percona.com/apt {{ ansible_lsb.codename }} main
- deb-src http://repo.percona.com/apt {{ ansible_lsb.codename }} main
percona_mysql_packages:
- percona-server-client-{{ percona_client_version }}
python_packages:
- pymysql==0.9.3 # temporary fix pinning to 0.9.3 to ensure warnings are handled (https://github.com/ansible-collections/community.mysql/pull/9#issuecomment-663040948)
install_prereqs:
- libaio1
- libnuma1
mysql_tarball: "mysql-{{ mysql_version }}-linux-glibc2.12-x86_64.tar.{{ mysql_compression_extension }}"
mysql_src: "https://dev.mysql.com/get/Downloads/MySQL-{{ mysql_major_version }}/{{ mysql_tarball }}"
mariadb_tarball: "mariadb-{{ mariadb_version }}-linux-x86_64.tar.gz"
mariadb_src: "https://downloads.mariadb.com/MariaDB/mariadb-{{ mariadb_version }}/bintar-linux-x86_64/{{ mariadb_tarball }}"

View file

@ -1,9 +1,5 @@
- name: delete temporary directory
include_tasks: default-cleanup.yml
tags:
- setup_remote_tmp_dir
- name: delete temporary directory (windows)
include_tasks: windows-cleanup.yml
tags:
- setup_remote_tmp_dir

View file

@ -7,13 +7,9 @@
setup:
gather_subset: distribution
when: ansible_facts == {}
tags:
- setup_remote_tmp_dir
- include_tasks: "{{ lookup('first_found', files)}}"
vars:
files:
- "{{ ansible_os_family | lower }}.yml"
- "default.yml"
tags:
- setup_remote_tmp_dir

View file

@ -2,20 +2,9 @@
# defaults file for test_mysql_db
mysql_user: root
mysql_password: msandbox
mysql_host: '{{ gateway_addr }}'
mysql_primary_port: 3307
# Database names
db_names:
- "data"
- "db%"
# Database formats
db_formats:
- { format_type: "sql", file: "dbdata.sql", format_msg_type: "ASCII", file2: "dump2.sql", file3: "dump3.sql", file4: "dump4.sql" }
- { format_type: "gz", file: "dbdata.gz", format_msg_type: "gzip", file2: "dump2.gz", file3: "dump3.gz", file4: "dump4.gz" }
- { format_type: "bz2", file: "dbdata.bz2", format_msg_type: "bzip2", file2: "dump2.bz2", file3: "dump3.bz2", file4: "dump4.bz2" }
db_name: 'data'
db_name2: 'data2'
db_user1: 'datauser1'
db_user2: 'datauser2'
@ -23,6 +12,3 @@ db_user2: 'datauser2'
tmp_dir: '/tmp'
db_latin1_name: 'db_latin1'
file4: 'latin1_file'
user_name_1: 'db_user1'
user_password_1: 'gadfFDSdtTU^Sdfuj'

View file

@ -1,2 +1,2 @@
dependencies:
- setup_controller
- setup_mysql

View file

@ -1,59 +1,20 @@
---
- name: Config overrides | Set facts
set_fact:
- set_fact:
db_to_create: testdb1
config_file: "{{ playbook_dir }}/.my1.cnf"
config_file: "/root/.my1.cnf"
fake_port: 9999
fake_host: "blahblah.local"
include_dir: "{{ playbook_dir }}/mycnf.d"
- name: Config overrides | Create custom config file
- name: Create custom config file
shell: 'echo "[client]" > {{ config_file }}'
- name: Config overrides | Add fake port to config file
- name: Add fake port to config file
shell: 'echo "port = {{ fake_port }}" >> {{ config_file }}'
- name: Config overrides | Add blank line
shell: 'echo "" >> {{ config_file }}'
when:
- >
connector_name != 'pymysql'
or (
connector_name == 'pymysql'
and connector_version is version('0.9.3', '>=')
)
- name: Config overrides | Create include_dir
file:
path: '{{ include_dir }}'
state: directory
mode: '0777'
when:
- >
connector_name != 'pymysql'
or (
connector_name == 'pymysql'
and connector_version is version('0.9.3', '>=')
)
- name: Config overrides | Add include_dir
lineinfile:
path: '{{ config_file }}'
line: '!includedir {{ include_dir }}'
insertafter: EOF
when:
- >
connector_name != 'pymysql'
or (
connector_name == 'pymysql'
and connector_version is version('0.9.3', '>=')
)
- name: Config overrides | Create database using fake port to connect to, must fail
- name: Create database using fake port to connect to, must fail
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name: '{{ db_to_create }}'
state: present
@ -63,17 +24,17 @@
ignore_errors: yes
register: result
- name: Config overrides | Must fail because login_port default has beed overriden by wrong value from config file
- name: Must fail because login_port default has beed overriden by wrong value from config file
assert:
that:
- result is failed
- result.msg is search("unable to connect to database")
- result is failed
- result.msg is search("unable to connect to database")
- name: Config overrides | Create database using default port
- name: Create database using default port
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name: '{{ db_to_create }}'
state: present
@ -82,45 +43,42 @@
config_overrides_defaults: no
register: result
- name: Config overrides | Must not fail because of the default of login_port is correct
- name: Must not fail because of the default of login_port is correct
assert:
that:
- result is changed
- result is changed
- name: Config overrides | Reinit custom config file
- name: Reinit custom config file
shell: 'echo "[client]" > {{ config_file }}'
- name: Config overrides | Add fake host to config file
- name: Add fake host to config file
shell: 'echo "host = {{ fake_host }}" >> {{ config_file }}'
- name: >-
Config overrides | Fail to Remove database using fake login_host
because its default has been overriden by wrong value from config file
- name: Remove database using fake login_host
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name: '{{ db_to_create }}'
state: absent
config_file: '{{ config_file }}'
config_overrides_defaults: true
config_overrides_defaults: yes
register: result
failed_when:
- result is succeeded
- result.msg is not search(pattern1)
- result.msg is not search(pattern2)
- result.msg is not search(pattern3)
vars:
pattern1: Can't connect to MySQL server on '{{ fake_host }}'
pattern2: Unknown MySQL server host '{{ fake_host }}'
pattern3: Unknown server host '{{ fake_host }}'
ignore_errors: yes
- name: Config overrides | Clean up test database
- name: Must fail because login_host default has beed overriden by wrong value from config file
assert:
that:
- result is failed
- result.msg is search("Can't connect to MySQL server on '{{ fake_host }}'")
# Clean up
- name: Remove test db
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name: '{{ db_to_create }}'
state: absent

View file

@ -1,105 +1,98 @@
---
- name: Encoding | Set fact
set_fact:
latin1_file1: "{{ tmp_dir }}/{{ file }}"
- set_fact:
latin1_file1: "{{tmp_dir}}/{{file}}"
- name: Deleting Latin1 encoded Database
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name: '{{ db_latin1_name }}'
state: absent
- name: Encoding | Create Latin1 encoded database
- name: create Latin1 encoded database
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name: '{{ db_latin1_name }}'
state: present
encoding: latin1
- name: Encoding | Create a table in Latin1 database
- name: create a table in Latin1 database
command: "{{ mysql_command }} {{ db_latin1_name }} -e \"create table testlatin1(id int, name varchar(100))\""
# Inserting a string in latin1 into table, , this string be tested later,
# so report any change of content in the test too
- name: Encoding | Inserting data into Latin1 database
- name: inserting data into Latin1 database
command: "{{ mysql_command }} {{ db_latin1_name }} -e \"insert into testlatin1 value(47,'Amédée Bôlüt')\""
- name: Encoding | Selecting table
- name: selecting table
command: "{{ mysql_command }} {{ db_latin1_name }} -e \"select * from testlatin1\""
register: output
- name: Encoding | Dumping a table in Latin1 database
- name: Dumping a table in Latin1 database
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name: "{{ db_latin1_name }}"
encoding: latin1
target: "{{ latin1_file1 }}"
state: dump
register: result
register: dump_result
- assert:
that:
- result is changed
- result is changed
- name: Encoding | State dump - file name should exist (latin1_file1)
- name: state dump - file name should exist
file:
name: '{{ latin1_file1 }}'
state: file
- name: od the file and check of latin1 encoded string is present
- name: od the file and check of latin1 encoded string is present
shell: grep -a 47 {{ latin1_file1 }} | od -c |grep "A m 351 d 351 e B 364\|A m 303 251 d 303 251 e B 303"
- name: Encoding | Dropping {{ db_latin1_name }} database
- name: Dropping {{ db_latin1_name }} database
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name: '{{ db_latin1_name }}'
state: absent
- name: Encoding | Importing the latin1 mysql script
- name: Importing the latin1 mysql script
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
state: import
encoding: latin1
name: '{{ db_latin1_name }}'
target: "{{ latin1_file1 }}"
register: result
- name: Encoding | Assert that importing latin1 is changed
assert:
- assert:
that:
- result is changed
- result is changed
- name: Encoding | Check encoding of table
ansible.builtin.command:
cmd: >
{{ mysql_command }}
{{ db_latin1_name }}
-e "SHOW FULL COLUMNS FROM {{ db_latin1_name }}.testlatin1"
- name: check encoding of table
shell: "{{ mysql_command }} {{ db_latin1_name }} -e \"SHOW FULL COLUMNS FROM testlatin1\""
register: output
failed_when: '"latin1_swedish_ci" not in output.stdout'
- name: Encoding | Clean up database
- name: remove database
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name: '{{ db_latin1_name }}'
state: absent

View file

@ -1,88 +0,0 @@
---
- name: set fact tls_enabled
command: "{{ mysql_command }} \"-e SHOW VARIABLES LIKE 'have_ssl';\""
register: result
- set_fact:
tls_enabled: "{{ 'YES' in result.stdout | bool | default('false', true) }}"
- vars:
mysql_parameters: &mysql_params
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_port: '{{ mysql_primary_port }}'
when: tls_enabled
block:
# ============================================================
- name: get server certificate
copy:
content: "{{ lookup('pipe', \"openssl s_client -starttls mysql -connect localhost:3307 -showcerts 2>/dev/null </dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p'\") }}"
dest: /tmp/cert.pem
delegate_to: localhost
- name: Drop mysql user if exists
mysql_user:
<<: *mysql_params
name: '{{ user_name_1 }}'
host_all: true
state: absent
ignore_errors: yes
- name: create user with ssl requirement
mysql_user:
<<: *mysql_params
name: "{{ user_name_1 }}"
host: "%"
password: "{{ user_password_1 }}"
priv: '*.*:ALL,GRANT'
tls_requires:
SSL:
- name: attempt connection with newly created user (expect failure)
mysql_db:
name: '{{ db_name }}'
state: absent
login_user: '{{ user_name_1 }}'
login_password: '{{ user_password_1 }}'
login_host: '{{ mysql_host }}'
login_port: '{{ mysql_primary_port }}'
ca_cert: /tmp/cert.pem
register: result
ignore_errors: yes
- assert:
that:
- result is failed
when:
- connector_name == 'pymysql'
- assert:
that:
- result is succeeded
when:
- connector_name != 'pymysql'
- name: attempt connection with newly created user ignoring hostname
mysql_db:
name: '{{ db_name }}'
state: absent
login_user: '{{ user_name_1 }}'
login_password: '{{ user_password_1 }}'
login_host: '{{ mysql_host }}'
login_port: '{{ mysql_primary_port }}'
ca_cert: /tmp/cert.pem
check_hostname: no
register: result
ignore_errors: yes
- assert:
that:
- result is succeeded or 'pymysql >= 0.7.11 is required' in result.msg
- name: Drop mysql user
mysql_user:
<<: *mysql_params
name: '{{ user_name_1 }}'
host_all: true
state: absent

View file

@ -1,149 +0,0 @@
---
# When mysqldump encountered an issue, mysql_db was still happy. But the
# dump produced was empty or worse, only contained `DROP TABLE IF EXISTS...`
- module_defaults:
community.mysql.mysql_db: &mysql_defaults
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_port: '{{ mysql_primary_port }}'
community.mysql.mysql_query: *mysql_defaults
block:
- name: Dumps errors | Setup test | Create 2 schemas
community.mysql.mysql_db:
name:
- "db1"
- "db2"
state: present
- name: Dumps errors | Setup test | Create 2 tables
community.mysql.mysql_query:
query:
- "CREATE TABLE db1.t1 (id int)"
- "CREATE TABLE db1.t2 (id int)"
- "CREATE VIEW db2.v1 AS SELECT id from db1.t1"
- name: Dumps errors | Full dump without compression
community.mysql.mysql_db:
state: dump
name: all
target: /tmp/full-dump.sql
register: full_dump
- name: Dumps errors | Full dump with gunzip
community.mysql.mysql_db:
state: dump
name: all
target: /tmp/full-dump.sql.gz
register: full_dump_gz
- name: Dumps errors | Distinct dump without compression
community.mysql.mysql_db:
state: dump
name: db2
target: /tmp/dump-db2.sql
register: dump_db2
- name: Dumps errors | Distinct dump with gunzip
community.mysql.mysql_db:
state: dump
name: db2
target: /tmp/dump-db2.sql.gz
register: dump_db2_gz
- name: Dumps errors | Check distinct dumps are changed
ansible.builtin.assert:
that:
- dump_db2 is changed
- dump_db2_gz is changed
# Now db2.v1 targets an inexistant table so mysqldump will fail
- name: Dumps errors | Drop t1
community.mysql.mysql_query:
query:
- "DROP TABLE db1.t1"
- name: Dumps errors | Full dump after drop t1 without compression
community.mysql.mysql_db:
state: dump
name: all
target: /tmp/full-dump-without-t1.sql
pipefail: true # This should do nothing
register: full_dump_without_t1
ignore_errors: true
- name: Dumps errors | Full dump after drop t1 with gzip without the fix
community.mysql.mysql_db:
state: dump
name: all
target: /tmp/full-dump-without-t1.sql.gz
register: full_dump_without_t1_gz_without_fix
ignore_errors: true
- name: Dumps errors | Full dump after drop t1 with gzip with the fix
community.mysql.mysql_db:
state: dump
name: all
target: /tmp/full-dump-without-t1.sql.gz
pipefail: true
register: full_dump_without_t1_gz_with_fix
ignore_errors: true
- name: Dumps errors | Check full dump
ansible.builtin.assert:
that:
- full_dump_without_t1 is failed
- full_dump_without_t1.msg is search(
'references invalid table')
- full_dump_without_t1_gz_without_fix is changed
- full_dump_without_t1_gz_with_fix is failed
- full_dump_without_t1_gz_with_fix.msg is search(
'references invalid table')
- name: Dumps errors | Distinct dump after drop t1 without compression
community.mysql.mysql_db:
state: dump
name: db2
target: /tmp/dump-db2-without_t1.sql
pipefail: true # This should do nothing
register: dump_db2_without_t1
ignore_errors: true
- name: Dumps errors | Distinct dump after drop t1 with gzip without the fix
community.mysql.mysql_db:
state: dump
name: db2
target: /tmp/dump-db2-without_t1.sql.gz
register: dump_db2_without_t1_gz_without_fix
ignore_errors: true
- name: Dumps errors | Distinct dump after drop t1 with gzip with the fix
community.mysql.mysql_db:
state: dump
name: db2
target: /tmp/dump-db2-without_t1.sql.gz
pipefail: true
register: dump_db2_without_t1_gz_with_fix
ignore_errors: true
- name: Dumps errors | Check distinct dump
ansible.builtin.assert:
that:
- dump_db2_without_t1 is failed
- dump_db2_without_t1.msg is search(
'references invalid table')
- dump_db2_without_t1_gz_without_fix is changed
- dump_db2_without_t1_gz_with_fix is failed
- dump_db2_without_t1_gz_with_fix.msg is search(
'references invalid table')
- name: Dumps errors | Cleanup
community.mysql.mysql_db:
name:
- "db1"
- "db2"
state: absent

View file

@ -1,4 +1,3 @@
---
####################################################################
# WARNING: These are designed specifically for Ansible tests #
# and should not be used as examples of how to write Ansible roles #
@ -22,44 +21,304 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
- name: Check state present/absent
include_tasks: state_present_absent.yml
vars:
db_name: "{{ item }}"
loop: "{{ db_names }}"
# ============================================================
- name: alias mysql command to include default options
set_fact:
mysql_command: "mysql -u{{ mysql_user }} -p{{ mysql_password }} -P{{ mysql_primary_port }} --protocol=tcp"
- name: Check state dump/import
include_tasks: state_dump_import.yml
vars:
db_name: "{{ item.0 }}"
file: "{{ item.1.file }}"
file2: "{{ item.1.file2 }}"
file3: "{{ item.1.file3 }}"
file4: "{{ item.1.file4 }}"
format_msg_type: "{{ item.1.format_msg_type }}"
format_type: "{{ item.1.format_type }}"
with_nested:
- "{{ db_names }}"
- "{{ db_formats }}"
- name: remove database if it exists
command: >
"{{ mysql_command }} -sse 'drop database {{ db_name }}'"
ignore_errors: True
- name: Check state present/absent with multiple databases
include_tasks: multi_db_create_delete.yml
- name: make sure the test database is not there
command: "{{ mysql_command }} {{ db_name }}"
register: mysql_db_check
failed_when: "'1049' not in mysql_db_check.stderr"
- name: Check state dump/import with encoding
include_tasks: encoding_dump_import.yml
vars:
file: "latin1.sql"
format_msg_type: "ASCII"
- name: test state=present for a database name (expect changed=true)
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name: '{{ db_name }}'
state: present
register: result
- name: Check MySQL config file
include_tasks: config_overrides_defaults.yml
- name: assert output message that database exist
assert:
that:
- result is changed
- result.db == '{{ db_name }}'
- result.executed_commands == ["CREATE DATABASE `{{ db_name }}`"]
- name: run command to test state=present for a database name (expect db_name in stdout)
command: "{{ mysql_command }} -e \"show databases like '{{ db_name }}'\""
register: result
- name: assert database exist
assert:
that:
- "'{{ db_name }}' in result.stdout"
# ============================================================
- name: test state=absent for a database name (expect changed=true)
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name: '{{ db_name }}'
state: absent
register: result
- name: assert output message that database does not exist
assert:
that:
- result is changed
- result.db == '{{ db_name }}'
- result.executed_commands == ["DROP DATABASE `{{ db_name }}`"]
- name: run command to test state=absent for a database name (expect db_name not in stdout)
command: "{{ mysql_command }} -e \"show databases like '{{ db_name }}'\""
register: result
- name: assert database does not exist
assert:
that:
- "'{{ db_name }}' not in result.stdout"
# ============================================================
- name: test mysql_db encoding param not valid - issue 8075
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name: datanotvalid
state: present
encoding: notvalid
register: result
ignore_errors: true
- name: assert test mysql_db encoding param not valid - issue 8075 (failed=true)
assert:
that:
- "result.failed == true"
- "'Traceback' not in result.msg"
- "'Unknown character set' in result.msg"
# ============================================================
- name: test mysql_db using a valid encoding utf8 (expect changed=true)
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name: 'en{{ db_name }}'
state: present
encoding: utf8
register: result
- name: assert output message created a database
assert:
that:
- result is changed
- result.executed_commands == ["CREATE DATABASE `en{{ db_name }}` CHARACTER SET 'utf8'"]
- name: test database was created
command: "{{ mysql_command }} -e \"SHOW CREATE DATABASE en{{ db_name }}\""
register: result
- name: assert created database is of encoding utf8
assert:
that:
- "'utf8' in result.stdout"
- name: remove database
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name: 'en{{ db_name }}'
state: absent
# ============================================================
- name: test mysql_db using valid encoding binary (expect changed=true)
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name: 'en{{ db_name }}'
state: present
encoding: binary
register: result
- name: assert output message that database was created
assert:
that:
- result is changed
- result.executed_commands == ["CREATE DATABASE `en{{ db_name }}` CHARACTER SET 'binary'"]
- name: run command to test database was created
command: "{{ mysql_command }} -e \"SHOW CREATE DATABASE en{{ db_name }}\""
register: result
- name: assert created database is of encoding binary
assert:
that:
- "'binary' in result.stdout"
- name: remove database
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name: 'en{{ db_name }}'
state: absent
# ============================================================
- name: create user1 to access database dbuser1
mysql_user:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name: user1
password: 'Hfd6fds^dfA8Ga'
priv: '*.*:ALL'
state: present
- name: create database dbuser1 using user1
mysql_db:
login_user: user1
login_password: 'Hfd6fds^dfA8Ga'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name: '{{ db_user1 }}'
state: present
register: result
- name: assert output message that database was created
assert:
that:
- "result.changed == true"
- name: run command to test database was created using user1
command: "{{ mysql_command }} -e \"show databases like '{{ db_user1 }}'\""
register: result
- name: assert database exist
assert:
that:
- "'{{ db_user1 }}' in result.stdout"
# ============================================================
- name: create user2 to access database with privilege select only
mysql_user:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name: user2
password: 'kjsfd&F7safjad'
priv: '*.*:SELECT'
state: present
- name: create database dbuser2 using user2 with no privilege to create (expect failed=true)
mysql_db:
login_user: user2
login_password: 'kjsfd&F7safjad'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name: '{{ db_user2 }}'
state: present
register: result
ignore_errors: true
- name: assert output message that database was not created using dbuser2
assert:
that:
- "result.failed == true"
- "'Access denied' in result.msg"
- name: run command to test that database was not created
command: "{{ mysql_command }} -e \"show databases like '{{ db_user2 }}'\""
register: result
- name: assert database does not exist
assert:
that:
- "'{{ db_user2 }}' not in result.stdout"
# ============================================================
- name: delete database using user2 with no privilege to delete (expect failed=true)
mysql_db:
login_user: user2
login_password: 'kjsfd&F7safjad'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name: '{{ db_user1 }}'
state: absent
register: result
ignore_errors: true
- name: assert output message that database was not deleted using dbuser2
assert:
that:
- "result.failed == true"
- "'Access denied' in result.msg"
- name: run command to test database was not deleted
command: "{{ mysql_command }} -e \"show databases like '{{ db_user1 }}'\""
register: result
- name: assert database still exist
assert:
that:
- "'{{ db_user1 }}' in result.stdout"
# ============================================================
- name: delete database using user1 with all privilege to delete a database (expect changed=true)
mysql_db:
login_user: user1
login_password: 'Hfd6fds^dfA8Ga'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name: '{{ db_user1 }}'
state: absent
register: result
ignore_errors: true
- name: assert output message that database was deleted using user1
assert:
that:
- result is changed
- result.executed_commands == ["DROP DATABASE `{{ db_user1 }}`"]
- name: run command to test database was deleted using user1
command: "{{ mysql_command }} -e \"show databases like '{{ db_name }}'\""
register: result
- name: assert database does not exist
assert:
that:
- "'{{ db_user1 }}' not in result.stdout"
# ============================================================
- include: state_dump_import.yml format_type=sql file=dbdata.sql format_msg_type=ASCII file2=dump2.sql file3=dump3.sql file4=dump4.sql
- include: state_dump_import.yml format_type=gz file=dbdata.gz format_msg_type=gzip file2=dump2.gz file3=dump3.gz file4=dump4.gz
- include: state_dump_import.yml format_type=bz2 file=dbdata.bz2 format_msg_type=bzip2 file2=dump2.bz2 file3=dump3.bz2 file4=dump4.bz2
- include: multi_db_create_delete.yml
- include: encoding_dump_import.yml file=latin1.sql format_msg_type=ASCII
- include: config_overrides_defaults.yml
when: ansible_python.version_info[0] >= 3
- name: Check issue 28
include_tasks: issue-28.yml
vars:
db_name: "{{ item }}"
loop: "{{ db_names }}"
- name: Check errors from mysqldump are seen issue 256
ansible.builtin.include_tasks: issue_256_mysqldump_errors.yml

View file

@ -14,18 +14,6 @@
#
# ==========================================================================
# Initial check - To confirm that database does not exist before executing check mode tasks
- name: Drop databases before test
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_port: '{{ mysql_primary_port }}'
name:
- '{{ db1_name }}'
- '{{ db2_name }}'
- '{{ db3_name }}'
state: absent
- name: run command to list databases like specified database name
command: "{{ mysql_command }} \"-e show databases like 'database%'\""
register: mysql_result
@ -43,7 +31,7 @@
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name:
- '{{ db1_name }}'
@ -56,7 +44,7 @@
- name: assert successful completion of create database using check_mode since databases does not exist prior
assert:
that:
- check_mode_result is changed
- check_mode_result.changed == true
- name: run command to list databases like specified database name
command: "{{ mysql_command }} \"-e show databases like 'database%'\""
@ -75,7 +63,7 @@
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name:
- '{{ db1_name }}'
@ -87,7 +75,7 @@
- name: assert successful completion of create database
assert:
that:
- result is changed
- result.changed == true
- result.db_list == ['{{ db1_name }}', '{{ db2_name }}', '{{ db3_name }}']
- name: run command to list databases like specified database name
@ -107,7 +95,7 @@
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name:
- '{{ db1_name }}'
@ -120,7 +108,7 @@
- name: assert that recreation of existing databases does not make change (since recreated using check mode)
assert:
that:
- check_mode_result is not changed
- check_mode_result.changed == false
- name: run command to list databases like specified database name
command: "{{ mysql_command }} \"-e show databases like 'database%'\""
@ -139,7 +127,7 @@
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name:
- '{{ db1_name }}'
@ -151,7 +139,7 @@
- name: assert that recreation of existing databases does not make change
assert:
that:
- result is not changed
- result.changed == false
- name: run command to list databases like specified database name
command: "{{ mysql_command }} \"-e show databases like 'database%'\""
@ -170,7 +158,7 @@
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name:
- '{{ db2_name }}'
@ -180,7 +168,7 @@
- name: assert successful completion of deleting database
assert:
that:
- result is changed
- result.changed == true
- name: run command to list databases like specified database name
command: "{{ mysql_command }} \"-e show databases like 'database%'\""
@ -199,7 +187,7 @@
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name:
- '{{ db1_name }}'
@ -212,7 +200,7 @@
- name: assert successful completion of recreation of partially existing database using check mode
assert:
that:
- check_mode_result is changed
- check_mode_result.changed == true
- name: run command to list databases like specified database name
command: "{{ mysql_command }} \"-e show databases like 'database%'\""
@ -231,7 +219,7 @@
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name:
- '{{ db1_name }}'
@ -243,7 +231,7 @@
- name: assert successful completion of create database
assert:
that:
- result is changed
- result.changed == true
- name: run command to list databases like specified database name
command: "{{ mysql_command }} \"-e show databases like 'database%'\""
@ -271,7 +259,7 @@
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name:
- '{{ db1_name }}'
@ -284,7 +272,7 @@
- name: assert successful completion of dump operation using check mode
assert:
that:
- check_mode_dump_result is changed
- check_mode_dump_result.changed == true
- name: run command to list databases like specified database name
command: "{{ mysql_command }} \"-e show databases like 'database%'\""
@ -308,7 +296,7 @@
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name:
- "{{ db1_name }}"
@ -348,7 +336,7 @@
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name:
- "{{ db4_name }}"
@ -384,12 +372,11 @@
# ==========================================================================
# Dump existing databases
- name: Dump existing databases
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name:
- '{{ db1_name }}'
@ -399,13 +386,13 @@
target: '{{ dump1_file }}'
register: dump_result
- name: Assert successful completion of dump operation (existing database)
- name: assert successful completion of dump operation
assert:
that:
- dump_result is changed
- dump_result.changed == true
- dump_result.db_list == ['{{ db1_name }}', '{{ db2_name }}', '{{ db3_name }}']
- name: Run command to list databases like specified database name
- name: run command to list databases like specified database name
command: "{{ mysql_command }} \"-e show databases like 'database%'\""
register: mysql_result
@ -416,7 +403,7 @@
- "'{{ db2_name }}' in mysql_result.stdout"
- "'{{ db3_name }}' in mysql_result.stdout"
- name: State dump - file name should exist (dump1_file)
- name: state dump - file name should exist
file:
name: '{{ dump1_file }}'
state: file
@ -442,7 +429,7 @@
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name: all
state: dump
@ -452,7 +439,7 @@
- name: assert successful completion of dump operation
assert:
that:
- dump_result is changed
- dump_result.changed == true
- name: run command to list databases like specified database name
command: "{{ mysql_command }} \"-e show databases like 'database%'\""
@ -467,7 +454,7 @@
- "'{{ db4_name }}' not in mysql_result.stdout"
- "'{{ db5_name }}' not in mysql_result.stdout"
- name: state dump - file name should exist (dump2_file)
- name: state dump - file name should exist
file:
name: '{{ dump2_file }}'
state: file
@ -480,7 +467,7 @@
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name:
- '{{ db2_name }}'
@ -492,7 +479,7 @@
- name: assert successful completion of delete databases which already exists using check mode
assert:
that:
- check_mode_result is changed
- check_mode_result.changed == true
- name: run command to test state=absent for a database name
command: "{{ mysql_command }} \"-e show databases like 'database%'\""
@ -510,7 +497,7 @@
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name:
- '{{ db2_name }}'
@ -521,7 +508,7 @@
- name: assert successful completion of deleting database
assert:
that:
- result is changed
- result.changed == true
- result.db_list == ['{{ db2_name }}', '{{ db3_name }}']
- name: run command to list databases like specified database name
@ -540,7 +527,7 @@
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name:
- '{{ db2_name }}'
@ -552,7 +539,7 @@
- name: assert that deletion of non existing databases does not make change (using check mode)
assert:
that:
- check_mode_result is not changed
- check_mode_result.changed == false
- name: run command to test state=absent for a database name
command: "{{ mysql_command }} \"-e show databases like 'database%'\""
@ -570,7 +557,7 @@
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name:
- '{{ db2_name }}'
@ -581,7 +568,7 @@
- name: assert that deletion of non existing databases does not make change
assert:
that:
- result is not changed
- result.changed == false
- name: run command to list databases like specified database name
command: "{{ mysql_command }} \"-e show databases like 'database%'\""
@ -599,7 +586,7 @@
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name:
- '{{ db1_name }}'
@ -613,7 +600,7 @@
- name: assert successful completion of deleting database
assert:
that:
- result is changed
- result.changed == true
- name: run command to list databases like specified database name
command: "{{ mysql_command }} \"-e show databases like 'database%'\""

View file

@ -17,129 +17,113 @@
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
# ============================================================
- name: Dump and Import | Set facts
set_fact:
- set_fact:
db_file_name: "{{ tmp_dir }}/{{ file }}"
wrong_sql_file: "{{ tmp_dir }}/wrong.sql"
dump_file1: "{{ tmp_dir }}/{{ file2 }}"
dump_file2: "{{ tmp_dir }}/{{ file3 }}"
db_user: "test"
db_user_unsafe_password: "pass!word"
config_file: "{{ playbook_dir }}/root/.my.cnf"
config_file: "/root/.my.cnf"
- name: Dump and Import | Create custom config file
- name: create custom config file
shell: 'echo "[client]" > {{ config_file }}'
- name: Dump and Import | Create user for test unsafe_login_password parameter
- name: create user for test unsafe_login_password parameter
mysql_user:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name: '{{ db_user }}'
host: '%'
password: '{{ db_user_unsafe_password }}'
priv: '*.*:ALL'
state: present
- name: Dump and Import | State dump/import - create database
- name: state dump/import - create database
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name: '{{ db_name }}'
state: present
check_implicit_admin: yes
- name: Dump and Import | Create database
- name: create database
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name: '{{ db_name2 }}'
state: present
check_implicit_admin: no
- name: Dump and Import | State dump/import - create table department
- name: state dump/import - create table department
command: "{{ mysql_command }} {{ db_name }} \"-e create table department(id int, name varchar(100))\""
- name: Dump and Import | State dump/import - create table employee
- name: state dump/import - create table employee
command: "{{ mysql_command }} {{ db_name }} \"-e create table employee(id int, name varchar(100))\""
- name: Dump and Import | State dump/import - insert data into table employee
- name: state dump/import - insert data into table employee
command: "{{ mysql_command }} {{ db_name }} \"-e insert into employee value(47,'Joe Smith')\""
- name: Dump and Import | State dump/import - insert data into table department
- name: state dump/import - insert data into table department
command: "{{ mysql_command }} {{ db_name }} \"-e insert into department value(2,'Engineering')\""
- name: Dump and Import | State dump/import - file name should not exist
- name: state dump/import - file name should not exist
file:
name: '{{ db_file_name }}'
state: absent
- name: Dump and Import | Database dump file1 should not exist
- name: database dump file1 should not exist
file:
name: '{{ dump_file1 }}'
state: absent
- name: Dump and Import | Database dump file2 should not exist
- name: database dump file2 should not exist
file:
name: '{{ dump_file2 }}'
state: absent
- name: Dump and Import | State dump without department table.
- name: state dump without department table.
mysql_db:
login_user: '{{ db_user }}'
login_password: '{{ db_user_unsafe_password }}'
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
unsafe_login_password: yes
name: '{{ db_name }}'
state: dump
target: '{{ db_file_name }}'
ignore_tables:
- "{{ db_name }}.department"
- "{{ db_name }}.department"
force: yes
master_data: 1
skip_lock_tables: yes
dump_extra_args: >-
--skip-triggers
dump_extra_args: --skip-triggers
config_file: '{{ config_file }}'
restrict_config_file: yes
check_implicit_admin: no
register: result
- name: Dump and Import | Assert successful completion of dump operation for MariaDB and MySQL < 8.2
- name: assert successful completion of dump operation
assert:
that:
- result is changed
- result.executed_commands[0] is search(".department --master-data=1 --skip-triggers")
when:
- >
db_engine == 'mariadb' or
(db_engine == 'mysql' and db_version is version('8.2', '<'))
- result.executed_commands[0] is search("mysqldump --defaults-file={{ config_file }} --user={{ db_user }} --password=\*\*\*\*\*\*\*\* --force --host=127.0.0.1 --port={{ mysql_primary_port }} {{ db_name }} --skip-lock-tables --quick --ignore-table={{ db_name }}.department --master-data=1 --skip-triggers")
- name: Dump and Import | Assert successful completion of dump operation for MySQL >= 8.2
assert:
that:
- result is changed
- result.executed_commands[0] is search(".department --source-data=1 --skip-triggers")
when:
- db_engine == 'mysql'
- db_version is version('8.2', '>=')
- name: Dump and Import | State dump/import - file name should exist (db_file_name)
- name: state dump/import - file name should exist
file:
name: '{{ db_file_name }}'
state: file
- name: Dump and Import | State dump with multiple databases in comma separated form for MySQL.
- name: state dump with multiple databases in comma separated form.
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name: "{{ db_name }},{{ db_name2 }}"
state: dump
@ -147,22 +131,22 @@
check_implicit_admin: yes
register: dump_result1
- name: Dump and Import | Assert successful completion of dump operation (with multiple databases in comma separated form)
- name: assert successful completion of dump operation (with multiple databases in comma separated form)
assert:
that:
- dump_result1 is changed
- dump_result1.executed_commands[0] is search(" --user=root --password=\*\*\*\*\*\*\*\*")
- name: Dump and Import | State dump - dump file1 should exist
- name: state dump - dump file1 should exist
file:
name: '{{ dump_file1 }}'
state: file
- name: Dump and Import | State dump with multiple databases in list form via check_mode
- name: state dump with multiple databases in list form via check_mode
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name:
- '{{ db_name }}'
@ -172,26 +156,26 @@
register: dump_result
check_mode: yes
- name: Dump and Import | Assert successful completion of dump operation (with multiple databases in list form) via check mode
- name: assert successful completion of dump operation (with multiple databases in list form) via check mode
assert:
that:
- dump_result is changed
- "dump_result.changed == true"
- name: Dump and Import | Database dump file2 should not exist
- name: database dump file2 should not exist
stat:
path: '{{ dump_file2 }}'
register: stat_result
- name: Dump and Import | Assert that check_mode does not create dump file for databases
- name: assert that check_mode does not create dump file for databases
assert:
that:
- stat_result.stat.exists is defined and not stat_result.stat.exists
- name: Dump and Import | State dump with multiple databases in list form.
- name: state dump with multiple databases in list form.
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name:
- '{{ db_name }}'
@ -200,39 +184,39 @@
target: '{{ dump_file2 }}'
register: dump_result2
- name: Dump and Import | Assert successful completion of dump operation (with multiple databases in list form)
- name: assert successful completion of dump operation (with multiple databases in list form)
assert:
that:
- dump_result2 is changed
- "dump_result2.changed == true"
- name: Dump and Import | State dump - dump file2 should exist
- name: state dump - dump file2 should exist
file:
name: '{{ dump_file2 }}'
state: file
- name: Dump and Import | State dump/import - remove database
- name: state dump/import - remove database
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name: '{{ db_name }}'
state: absent
- name: Dump and Import | Remove database
- name: remove database
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name: '{{ db_name2 }}'
state: absent
- name: Dump and Import | Test state=import to restore the database of type {{ format_type }} (expect changed=true)
- name: test state=import to restore the database of type {{ format_type }} (expect changed=true)
mysql_db:
login_user: '{{ db_user }}'
login_password: '{{ db_user_unsafe_password }}'
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
unsafe_login_password: yes
name: '{{ db_name }}'
@ -241,20 +225,20 @@
use_shell: yes
register: result
- name: Dump and Import | Show the tables
- name: show the tables
command: "{{ mysql_command }} {{ db_name }} \"-e show tables\""
register: result
- name: Dump and Import | Assert that the department table is absent.
- name: assert that the department table is absent.
assert:
that:
- "'department' not in result.stdout"
- name: Dump and Import | Test state=import to restore a database from multiple database dumped file1
- name: test state=import to restore a database from multiple database dumped file1
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name: '{{ db_name2 }}'
state: import
@ -262,34 +246,34 @@
use_shell: no
register: import_result
- name: Dump and Import | Assert output message restored a database from dump file1
- name: assert output message restored a database from dump file1
assert:
that:
- import_result is changed
- "import_result.changed == true"
- name: Dump and Import | Remove database
- name: remove database
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name: '{{ db_name2 }}'
state: absent
- name: Dump and Import | Run command to list databases
- name: run command to list databases
command: "{{ mysql_command }} \"-e show databases like 'data%'\""
register: mysql_result
- name: Dump and Import | Assert that db_name2 database does not exist
- name: assert that db_name2 database does not exist
assert:
that:
- "'{{ db_name2 }}' not in mysql_result.stdout"
- name: Dump and Import | Test state=import to restore a database from dumped file2 (check mode)
- name: test state=import to restore a database from dumped file2 (check mode)
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name: '{{ db_name2 }}'
state: import
@ -297,96 +281,96 @@
register: check_import_result
check_mode: yes
- name: Dump and Import | Assert output message restored a database from dump file2 (check mode)
- name: assert output message restored a database from dump file2 (check mode)
assert:
that:
- check_import_result is changed
- "check_import_result.changed == true"
- name: Dump and Import | Run command to list databases
- name: run command to list databases
command: "{{ mysql_command }} \"-e show databases like 'data%'\""
register: mysql_result
- name: Dump and Import | Assert that db_name2 database does not exist (check mode)
- name: assert that db_name2 database does not exist (check mode)
assert:
that:
- "'{{ db_name2 }}' not in mysql_result.stdout"
- name: Dump and Import | Test state=import to restore a database from multiple database dumped file2
- name: test state=import to restore a database from multiple database dumped file2
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name: '{{ db_name2 }}'
state: import
target: '{{ dump_file2 }}'
register: import_result2
- name: Dump and Import | Assert output message restored a database from dump file2
- name: assert output message restored a database from dump file2
assert:
that:
- import_result2 is changed
- import_result2.changed == true
- import_result2.db_list == ['{{ db_name2 }}']
- name: Dump and Import | Run command to list databases
- name: run command to list databases
command: "{{ mysql_command }} \"-e show databases like 'data%'\""
register: mysql_result
- name: Dump and Import | Assert that db_name2 database does exist after import
- name: assert that db_name2 database does exist after import
assert:
that:
- "'{{ db_name2 }}' in mysql_result.stdout"
- name: Dump and Import | Test state=dump to backup the database of type {{ format_type }} (expect changed=true)
- name: test state=dump to backup the database of type {{ format_type }} (expect changed=true)
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name: '{{ db_name }}'
state: dump
target: '{{ db_file_name }}'
register: result
- name: Dump and Import | Assert output message backup the database
- name: assert output message backup the database
assert:
that:
- result is changed
- result.db == db_name
- "result.changed == true"
- "result.db =='{{ db_name }}'"
# - name: Dump and Import | Assert database was backed up successfully
# - name: assert database was backed up successfully
# command: "file {{ db_file_name }}"
# register: result
#
# - name: Dump and Import | Assert file format type
# - name: assert file format type
# assert:
# that:
# - "'{{ format_msg_type }}' in result.stdout"
- name: Dump and Import | Update database table employee
- name: update database table employee
command: "{{ mysql_command }} {{ db_name }} \"-e update employee set name='John Doe' where id=47\""
- name: Dump and Import | Test state=import to restore the database of type {{ format_type }} (expect changed=true)
- name: test state=import to restore the database of type {{ format_type }} (expect changed=true)
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name: '{{ db_name }}'
state: import
target: '{{ db_file_name }}'
register: result
- name: Dump and Import | Assert output message restore the database
- name: assert output message restore the database
assert:
that:
- result is changed
- "result.changed == true"
- name: Dump and Import | Select data from table employee
- name: select data from table employee
command: "{{ mysql_command }} {{ db_name }} \"-e select * from employee\""
register: result
- name: Dump and Import | Assert data in database is from the restore database
- name: assert data in database is from the restore database
assert:
that:
- "'47' in result.stdout"
@ -396,14 +380,14 @@
# Test ``force`` parameter
##########################
- name: Dump and Import | Create wrong sql file
- name: create wrong sql file
shell: echo 'CREATE TABLE hello (id int); CREATE ELBAT ehlo (int id);' >> '{{ wrong_sql_file }}'
- name: Dump and Import | Try to import without force parameter, must fail
- name: try to import without force parameter, must fail
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name: '{{ db_name }}'
state: import
@ -414,13 +398,13 @@
- assert:
that:
- result is failed
- result.failed == true
- name: Dump and Import | Try to import with force parameter
- name: try to import with force parameter
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name: '{{ db_name }}'
state: import
@ -432,73 +416,44 @@
that:
- result is changed
########################
# Test import with chdir
- name: Dump and Import | Create dir
file:
path: ~/subdir
state: directory
- name: Dump and Import | Create test dump
shell: 'echo "SOURCE ./subdir_test.sql" > ~/original_test.sql'
- name: Dump and Import | Create test source
shell: 'echo "SELECT 1" > ~/subdir/subdir_test.sql'
- name: Dump and Import | Try to restore without chdir argument, must fail
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_port: '{{ mysql_primary_port }}'
name: '{{ db_name }}'
state: import
target: '~/original_test.sql'
ignore_errors: yes
register: result
- assert:
that:
- result is failed
- result.msg is search('Failed to open file')
- name: Dump and Import | Restore with chdir argument, must pass
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_port: '{{ mysql_primary_port }}'
name: '{{ db_name }}'
state: import
target: '~/original_test.sql'
chdir: ~/subdir
register: result
- assert:
that:
- result is succeeded
##########
# Clean up
##########
- name: Dump and Import | Clean up databases
- name: remove database name
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name: '{{ item }}'
name: '{{ db_name }}'
state: absent
loop:
- '{{ db_name }}'
- '{{ db_name2 }}'
- name: Dump and Import | Clean up files
file:
name: '{{ item }}'
- name: remove database
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
name: '{{ db_name2 }}'
state: absent
- name: remove file name
file:
name: '{{ db_file_name }}'
state: absent
- name: remove file name
file:
name: '{{ wrong_sql_file }}'
state: absent
- name: remove dump file1
file:
name: '{{ dump_file1 }}'
state: absent
- name: remove dump file2
file:
name: '{{ dump_file2 }}'
state: absent
loop:
- '{{ db_file_name }}'
- '{{ wrong_sql_file }}'
- '{{ dump_file1 }}'
- '{{ dump_file2 }}'

View file

@ -1,302 +0,0 @@
---
# test code for mysql_db module with database name containing special chars
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
# ============================================================
- name: State Present Absent | Remove database if it exists
command: >
"{{ mysql_command }} -sse 'DROP DATABASE IF EXISTS {{ db_name }}'"
ignore_errors: true
- name: State Present Absent | Make sure the test database is not there
command: "{{ mysql_command }} {{ db_name }}"
register: mysql_db_check
failed_when: "'1049' not in mysql_db_check.stderr"
- name: State Present Absent | Test state=present for a database name (expect changed=true)
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_port: '{{ mysql_primary_port }}'
name: '{{ db_name }}'
state: present
register: result
- name: State Present Absent | Assert output message that database exist
assert:
that:
- result is changed
- result.db == '{{ db_name }}'
- result.executed_commands == ["CREATE DATABASE `{{ db_name }}`"]
- name: State Present Absent | Run command to test state=present for a database name (expect db_name in stdout)
command: "{{ mysql_command }} -e \"show databases like '{{ db_name | regex_replace(\"([%_\\\\])\", \"\\\\\\1\") }}'\""
register: result
- name: State Present Absent | Assert database exist
assert:
that:
- "'{{ db_name }}' in result.stdout"
# ============================================================
- name: State Present Absent | Test state=absent for a database name (expect changed=true)
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_port: '{{ mysql_primary_port }}'
name: '{{ db_name }}'
state: absent
register: result
- name: State Present Absent | Assert output message that database does not exist
assert:
that:
- result is changed
- result.db == '{{ db_name }}'
- result.executed_commands == ["DROP DATABASE `{{ db_name }}`"]
- name: State Present Absent | Run command to test state=absent for a database name (expect db_name not in stdout)
command: "{{ mysql_command }} -e \"show databases like '{{ db_name | regex_replace(\"([%_\\\\])\", \"\\\\\\1\") }}'\""
register: result
- name: State Present Absent | Assert database does not exist
assert:
that:
- "'{{ db_name }}' not in result.stdout"
# ============================================================
- name: State Present Absent | Test mysql_db encoding param not valid - issue 8075
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_port: '{{ mysql_primary_port }}'
name: datanotvalid
state: present
encoding: notvalid
register: result
ignore_errors: true
- name: State Present Absent | Assert test mysql_db encoding param not valid - issue 8075 (failed=true)
assert:
that:
- result is failed
- "'Traceback' not in result.msg"
- "'Unknown character set' in result.msg"
# ============================================================
- name: State Present Absent | Test mysql_db using a valid encoding utf8 (expect changed=true)
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_port: '{{ mysql_primary_port }}'
name: 'en{{ db_name }}'
state: present
encoding: utf8
register: result
- name: State Present Absent | Assert output message created a database
assert:
that:
- result is changed
- result.executed_commands == ["CREATE DATABASE `en{{ db_name }}` CHARACTER SET 'utf8'"]
- name: State Present Absent | Test database was created
command: "{{ mysql_command }} -e \"SHOW CREATE DATABASE `en{{ db_name }}`\""
register: result
- name: State Present Absent | Assert created database is of encoding utf8
assert:
that:
- "'utf8' in result.stdout"
- name: State Present Absent | Remove database
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_port: '{{ mysql_primary_port }}'
name: 'en{{ db_name }}'
state: absent
# ============================================================
- name: State Present Absent | Test mysql_db using valid encoding binary (expect changed=true)
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_port: '{{ mysql_primary_port }}'
name: 'en{{ db_name }}'
state: present
encoding: binary
register: result
- name: State Present Absent | Assert output message that database was created
assert:
that:
- result is changed
- result.executed_commands == ["CREATE DATABASE `en{{ db_name }}` CHARACTER SET 'binary'"]
- name: State Present Absent | Run command to test database was created
command: "{{ mysql_command }} -e \"SHOW CREATE DATABASE `en{{ db_name }}`\""
register: result
- name: State Present Absent | Assert created database is of encoding binary
assert:
that:
- "'binary' in result.stdout"
- name: State Present Absent | Remove database
mysql_db:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_port: '{{ mysql_primary_port }}'
name: 'en{{ db_name }}'
state: absent
# ============================================================
- name: State Present Absent | Create user1 to access database dbuser1
mysql_user:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_port: '{{ mysql_primary_port }}'
name: user1
host: '%'
password: 'Hfd6fds^dfA8Ga'
priv: '*.*:ALL'
state: present
- name: State Present Absent | Create database dbuser1 using user1
mysql_db:
login_user: user1
login_password: 'Hfd6fds^dfA8Ga'
login_host: '{{ mysql_host }}'
login_port: '{{ mysql_primary_port }}'
name: '{{ db_user1 }}'
state: present
register: result
- name: State Present Absent | Assert output message that database was created
assert:
that:
- result is changed
- name: State Present Absent | Run command to test database was created using user1
command: "{{ mysql_command }} -e \"show databases like '{{ db_user1 | regex_replace(\"([%_\\\\])\", \"\\\\\\1\") }}'\""
register: result
- name: State Present Absent | Assert database exist
assert:
that:
- "'{{ db_user1 }}' in result.stdout"
# ============================================================
- name: State Present Absent | Create user2 to access database with privilege select only
mysql_user:
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_port: '{{ mysql_primary_port }}'
name: user2
password: 'kjsfd&F7safjad'
priv: '*.*:SELECT'
state: present
- name: State Present Absent | Create database dbuser2 using user2 with no privilege to create (expect failed=true)
mysql_db:
login_user: user2
login_password: 'kjsfd&F7safjad'
login_host: '{{ mysql_host }}'
login_port: '{{ mysql_primary_port }}'
name: '{{ db_user2 }}'
state: present
register: result
ignore_errors: true
- name: State Present Absent | Assert output message that database was not created using dbuser2
assert:
that:
- result is failed
- "'Access denied' in result.msg"
- name: State Present Absent | Run command to test that database was not created
command: "{{ mysql_command }} -e \"show databases like '{{ db_user2 | regex_replace(\"([%_\\\\])\", \"\\\\\\1\") }}'\""
register: result
- name: State Present Absent | Assert database does not exist
assert:
that:
- "'{{ db_user2 }}' not in result.stdout"
# ============================================================
- name: State Present Absent | Delete database using user2 with no privilege to delete (expect failed=true)
mysql_db:
login_user: user2
login_password: 'kjsfd&F7safjad'
login_host: '{{ mysql_host }}'
login_port: '{{ mysql_primary_port }}'
name: '{{ db_user1 }}'
state: absent
register: result
ignore_errors: true
- name: State Present Absent | Assert output message that database was not deleted using dbuser2
assert:
that:
- result is failed
- "'Access denied' in result.msg"
- name: State Present Absent | Run command to test database was not deleted
command: "{{ mysql_command }} -e \"show databases like '{{ db_user1 | regex_replace(\"([%_\\\\])\", \"\\\\\\1\") }}'\""
register: result
- name: State Present Absent | Assert database still exist
assert:
that:
- "'{{ db_user1 }}' in result.stdout"
# ============================================================
- name: State Present Absent | Delete database using user1 with all privilege to delete a database (expect changed=true)
mysql_db:
login_user: user1
login_password: 'Hfd6fds^dfA8Ga'
login_host: '{{ mysql_host }}'
login_port: '{{ mysql_primary_port }}'
name: '{{ db_user1 }}'
state: absent
register: result
ignore_errors: true
- name: State Present Absent | Assert output message that database was deleted using user1
assert:
that:
- result is changed
- result.executed_commands == ["DROP DATABASE `{{ db_user1 }}`"]
- name: State Present Absent | Run command to test database was deleted using user1
command: "{{ mysql_command }} -e \"show databases like '{{ db_name | regex_replace(\"([%_\\\\])\", \"\\\\\\1\") }}'\""
register: result
- name: State Present Absent | Assert database does not exist
assert:
that:
- "'{{ db_user1 }}' not in result.stdout"

View file

@ -2,10 +2,7 @@
# defaults file for test_mysql_info
mysql_user: root
mysql_password: msandbox
mysql_host: '{{ gateway_addr }}'
mysql_host: 127.0.0.1
mysql_primary_port: 3307
db_name: data
user_name_1: 'db_user1'
user_password_1: 'gadfFDSdtTU^Sdfuj'

View file

@ -1,7 +0,0 @@
DELIMITER //
DROP PROCEDURE IF EXISTS users_info_db.get_all_items;
CREATE PROCEDURE users_info_db.get_all_items()
BEGIN
SELECT * from users_info_db.t1;
END //
DELIMITER ;

View file

@ -1,4 +1,3 @@
---
dependencies:
- setup_controller
- setup_mysql
- setup_remote_tmp_dir

View file

@ -1,30 +0,0 @@
---
# Added in 3.6.0 in
# https://github.com/ansible-collections/community.mysql/pull/497
- name: Connector info | Assert connector_name exists and has expected values
ansible.builtin.assert:
that:
- result.connector_name is defined
- result.connector_name is in ['pymysql', 'MySQLdb']
success_msg: >-
Assertions passed, result.connector_name is {{ result.connector_name }}
fail_msg: >-
Assertion failed, result.connector_name is
{{ result.connector_name | d('Unknown')}} which is different than expected
pymysql or MySQLdb
- name: Connector info | Assert connector_version exists and has expected values
ansible.builtin.assert:
that:
- result.connector_version is defined
- >
result.connector_version == 'Unknown'
or result.connector_version is version(connector_version, '==')
success_msg: >-
Assertions passed, result.connector_version is
{{ result.connector_version }}
fail_msg: >-
Assertion failed, result.connector_version is
{{ result.connector_version }} which is different than expected
{{ connector_version }}

View file

@ -1,161 +0,0 @@
---
- 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

View file

@ -1,321 +0,0 @@
---
- module_defaults:
community.mysql.mysql_db: &mysql_defaults
login_user: "{{ mysql_user }}"
login_password: "{{ mysql_password }}"
login_host: "{{ mysql_host }}"
login_port: "{{ mysql_primary_port }}"
community.mysql.mysql_query: *mysql_defaults
community.mysql.mysql_info: *mysql_defaults
community.mysql.mysql_user: *mysql_defaults
block:
# ================================ Prepare ==============================
- name: Mysql_info users_info | Create databases
community.mysql.mysql_db:
name:
- users_info_db
- users_info_db2
- users_info_db3
state: present
- name: Mysql_info users_info | Create tables
community.mysql.mysql_query:
query:
- >-
CREATE TABLE IF NOT EXISTS users_info_db.t1
(id int, name varchar(9))
- >-
CREATE TABLE IF NOT EXISTS users_info_db.T_UPPER
(id int, name1 varchar(9), NAME2 varchar(9), Name3 varchar(9))
# I failed to create a procedure using community.mysql.mysql_query.
# Maybe it's because we must changed the delimiter.
- name: Mysql_info users_info | Create procedure SQL file
ansible.builtin.template:
src: files/users_info_create_procedure.sql
dest: /root/create_procedure.sql
owner: root
group: root
mode: '0700'
- name: Mysql_info users_info | Create a procedure
community.mysql.mysql_db:
name: all
state: import
target: /root/create_procedure.sql
# Use a query instead of mysql_user, because we want to catch differences
# at the end and a bug in mysql_user would be invisible to this tests
- name: Mysql_info users_info | Prepare common tests users
community.mysql.mysql_query:
query:
- >-
CREATE USER users_info_adm@'users_info.com' IDENTIFIED WITH
mysql_native_password AS '*6C387FC3893DBA1E3BA155E74754DA6682D04747'
- >
GRANT ALL ON *.* to users_info_adm@'users_info.com' WITH GRANT
OPTION
- >-
CREATE USER users_info_schema@'users_info.com' IDENTIFIED WITH
mysql_native_password AS '*6C387FC3893DBA1E3BA155E74754DA6682D04747'
- >-
GRANT SELECT, INSERT, UPDATE, DELETE ON users_info_db.* TO
users_info_schema@'users_info.com'
- >-
CREATE USER users_info_table@'users_info.com' IDENTIFIED WITH
mysql_native_password AS '*6C387FC3893DBA1E3BA155E74754DA6682D04747'
- >-
GRANT SELECT, INSERT, UPDATE ON users_info_db.t1 TO
users_info_table@'users_info.com'
- >-
CREATE USER users_info_col@'users_info.com' IDENTIFIED WITH
mysql_native_password AS '*6C387FC3893DBA1E3BA155E74754DA6682D04747'
WITH MAX_USER_CONNECTIONS 100
- >-
GRANT SELECT (id) ON users_info_db.t1 TO
users_info_col@'users_info.com'
- >-
CREATE USER users_info_proc@'users_info.com' IDENTIFIED WITH
mysql_native_password AS '*6C387FC3893DBA1E3BA155E74754DA6682D04747'
WITH MAX_USER_CONNECTIONS 2 MAX_CONNECTIONS_PER_HOUR 60
- >-
GRANT EXECUTE ON PROCEDURE users_info_db.get_all_items TO
users_info_proc@'users_info.com'
- >-
CREATE USER users_info_multi@'users_info.com' IDENTIFIED WITH
mysql_native_password AS '*6C387FC3893DBA1E3BA155E74754DA6682D04747'
- >-
GRANT SELECT ON mysql.* TO
users_info_multi@'users_info.com'
- >-
GRANT ALL ON users_info_db.* TO
users_info_multi@'users_info.com'
- >-
GRANT ALL ON users_info_db2.* TO
users_info_multi@'users_info.com'
- >-
GRANT ALL ON users_info_db3.* TO
users_info_multi@'users_info.com'
- >-
CREATE USER users_info_usage_only@'users_info.com' IDENTIFIED WITH
mysql_native_password AS '*6C387FC3893DBA1E3BA155E74754DA6682D04747'
- >-
GRANT USAGE ON *.* TO
users_info_usage_only@'users_info.com'
- >-
CREATE USER users_info_columns_uppercase@'users_info.com'
IDENTIFIED WITH mysql_native_password AS
'*6C387FC3893DBA1E3BA155E74754DA6682D04747'
- >-
GRANT SELECT,UPDATE(name1,NAME2,Name3) ON users_info_db.T_UPPER TO
users_info_columns_uppercase@'users_info.com'
- >-
CREATE USER users_info_multi_hosts@'%'
IDENTIFIED WITH mysql_native_password AS
'*6C387FC3893DBA1E3BA155E74754DA6682D04747'
- GRANT SELECT ON users_info_db.* TO users_info_multi_hosts@'%'
- >-
CREATE USER users_info_multi_hosts@'localhost'
IDENTIFIED WITH mysql_native_password AS
'*6C387FC3893DBA1E3BA155E74754DA6682D04747'
- >-
GRANT SELECT ON users_info_db.* TO
users_info_multi_hosts@'localhost'
- >-
CREATE USER users_info_multi_hosts@'host1'
IDENTIFIED WITH mysql_native_password AS
'*6C387FC3893DBA1E3BA155E74754DA6682D04747'
- GRANT SELECT ON users_info_db.* TO users_info_multi_hosts@'host1'
# Different password than the others users_info_multi_hosts
- >-
CREATE USER users_info_multi_hosts@'host2'
IDENTIFIED WITH mysql_native_password AS
'*CB3326D5279DE7915FE5D743232165EE887883CA'
- GRANT SELECT ON users_info_db.* TO users_info_multi_hosts@'host2'
- >-
CREATE USER users_info_tls_none@'host'
IDENTIFIED WITH mysql_native_password AS
'*CB3326D5279DE7915FE5D743232165EE887883CA' REQUIRE NONE
- GRANT SELECT ON users_info_db.* TO users_info_tls_none@'host'
- >-
CREATE USER users_info_tls_ssl@'host'
IDENTIFIED WITH mysql_native_password AS
'*CB3326D5279DE7915FE5D743232165EE887883CA' REQUIRE SSL
- GRANT SELECT ON users_info_db.* TO users_info_tls_ssl@'host'
- >-
CREATE USER users_info_tls_cipher@'host'
IDENTIFIED WITH mysql_native_password AS
'*CB3326D5279DE7915FE5D743232165EE887883CA'
REQUIRE CIPHER 'ECDH-RSA-AES256-SHA384'
- GRANT SELECT ON users_info_db.* TO users_info_tls_cipher@'host'
- >-
CREATE USER users_info_tls_x509@'host'
IDENTIFIED WITH mysql_native_password AS
'*CB3326D5279DE7915FE5D743232165EE887883CA' REQUIRE X509
- GRANT SELECT ON users_info_db.* TO users_info_tls_x509@'host'
- >-
CREATE USER users_info_tls_subject@'host'
IDENTIFIED WITH mysql_native_password AS
'*CB3326D5279DE7915FE5D743232165EE887883CA'
REQUIRE SUBJECT '/CN=Bob/O=MyDom/C=US/ST=Oregon/L=Portland'
- GRANT SELECT ON users_info_db.* TO users_info_tls_subject@'host'
- >-
CREATE USER users_info_tls_issuer@'host'
IDENTIFIED WITH mysql_native_password AS
'*CB3326D5279DE7915FE5D743232165EE887883CA'
REQUIRE ISSUER '/C=FI/ST=Somewhere/L=City/
O=CompanyX/CN=Bob/emailAddress=bob@companyx.com'
- GRANT SELECT ON users_info_db.* TO users_info_tls_issuer@'host'
- >-
CREATE USER users_info_tls_subject_issuer@'host'
IDENTIFIED WITH mysql_native_password AS
'*CB3326D5279DE7915FE5D743232165EE887883CA'
REQUIRE SUBJECT '/CN=Bob/O=MyDom/C=US/ST=Oregon/L=Portland'
AND ISSUER '/C=FI/ST=Somewhere/L=City/
O=CompanyX/CN=Bob/emailAddress=bob@companyx.com'
- >-
GRANT SELECT ON users_info_db.*
TO users_info_tls_subject_issuer@'host'
- >-
CREATE USER users_info_tls_sub_issu_ciph@'host'
IDENTIFIED WITH mysql_native_password AS
'*CB3326D5279DE7915FE5D743232165EE887883CA'
REQUIRE SUBJECT '/CN=Bob/O=MyDom/C=US/ST=Oregon/L=Portland'
AND ISSUER '/C=FI/ST=Somewhere/L=City/
O=CompanyX/CN=Bob/emailAddress=bob@companyx.com'
AND CIPHER 'ECDH-RSA-AES256-SHA384'
- >-
GRANT SELECT ON users_info_db.*
TO users_info_tls_sub_issu_ciph@'host'
- name: Mysql_info users_info | Prepare tests users for MariaDB
community.mysql.mysql_query:
query:
- >-
CREATE USER users_info_socket@'users_info.com' IDENTIFIED WITH
unix_socket
- GRANT ALL ON *.* to users_info_socket@'users_info.com'
when:
- db_engine == 'mariadb'
- name: Mysql_info users_info | Prepare tests users for MySQL
community.mysql.mysql_query:
query:
- >-
CREATE USER users_info_sha256@'users_info.com' IDENTIFIED WITH
sha256_password BY 'msandbox'
- GRANT ALL ON *.* to users_info_sha256@'users_info.com'
when:
- db_engine == 'mysql'
- name: Mysql_info users_info | Prepare tests users for MySQL 8+
community.mysql.mysql_query:
query:
- >-
CREATE USER users_info_caching_sha2@'users_info.com' IDENTIFIED WITH
caching_sha2_password BY 'msandbox'
- GRANT ALL ON *.* to users_info_caching_sha2@'users_info.com'
when:
- db_engine == 'mysql'
- db_version is version('8.0', '>=')
# ================================== Tests ==============================
- name: Mysql_info users_info | Collect users_info
community.mysql.mysql_info:
filter:
- users_info
register: result
- name: Mysql_info users_info | Recreate users from mysql_info result
community.mysql.mysql_user:
name: "{{ item.name }}"
host: "{{ item.host }}"
plugin: "{{ item.plugin | default(omit) }}"
plugin_auth_string: "{{ item.plugin_auth_string | default(omit) }}"
plugin_hash_string: "{{ item.plugin_hash_string | default(omit) }}"
tls_requires: "{{ item.tls_requires | default(omit) }}"
priv: "{{ item.priv | default(omit) }}"
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 }}"
register: recreate_users_result
failed_when:
- >-
recreate_users_result is changed or
recreate_users_result.msg != 'User unchanged'
when:
- item.name != 'root'
- item.name != 'mysql'
- item.name != 'mariadb.sys'
- item.name != 'mysql.sys'
- item.name != 'mysql.infoschema'
- item.name != 'mysql.session'
# ================================== Cleanup ============================
- name: Mysql_info users_info | Cleanup users_info
community.mysql.mysql_user:
name: "{{ item }}"
host_all: true
column_case_sensitive: true
state: absent
loop:
- users_info_adm
- users_info_schema
- users_info_table
- users_info_col
- users_info_proc
- users_info_multi
- users_info_db
- users_info_usage_only
- users_info_columns_uppercase
- users_info_multi_hosts
- users_info_tls_none
- users_info_tls_ssl
- users_info_tls_cipher
- users_info_tls_x509
- users_info_tls_subject
- users_info_tls_issuer
- users_info_tls_subject_issuer
- users_info_tls_sub_issu_ciph
- name: Mysql_info users_info | Cleanup databases
community.mysql.mysql_db:
name:
- users_info_db
- users_info_db2
- users_info_db3
state: absent
- name: Mysql_info users_info | Cleanup sql file for the procedure
ansible.builtin.file:
path: /root/create_procedure.sql
state: absent

View file

@ -1,86 +0,0 @@
---
- name: set fact tls_enabled
command: "{{ mysql_command }} \"-e SHOW VARIABLES LIKE 'have_ssl';\""
register: result
- set_fact:
tls_enabled: "{{ 'YES' in result.stdout | bool | default('false', true) }}"
- vars:
mysql_parameters: &mysql_params
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_port: '{{ mysql_primary_port }}'
when: tls_enabled
block:
# ============================================================
- name: get server certificate
copy:
content: "{{ lookup('pipe', \"openssl s_client -starttls mysql -connect localhost:3307 -showcerts 2>/dev/null </dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p'\") }}"
dest: /tmp/cert.pem
delegate_to: localhost
- name: Drop mysql user if exists
mysql_user:
<<: *mysql_params
name: '{{ user_name_1 }}'
host_all: true
state: absent
ignore_errors: yes
- name: create user with ssl requirement
mysql_user:
<<: *mysql_params
name: "{{ user_name_1 }}"
host: "%"
password: "{{ user_password_1 }}"
tls_requires:
SSL:
- name: attempt connection with newly created user (expect failure)
mysql_info:
filter: version
login_user: '{{ user_name_1 }}'
login_password: '{{ user_password_1 }}'
login_host: '{{ mysql_host }}'
login_port: '{{ mysql_primary_port }}'
ca_cert: /tmp/cert.pem
register: result
ignore_errors: yes
- assert:
that:
- result is failed
when:
- connector_name == 'pymysql'
- assert:
that:
- result is succeeded
when:
- connector_name != 'pymysql'
- name: attempt connection with newly created user ignoring hostname
mysql_info:
filter: version
login_user: '{{ user_name_1 }}'
login_password: '{{ user_password_1 }}'
login_host: '{{ mysql_host }}'
login_port: '{{ mysql_primary_port }}'
ca_cert: /tmp/cert.pem
check_hostname: no
register: result
ignore_errors: yes
- assert:
that:
- result is succeeded or 'pymysql >= 0.7.11 is required' in result.msg
- name: Drop mysql user
mysql_user:
<<: *mysql_params
name: '{{ user_name_1 }}'
host_all: true
state: absent

View file

@ -1,11 +1,10 @@
---
####################################################################
# WARNING: These are designed specifically for Ansible tests #
# and should not be used as examples of how to write Ansible roles #
####################################################################
# Test code for mysql_info module
# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) <andrew.a.klychkov@gmail.com>
# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) <aaklychkov@mail.ru>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
###################
@ -25,14 +24,14 @@
- name: mysql_info - create default config file
template:
src: my.cnf.j2
dest: "{{ playbook_dir }}/root/.my.cnf"
dest: /root/.my.cnf
mode: '0400'
# Create non-default MySQL config file with credentials
- name: mysql_info - create non-default config file
template:
src: my.cnf.j2
dest: "{{ playbook_dir }}/root/non-default_my.cnf"
dest: /root/non-default_my.cnf
mode: '0400'
###############
@ -44,23 +43,17 @@
login_user: '{{ mysql_user }}'
login_host: '{{ mysql_host }}'
login_port: '{{ mysql_primary_port }}'
config_file: "{{ playbook_dir }}/root/.my.cnf"
register: result
- assert:
that:
- result is not changed
- db_version in result.version.full
- result.settings != {}
- result.global_status != {}
- result.databases != {}
- result.engines != {}
- result.users != {}
- result.server_engine == 'MariaDB' or result.server_engine == 'MySQL'
- name: mysql_info - Test connector informations display
ansible.builtin.import_tasks:
file: connector_info.yml
- result.changed == false
- result.version != {}
- result.settings != {}
- result.global_status != {}
- result.databases != {}
- result.engines != {}
- result.users != {}
# Access by non-default cred file
- name: mysql_info - check non-default cred file
@ -68,12 +61,12 @@
login_user: '{{ mysql_user }}'
login_host: '{{ mysql_host }}'
login_port: '{{ mysql_primary_port }}'
config_file: "{{ playbook_dir }}/root/non-default_my.cnf"
config_file: /root/non-default_my.cnf
register: result
- assert:
that:
- result is not changed
- result.changed == false
- result.version != {}
# Remove cred files
@ -81,9 +74,9 @@
file:
path: '{{ item }}'
state: absent
loop:
- "{{ playbook_dir }}/.my.cnf"
- "{{ playbook_dir }}/non-default_my.cnf"
with_items:
- /root/.my.cnf
- /root/non-default_my.cnf
# Access with password
- name: mysql_info - check access with password
@ -93,8 +86,8 @@
- assert:
that:
- result is not changed
- result.version != {}
- result.changed == false
- result.version != {}
# Test excluding
- name: Collect all info except settings and users
@ -105,13 +98,13 @@
- assert:
that:
- result is not changed
- result.version != {}
- result.global_status != {}
- result.databases != {}
- result.engines != {}
- result.settings is not defined
- result.users is not defined
- result.changed == false
- result.version != {}
- result.global_status != {}
- result.databases != {}
- result.engines != {}
- result.settings is not defined
- result.users is not defined
# Test including
- name: Collect info only about version and databases
@ -124,20 +117,75 @@
- assert:
that:
- result is not changed
- result.version != {}
- result.databases != {}
- result.engines is not defined
- result.settings is not defined
- result.global_status is not defined
- result.users is not defined
- result.changed == false
- result.version != {}
- result.databases != {}
- result.engines is not defined
- result.settings is not defined
- result.global_status is not defined
- result.users is not defined
- include_tasks: issue-28.yml
# 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
- name: Import tasks file to tests tables count in database filter
ansible.builtin.import_tasks:
file: filter_databases.yml
- assert:
that:
- result.changed == false
- result.databases != {}
- result.databases.mysql == {}
- name: Import tasks file to tests users_info filter
ansible.builtin.import_tasks:
file: filter_users_info.yml
########################################################
# 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.changed == false
- 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.changed == false
- result.databases.acme == {}
- result.databases.mysql == {}
- name: Remove acme database
mysql_db:
<<: *mysql_params
name: acme
state: absent

View file

@ -1,15 +1,9 @@
mysql_user: root
mysql_password: msandbox
mysql_host: '{{ gateway_addr }}'
mysql_primary_port: 3307
db_name: data
test_db: testdb
test_table1: test1
test_table2: test2
test_table3: test3
test_table4: test4
test_script_path: /tmp/test.sql
user_name_1: 'db_user1'
user_password_1: 'gadfFDSdtTU^Sdfuj'

View file

@ -1,3 +1,2 @@
---
dependencies:
- setup_controller
- setup_mysql

View file

@ -1,86 +0,0 @@
---
- name: set fact tls_enabled
command: "{{ mysql_command }} \"-e SHOW VARIABLES LIKE 'have_ssl';\""
register: result
- set_fact:
tls_enabled: "{{ 'YES' in result.stdout | bool | default('false', true) }}"
- vars:
mysql_parameters: &mysql_params
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_port: '{{ mysql_primary_port }}'
when: tls_enabled
block:
# ============================================================
- name: get server certificate
copy:
content: "{{ lookup('pipe', \"openssl s_client -starttls mysql -connect localhost:3307 -showcerts 2>/dev/null </dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p'\") }}"
dest: /tmp/cert.pem
delegate_to: localhost
- name: Drop mysql user if exists
mysql_user:
<<: *mysql_params
name: '{{ user_name_1 }}'
host_all: true
state: absent
ignore_errors: yes
- name: create user with ssl requirement
mysql_user:
<<: *mysql_params
name: "{{ user_name_1 }}"
host: "%"
password: "{{ user_password_1 }}"
tls_requires:
SSL:
- name: attempt connection with newly created user (expect failure)
mysql_query:
query: 'SHOW DATABASES'
login_user: '{{ user_name_1 }}'
login_password: '{{ user_password_1 }}'
login_host: '{{ mysql_host }}'
login_port: '{{ mysql_primary_port }}'
ca_cert: /tmp/cert.pem
register: result
ignore_errors: yes
- assert:
that:
- result is failed
when:
- connector_name == 'pymysql'
- assert:
that:
- result is succeeded
when:
- connector_name != 'pymysql'
- name: attempt connection with newly created user ignoring hostname
mysql_query:
query: 'SHOW DATABASES'
login_user: '{{ user_name_1 }}'
login_password: '{{ user_password_1 }}'
login_host: '{{ mysql_host }}'
login_port: '{{ mysql_primary_port }}'
ca_cert: /tmp/cert.pem
check_hostname: no
register: result
ignore_errors: yes
- assert:
that:
- result is succeeded or 'pymysql >= 0.7.11 is required' in result.msg
- name: Drop mysql user
mysql_user:
<<: *mysql_params
name: '{{ user_name_1 }}'
host: "%"
state: absent

View file

@ -5,5 +5,3 @@
# mysql_query module initial CI tests
- import_tasks: mysql_query_initial.yml
- include_tasks: issue-28.yml

View file

@ -1,12 +1,11 @@
---
# Test code for mysql_query module
# Copyright: (c) 2020, Andrew Klychkov (@Andersson007) <andrew.a.klychkov@gmail.com>
# Copyright: (c) 2020, Andrew Klychkov (@Andersson007) <aaklychkov@mail.ru>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
- vars:
mysql_parameters: &mysql_params
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
block:
@ -17,11 +16,10 @@
query: 'CREATE DATABASE {{ test_db }}'
register: result
- name: Assert that create db test_db is changed and returns expected query
assert:
- assert:
that:
- result is changed
- result.executed_queries == ['CREATE DATABASE {{ test_db }}']
- result is changed
- result.executed_queries == ['CREATE DATABASE {{ test_db }}']
- name: Create {{ test_table1 }}
mysql_query:
@ -30,31 +28,26 @@
query: 'CREATE TABLE {{ test_table1 }} (id int)'
register: result
- name: Assert that create table test_table1 is changed and returns expected query
assert:
- assert:
that:
- result is changed
- result.executed_queries == ['CREATE TABLE {{ test_table1 }} (id int)']
- result.execution_time_ms[0] > 0
- result is changed
- result.executed_queries == ['CREATE TABLE {{ test_table1 }} (id int)']
- name: Insert test data
mysql_query:
<<: *mysql_params
login_db: '{{ test_db }}'
query:
- 'INSERT INTO {{ test_table1 }} VALUES (1), (2)'
- 'INSERT INTO {{ test_table1 }} VALUES (3)'
- 'INSERT INTO {{ test_table1 }} VALUES (1), (2)'
- 'INSERT INTO {{ test_table1 }} VALUES (3)'
single_transaction: yes
register: result
- name: Assert that inserting test data is changed and returns expected query and results
assert:
- assert:
that:
- result is changed
- result.rowcount == [2, 1]
- result.executed_queries == ['INSERT INTO {{ test_table1 }} VALUES (1), (2)', 'INSERT INTO {{ test_table1 }} VALUES (3)']
- result.execution_time_ms[0] > 0
- result.execution_time_ms[1] > 0
- result is changed
- result.rowcount == [2, 1]
- result.executed_queries == ['INSERT INTO {{ test_table1 }} VALUES (1), (2)', 'INSERT INTO {{ test_table1 }} VALUES (3)']
- name: Check data in {{ test_table1 }}
mysql_query:
@ -63,15 +56,14 @@
query: 'SELECT * FROM {{ test_table1 }}'
register: result
- name: Assert that query data in test_table1 is not changed and returns expected query and results
assert:
- assert:
that:
- result is not changed
- result.executed_queries == ['SELECT * FROM {{ test_table1 }}']
- result.rowcount == [3]
- result.query_result[0][0].id == 1
- result.query_result[0][1].id == 2
- result.query_result[0][2].id == 3
- result is not changed
- result.executed_queries == ['SELECT * FROM {{ test_table1 }}']
- result.rowcount == [3]
- result.query_result[0][0].id == 1
- result.query_result[0][1].id == 2
- result.query_result[0][2].id == 3
- name: Check data in {{ test_table1 }} using positional args
mysql_query:
@ -82,13 +74,12 @@
- 1
register: result
- name: Assert that query data in test_table1 using positional args is not changed and returns expected query and results
assert:
- assert:
that:
- result is not changed
- result.executed_queries == ["SELECT * FROM {{ test_table1 }} WHERE id = 1"]
- result.rowcount == [1]
- result.query_result[0][0].id == 1
- result is not changed
- result.executed_queries == ["SELECT * FROM {{ test_table1 }} WHERE id = 1"]
- result.rowcount == [1]
- result.query_result[0][0].id == 1
- name: Check data in {{ test_table1 }} using named args
mysql_query:
@ -99,13 +90,12 @@
some_id: 1
register: result
- name: Assert that query data in test_table1 using named args is not changed and returns expected query and results
assert:
- assert:
that:
- result is not changed
- result.executed_queries == ["SELECT * FROM {{ test_table1 }} WHERE id = 1"]
- result.rowcount == [1]
- result.query_result[0][0].id == 1
- result is not changed
- result.executed_queries == ["SELECT * FROM {{ test_table1 }} WHERE id = 1"]
- result.rowcount == [1]
- result.query_result[0][0].id == 1
- name: Update data in {{ test_table1 }}
mysql_query:
@ -117,12 +107,11 @@
new_id: 0
register: result
- name: Assert that update data in test_table1 is changed and returns the expected query
assert:
- assert:
that:
- result is changed
- result.executed_queries == ['UPDATE {{ test_table1 }} SET id = 0 WHERE id = 1']
- result.rowcount == [1]
- result is changed
- result.executed_queries == ['UPDATE {{ test_table1 }} SET id = 0 WHERE id = 1']
- result.rowcount == [1]
- name: Check the prev update - row with value 1 does not exist anymore
mysql_query:
@ -133,12 +122,11 @@
some_id: 1
register: result
- name: Assert that query that check the prev update is not changed and returns the expected query with id = 1
assert:
- assert:
that:
- result is not changed
- result.executed_queries == ['SELECT * FROM {{ test_table1 }} WHERE id = 1']
- result.rowcount == [0]
- result is not changed
- result.executed_queries == ['SELECT * FROM {{ test_table1 }} WHERE id = 1']
- result.rowcount == [0]
- name: Check the prev update - row with value - exist
mysql_query:
@ -149,12 +137,11 @@
some_id: 0
register: result
- name: Assert that query that check the prev update is not changed and returns the expected query with id = 0
assert:
- assert:
that:
- result is not changed
- result.executed_queries == ['SELECT * FROM {{ test_table1 }} WHERE id = 0']
- result.rowcount == [1]
- result is not changed
- result.executed_queries == ['SELECT * FROM {{ test_table1 }} WHERE id = 0']
- result.rowcount == [1]
- name: Update data in {{ test_table1 }} again
mysql_query:
@ -166,12 +153,11 @@
new_id: 0
register: result
- name: Assert that update data in test_table1 again is not changed and returns expected query
assert:
- assert:
that:
- result is not changed
- result.executed_queries == ['UPDATE {{ test_table1 }} SET id = 0 WHERE id = 1']
- result.rowcount == [0]
- result is not changed
- result.executed_queries == ['UPDATE {{ test_table1 }} SET id = 0 WHERE id = 1']
- result.rowcount == [0]
- name: Delete data from {{ test_table1 }}
mysql_query:
@ -182,12 +168,11 @@
- 'SELECT * FROM {{ test_table1 }} WHERE id = 0'
register: result
- name: Assert that delete data from test_table1 is changed an returns expected query
assert:
- assert:
that:
- result is changed
- result.executed_queries == ['DELETE FROM {{ test_table1 }} WHERE id = 0', 'SELECT * FROM {{ test_table1 }} WHERE id = 0']
- result.rowcount == [1, 0]
- result is changed
- result.executed_queries == ['DELETE FROM {{ test_table1 }} WHERE id = 0', 'SELECT * FROM {{ test_table1 }} WHERE id = 0']
- result.rowcount == [1, 0]
- name: Delete data from {{ test_table1 }} again
mysql_query:
@ -196,28 +181,26 @@
query: 'DELETE FROM {{ test_table1 }} WHERE id = 0'
register: result
- name: Assert that delete data from test_table1 again is not changed and returns expected query
assert:
- assert:
that:
- result is not changed
- result.executed_queries == ['DELETE FROM {{ test_table1 }} WHERE id = 0']
- result.rowcount == [0]
- result is not changed
- result.executed_queries == ['DELETE FROM {{ test_table1 }} WHERE id = 0']
- result.rowcount == [0]
- name: Truncate {{ test_table1 }}
mysql_query:
<<: *mysql_params
login_db: '{{ test_db }}'
query:
- 'TRUNCATE {{ test_table1 }}'
- 'SELECT * FROM {{ test_table1 }}'
- 'TRUNCATE {{ test_table1 }}'
- 'SELECT * FROM {{ test_table1 }}'
register: result
- name: Assert that truncate test_table1 is changed and returns expected query
assert:
- assert:
that:
- result is changed
- result.executed_queries == ['TRUNCATE {{ test_table1 }}', 'SELECT * FROM {{ test_table1 }}']
- result.rowcount == [0, 0]
- result is changed
- result.executed_queries == ['TRUNCATE {{ test_table1 }}', 'SELECT * FROM {{ test_table1 }}']
- result.rowcount == [0, 0]
- name: Rename {{ test_table1 }}
mysql_query:
@ -226,12 +209,11 @@
query: 'RENAME TABLE {{ test_table1 }} TO {{ test_table2 }}'
register: result
- name: Assert that rename table test_table1 is changed and returns expected query
assert:
- assert:
that:
- result is changed
- result.executed_queries == ['RENAME TABLE {{ test_table1 }} TO {{ test_table2 }}']
- result.rowcount == [0]
- result is changed
- result.executed_queries == ['RENAME TABLE {{ test_table1 }} TO {{ test_table2 }}']
- result.rowcount == [0]
- name: Check the prev rename
mysql_query:
@ -241,10 +223,9 @@
register: result
ignore_errors: yes
- name: Assert that query old table is failed
assert:
- assert:
that:
- result is failed
- result.failed == true
- name: Check the prev rename
mysql_query:
@ -253,140 +234,9 @@
query: 'SELECT * FROM {{ test_table2 }}'
register: result
- name: Assert that query new table succeed and returns 0 row
assert:
that:
- result.rowcount == [0]
- name: Create {{ test_table3 }}
mysql_query:
<<: *mysql_params
login_db: '{{ test_db }}'
query: 'CREATE TABLE {{ test_table3 }} (id int, story text)'
- name: Add data to {{ test_table3 }}
mysql_query:
<<: *mysql_params
login_db: '{{ test_db }}'
query: "INSERT INTO {{ test_table3 }} (id, story) VALUES (1, 'first'), (2, 'second')"
- name: Select from {{ test_table3 }}
mysql_query:
<<: *mysql_params
login_db: '{{ test_db }}'
query: 'SELECT id, story FROM {{ test_table3 }}'
register: result
- name: Assert that select from test_table3 returns 2 rows
assert:
that:
- result.rowcount == [2]
- name: Pass wrong query type
mysql_query:
<<: *mysql_params
login_db: '{{ test_db }}'
query: {'this type is': 'wrong'}
register: result
ignore_errors: yes
- name: Assert that pass wrong query type is failed
assert:
that:
- result is failed
- result.msg is search('the query option value must be a string or list')
- name: Pass wrong query element
mysql_query:
<<: *mysql_params
login_db: '{{ test_db }}'
query:
- 'SELECT now()'
- {'this type is': 'wrong'}
register: result
ignore_errors: yes
- name: Assert that pass wrong query element is failed
assert:
that:
- result is failed
- result.msg is search('the elements in query list must be strings')
- name: Create {{ test_table4 }}
mysql_query:
<<: *mysql_params
login_db: '{{ test_db }}'
query: 'CREATE TABLE {{ test_table4 }} (id int primary key, story text)'
- name: Insert test data using replace statement
mysql_query:
<<: *mysql_params
login_db: '{{ test_db }}'
query: "REPLACE INTO {{ test_table4 }} VALUES (1, 'first')"
single_transaction: yes
register: result
- name: Assert that insert test data using replace statement is changed
assert:
that:
- result is changed
- result.rowcount == [1]
- name: Replace test data
mysql_query:
<<: *mysql_params
login_db: '{{ test_db }}'
query: "REPLACE INTO {{ test_table4 }} VALUES (1, 'one')"
single_transaction: yes
register: result
- assert:
that:
- result is changed
- result.rowcount == [2]
# Issue https://github.com/ansible-collections/community.mysql/issues/268
- name: Create table
mysql_query:
<<: *mysql_params
login_db: '{{ test_db }}'
query: "CREATE TABLE issue268 (id int)"
single_transaction: yes
# Issue https://github.com/ansible-collections/community.mysql/issues/268
- name: Create table with IF NOT EXISTS
mysql_query:
<<: *mysql_params
login_db: '{{ test_db }}'
query: "CREATE TABLE IF NOT EXISTS issue268 (id int)"
single_transaction: yes
register: result
# Issue https://github.com/ansible-collections/community.mysql/issues/268
- name: Assert that create table IF NOT EXISTS is not changed with pymysql
assert:
that:
# PyMySQL driver throws a warning for version before 0.10.0
- result is not changed
when:
- connector_name == 'pymysql'
- connector_version is version('0.10.0', '<')
# Issue https://github.com/ansible-collections/community.mysql/issues/268
- name: Assert that create table IF NOT EXISTS is changed with mysqlclient
assert:
that:
# Mysqlclient 2.0.1 and pymysql 0.10.0+ drivers throws no warning,
# so it's impossible to figure out if the state was changed or not.
# We assume that it was for DDL queries by default in the code
- result is changed
when:
- >
connector_name == 'mysqlclient'
or (
connector_name == 'pymysql'
and connector_version is version('0.10.0', '>')
)
- result.rowcount == [0]
- name: Drop db {{ test_db }}
mysql_query:
@ -394,15 +244,7 @@
query: 'DROP DATABASE {{ test_db }}'
register: result
- name: Assert that drop database is changed and returns expected query
assert:
- assert:
that:
- result is changed
- result.executed_queries == ['DROP DATABASE {{ test_db }}']
always:
- name: Clean up test_db
mysql_query:
<<: *mysql_params
query: 'DROP DATABASE IF EXISTS {{ test_db }}'
- result is changed
- result.executed_queries == ['DROP DATABASE {{ test_db }}']

View file

@ -1,17 +1,14 @@
mysql_user: root
mysql_password: msandbox
mysql_host: '{{ gateway_addr }}'
mysql_host: 127.0.0.1
mysql_primary_port: 3307
mysql_replica1_port: 3308
mysql_replica2_port: 3309
test_db: test_db
test_table: test_table
test_primary_delay: 60
test_master_delay: 60
replication_user: replication_user
replication_pass: replication_pass
dump_path: /tmp/dump.sql
test_channel: test_channel-1
user_name_1: 'db_user1'
user_password_1: 'gadfFDSdtTU^Sdfuj'

View file

@ -1,3 +1,3 @@
---
dependencies:
- setup_controller
- setup_mysql

View file

@ -1,167 +0,0 @@
---
- vars:
mysql_parameters: &mysql_params
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_port: '{{ mysql_primary_port }}'
block:
# start replica so it is available for testing
- name: Start replica
mysql_replication:
<<: *mysql_params
login_port: '{{ mysql_replica1_port }}'
mode: startreplica
register: result
- assert:
that:
- result is changed
- result.queries == ["START SLAVE"] or result.queries == ["START REPLICA"]
- name: Drop {{ user_name_1 }} if exists
mysql_user:
<<: *mysql_params
name: '{{ user_name_1 }}'
host: '{{ gateway_addr }}'
state: absent
ignore_errors: yes
# First test
# check if user creation works with force_context and is replicated
- name: create user with force_context
mysql_user:
<<: *mysql_params
name: "{{ user_name_1 }}"
host: '{{ gateway_addr }}'
password: "{{ user_password_1 }}"
priv: '*.*:ALL,GRANT'
force_context: yes
- name: attempt connection on replica1 with newly created user (expect success)
mysql_replication:
mode: getprimary
login_user: '{{ user_name_1 }}'
login_password: '{{ user_password_1 }}'
login_host: '{{ mysql_host }}'
login_port: '{{ mysql_replica1_port }}'
register: result
ignore_errors: yes
- assert:
that:
- result is succeeded
- name: Drop user
mysql_user:
<<: *mysql_params
name: '{{ user_name_1 }}'
host: '{{ gateway_addr }}'
state: absent
force_context: yes
- name: attempt connection on replica with freshly removed user (expect failure)
mysql_replication:
mode: getprimary
login_user: '{{ user_name_1 }}'
login_password: '{{ user_password_1 }}'
login_host: '{{ mysql_host }}'
login_port: '{{ mysql_replica1_port }}'
register: result
ignore_errors: yes
- assert:
that:
- result is failed
# Prepare replica1 for testing with a replication filter in place
# Stop replication, create a filter and restart replication on replica1.
- name: Stop replica
mysql_replication:
<<: *mysql_params
login_port: '{{ mysql_replica1_port }}'
mode: stopreplica
register: result
- assert:
that:
- result is changed
- result.queries == ["STOP SLAVE"] or result.queries == ["STOP REPLICA"]
- name: Create replication filter MySQL
shell: "echo \"CHANGE REPLICATION FILTER REPLICATE_IGNORE_DB = (mysql);\" | {{ mysql_command_wo_port }} -P{{ mysql_replica1_port }}"
when: db_engine == 'mysql'
- name: Create replication filter MariaDB
shell: "echo \"SET GLOBAL replicate_ignore_db = 'mysql';\" | {{ mysql_command_wo_port }} -P{{ mysql_replica1_port }}"
when: db_engine == 'mariadb'
- name: Start replica
mysql_replication:
<<: *mysql_params
login_port: '{{ mysql_replica1_port }}'
mode: startreplica
register: result
- assert:
that:
- result is changed
- result.queries == ["START SLAVE"] or result.queries == ["START REPLICA"]
# Second test
# Filter in place, ready to test if user creation is filtered with force_context
- name: create user with force_context
mysql_user:
<<: *mysql_params
name: "{{ user_name_1 }}"
host: "{{ gateway_addr }}"
password: "{{ user_password_1 }}"
priv: '*.*:ALL,GRANT'
force_context: yes
- name: attempt connection on replica with newly created user (expect failure)
mysql_replication:
mode: getprimary
login_user: '{{ user_name_1 }}'
login_password: '{{ user_password_1 }}'
login_host: '{{ mysql_host }}'
login_port: '{{ mysql_replica1_port }}'
register: result
ignore_errors: yes
- assert:
that:
- result is failed
- name: Drop user
mysql_user:
<<: *mysql_params
name: '{{ user_name_1 }}'
host: "{{ gateway_addr }}"
state: absent
force_context: yes
# restore normal replica1 operation
# Stop replication and remove the filter
- name: Stop replica
mysql_replication:
<<: *mysql_params
login_port: '{{ mysql_replica1_port }}'
mode: stopreplica
register: result
- assert:
that:
- result is changed
- result.queries == ["STOP SLAVE"] or result.queries == ["STOP REPLICA"]
- name: Remove replication filter MySQL
shell: "echo \"CHANGE REPLICATION FILTER REPLICATE_IGNORE_DB = ();\" | {{ mysql_command_wo_port }} -P{{ mysql_replica1_port }}"
when: db_engine == 'mysql'
- name: Remove replication filter MariaDB
shell: "echo \"SET GLOBAL replicate_ignore_db = '';\" | {{ mysql_command_wo_port }} -P{{ mysql_replica1_port }}"
when: db_engine == 'mariadb'

View file

@ -1,86 +0,0 @@
---
- name: set fact tls_enabled
command: "{{ mysql_command }} \"-e SHOW VARIABLES LIKE 'have_ssl';\""
register: result
- set_fact:
tls_enabled: "{{ 'YES' in result.stdout | bool | default('false', true) }}"
- vars:
mysql_parameters: &mysql_params
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_port: '{{ mysql_primary_port }}'
when: tls_enabled
block:
# ============================================================
- name: get server certificate
copy:
content: "{{ lookup('pipe', \"openssl s_client -starttls mysql -connect localhost:3307 -showcerts 2>/dev/null </dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p'\") }}"
dest: /tmp/cert.pem
delegate_to: localhost
- name: Drop mysql user if exists
mysql_user:
<<: *mysql_params
name: '{{ user_name_1 }}'
host_all: true
state: absent
ignore_errors: yes
- name: create user with ssl requirement
mysql_user:
<<: *mysql_params
name: "{{ user_name_1 }}"
password: "{{ user_password_1 }}"
priv: '*.*:ALL,GRANT'
tls_requires:
SSL:
- name: attempt connection with newly created user (expect failure)
mysql_replication:
mode: getprimary
login_user: '{{ user_name_1 }}'
login_password: '{{ user_password_1 }}'
login_host: '{{ mysql_host }}'
login_port: '{{ mysql_primary_port }}'
ca_cert: /tmp/cert.pem
register: result
ignore_errors: yes
- assert:
that:
- result is failed
when:
- connector_name == 'pymysql'
- assert:
that:
- result is succeeded
when:
- connector_name != 'pymysql'
- name: attempt connection with newly created user ignoring hostname
mysql_replication:
mode: getprimary
login_user: '{{ user_name_1 }}'
login_password: '{{ user_password_1 }}'
login_host: '{{ mysql_host }}'
login_port: '{{ mysql_primary_port }}'
ca_cert: /tmp/cert.pem
check_hostname: no
register: result
ignore_errors: yes
- assert:
that:
- result is succeeded or 'pymysql >= 0.7.11 is required' in result.msg
- name: Drop mysql user
mysql_user:
<<: *mysql_params
name: '{{ user_name_1 }}'
host: '{{ gateway_addr }}'
state: absent

View file

@ -1,33 +1,19 @@
---
####################################################################
# WARNING: These are designed specifically for Ansible tests #
# and should not be used as examples of how to write Ansible roles #
####################################################################
# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) <andrew.a.klychkov@gmail.com>
# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) <aaklychkov@mail.ru>
# 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:
- import_tasks: mysql_replication_initial.yml
# Tests of replication filters and force_context
- include_tasks: issue-265.yml
# Tests of primary_delay parameter:
- import_tasks: mysql_replication_primary_delay.yml
# Tests of master_delay parameter:
- import_tasks: mysql_replication_master_delay.yml
# Tests of channel parameter:
- import_tasks: mysql_replication_channel.yml
when:
- db_engine == 'mysql' # FIXME: mariadb introduces FOR CHANNEL in 10.7
# Tests of resetprimary mode:
- import_tasks: mysql_replication_resetprimary_mode.yml
- include_tasks: issue-28.yml
# Tests of changereplication mode:
- import_tasks: mysql_replication_changereplication_mode.yml
when:
- db_engine == 'mysql'
- db_version is version('8.0.23', '>=')
# Tests of resetmaster mode:
- import_tasks: mysql_replication_resetmaster_mode.yml

View file

@ -1,65 +0,0 @@
---
- vars:
mysql_params: &mysql_params
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
block:
# Get primary log file and log pos:
- name: Get primary status
mysql_replication:
<<: *mysql_params
login_port: '{{ mysql_primary_port }}'
mode: getprimary
register: mysql_primary_status
# Test changereplication mode:
- name: Run replication
mysql_replication:
<<: *mysql_params
login_port: '{{ mysql_replica1_port }}'
mode: changereplication
primary_host: '{{ mysql_host }}'
primary_port: '{{ mysql_primary_port }}'
primary_user: '{{ replication_user }}'
primary_password: '{{ replication_pass }}'
primary_log_file: '{{ mysql_primary_status.File }}'
primary_log_pos: '{{ mysql_primary_status.Position }}'
primary_ssl_ca: ''
primary_ssl: no
register: result
- name: Assert that changereplication is changed and return expected query
assert:
that:
- result is changed
- result.queries == expected_queries
vars:
expected_queries: ["CHANGE REPLICATION SOURCE TO SOURCE_HOST='{{ mysql_host }}',\
SOURCE_USER='{{ replication_user }}',SOURCE_PASSWORD='********',\
SOURCE_PORT={{ mysql_primary_port }},SOURCE_LOG_FILE=\
'{{ mysql_primary_status.File }}',SOURCE_LOG_POS=\
{{ mysql_primary_status.Position }},SOURCE_SSL=0,SOURCE_SSL_CA=''"]
# Test changereplication mode with channel:
- name: Run replication
mysql_replication:
<<: *mysql_params
login_port: '{{ mysql_replica1_port }}'
mode: changereplication
primary_user: '{{ replication_user }}'
primary_password: '{{ replication_pass }}'
channel: '{{ test_channel }}'
register: with_channel_result_queries
- name: Assert that changereplication is changed and is called correctly with channel
assert:
that:
- with_channel_result_queries is changed
- with_channel_result_queries.queries == expected_queries
vars:
expected_queries: ["CHANGE REPLICATION SOURCE TO SOURCE_USER='{{ replication_user }}',\
SOURCE_PASSWORD='********' FOR CHANNEL '{{ test_channel }}'"]

View file

@ -1,179 +1,113 @@
---
# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) <andrew.a.klychkov@gmail.com>
# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) <aaklychkov@mail.ru>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
- vars:
mysql_params: &mysql_params
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
block:
# Get primary log file and log pos:
- name: Get primary status
# Get master log file and log pos:
- name: Get master status
mysql_replication:
<<: *mysql_params
login_port: '{{ mysql_primary_port }}'
mode: getprimary
mode: getmaster
register: mysql_primary_status
# Test changeprimary mode:
# Test changemaster mode:
- name: Run replication with channel
mysql_replication:
<<: *mysql_params
login_port: '{{ mysql_replica2_port }}'
mode: changeprimary
primary_host: '{{ mysql_host }}'
primary_port: '{{ mysql_primary_port }}'
primary_user: '{{ replication_user }}'
primary_password: '{{ replication_pass }}'
primary_log_file: '{{ mysql_primary_status.File }}'
primary_log_pos: '{{ mysql_primary_status.Position }}'
channel: '{{ test_channel }}'
register: result
- name: Assert that run replication with channel is changed and query matches for MariaDB and MySQL < 8.0.23
ansible.builtin.assert:
that:
- result is changed
- result.queries == result_query
when:
- >
db_engine == 'mariadb' or
(db_engine == 'mysql' and db_version is version('8.0.23', '<'))
vars:
result_query: ["CHANGE MASTER TO MASTER_HOST='{{ mysql_host }}',\
MASTER_USER='{{ replication_user }}',MASTER_PASSWORD='********',\
MASTER_PORT={{ mysql_primary_port }},MASTER_LOG_FILE=\
'{{ mysql_primary_status.File }}',MASTER_LOG_POS=\
{{ mysql_primary_status.Position }} FOR CHANNEL '{{ test_channel }}'"]
- name: Assert that run replication with channel is changed and query matches for MySQL >= 8.0.23
ansible.builtin.assert:
that:
- result is changed
- result.queries == result_query
when:
- db_engine == 'mysql'
- db_version is version('8.0.23', '>=')
vars:
result_query: ["CHANGE REPLICATION SOURCE TO SOURCE_HOST='{{ mysql_host }}',\
SOURCE_USER='{{ replication_user }}',SOURCE_PASSWORD='********',\
SOURCE_PORT={{ mysql_primary_port }},SOURCE_LOG_FILE=\
'{{ mysql_primary_status.File }}',SOURCE_LOG_POS=\
{{ mysql_primary_status.Position }} FOR CHANNEL '{{ test_channel }}'"]
# Test startreplica mode:
- name: Start replica with channel
mysql_replication:
<<: *mysql_params
login_port: '{{ mysql_replica2_port }}'
mode: startreplica
mode: changemaster
master_host: '{{ mysql_host }}'
master_port: '{{ mysql_primary_port }}'
master_user: '{{ replication_user }}'
master_password: '{{ replication_pass }}'
master_log_file: '{{ mysql_primary_status.File }}'
master_log_pos: '{{ mysql_primary_status.Position }}'
channel: '{{ test_channel }}'
register: result
- assert:
that:
- result is changed
- result.queries == result_query or result_query2
vars:
result_query: ["START SLAVE FOR CHANNEL '{{ test_channel }}'"]
result_query2: ["START REPLICA FOR CHANNEL '{{ test_channel }}'"]
- result is changed
- result.queries == ["CHANGE MASTER TO MASTER_HOST='{{ mysql_host }}',MASTER_USER='{{ replication_user }}',MASTER_PASSWORD='********',MASTER_PORT={{ mysql_primary_port }},MASTER_LOG_FILE='{{ mysql_primary_status.File }}',MASTER_LOG_POS={{ mysql_primary_status.Position }} FOR CHANNEL '{{ test_channel }}'"]
# Test getreplica mode:
# Test startslave mode:
- name: Start slave with channel
mysql_replication:
<<: *mysql_params
login_port: '{{ mysql_replica2_port }}'
mode: startslave
channel: '{{ test_channel }}'
register: result
- assert:
that:
- result is changed
- result.queries == ["START SLAVE FOR CHANNEL '{{ test_channel }}'"]
# Test getslave mode:
- name: Get standby status with channel
mysql_replication:
<<: *mysql_params
login_port: '{{ mysql_replica2_port }}'
mode: getreplica
mode: getslave
channel: '{{ test_channel }}'
register: replica_status
register: slave_status
- assert:
that:
- replica_status.Is_Replica is truthy(convert_bool=True)
- replica_status.Master_Host == mysql_host_value
- replica_status.Exec_Master_Log_Pos == mysql_primary_status.Position
- replica_status.Master_Port == mysql_primary_port_value
- replica_status.Last_IO_Errno == 0
- replica_status.Last_IO_Error == ''
- replica_status.Channel_Name == test_channel_value
- replica_status is not changed
vars:
mysql_host_value: '{{ mysql_host }}'
mysql_primary_port_value: '{{ mysql_primary_port }}'
test_channel_value: '{{ test_channel }}'
when:
- >
db_engine == 'mariadb' or
(db_engine == 'mysql' and db_version is version('8.0.22', '<'))
- slave_status.Is_Slave == true
- slave_status.Master_Host == '{{ mysql_host }}'
- slave_status.Exec_Master_Log_Pos == mysql_primary_status.Position
- slave_status.Master_Port == {{ mysql_primary_port }}
- slave_status.Last_IO_Errno == 0
- slave_status.Last_IO_Error == ''
- slave_status.Channel_Name == '{{ test_channel }}'
- slave_status is not changed
- assert:
that:
- replica_status.Is_Replica is truthy(convert_bool=True)
- replica_status.Source_Host == mysql_host_value
- replica_status.Exec_Source_Log_Pos == mysql_primary_status.Position
- replica_status.Source_Port == mysql_primary_port_value
- replica_status.Last_IO_Errno == 0
- replica_status.Last_IO_Error == ''
- replica_status.Channel_Name == test_channel_value
- replica_status is not changed
vars:
mysql_host_value: '{{ mysql_host }}'
mysql_primary_port_value: '{{ mysql_primary_port }}'
test_channel_value: '{{ test_channel }}'
when:
- db_engine == 'mysql'
- db_version is version('8.0.22', '>=')
# Test stopreplica mode:
- name: Stop replica with channel
# Test stopslave mode:
- name: Stop slave with channel
mysql_replication:
<<: *mysql_params
login_port: '{{ mysql_replica2_port }}'
mode: stopreplica
mode: stopslave
channel: '{{ test_channel }}'
register: result
- assert:
that:
- result is changed
- result.queries == result_query or result.queries == result_query2
vars:
result_query: ["STOP SLAVE FOR CHANNEL '{{ test_channel }}'"]
result_query2: ["STOP REPLICA FOR CHANNEL '{{ test_channel }}'"]
- result is changed
- result.queries == ["STOP SLAVE FOR CHANNEL '{{ test_channel }}'"]
# Test reset
- name: Reset replica with channel
- name: Reset slave with channel
mysql_replication:
<<: *mysql_params
login_port: '{{ mysql_replica2_port }}'
mode: resetreplica
mode: resetslave
channel: '{{ test_channel }}'
register: result
- assert:
that:
- result is changed
- result.queries == result_query or result.queries == result_query2
vars:
result_query: ["RESET SLAVE FOR CHANNEL '{{ test_channel }}'"]
result_query2: ["RESET REPLICA FOR CHANNEL '{{ test_channel }}'"]
- result is changed
- result.queries == ["RESET SLAVE FOR CHANNEL '{{ test_channel }}'"]
# Test reset all
- name: Reset replica all with channel
- name: Reset slave all with channel
mysql_replication:
<<: *mysql_params
login_port: '{{ mysql_replica2_port }}'
mode: resetreplicaall
mode: resetslaveall
channel: '{{ test_channel }}'
register: result
- assert:
that:
- result is changed
- result.queries == result_query or result.queries == result_query2
vars:
result_query: ["RESET SLAVE ALL FOR CHANNEL '{{ test_channel }}'"]
result_query2: ["RESET REPLICA ALL FOR CHANNEL '{{ test_channel }}'"]
- result is changed
- result.queries == ["RESET SLAVE ALL FOR CHANNEL '{{ test_channel }}'"]

View file

@ -1,37 +1,21 @@
---
# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) <andrew.a.klychkov@gmail.com>
# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) <aaklychkov@mail.ru>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
- vars:
mysql_params: &mysql_params
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_host: 127.0.0.1
block:
# We use iF NOT EXISTS because the GITHUB Action:
# "ansible-community/ansible-test-gh-action" uses "--retry-on-error".
# If test_mysql_replication fails, test will run again an without the IF
# NOT EXISTS, we see "Error 1396 (HY000): Operation CREATE USER failed..."
# which is misleading.
- name: Create user for mysql replication
shell:
"echo \"CREATE USER IF NOT EXISTS \
'{{ replication_user }}'@'{{ mysql_host }}' \
IDENTIFIED WITH mysql_native_password BY '{{ replication_pass }}'; \
GRANT REPLICATION SLAVE ON *.* TO \
'{{ replication_user }}'@'{{ mysql_host }}';\" | {{ mysql_command }}"
when: db_engine == 'mysql'
- name: alias mysql command to include default options
set_fact:
mysql_command: "mysql -u{{ mysql_user }} -p{{ mysql_password }} --protocol=tcp"
- name: Create user for mariadb replication
shell:
"echo \"CREATE USER IF NOT EXISTS \
'{{ replication_user }}'@'{{ mysql_host }}' \
IDENTIFIED BY '{{ replication_pass }}'; \
GRANT REPLICATION SLAVE ON *.* TO \
'{{ replication_user }}'@'{{ mysql_host }}';\" | {{ mysql_command }}"
when: db_engine == 'mariadb'
# Preparation:
- name: Create user for replication
shell: "echo \"CREATE USER '{{ replication_user }}'@'localhost' IDENTIFIED WITH mysql_native_password BY '{{ replication_pass }}'; GRANT REPLICATION SLAVE ON *.* TO '{{ replication_user }}'@'localhost';\" | {{ mysql_command }} -P{{ mysql_primary_port }}"
- name: Create test database
mysql_db:
@ -41,301 +25,180 @@
name: '{{ test_db }}'
- name: Dump all databases from the primary
shell:
cmd: >-
mysqldump
-u{{ mysql_user }}
-p{{ mysql_password }}
-h{{ mysql_host }}
-P{{ mysql_primary_port }}
--protocol=tcp
--all-databases
--ignore-table=mysql.innodb_index_stats
--ignore-table=mysql.innodb_table_stats
--master-data=2
> {{ dump_path }}
shell: 'mysqldump -u{{ mysql_user }} -p{{ mysql_password }} -h{{ mysql_host }} --protocol=tcp -P{{ mysql_primary_port }} --all-databases --ignore-table=mysql.innodb_index_stats --ignore-table=mysql.innodb_table_stats --master-data=2 > {{ dump_path }}'
- name: Restore the dump to replica1
shell:
cmd: >-
{{ mysql_command_wo_port }}
-P{{ mysql_replica1_port }} < {{ dump_path }}
shell: '{{ mysql_command }} -P{{ mysql_replica1_port }} < {{ dump_path }}'
- name: Restore the dump to replica2
shell:
cmd: >-
{{ mysql_command_wo_port }}
-P{{ mysql_replica2_port }} < {{ dump_path }}
shell: '{{ mysql_command }} -P{{ mysql_replica2_port }} < {{ dump_path }}'
# Test getprimary mode:
- name: Get primary status
# Test getmaster mode:
- name: Get master status
mysql_replication:
<<: *mysql_params
login_port: '{{ mysql_primary_port }}'
mode: getprimary
mode: getmaster
register: mysql_primary_status
- name: Assert that primary is in expected state
assert:
- assert:
that:
- mysql_primary_status.Is_Primary == true
- mysql_primary_status.Position != 0
- mysql_primary_status is not changed
- mysql_primary_status.Is_Master == true
- mysql_primary_status.Position != 0
- mysql_primary_status is not changed
# Test startreplica fails without changeprimary first. This needs fail_on_error
- name: Start replica and fail because primary is not specified; failing on error as requested
# Test startslave fails without changemaster first. This needs fail_on_error
- name: Start slave and fail because master is not specified; failing on error as requested
mysql_replication:
<<: *mysql_params
login_port: '{{ mysql_replica1_port }}'
mode: startreplica
primary_use_gtid: replica_pos
mode: startslave
fail_on_error: yes
register: result
ignore_errors: yes
- name: Assert that startreplica is failed
assert:
- assert:
that:
- result is failed
- result is failed
# Test startreplica doesn't fail if fail_on_error: no
- name: Start replica and fail without propagating it to ansible as we were asked not to
# Test startslave doesn't fail if fail_on_error: no
- name: Start slave and fail without propagating it to ansible as we were asked not to
mysql_replication:
<<: *mysql_params
login_port: '{{ mysql_replica1_port }}'
mode: startreplica
mode: startslave
fail_on_error: no
register: result
- name: Assert that startreplica succeeded
assert:
- assert:
that:
- result is not failed
- result is not failed
# Test startreplica doesn't fail if there is no fail_on_error.
# Test startslave doesn't fail if there is no fail_on_error.
# This is suboptimal because nothing happens, but it's the old behavior.
- name: Start replica and fail without propagating it to ansible as previous versions did not fail on error
- name: Start slave and fail without propagating it to ansible as previous versions did not fail on error
mysql_replication:
<<: *mysql_params
login_port: '{{ mysql_replica1_port }}'
mode: startreplica
mode: startslave
register: result
- name: Assert that start replica succeeded again
assert:
- assert:
that:
- result is not failed
- result is not failed
# primary_ssl_ca will be set as '' to check the module's behaviour for #23976,
# Test changemaster mode:
# master_ssl_ca will be set as '' to check the module's behaviour for #23976,
# must be converted to an empty string
- name: Test changeprimary mode with empty primary_ssl_ca
community.mysql.mysql_replication:
<<: *mysql_params
login_port: '{{ mysql_replica1_port }}'
mode: changeprimary
primary_host: '{{ mysql_host }}'
primary_port: '{{ mysql_primary_port }}'
primary_user: '{{ replication_user }}'
primary_password: '{{ replication_pass }}'
primary_log_file: '{{ mysql_primary_status.File }}'
primary_log_pos: '{{ mysql_primary_status.Position }}'
primary_ssl_ca: ''
primary_ssl: 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_HOST='{{ mysql_host }}',\
MASTER_USER='{{ replication_user }}',MASTER_PASSWORD='********',\
MASTER_PORT={{ mysql_primary_port }},MASTER_LOG_FILE=\
'{{ mysql_primary_status.File }}',MASTER_LOG_POS=\
{{ mysql_primary_status.Position }},MASTER_SSL=0,MASTER_SSL_CA=''"]
- name: Assert that changeprimmary is changed and return expected query for MySQL > 8.0.23
ansible.builtin.assert:
that:
- result is changed
- result.queries == expected_queries
when:
- db_engine == 'mysql'
- db_version is version('8.0.23', '>=')
vars:
expected_queries: ["CHANGE REPLICATION SOURCE TO \
SOURCE_HOST='{{ mysql_host }}',\
SOURCE_USER='{{ replication_user }}',SOURCE_PASSWORD='********',\
SOURCE_PORT={{ mysql_primary_port }},SOURCE_LOG_FILE=\
'{{ mysql_primary_status.File }}',SOURCE_LOG_POS=\
{{ mysql_primary_status.Position }},SOURCE_SSL=0,SOURCE_SSL_CA=''"]
# Test startreplica mode:
- name: Start replica
- name: Run replication
mysql_replication:
<<: *mysql_params
login_port: '{{ mysql_replica1_port }}'
mode: startreplica
mode: changemaster
master_host: '{{ mysql_host }}'
master_port: '{{ mysql_primary_port }}'
master_user: '{{ replication_user }}'
master_password: '{{ replication_pass }}'
master_log_file: '{{ mysql_primary_status.File }}'
master_log_pos: '{{ mysql_primary_status.Position }}'
master_ssl_ca: ''
register: result
- name: Assert that startreplica is changed and returns expected query
assert:
- assert:
that:
- result is changed
- result.queries == ["START SLAVE"] or result.queries == ["START REPLICA"]
- result is changed
- result.queries == ["CHANGE MASTER TO MASTER_HOST='{{ mysql_host }}',MASTER_USER='{{ replication_user }}',MASTER_PASSWORD='********',MASTER_PORT={{ mysql_primary_port }},MASTER_LOG_FILE='{{ mysql_primary_status.File }}',MASTER_LOG_POS={{ mysql_primary_status.Position }},MASTER_SSL_CA=''"]
# Test getreplica mode:
- name: Get replica status
# Test startslave mode:
- name: Start slave
mysql_replication:
<<: *mysql_params
login_port: '{{ mysql_replica1_port }}'
mode: getreplica
register: replica_status
mode: startslave
register: result
- name: Assert that getreplica returns expected values for MySQL older than 8.0.22 and Mariadb
assert:
- assert:
that:
- replica_status.Is_Replica is truthy(convert_bool=True)
- replica_status.Master_Host == mysql_host_value
- replica_status.Exec_Master_Log_Pos == mysql_primary_status.Position
- replica_status.Master_Port == mysql_primary_port_value
- replica_status.Last_IO_Errno == 0
- replica_status.Last_IO_Error == ''
- replica_status is not changed
vars:
mysql_host_value: "{{ mysql_host }}"
mysql_primary_port_value: "{{ mysql_primary_port }}"
when:
- >
db_engine == 'mariadb' or
(db_engine == 'mysql' and db_version is version('8.0.22', '<'))
- result is changed
- result.queries == ["START SLAVE"]
- name: Assert that getreplica returns expected values for MySQL newer than 8.0.22
assert:
# Test getslave mode:
- name: Get standby status
mysql_replication:
<<: *mysql_params
login_port: '{{ mysql_replica1_port }}'
mode: getslave
register: slave_status
- assert:
that:
- replica_status.Is_Replica is truthy(convert_bool=True)
- replica_status.Source_Host == mysql_host_value
- replica_status.Exec_Source_Log_Pos == mysql_primary_status.Position
- replica_status.Source_Port == mysql_primary_port_value
- replica_status.Last_IO_Errno == 0
- replica_status.Last_IO_Error == ''
- replica_status is not changed
vars:
mysql_host_value: "{{ mysql_host }}"
mysql_primary_port_value: "{{ mysql_primary_port }}"
when:
- db_engine == 'mysql'
- db_version is version('8.0.22', '>=')
- slave_status.Is_Slave == true
- slave_status.Master_Host == '{{ mysql_host }}'
- slave_status.Exec_Master_Log_Pos == mysql_primary_status.Position
- slave_status.Master_Port == {{ mysql_primary_port }}
- slave_status.Last_IO_Errno == 0
- slave_status.Last_IO_Error == ''
- slave_status is not changed
# Create test table and add data to it:
- name: Create test table
shell: "echo \"CREATE TABLE {{ test_table }} (id int);\" | {{ mysql_command_wo_port }} -P{{ mysql_primary_port }} {{ test_db }}"
shell: "echo \"CREATE TABLE {{ test_table }} (id int);\" | {{ mysql_command }} -P{{ mysql_primary_port }} {{ test_db }}"
- name: Insert data
shell: "echo \"INSERT INTO {{ test_table }} (id) VALUES (1), (2), (3); FLUSH LOGS;\" | {{ mysql_command_wo_port }} -P{{ mysql_primary_port }} {{ test_db }}"
shell: "echo \"INSERT INTO {{ test_table }} (id) VALUES (1), (2), (3); FLUSH LOGS;\" | {{ mysql_command }} -P{{ mysql_primary_port }} {{ test_db }}"
- name: Small pause to be sure the bin log, which was flushed previously, reached the replica
ansible.builtin.wait_for:
timeout: 2
- name: Small pause to be sure the bin log, which was flushed previously, reached the slave
pause:
seconds: 2
# Test primary log pos has been changed:
- name: Get replica status
# Test master log pos has been changed:
- name: Get standby status
mysql_replication:
<<: *mysql_params
login_port: '{{ mysql_replica1_port }}'
mode: getreplica
register: replica_status
mode: getslave
register: slave_status
# mysql_primary_status.Position is not actual and it has been changed by the prev step,
# so replica_status.Exec_Master_Log_Pos must be different:
- name: Assert that getreplica Log_Pos is different for MySQL older than 8.0.22 and MariaDB
assert:
# so slave_status.Exec_Master_Log_Pos must be different:
- assert:
that:
- replica_status.Exec_Master_Log_Pos != mysql_primary_status.Position
when:
- >
db_engine == 'mariadb' or
(db_engine == 'mysql' and db_version is version('8.0.22', '<'))
- slave_status.Exec_Master_Log_Pos != mysql_primary_status.Position
- name: Assert that getreplica Log_Pos is different for MySQL newer than 8.0.22
assert:
that:
- replica_status.Exec_Source_Log_Pos != mysql_primary_status.Position
when:
- db_engine == 'mysql'
- db_version is version('8.0.22', '>=')
- name: Start replica that is already running
- name: Start slave that is already running
mysql_replication:
<<: *mysql_params
login_port: '{{ mysql_replica1_port }}'
mode: startreplica
mode: startslave
fail_on_error: true
register: result
# mysqlclient 2.0.1 and pymysql 0.10.0+ always return "changed"
- name: Assert that startreplica is not changed
assert:
- assert:
that:
- result is not changed
when:
- connector_name == 'pymysql'
- connector_version is version('0.10.0', '<')
- result is not changed
# Test stopreplica mode:
- name: Stop replica
# Test stopslave mode:
- name: Stop slave
mysql_replication:
<<: *mysql_params
login_port: '{{ mysql_replica1_port }}'
mode: stopreplica
mode: stopslave
register: result
- name: Assert that stopreplica is changed and returns expected query
assert:
- assert:
that:
- result is changed
- result.queries == ["STOP SLAVE"] or result.queries == ["STOP REPLICA"]
- result is changed
- result.queries == ["STOP SLAVE"]
- name: Pause for 2 seconds to let the replication stop
ansible.builtin.wait_for:
timeout: 2
# Test stopreplica mode:
# mysqlclient 2.0.1 and pymysql 0.10.0+ always return "changed"
- name: Stop replica that is no longer running
mysql_replication:
<<: *mysql_params
login_port: '{{ mysql_replica1_port }}'
mode: stopreplica
fail_on_error: true
register: result
- name: Assert that stopreplica is not changed
assert:
that:
- result is not changed
when:
- connector_name == 'pymysql'
- connector_version is version('0.10.0', '<')
# master / slave related choices were removed in 3.0.0
# https://github.com/ansible-collections/community.mysql/pull/252
- name: Test invoking the module with unsupported choice
# Test stopslave mode:
- name: Stop slave that is no longer running
mysql_replication:
<<: *mysql_params
login_port: '{{ mysql_replica1_port }}'
mode: stopslave
fail_on_error: true
register: result
ignore_errors: yes
- name: Assert that stopslave returns expected error message
assert:
- assert:
that:
- result.msg == "value of mode must be one of{{ ":" }} getprimary, getreplica, changeprimary, stopreplica, startreplica, resetprimary, resetreplica, resetreplicaall, changereplication, got{{ ":" }} stopslave"
- result is failed
- result is not changed

View file

@ -0,0 +1,45 @@
# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) <aaklychkov@mail.ru>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
- vars:
mysql_params: &mysql_params
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: 127.0.0.1
block:
# Test master_delay mode:
- name: Run replication
mysql_replication:
<<: *mysql_params
login_port: '{{ mysql_replica1_port }}'
mode: changemaster
master_delay: '{{ test_master_delay }}'
register: result
- assert:
that:
- result is changed
- result.queries == ["CHANGE MASTER TO MASTER_DELAY=60"]
# Auxiliary step:
- name: Start slave
mysql_replication:
<<: *mysql_params
login_port: '{{ mysql_replica1_port }}'
mode: startslave
register: result
# Check master_delay:
- name: Get standby status
mysql_replication:
<<: *mysql_params
login_port: '{{ mysql_replica1_port }}'
mode: getslave
register: slave_status
- assert:
that:
- slave_status.SQL_Delay == {{ test_master_delay }}
- slave_status is not changed

View file

@ -1,59 +0,0 @@
# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) <andrew.a.klychkov@gmail.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
- vars:
mysql_params: &mysql_params
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
block:
# Test primary_delay mode:
- name: Run replication
mysql_replication:
<<: *mysql_params
login_port: '{{ mysql_replica1_port }}'
mode: changeprimary
primary_delay: '{{ test_primary_delay }}'
register: result
- name: Assert that run replication is changed and query match expectation for MariaDB and MySQL < 8.0.23
ansible.builtin.assert:
that:
- result is changed
- result.queries == ["CHANGE MASTER TO MASTER_DELAY=60"]
when:
- >
db_engine == 'mariadb' or
(db_engine == 'mysql' and db_version is version('8.0.23', '<'))
- name: Assert that run replication is changed and query match expectation for MySQL >= 8.0.23
ansible.builtin.assert:
that:
- result is changed
- result.queries == ["CHANGE REPLICATION SOURCE TO SOURCE_DELAY=60"]
when:
- db_engine == 'mysql'
- db_version is version('8.0.23', '>=')
# Auxiliary step:
- name: Start replica
mysql_replication:
<<: *mysql_params
login_port: '{{ mysql_replica1_port }}'
mode: startreplica
register: result
# Check primary_delay:
- name: Get standby status
mysql_replication:
<<: *mysql_params
login_port: '{{ mysql_replica1_port }}'
mode: getreplica
register: replica_status
- assert:
that:
- replica_status.SQL_Delay == {{ test_primary_delay }}
- replica_status is not changed

View file

@ -0,0 +1,56 @@
# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) <aaklychkov@mail.ru>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
- vars:
mysql_params: &mysql_params
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: 127.0.0.1
block:
# Needs for further tests:
- name: Stop slave
mysql_replication:
<<: *mysql_params
login_port: '{{ mysql_replica1_port }}'
mode: stopslave
- name: Reset slave all
mysql_replication:
<<: *mysql_params
login_port: '{{ mysql_replica1_port }}'
mode: resetslaveall
# Get master initial status:
- name: Get master status
mysql_replication:
<<: *mysql_params
login_port: '{{ mysql_primary_port }}'
mode: getmaster
register: mysql_primary_initial_status
# Test resetmaster mode:
- name: Reset master
mysql_replication:
<<: *mysql_params
login_port: '{{ mysql_primary_port }}'
mode: resetmaster
register: result
- assert:
that:
- result is changed
- result.queries == ["RESET MASTER"]
# Get master final status:
- name: Get master status
mysql_replication:
<<: *mysql_params
login_port: '{{ mysql_primary_port }}'
mode: getmaster
register: mysql_primary_final_status
- assert:
that:
- mysql_primary_initial_status.File != mysql_primary_final_status.File

View file

@ -1,71 +0,0 @@
---
# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) <andrew.a.klychkov@gmail.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
- vars:
mysql_params: &mysql_params
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
block:
# Needs for further tests:
- name: Stop replica
mysql_replication:
<<: *mysql_params
login_port: '{{ mysql_replica1_port }}'
mode: stopreplica
- name: Reset replica all
mysql_replication:
<<: *mysql_params
login_port: '{{ mysql_replica1_port }}'
mode: resetreplicaall
# Get primary initial status:
- name: Get primary status
mysql_replication:
<<: *mysql_params
login_port: '{{ mysql_primary_port }}'
mode: getprimary
register: mysql_primary_initial_status
# Test resetprimary mode:
- name: Reset primary
mysql_replication:
<<: *mysql_params
login_port: '{{ mysql_primary_port }}'
mode: resetprimary
register: result
- name: Assert that reset primary is changed and query matches for MariaDB and MySQL < 8.4
ansible.builtin.assert:
that:
- result is changed
- result.queries == ["RESET MASTER"]
when:
- >
db_engine == 'mariadb' or
(db_engine == 'mysql' and db_version is version('8.4.0', '<'))
- name: Assert that reset primary is changed and query matches for MySQL > 8.4
ansible.builtin.assert:
that:
- result is changed
- result.queries == ["RESET BINARY LOGS AND GTIDS"]
when:
- db_engine == 'mysql'
- db_version is version('8.4.0', '>=')
# Get primary final status:
- name: Get primary status
mysql_replication:
<<: *mysql_params
login_port: '{{ mysql_primary_port }}'
mode: getprimary
register: mysql_primary_final_status
- assert:
that:
- mysql_primary_initial_status.File != mysql_primary_final_status.File

Some files were not shown because too many files have changed in this diff Show more