Compare commits

...

8 commits
3.11.0 ... main

Author SHA1 Message Date
Andrew Klychkov
b26235b7d7
Release 3.13.0 commit (#705) 2025-03-21 07:02:43 +01:00
Keeper-of-the-Keys
45a29408ad
User locking (#702)
* function to check if a user is locked already

Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys <es.rosenberg+github@gmail.com>

* Add the location and logic of where I think user locking would happen.

Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys <es.rosenberg+github@gmail.com>

* Fix missing parameters for execute()

Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys <es.rosenberg+github@gmail.com>

* Add the locked attribute

Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys <es.rosenberg+github@gmail.com>

* Initial user locking integration tests

Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys <es.rosenberg+github@gmail.com>

* Add attribute documentation

Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys <es.rosenberg+github@gmail.com>

* More descriptive names in the integration tests

Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys <es.rosenberg+github@gmail.com>

* - Changes requested/suggested by @Andersson007
- Example usage
- Changelog fragment

Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys <es.rosenberg+github@gmail.com>

* Fix user_is_locked and remove host_all option.

Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys <es.rosenberg+github@gmail.com>

* Fix host of user (was % should have been localhost after deleting `host:` earlier)

Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys <es.rosenberg+github@gmail.com>

* Switch locked to named instead of positional.

Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys <es.rosenberg+github@gmail.com>

* Add check_mode support.

Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys <es.rosenberg+github@gmail.com>

* Add check_mode: true test cases

Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys <es.rosenberg+github@gmail.com>

* Fix names that included `check_mode: true`

Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys <es.rosenberg+github@gmail.com>

* Add idempotence checks

Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys <es.rosenberg+github@gmail.com>

* Switch calls to user_mod with sequences of None positional arguments to full named arguments

Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys <es.rosenberg+github@gmail.com>

* locked check should not run for roles.

Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys <es.rosenberg+github@gmail.com>

* check_mode is set at the task level and not the module level

Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys <es.rosenberg+github@gmail.com>

* Add user locking to info module and test.

Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys <es.rosenberg+github@gmail.com>

* Handle DictCursor

Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys <es.rosenberg+github@gmail.com>


* Add check_mode feedback

Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys <es.rosenberg+github@gmail.com>


* Add another builtin account to the exclusion list

Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys <es.rosenberg+github@gmail.com>

* Initial switch to default=None for locked, will need to add a test for it.

Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys <es.rosenberg+github@gmail.com>

* Add check that missing locked argument does not unlock a user

Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys <es.rosenberg+github@gmail.com>
---------

Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys <es.rosenberg+github@gmail.com>
2025-03-19 14:40:59 +01:00
Laurent Indermühle
dd7e297d50
Add support for MariaDB 11.4 (#703)
* fix missing symlink to mysql binaries for MariaDB 11+

* update tested version of MariaDB 11.4 instead of 10.5

* add changelog fragment

* [CI] add way to trigger workflow manually

Useful in the case we don't modifiy any files in the paths: sections of the push event.

* add version check for mariadb < 10.4.6 without mariadb* binaries

* Use same concatenation method between functions to avoid future confusion

I didn't notice that db_dump and db_import were different, thus I introduced a bug with the initialization of the variable cmd. This commit fixes that.
2025-03-10 18:55:42 +01:00
Andrew Klychkov
e9845b0a1c
Release 3.12.0 commit (#698) 2025-01-17 10:11:27 +01:00
Andrew Klychkov
960ac32adf
mysql_query: returns execution_time_ms list containing execution time per query (#697)
* mysql_query: returns execution_time_ms list containing execution time per query

* Update changelogs/fragments/0-mysql_query-returns-exec-time-ms.yml

Co-authored-by: Laurent Indermühle <laurent.indermuehle@pm.me>
2025-01-16 15:49:53 +01:00
Sergio
a45a0d006d
mysql_db: added zstd support (#696) 2025-01-16 09:35:04 +01:00
Andrew Klychkov
022ed60906
Fix linting issues (#693) 2024-12-13 09:21:06 +01:00
Laurent Indermuehle
3d3f115574
Add next expected version 2024-11-19 10:56:37 +01:00
18 changed files with 474 additions and 70 deletions

View file

@ -13,7 +13,7 @@ on: # yamllint disable-line rule:truthy
- '.github/workflows/ansible-test-plugins.yml' - '.github/workflows/ansible-test-plugins.yml'
schedule: schedule:
- cron: '0 6 * * *' - cron: '0 6 * * *'
workflow_dispatch:
jobs: jobs:
sanity: sanity:
@ -54,8 +54,8 @@ jobs:
db_engine_version: db_engine_version:
- '8.0.38' - '8.0.38'
- '8.4.1' - '8.4.1'
- '10.5.25'
- '10.11.8' - '10.11.8'
- '11.4.5'
connector_name: connector_name:
- pymysql - pymysql
- mysqlclient - mysqlclient
@ -87,10 +87,10 @@ jobs:
exclude: exclude:
- db_engine_name: mysql - db_engine_name: mysql
db_engine_version: '10.5.25' db_engine_version: '10.11.8'
- db_engine_name: mysql - db_engine_name: mysql
db_engine_version: '10.11.8' db_engine_version: '11.4.5'
- db_engine_name: mariadb - db_engine_name: mariadb
db_engine_version: '8.0.38' db_engine_version: '8.0.38'
@ -119,13 +119,13 @@ jobs:
- db_engine_version: '8.0.38' - db_engine_version: '8.0.38'
ansible: stable-2.17 ansible: stable-2.17
- db_engine_version: '10.5.25' - db_engine_version: '10.11.8'
ansible: stable-2.17 ansible: stable-2.17
- db_engine_version: '8.0.38' - db_engine_version: '8.0.38'
ansible: devel ansible: devel
- db_engine_version: '10.5.25' - db_engine_version: '10.11.8'
ansible: devel ansible: devel
- db_engine_version: '8.4.1' - db_engine_version: '8.4.1'
@ -162,7 +162,7 @@ jobs:
db_engine_version: '8.0.38' db_engine_version: '8.0.38'
- connector_version: '1.1.1' - connector_version: '1.1.1'
db_engine_version: '10.5.25' db_engine_version: '10.11.8'
services: services:
db_primary: db_primary:
@ -175,7 +175,7 @@ jobs:
# We write our own health-cmd because the mariadb container does not # We write our own health-cmd because the mariadb container does not
# provide a healthcheck # provide a healthcheck
options: >- options: >-
--health-cmd "mysqladmin ping -P 3306 -pmsandbox |grep alive || exit 1" --health-cmd "${{ matrix.db_engine_name == 'mysql' && 'mysqladmin' || 'mariadb-admin' }} ping -P 3306 -pmsandbox |grep alive || exit 1"
--health-start-period 10s --health-start-period 10s
--health-interval 10s --health-interval 10s
--health-timeout 5s --health-timeout 5s
@ -189,7 +189,7 @@ jobs:
ports: ports:
- 3308:3306 - 3308:3306
options: >- options: >-
--health-cmd "mysqladmin ping -P 3306 -pmsandbox |grep alive || exit 1" --health-cmd "${{ matrix.db_engine_name == 'mysql' && 'mysqladmin' || 'mariadb-admin' }} ping -P 3306 -pmsandbox |grep alive || exit 1"
--health-start-period 10s --health-start-period 10s
--health-interval 10s --health-interval 10s
--health-timeout 5s --health-timeout 5s
@ -203,7 +203,7 @@ jobs:
ports: ports:
- 3309:3306 - 3309:3306
options: >- options: >-
--health-cmd "mysqladmin ping -P 3306 -pmsandbox |grep alive || exit 1" --health-cmd "${{ matrix.db_engine_name == 'mysql' && 'mysqladmin' || 'mariadb-admin' }} ping -P 3306 -pmsandbox |grep alive || exit 1"
--health-start-period 10s --health-start-period 10s
--health-interval 10s --health-interval 10s
--health-timeout 5s --health-timeout 5s

View file

@ -6,6 +6,43 @@ Community MySQL and MariaDB Collection Release Notes
This changelog describes changes after version 2.0.0. 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 v3.11.0
======= =======
@ -13,6 +50,7 @@ Release Summary
--------------- ---------------
This is a minor release of the ``community.mysql`` collection. This is a minor release of the ``community.mysql`` collection.
This changelog contains all changes to the modules and plugins in this This changelog contains all changes to the modules and plugins in this
collection that have been made after the previous release. collection that have been made after the previous release.

View file

@ -11,6 +11,17 @@ ifdef continue_on_errors
_continue_on_errors = --continue-on-error _continue_on_errors = --continue-on-error
endif 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 .PHONY: test-integration
test-integration: test-integration:
@echo -n $(db_engine_name) > tests/integration/db_engine_name @echo -n $(db_engine_name) > tests/integration/db_engine_name
@ -29,9 +40,9 @@ test-integration:
--env MYSQL_ROOT_PASSWORD=msandbox \ --env MYSQL_ROOT_PASSWORD=msandbox \
--network podman \ --network podman \
--publish 3307:3306 \ --publish 3307:3306 \
--health-cmd 'mysqladmin ping -P 3306 -pmsandbox | grep alive || exit 1' \ --health-cmd '$(_health_cmd) ping -P 3306 -pmsandbox | grep alive || exit 1' \
docker.io/library/$(db_engine_name):$(db_engine_version) \ docker.io/library/$(db_engine_name):$(db_engine_version) \
mysqld $(_command)
podman run \ podman run \
--detach \ --detach \
--replace \ --replace \
@ -40,9 +51,9 @@ test-integration:
--env MYSQL_ROOT_PASSWORD=msandbox \ --env MYSQL_ROOT_PASSWORD=msandbox \
--network podman \ --network podman \
--publish 3308:3306 \ --publish 3308:3306 \
--health-cmd 'mysqladmin ping -P 3306 -pmsandbox | grep alive || exit 1' \ --health-cmd '$(_health_cmd) ping -P 3306 -pmsandbox | grep alive || exit 1' \
docker.io/library/$(db_engine_name):$(db_engine_version) \ docker.io/library/$(db_engine_name):$(db_engine_version) \
mysqld $(_command)
podman run \ podman run \
--detach \ --detach \
--replace \ --replace \
@ -51,9 +62,9 @@ test-integration:
--env MYSQL_ROOT_PASSWORD=msandbox \ --env MYSQL_ROOT_PASSWORD=msandbox \
--network podman \ --network podman \
--publish 3309:3306 \ --publish 3309:3306 \
--health-cmd 'mysqladmin ping -P 3306 -pmsandbox | grep alive || exit 1' \ --health-cmd '$(_health_cmd) ping -P 3306 -pmsandbox | grep alive || exit 1' \
docker.io/library/$(db_engine_name):$(db_engine_version) \ docker.io/library/$(db_engine_name):$(db_engine_version) \
mysqld $(_command)
# Setup replication and restart containers using the same subshell to keep variables alive # Setup replication and restart containers using the same subshell to keep variables alive
db_ver=$(db_engine_version); \ db_ver=$(db_engine_version); \
maj="$${db_ver%.*.*}"; \ maj="$${db_ver%.*.*}"; \

View file

@ -112,10 +112,10 @@ For MariaDB, only Long Term releases are tested. When multiple LTS are available
- mariadb:10.3.34 (collection version < 3.5.1) - mariadb:10.3.34 (collection version < 3.5.1)
- mariadb:10.4.24 (collection version >= 3.5.2, < 3.10.0) - 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.18 (collection version >= 3.5.2, < 3.10.0)
- mariadb:10.5.25 (collection version >= 3.10.0) - mariadb:10.5.25 (collection version >= 3.10.0, <3.13.0)
- mariadb:10.6.11 (collection version >= 3.5.2, < 3.10.0) - mariadb:10.6.11 (collection version >= 3.5.2, < 3.10.0)
- mariadb:10.11.8 (collection version >= 3.10.0) - mariadb:10.11.8 (collection version >= 3.10.0)
- mariadb:11.4.5 (collection version >= 3.13.0)
### Database connectors ### Database connectors

View file

@ -65,8 +65,8 @@ The Makefile accept the following options
- Choices: - Choices:
- "8.0.38" <- mysql - "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) - "8.4.1" <- mysql (NOT WORKING YET, ansible-test uses Ubuntu 20.04 which is too old to install mysql-community-client 8.4)
- "10.5.25" <- mariadb
- "10.11.8" <- mariadb - "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. - 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` - `connector_name`
@ -121,7 +121,7 @@ make ansible="stable-2.16" db_engine_name="mysql" db_engine_version="8.0.31" con
make ansible="stable-2.17" db_engine_name="mysql" db_engine_version="8.0.31" connector_name="mysqlclient" connector_version="2.0.3" target="test_mysql_query" keep_containers_alive=1 continue_on_errors=1 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: # If your system has an usupported version of Python:
make local_python_version="3.10" ansible="stable-2.17" db_engine_name="mariadb" db_engine_version="10.6.11" connector_name="pymysql" connector_version="1.0.2" make local_python_version="3.10" ansible="stable-2.17" db_engine_name="mariadb" db_engine_version="11.4.5" connector_name="pymysql" connector_version="1.0.2"
``` ```

View file

@ -230,6 +230,43 @@ releases:
- 591-mysql_info-db_tables_count.yml - 591-mysql_info-db_tables_count.yml
- 671-modules_util_user.yml - 671-modules_util_user.yml
release_date: '2024-11-19' 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: 3.2.0:
changes: changes:
bugfixes: bugfixes:

View file

@ -1,7 +1,7 @@
--- ---
namespace: community namespace: community
name: mysql name: mysql
version: 3.11.0 version: 3.13.0
readme: README.md readme: README.md
authors: authors:
- Ansible community - Ansible community

View file

@ -52,6 +52,25 @@ def user_exists(cursor, user, host, host_all):
return count[0] > 0 return count[0] > 0
def user_is_locked(cursor, user, host):
cursor.execute("SHOW CREATE USER %s@%s", (user, host))
# Per discussions on irc:libera.chat:#maria the query may return up to 2 rows but "ACCOUNT LOCK" should always be in the first row.
result = cursor.fetchone()
# ACCOUNT LOCK does not have to be the last option in the CREATE USER query.
# Need to handle both DictCursor and non-DictCursor
if isinstance(result, tuple):
if result[0].find('ACCOUNT LOCK') > 0:
return True
elif isinstance(result, dict):
for res in result.values():
if res.find('ACCOUNT LOCK') > 0:
return True
return False
def sanitize_requires(tls_requires): def sanitize_requires(tls_requires):
sanitized_requires = {} sanitized_requires = {}
if tls_requires: if tls_requires:
@ -160,7 +179,7 @@ def get_existing_authentication(cursor, user, host=None):
def user_add(cursor, user, host, host_all, password, encrypted, def user_add(cursor, user, host, host_all, password, encrypted,
plugin, plugin_hash_string, plugin_auth_string, salt, new_priv, plugin, plugin_hash_string, plugin_auth_string, salt, new_priv,
attributes, tls_requires, reuse_existing_password, module, attributes, tls_requires, reuse_existing_password, module,
password_expire, password_expire_interval): password_expire, password_expire_interval, locked=False):
# If attributes are set, perform a sanity check to ensure server supports user attributes before creating user # If attributes are set, perform a sanity check to ensure server supports user attributes before creating user
if attributes and not get_attribute_support(cursor): if attributes and not get_attribute_support(cursor):
module.fail_json(msg="user attributes were specified but the server does not support user attributes") module.fail_json(msg="user attributes were specified but the server does not support user attributes")
@ -250,6 +269,9 @@ def user_add(cursor, user, host, host_all, password, encrypted,
cursor.execute("ALTER USER %s@%s ATTRIBUTE %s", (user, host, json.dumps(attributes))) cursor.execute("ALTER USER %s@%s ATTRIBUTE %s", (user, host, json.dumps(attributes)))
final_attributes = attributes_get(cursor, user, host) final_attributes = attributes_get(cursor, user, host)
if locked:
cursor.execute("ALTER USER %s@%s ACCOUNT LOCK", (user, host))
return {'changed': True, 'password_changed': not used_existing_password, 'attributes': final_attributes} return {'changed': True, 'password_changed': not used_existing_password, 'attributes': final_attributes}
@ -264,7 +286,7 @@ def is_hash(password):
def user_mod(cursor, user, host, host_all, password, encrypted, def user_mod(cursor, user, host, host_all, password, encrypted,
plugin, plugin_hash_string, plugin_auth_string, salt, new_priv, plugin, plugin_hash_string, plugin_auth_string, salt, new_priv,
append_privs, subtract_privs, attributes, tls_requires, module, append_privs, subtract_privs, attributes, tls_requires, module,
password_expire, password_expire_interval, role=False, maria_role=False): password_expire, password_expire_interval, locked=None, role=False, maria_role=False):
changed = False changed = False
msg = "User unchanged" msg = "User unchanged"
grant_option = False grant_option = False
@ -536,6 +558,22 @@ def user_mod(cursor, user, host, host_all, password, encrypted,
if attribute_support: if attribute_support:
final_attributes = attributes_get(cursor, user, host) final_attributes = attributes_get(cursor, user, host)
if not role and locked is not None and user_is_locked(cursor, user, host) != locked:
if not module.check_mode:
if locked:
cursor.execute("ALTER USER %s@%s ACCOUNT LOCK", (user, host))
msg = 'User locked'
else:
cursor.execute("ALTER USER %s@%s ACCOUNT UNLOCK", (user, host))
msg = 'User unlocked'
else:
if locked:
msg = 'User will be locked'
else:
msg = 'User will be unlocked'
changed = True
if role: if role:
continue continue

View file

@ -46,8 +46,8 @@ options:
target: target:
description: description:
- Location, on the remote host, of the dump file to read from or write to. - Location, on the remote host, of the dump file to read from or write to.
- Uncompressed SQL files (C(.sql)) as well as bzip2 (C(.bz2)), gzip (C(.gz)) and - Uncompressed SQL files (C(.sql)) as well as bzip2 (C(.bz2)), gzip (C(.gz)),
xz (Added in 2.0) compressed files are supported. xz (Added in 2.0) and zstd (C(.zst)) (Added in 3.12.0) compressed files are supported.
type: path type: path
single_transaction: single_transaction:
description: description:
@ -386,67 +386,75 @@ def db_dump(module, host, user, password, db_name, target, all_databases, port,
encoding=None, force=False, master_data=0, skip_lock_tables=False, encoding=None, force=False, master_data=0, skip_lock_tables=False,
dump_extra_args=None, unsafe_password=False, restrict_config_file=False, dump_extra_args=None, unsafe_password=False, restrict_config_file=False,
check_implicit_admin=False, pipefail=False): check_implicit_admin=False, pipefail=False):
cmd = module.get_bin_path('mysqldump', True)
cmd_str = 'mysqldump'
if server_implementation == 'mariadb' and LooseVersion(server_version) >= LooseVersion("10.4.6"):
cmd_str = 'mariadb-dump'
try:
cmd = [module.get_bin_path(cmd_str, True)]
except Exception as e:
return 1, "", "Error determining dump command: %s" % str(e)
# If defined, mysqldump demands --defaults-extra-file be the first option # If defined, mysqldump demands --defaults-extra-file be the first option
if config_file: if config_file:
if restrict_config_file: if restrict_config_file:
cmd += " --defaults-file=%s" % shlex_quote(config_file) cmd.append("--defaults-file=%s" % shlex_quote(config_file))
else: else:
cmd += " --defaults-extra-file=%s" % shlex_quote(config_file) cmd.append("--defaults-extra-file=%s" % shlex_quote(config_file))
if check_implicit_admin: if check_implicit_admin:
cmd += " --user=root --password=''" cmd.append("--user=root --password=''")
else: else:
if user is not None: if user is not None:
cmd += " --user=%s" % shlex_quote(user) cmd.append("--user=%s" % shlex_quote(user))
if password is not None: if password is not None:
if not unsafe_password: if not unsafe_password:
cmd += " --password=%s" % shlex_quote(password) cmd.append("--password=%s" % shlex_quote(password))
else: else:
cmd += " --password=%s" % password cmd.append("--password=%s" % password)
if ssl_cert is not None: if ssl_cert is not None:
cmd += " --ssl-cert=%s" % shlex_quote(ssl_cert) cmd.append("--ssl-cert=%s" % shlex_quote(ssl_cert))
if ssl_key is not None: if ssl_key is not None:
cmd += " --ssl-key=%s" % shlex_quote(ssl_key) cmd.append("--ssl-key=%s" % shlex_quote(ssl_key))
if ssl_ca is not None: if ssl_ca is not None:
cmd += " --ssl-ca=%s" % shlex_quote(ssl_ca) cmd.append("--ssl-ca=%s" % shlex_quote(ssl_ca))
if force: if force:
cmd += " --force" cmd.append("--force")
if socket is not None: if socket is not None:
cmd += " --socket=%s" % shlex_quote(socket) cmd.append("--socket=%s" % shlex_quote(socket))
else: else:
cmd += " --host=%s --port=%i" % (shlex_quote(host), port) cmd.append("--host=%s --port=%i" % (shlex_quote(host), port))
if all_databases: if all_databases:
cmd += " --all-databases" cmd.append("--all-databases")
elif len(db_name) > 1: elif len(db_name) > 1:
cmd += " --databases {0}".format(' '.join(db_name)) cmd.append("--databases {0}".format(' '.join(db_name)))
else: else:
cmd += " %s" % shlex_quote(' '.join(db_name)) cmd.append("%s" % shlex_quote(' '.join(db_name)))
if skip_lock_tables: if skip_lock_tables:
cmd += " --skip-lock-tables" cmd.append("--skip-lock-tables")
if (encoding is not None) and (encoding != ""): if (encoding is not None) and (encoding != ""):
cmd += " --default-character-set=%s" % shlex_quote(encoding) cmd.append("--default-character-set=%s" % shlex_quote(encoding))
if single_transaction: if single_transaction:
cmd += " --single-transaction=true" cmd.append("--single-transaction=true")
if quick: if quick:
cmd += " --quick" cmd.append("--quick")
if ignore_tables: if ignore_tables:
for an_ignored_table in ignore_tables: for an_ignored_table in ignore_tables:
cmd += " --ignore-table={0}".format(an_ignored_table) cmd.append("--ignore-table={0}".format(an_ignored_table))
if hex_blob: if hex_blob:
cmd += " --hex-blob" cmd.append("--hex-blob")
if master_data: if master_data:
if (server_implementation == 'mysql' and if (server_implementation == 'mysql' and
LooseVersion(server_version) >= LooseVersion("8.2.0")): LooseVersion(server_version) >= LooseVersion("8.2.0")):
cmd += " --source-data=%s" % master_data cmd.append("--source-data=%s" % master_data)
else: else:
cmd += " --master-data=%s" % master_data cmd.append("--master-data=%s" % master_data)
if dump_extra_args is not None: if dump_extra_args is not None:
cmd += " " + dump_extra_args cmd.append(dump_extra_args)
path = None path = None
if os.path.splitext(target)[-1] == '.gz': if os.path.splitext(target)[-1] == '.gz':
@ -455,6 +463,10 @@ def db_dump(module, host, user, password, db_name, target, all_databases, port,
path = module.get_bin_path('bzip2', True) path = module.get_bin_path('bzip2', True)
elif os.path.splitext(target)[-1] == '.xz': elif os.path.splitext(target)[-1] == '.xz':
path = module.get_bin_path('xz', True) 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: if path:
cmd = '%s | %s > %s' % (cmd, path, shlex_quote(target)) cmd = '%s | %s > %s' % (cmd, path, shlex_quote(target))
@ -474,13 +486,21 @@ def db_dump(module, host, user, password, db_name, target, all_databases, port,
def db_import(module, host, user, password, db_name, target, all_databases, port, config_file, def db_import(module, host, user, password, db_name, target, all_databases, port, config_file,
socket=None, ssl_cert=None, ssl_key=None, ssl_ca=None, encoding=None, force=False, server_implementation, server_version, socket=None, ssl_cert=None, ssl_key=None, ssl_ca=None,
encoding=None, force=False,
use_shell=False, unsafe_password=False, restrict_config_file=False, use_shell=False, unsafe_password=False, restrict_config_file=False,
check_implicit_admin=False): check_implicit_admin=False):
if not os.path.exists(target): if not os.path.exists(target):
return module.fail_json(msg="target %s does not exist on the host" % target) return module.fail_json(msg="target %s does not exist on the host" % target)
cmd = [module.get_bin_path('mysql', True)] cmd_str = 'mysql'
if server_implementation == 'mariadb' and LooseVersion(server_version) >= LooseVersion("10.4.6"):
cmd_str = 'mariadb'
try:
cmd = [module.get_bin_path(cmd_str, True)]
except Exception as e:
return 1, "", "Error determining mysql/mariadb command: %s" % str(e)
# --defaults-file must go first, or errors out # --defaults-file must go first, or errors out
if config_file: if config_file:
if restrict_config_file: if restrict_config_file:
@ -526,6 +546,8 @@ def db_import(module, host, user, password, db_name, target, all_databases, port
comp_prog_path = module.get_bin_path('bzip2', required=True) comp_prog_path = module.get_bin_path('bzip2', required=True)
elif os.path.splitext(target)[-1] == '.xz': elif os.path.splitext(target)[-1] == '.xz':
comp_prog_path = module.get_bin_path('xz', required=True) 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: if comp_prog_path:
# The line below is for returned data only: # The line below is for returned data only:
executed_commands.append('%s -dc %s | %s' % (comp_prog_path, target, cmd)) executed_commands.append('%s -dc %s | %s' % (comp_prog_path, target, cmd))
@ -768,8 +790,8 @@ def main():
rc, stdout, stderr = db_import(module, login_host, login_user, rc, stdout, stderr = db_import(module, login_host, login_user,
login_password, db, target, login_password, db, target,
all_databases, all_databases,
login_port, config_file, login_port, config_file, server_implementation,
socket, ssl_cert, ssl_key, ssl_ca, server_version, socket, ssl_cert, ssl_key, ssl_ca,
encoding, force, use_shell, unsafe_login_password, encoding, force, use_shell, unsafe_login_password,
restrict_config_file, check_implicit_admin) restrict_config_file, check_implicit_admin)
if rc != 0: if rc != 0:

View file

@ -4,6 +4,7 @@
# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) <andrew.a.klychkov@gmail.com> # 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) # 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 from __future__ import absolute_import, division, print_function
__metaclass__ = type __metaclass__ = type
@ -318,6 +319,7 @@ from ansible_collections.community.mysql.plugins.module_utils.user import (
get_resource_limits, get_resource_limits,
get_existing_authentication, get_existing_authentication,
get_user_implementation, get_user_implementation,
user_is_locked,
) )
from ansible.module_utils.six import iteritems from ansible.module_utils.six import iteritems
from ansible.module_utils._text import to_native from ansible.module_utils._text import to_native
@ -652,8 +654,10 @@ class MySQL_Info(object):
if authentications: if authentications:
output_dict.update(authentications[0]) output_dict.update(authentications[0])
if line.get('is_role') and line['is_role'] == 'N':
output_dict['locked'] = user_is_locked(self.cursor, user, host)
# TODO password_option # TODO password_option
# TODO lock_option
# but both are not supported by mysql_user atm. So no point yet. # but both are not supported by mysql_user atm. So no point yet.
output.append(output_dict) output.append(output_dict)

View file

@ -62,7 +62,6 @@ author:
- Andrew Klychkov (@Andersson007) - Andrew Klychkov (@Andersson007)
extends_documentation_fragment: extends_documentation_fragment:
- community.mysql.mysql - community.mysql.mysql
''' '''
EXAMPLES = r''' EXAMPLES = r'''
@ -117,8 +116,18 @@ rowcount:
returned: changed returned: changed
type: list type: list
sample: [5, 1] 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 import warnings
from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import AnsibleModule
@ -139,6 +148,18 @@ DDL_QUERY_KEYWORDS = ('CREATE', 'DROP', 'ALTER', 'RENAME', 'TRUNCATE')
# Module execution. # 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(): def main():
argument_spec = mysql_common_argument_spec() argument_spec = mysql_common_argument_spec()
argument_spec.update( argument_spec.update(
@ -213,6 +234,7 @@ def main():
query_result = [] query_result = []
executed_queries = [] executed_queries = []
rowcount = [] rowcount = []
execution_time_ms = []
already_exists = False already_exists = False
for q in query: for q in query:
@ -223,7 +245,8 @@ def main():
category=mysql_driver.Warning) category=mysql_driver.Warning)
try: try:
cursor.execute(q, arguments) cursor, exec_time_ms = execute_and_return_time(cursor, q, arguments)
execution_time_ms.append(exec_time_ms)
except mysql_driver.Warning: except mysql_driver.Warning:
# When something is run with IF NOT EXISTS # When something is run with IF NOT EXISTS
# and there's "already exists" MySQL warning, # and there's "already exists" MySQL warning,
@ -280,6 +303,7 @@ def main():
'executed_queries': executed_queries, 'executed_queries': executed_queries,
'query_result': query_result, 'query_result': query_result,
'rowcount': rowcount, 'rowcount': rowcount,
'execution_time_ms': execution_time_ms,
} }
# Exit: # Exit:

View file

@ -284,7 +284,6 @@ EXAMPLES = r'''
community.mysql.mysql_replication: community.mysql.mysql_replication:
mode: changeprimary mode: changeprimary
fail_on_error: true fail_on_error: true
''' '''
RETURN = r''' RETURN = r'''

View file

@ -930,11 +930,12 @@ class Role():
set_default_role_all=set_default_role_all) set_default_role_all=set_default_role_all)
if privs: if privs:
result = user_mod(self.cursor, self.name, self.host, result = user_mod(cursor=self.cursor, user=self.name, host=self.host,
None, None, None, None, None, None, None, host_all=None, password=None, encrypted=None, plugin=None,
privs, append_privs, subtract_privs, None, None, plugin_auth_string=None, plugin_hash_string=None, salt=None,
self.module, None, None, role=True, new_priv=privs, append_privs=append_privs, subtract_privs=subtract_privs,
maria_role=self.is_mariadb) attributes=None, tls_requires=None, module=self.module, password_expire=None,
password_expire_interval=None, role=True, maria_role=self.is_mariadb)
changed = result['changed'] changed = result['changed']
if admin: if admin:

View file

@ -189,6 +189,15 @@ options:
fields names in privileges. fields names in privileges.
type: bool type: bool
version_added: '3.8.0' version_added: '3.8.0'
locked:
description:
- Lock account to prevent connections using it.
- This is primarily used for creating a user that will act as a DEFINER on stored procedures.
- If not specified leaves the lock state as is (for a new user creates unlocked).
type: bool
version_added: '3.13.0'
attributes: attributes:
description: description:
- "Create, update, or delete user attributes (arbitrary 'key: value' comments) for the user." - "Create, update, or delete user attributes (arbitrary 'key: value' comments) for the user."
@ -225,6 +234,7 @@ author:
- Lukasz Tomaszkiewicz (@tomaszkiewicz) - Lukasz Tomaszkiewicz (@tomaszkiewicz)
- kmarse (@kmarse) - kmarse (@kmarse)
- Laurent Indermühle (@laurent-indermuehle) - Laurent Indermühle (@laurent-indermuehle)
- E.S. Rosenberg (@Keeper-of-the-Keys)
extends_documentation_fragment: extends_documentation_fragment:
- community.mysql.mysql - community.mysql.mysql
@ -269,7 +279,7 @@ EXAMPLES = r'''
priv: '*.*:ALL,GRANT' priv: '*.*:ALL,GRANT'
state: present state: present
session_vars: session_vars:
wsrep_on: off wsrep_on: 'off'
- name: Create user with password, all database privileges and 'WITH GRANT OPTION' in db1 and db2 - name: Create user with password, all database privileges and 'WITH GRANT OPTION' in db1 and db2
community.mysql.mysql_user: community.mysql.mysql_user:
@ -400,6 +410,13 @@ EXAMPLES = r'''
priv: priv:
'db1.*': DELETE 'db1.*': DELETE
- name: Create locked user to act as a definer on procedures
community.mysql.mysql_user:
name: readonly_procedures_locked
locked: true
priv:
db1.*: SELECT
# Example .my.cnf file for setting the root password # Example .my.cnf file for setting the root password
# [client] # [client]
# user=root # user=root
@ -470,6 +487,7 @@ def main():
column_case_sensitive=dict(type='bool', default=None), # TODO 4.0.0 add default=True column_case_sensitive=dict(type='bool', default=None), # TODO 4.0.0 add default=True
password_expire=dict(type='str', choices=['now', 'never', 'default', 'interval'], no_log=True), password_expire=dict(type='str', choices=['now', 'never', 'default', 'interval'], no_log=True),
password_expire_interval=dict(type='int', required_if=[('password_expire', 'interval', True)], no_log=True), password_expire_interval=dict(type='int', required_if=[('password_expire', 'interval', True)], no_log=True),
locked=dict(type='bool'),
) )
module = AnsibleModule( module = AnsibleModule(
argument_spec=argument_spec, argument_spec=argument_spec,
@ -510,6 +528,7 @@ def main():
column_case_sensitive = module.params["column_case_sensitive"] column_case_sensitive = module.params["column_case_sensitive"]
password_expire = module.params["password_expire"] password_expire = module.params["password_expire"]
password_expire_interval = module.params["password_expire_interval"] password_expire_interval = module.params["password_expire_interval"]
locked = module.boolean(module.params['locked'])
if priv and not isinstance(priv, (str, dict)): if priv and not isinstance(priv, (str, dict)):
module.fail_json(msg="priv parameter must be str or dict but %s was passed" % type(priv)) module.fail_json(msg="priv parameter must be str or dict but %s was passed" % type(priv))
@ -577,13 +596,15 @@ def main():
result = user_mod(cursor, user, host, host_all, password, encrypted, result = user_mod(cursor, user, host, host_all, password, encrypted,
plugin, plugin_hash_string, plugin_auth_string, salt, plugin, plugin_hash_string, plugin_auth_string, salt,
priv, append_privs, subtract_privs, attributes, tls_requires, module, priv, append_privs, subtract_privs, attributes, tls_requires, module,
password_expire, password_expire_interval) password_expire, password_expire_interval, locked=locked)
else: else:
result = user_mod(cursor, user, host, host_all, None, encrypted, result = user_mod(cursor=cursor, user=user, host=host, host_all=host_all, password=None,
None, None, None, None, encrypted=encrypted, plugin=None, plugin_hash_string=None, plugin_auth_string=None,
priv, append_privs, subtract_privs, attributes, tls_requires, module, salt=None, new_priv=priv, append_privs=append_privs, subtract_privs=subtract_privs,
password_expire, password_expire_interval) attributes=attributes, tls_requires=tls_requires, module=module,
password_expire=password_expire, password_expire_interval=password_expire_interval,
locked=locked)
changed = result['changed'] changed = result['changed']
msg = result['msg'] msg = result['msg']
password_changed = result['password_changed'] password_changed = result['password_changed']
@ -601,7 +622,7 @@ def main():
result = user_add(cursor, user, host, host_all, password, encrypted, result = user_add(cursor, user, host, host_all, password, encrypted,
plugin, plugin_hash_string, plugin_auth_string, salt, plugin, plugin_hash_string, plugin_auth_string, salt,
priv, attributes, tls_requires, reuse_existing_password, module, priv, attributes, tls_requires, reuse_existing_password, module,
password_expire, password_expire_interval) password_expire, password_expire_interval, locked=locked)
changed = result['changed'] changed = result['changed']
password_changed = result['password_changed'] password_changed = result['password_changed']
final_attributes = result['attributes'] final_attributes = result['attributes']

View file

@ -261,6 +261,7 @@
resource_limits: "{{ item.resource_limits | default(omit) }}" resource_limits: "{{ item.resource_limits | default(omit) }}"
column_case_sensitive: true column_case_sensitive: true
state: present state: present
locked: "{{ item.locked | default(omit) }}"
loop: "{{ result.users_info }}" loop: "{{ result.users_info }}"
loop_control: loop_control:
label: "{{ item.name }}@{{ item.host }}" label: "{{ item.name }}@{{ item.host }}"
@ -275,6 +276,7 @@
- item.name != 'mariadb.sys' - item.name != 'mariadb.sys'
- item.name != 'mysql.sys' - item.name != 'mysql.sys'
- item.name != 'mysql.infoschema' - item.name != 'mysql.infoschema'
- item.name != 'mysql.session'
# ================================== Cleanup ============================ # ================================== Cleanup ============================

View file

@ -35,6 +35,7 @@
that: that:
- result is changed - result is changed
- result.executed_queries == ['CREATE TABLE {{ test_table1 }} (id int)'] - result.executed_queries == ['CREATE TABLE {{ test_table1 }} (id int)']
- result.execution_time_ms[0] > 0
- name: Insert test data - name: Insert test data
mysql_query: mysql_query:
@ -52,6 +53,8 @@
- result is changed - result is changed
- result.rowcount == [2, 1] - result.rowcount == [2, 1]
- result.executed_queries == ['INSERT INTO {{ test_table1 }} VALUES (1), (2)', 'INSERT INTO {{ test_table1 }} VALUES (3)'] - result.executed_queries == ['INSERT INTO {{ test_table1 }} VALUES (1), (2)', 'INSERT INTO {{ test_table1 }} VALUES (3)']
- result.execution_time_ms[0] > 0
- result.execution_time_ms[1] > 0
- name: Check data in {{ test_table1 }} - name: Check data in {{ test_table1 }}
mysql_query: mysql_query:

View file

@ -305,3 +305,7 @@
- name: Mysql_user - test update_password - name: Mysql_user - test update_password
ansible.builtin.import_tasks: ansible.builtin.import_tasks:
file: test_update_password.yml file: test_update_password.yml
- name: Mysql_user - test user_locking
ansible.builtin.import_tasks:
file: test_user_locking.yml

View file

@ -0,0 +1,200 @@
---
- vars:
mysql_parameters: &mysql_params
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: '{{ mysql_host }}'
login_port: '{{ mysql_primary_port }}'
block:
# ========================= Prepare =======================================
- name: Mysql_user Lock user | Create a test database
community.mysql.mysql_db:
<<: *mysql_params
name: mysql_lock_user_test
state: present
# ========================== Tests ========================================
- name: Mysql_user Lock user | create locked | Create test user
community.mysql.mysql_user:
<<: *mysql_params
name: mysql_locked_user
password: 'msandbox'
locked: true
priv:
'mysql_lock_user_test.*': 'SELECT'
- name: Mysql_user Lock user | create locked | Assert that test user is locked
community.mysql.mysql_query:
<<: *mysql_params
query:
- SHOW CREATE USER 'mysql_locked_user'@'localhost'
register: locked_user_creation
failed_when:
- locked_user_creation.query_result[0][0] is not search('ACCOUNT LOCK')
- name: 'Mysql_user Lock user | create locked | Idempotence check'
check_mode: true
community.mysql.mysql_user:
<<: *mysql_params
name: mysql_locked_user
locked: true
priv:
'mysql_lock_user_test.*': 'SELECT'
register: idempotence_check
failed_when: idempotence_check is changed
- name: 'Mysql_user Lock user | create locked | Check that absense of locked does not unlock the user'
check_mode: true
community.mysql.mysql_user:
<<: *mysql_params
name: mysql_locked_user
priv:
'mysql_lock_user_test.*': 'SELECT'
register: idempotence_check
failed_when: idempotence_check is changed
- name: 'Mysql_user Lock user | create locked | Unlock test user check_mode: true'
check_mode: true
community.mysql.mysql_user:
<<: *mysql_params
name: mysql_locked_user
locked: false
priv:
'mysql_lock_user_test.*': 'SELECT'
- name: Mysql_user Lock user | create locked | Assert that test user is locked
community.mysql.mysql_query:
<<: *mysql_params
query:
- SHOW CREATE USER 'mysql_locked_user'@'localhost'
register: locked_user_creation
failed_when:
- locked_user_creation.query_result[0][0] is not search('ACCOUNT LOCK')
- name: Mysql_user Lock user | create locked | Unlock test user
community.mysql.mysql_user:
<<: *mysql_params
name: mysql_locked_user
locked: false
priv:
'mysql_lock_user_test.*': 'SELECT'
- name: Mysql_user Lock user | create locked | Assert that test user is not locked
community.mysql.mysql_query:
<<: *mysql_params
query:
- SHOW CREATE USER 'mysql_locked_user'@'localhost'
register: locked_user_creation
failed_when:
- locked_user_creation.query_result[0][0] is search('ACCOUNT LOCK')
- name: Mysql_user Lock user | create locked | Remove test user
community.mysql.mysql_user:
<<: *mysql_params
name: mysql_locked_user
state: absent
- name: Mysql_user Lock user | create unlocked | Create test user
community.mysql.mysql_user:
<<: *mysql_params
name: mysql_locked_user
password: 'msandbox'
locked: false
priv:
'mysql_lock_user_test.*': 'SELECT'
- name: Mysql_user Lock user | create unlocked | Assert that test user is not locked
community.mysql.mysql_query:
<<: *mysql_params
query:
- SHOW CREATE USER 'mysql_locked_user'@'localhost'
register: locked_user_creation
failed_when:
- locked_user_creation.query_result[0][0] is search('ACCOUNT LOCK')
- name: 'Mysql_user Lock user | create unlocked | Idempotence check'
check_mode: true
community.mysql.mysql_user:
<<: *mysql_params
name: mysql_locked_user
locked: false
priv:
'mysql_lock_user_test.*': 'SELECT'
register: idempotence_check
failed_when: idempotence_check is changed
- name: 'Mysql_user Lock user | create unlocked | Lock test user check_mode: true'
check_mode: true
community.mysql.mysql_user:
<<: *mysql_params
name: mysql_locked_user
locked: true
priv:
'mysql_lock_user_test.*': 'SELECT'
- name: Mysql_user Lock user | create unlocked | Assert that test user is not locked
community.mysql.mysql_query:
<<: *mysql_params
query:
- SHOW CREATE USER 'mysql_locked_user'@'localhost'
register: locked_user_creation
failed_when:
- locked_user_creation.query_result[0][0] is search('ACCOUNT LOCK')
- name: Mysql_user Lock user | create unlocked | Lock test user
community.mysql.mysql_user:
<<: *mysql_params
name: mysql_locked_user
locked: true
priv:
'mysql_lock_user_test.*': 'SELECT'
- name: Mysql_user Lock user | create unlocked | Assert that test user is locked
community.mysql.mysql_query:
<<: *mysql_params
query:
- SHOW CREATE USER 'mysql_locked_user'@'localhost'
register: locked_user_creation
failed_when:
- locked_user_creation.query_result[0][0] is not search('ACCOUNT LOCK')
- name: Mysql_user Lock user | create unlocked | Remove test user
community.mysql.mysql_user:
<<: *mysql_params
name: mysql_locked_user
state: absent
- name: Mysql_user Lock user | create default | Create test user
community.mysql.mysql_user:
<<: *mysql_params
name: mysql_locked_user
password: 'msandbox'
priv:
'mysql_lock_user_test.*': 'SELECT'
- name: Mysql_user Lock user | create default | Assert that test user is not locked
community.mysql.mysql_query:
<<: *mysql_params
query:
- SHOW CREATE USER 'mysql_locked_user'@'localhost'
register: locked_user_creation
failed_when:
- locked_user_creation.query_result[0][0] is search('ACCOUNT LOCK')
- name: Mysql_user Lock user | create default | Remove test user
community.mysql.mysql_user:
<<: *mysql_params
name: mysql_locked_user
state: absent
# ========================= Teardown ======================================
- name: Mysql_user Lock user | Delete test database
community.mysql.mysql_db:
<<: *mysql_params
name: mysql_lock_user_test
state: absent