1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2024-09-14 20:13:21 +02:00

Migrating MySQL to community.mysql (#633)

* Migrating MySQL to community.mysql

* Added PR to changelog

* Removed missed tests

* Removed missed changelog fragments

* Update meta/runtime.yml

Co-authored-by: Ben Mildren <bmildren@digitalocean.com>
Co-authored-by: Felix Fontein <felix@fontein.de>
This commit is contained in:
Ben Mildren 2020-07-10 17:54:04 +01:00 committed by GitHub
parent d0b07885f0
commit f420e8f02e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
109 changed files with 42 additions and 8303 deletions

25
.github/BOTMETA.yml vendored
View file

@ -64,10 +64,6 @@ files:
$doc_fragments/hwc.py:
maintainers: $team_huawei
labels: hwc
$doc_fragments/mysql.py:
maintainers: $team_mysql
labels: database mysql
keywords: mariadb
$doc_fragments/postgres.py:
maintainers: $team_postgresql
labels: postgres postgresql
@ -155,10 +151,6 @@ files:
$module_utils/memset.py:
maintainers: glitchcrab
labels: cloud memset
$module_utils/mysql.py:
maintainers: $team_mysql
labels: database mysql
keywords: mariadb
$module_utils/net_tools/nios/api.py:
maintainers: $team_networking sganesh-infoblox
labels: infoblox networking
@ -498,22 +490,6 @@ files:
authors: vedit
maintainers: Jmainguy kenichi-ogawa-1988
labels: mssql_db
$modules/database/mysql/mysql_db.py:
authors: ansible
maintainers: $team_mysql
$modules/database/mysql/:
authors: Andersson007
maintainers: Alexander198961 Xyon bmalynovytch bmildren kurtdavis michaelcoburn oneiroi tolland
keywords: mariadb
$modules/database/mysql/mysql_replication.py:
authors: Andersson007 banyek
$modules/database/mysql/mysql_user.py:
authors: Jmainguy bmalynovytch
maintainers: Alexander198961 Andersson007 Xyon bmildren kurtdavis michaelcoburn oneiroi tolland
ignore: tomaszkiewicz
$modules/database/mysql/mysql_variables.py:
authors: banyek
maintainers: $team_mysql
$modules/database/postgresql/postgresql_db.py:
authors: ansible
maintainers: $team_postgresql
@ -1341,7 +1317,6 @@ macros:
team_linode: InTheCloudDan decentral1se displague rmcintosh
team_macos: akasurde kyleabenson martinm82
team_manageiq: abellotti cben gtanzillo yaacov zgalor
team_mysql: Alexander198961 Andersson007 Xyon bmalynovytch bmildren kurtdavis michaelcoburn oneiroi tolland
team_netapp: amit0701 carchi8py hulquest lmprice lonico ndswartz schmots1
team_netscaler: chiradeep giorgos-nikolopoulos
team_netvisor: Qalthos amitsi csharpe-pn pdam preetiparasar

View file

@ -1,2 +0,0 @@
bugfixes:
- mysql_user - fix overriding password to the same (https://github.com/ansible-collections/community.general/issues/543).

View file

@ -1,2 +0,0 @@
bugfixes:
- mysql_user - add missed privileges to support (https://github.com/ansible-collections/community.general/issues/617).

View file

@ -0,0 +1,2 @@
removed_features:
- "mysql_* - all MySQL modules have been moved to the ``community.mysql`` collection. A redirection is active, which will be removed in version 2.0.0 (https://github.com/ansible-collections/community.general/pull/633)."

View file

@ -286,6 +286,36 @@ plugin_routing:
deprecation:
removal_version: 3.0.0
warning_text: see plugin documentation for details
mysql_db:
deprecation:
removal_version: 2.0.0
warning_text: The mysql_db module has been moved to the community.mysql collection.
redirect: community.mysql.mysql_db
mysql_info:
deprecation:
removal_version: 2.0.0
warning_text: The mysql_info module has been moved to the community.mysql collection.
redirect: community.mysql.mysql_info
mysql_query:
deprecation:
removal_version: 2.0.0
warning_text: The mysql_query module has been moved to the community.mysql collection.
redirect: community.mysql.mysql_query
mysql_replication:
deprecation:
removal_version: 2.0.0
warning_text: The mysql_replication module has been moved to the community.mysql collection.
redirect: community.mysql.mysql_replication
mysql_user:
deprecation:
removal_version: 2.0.0
warning_text: The mysql_user module has been moved to the community.mysql collection.
redirect: community.mysql.mysql_user
mysql_variables:
deprecation:
removal_version: 2.0.0
warning_text: The mysql_variables module has been moved to the community.mysql collection.
redirect: community.mysql.mysql_variables
ovirt:
deprecation:
removal_version: 3.0.0
@ -627,6 +657,11 @@ plugin_routing:
removal_version: 2.0.0
warning_text: The digital_ocean docs_fragment has been moved to the community.digitalocean collection.
redirect: community.digitalocean.digital_ocean
mysql:
deprecation:
removal_version: 2.0.0
warning_text: The mysql docs_fragment has been moved to the community.mysql collection.
redirect: community.mysql.mysql
proxysql:
deprecation:
removal_version: 2.0.0
@ -638,3 +673,8 @@ plugin_routing:
removal_version: 2.0.0
warning_text: The digital_ocean module_utils has been moved to the community.digitalocean collection.
redirect: community.digitalocean.digital_ocean
mysql:
deprecation:
removal_version: 2.0.0
warning_text: The mysql module_utils has been moved to the community.mysql collection.
redirect: community.mysql.mysql

View file

@ -1,82 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2015, Jonathan Mainguy <jon@soh.re>
# 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
class ModuleDocFragment(object):
# Standard mysql documentation fragment
DOCUMENTATION = r'''
options:
login_user:
description:
- The username used to authenticate with.
type: str
login_password:
description:
- The password used to authenticate with.
type: str
login_host:
description:
- Host running the database.
- In some cases for local connections the I(login_unix_socket=/path/to/mysqld/socket),
that is usually C(/var/run/mysqld/mysqld.sock), needs to be used instead of I(login_host=localhost).
type: str
default: localhost
login_port:
description:
- Port of the MySQL server. Requires I(login_host) be defined as other than localhost if login_port is used.
type: int
default: 3306
login_unix_socket:
description:
- The path to a Unix domain socket for local connections.
type: str
connect_timeout:
description:
- The connection timeout when connecting to the MySQL server.
type: int
default: 30
config_file:
description:
- Specify a config file from which user and password are to be read.
type: path
default: '~/.my.cnf'
ca_cert:
description:
- The path to a Certificate Authority (CA) certificate. This option, if used, must specify the same certificate
as used by the server.
type: path
aliases: [ ssl_ca ]
client_cert:
description:
- The path to a client public key certificate.
type: path
aliases: [ ssl_cert ]
client_key:
description:
- The path to the client private key.
type: path
aliases: [ ssl_key ]
requirements:
- PyMySQL (Python 2.7 and Python 3.X), or
- MySQLdb (Python 2.x)
notes:
- Requires the PyMySQL (Python 2.7 and Python 3.X) or MySQL-python (Python 2.X) package 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).
- 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
default login of 'root' with no password.
- If there are problems with local connections, using I(login_unix_socket=/path/to/mysqld/socket)
instead of I(login_host=localhost) might help. As an example, the default MariaDB installation of version 10.4
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``.
'''

View file

@ -1,110 +0,0 @@
# This code is part of Ansible, but is an independent component.
# This particular file snippet, and this file snippet only, is BSD licensed.
# Modules you write using this snippet, which is embedded dynamically by Ansible
# still belong to the author of the module, and may assign their own license
# to the complete work.
#
# 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 licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import os
from ansible.module_utils.six.moves import configparser
try:
import pymysql as mysql_driver
_mysql_cursor_param = 'cursor'
except ImportError:
try:
import MySQLdb as mysql_driver
import MySQLdb.cursors
_mysql_cursor_param = 'cursorclass'
except ImportError:
mysql_driver = None
mysql_driver_fail_msg = 'The PyMySQL (Python 2.7 and Python 3.X) or MySQL-python (Python 2.X) module is required.'
def parse_from_mysql_config_file(cnf):
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):
config = {}
if config_file and os.path.exists(config_file):
config['read_default_file'] = config_file
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:
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('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:
config['ssl'] = {}
if module.params['login_unix_socket']:
config['unix_socket'] = module.params['login_unix_socket']
else:
config['host'] = module.params['login_host']
config['port'] = module.params['login_port']
# If login_user or login_password are given, they should override the
# config file
if login_user is not None:
config['user'] = login_user
if login_password is not None:
config['passwd'] = login_password
if ssl_cert is not None:
config['ssl']['cert'] = ssl_cert
if ssl_key is not None:
config['ssl']['key'] = ssl_key
if ssl_ca is not None:
config['ssl']['ca'] = ssl_ca
if db is not None:
config['db'] = db
if connect_timeout is not None:
config['connect_timeout'] = connect_timeout
if _mysql_cursor_param == 'cursor':
# In case of PyMySQL driver:
db_connection = mysql_driver.connect(autocommit=autocommit, **config)
else:
# In case of MySQLdb driver
db_connection = mysql_driver.connect(**config)
if autocommit:
db_connection.autocommit(True)
if cursor_class == 'DictCursor':
return db_connection.cursor(**{_mysql_cursor_param: mysql_driver.cursors.DictCursor}), db_connection
else:
return db_connection.cursor(), db_connection
def mysql_common_argument_spec():
return dict(
login_user=dict(type='str', default=None),
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'),
config_file=dict(type='path', default='~/.my.cnf'),
connect_timeout=dict(type='int', default=30),
client_cert=dict(type='path', aliases=['ssl_cert']),
client_key=dict(type='path', aliases=['ssl_key']),
ca_cert=dict(type='path', aliases=['ssl_ca']),
)

View file

@ -1,727 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2012, Mark Theunissen <mark.theunissen@gmail.com>
# Sponsored by Four Kitchens http://fourkitchens.com.
# 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_db
short_description: Add or remove MySQL databases from a remote host
description:
- Add or remove MySQL databases from a remote host.
options:
name:
description:
- Name of the database to add or remove.
- I(name=all) may only be provided if I(state) is C(dump) or C(import).
- List of databases is provided with I(state=dump), I(state=present) and I(state=absent).
- If I(name=all) it works like --all-databases option for mysqldump (Added in 2.0).
required: true
type: list
elements: str
aliases: [db]
state:
description:
- The database state
type: str
default: present
choices: ['absent', 'dump', 'import', 'present']
collation:
description:
- Collation mode (sorting). This only applies to new table/databases and
does not update existing ones, this is a limitation of MySQL.
type: str
default: ''
encoding:
description:
- Encoding mode to use, examples include C(utf8) or C(latin1_swedish_ci),
at creation of database, dump or importation of sql script.
type: str
default: ''
target:
description:
- Location, on the remote host, of the dump file to read from or write to.
- Uncompressed SQL files (C(.sql)) as well as bzip2 (C(.bz2)), gzip (C(.gz)) and
xz (Added in 2.0) compressed files are supported.
type: path
single_transaction:
description:
- Execute the dump in a single transaction.
type: bool
default: no
quick:
description:
- Option used for dumping large tables.
type: bool
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
version_added: '0.2.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: no
version_added: '0.2.0'
master_data:
description:
- Option to dump a master replication server to produce a dump file
that can be used to set up another server as a slave of the master.
- C(0) to not include master data.
- C(1) to generate a 'CHANGE MASTER TO' statement
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
version_added: '0.2.0'
skip_lock_tables:
description:
- Skip locking tables for read. Used when I(state=dump), ignored otherwise.
required: no
type: bool
default: no
version_added: '0.2.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.2.0'
use_shell:
description:
- 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: no
version_added: '0.2.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.
type: bool
default: no
version_added: '0.2.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
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: no
version_added: '0.2.0'
check_implicit_admin:
description:
- Check if mysql allows login as root/nopassword before trying supplied credentials.
- If success, passed I(login_user)/I(login_password) will be ignored.
type: bool
default: no
version_added: '0.2.0'
config_overrides_defaults:
description:
- If C(yes), connection parameters from I(config_file) will override the default
values of I(login_host) and I(login_port) parameters.
- Used when I(stat) is C(present) or C(absent), ignored otherwise.
- It needs Python 3.5+ as the default interpreter on a target host.
type: bool
default: no
version_added: '0.2.0'
seealso:
- module: community.general.mysql_info
- module: community.general.mysql_variables
- module: community.general.mysql_user
- module: community.general.mysql_replication
- name: MySQL command-line client reference
description: Complete reference of the MySQL command-line client documentation.
link: https://dev.mysql.com/doc/refman/8.0/en/mysql.html
- name: mysqldump reference
description: Complete reference of the ``mysqldump`` client utility documentation.
link: https://dev.mysql.com/doc/refman/8.0/en/mysqldump.html
- name: CREATE DATABASE reference
description: Complete reference of the CREATE DATABASE command documentation.
link: https://dev.mysql.com/doc/refman/8.0/en/create-database.html
- name: DROP DATABASE reference
description: Complete reference of the DROP DATABASE command documentation.
link: https://dev.mysql.com/doc/refman/8.0/en/drop-database.html
author: "Ansible Core Team"
requirements:
- mysql (command line binary)
- mysqldump (command line binary)
notes:
- 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.
extends_documentation_fragment:
- community.general.mysql
'''
EXAMPLES = r'''
- name: Create a new database with name 'bobdata'
mysql_db:
name: bobdata
state: present
- name: Create new databases with names 'foo' and 'bar'
mysql_db:
name:
- foo
- bar
state: present
# Copy database dump file to remote host and restore it to database 'my_db'
- name: Copy database dump file
copy:
src: dump.sql.bz2
dest: /tmp
- name: Restore database
mysql_db:
name: my_db
state: import
target: /tmp/dump.sql.bz2
- name: Restore database ignoring errors
mysql_db:
name: my_db
state: import
target: /tmp/dump.sql.bz2
force: yes
- name: Dump multiple databases
mysql_db:
state: dump
name: db_1,db_2
target: /tmp/dump.sql
- name: Dump multiple databases
mysql_db:
state: dump
name:
- db_1
- db_2
target: /tmp/dump.sql
- name: Dump all databases to hostname.sql
mysql_db:
state: dump
name: all
target: /tmp/dump.sql
- name: Dump all databases to hostname.sql including master data
mysql_db:
state: dump
name: all
target: /tmp/dump.sql
master_data: 1
# Import of sql script with encoding option
- name: >
Import dump.sql with specific latin1 encoding,
similar to mysql -u <username> --default-character-set=latin1 -p <password> < dump.sql
mysql_db:
state: import
name: all
encoding: latin1
target: /tmp/dump.sql
# Dump of database with encoding option
- name: >
Dump of Databse with specific latin1 encoding,
similar to mysqldump -u <username> --default-character-set=latin1 -p <password> <database>
mysql_db:
state: dump
name: db_1
encoding: latin1
target: /tmp/dump.sql
- name: Delete database with name 'bobdata'
mysql_db:
name: bobdata
state: absent
- name: Make sure there is neither a database with name 'foo', nor one with name 'bar'
mysql_db:
name:
- foo
- bar
state: absent
# Dump database with argument not directly supported by this module
# using dump_extra_args parameter
- name: Dump databases without including triggers
mysql_db:
state: dump
name: foo
target: /tmp/dump.sql
dump_extra_args: --skip-triggers
- name: Try to create database as root/nopassword first. If not allowed, pass the credentials
mysql_db:
check_implicit_admin: yes
login_user: bob
login_password: 123456
name: bobdata
state: present
'''
RETURN = r'''
db:
description: Database names in string format delimited by white space.
returned: always
type: str
sample: "foo bar"
db_list:
description: List of database names.
returned: always
type: list
sample: ["foo", "bar"]
executed_commands:
description: List of commands which tried to run.
returned: if executed
type: list
sample: ["CREATE DATABASE acme"]
version_added: '0.2.0'
'''
import os
import subprocess
import traceback
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.database import mysql_quote_identifier
from ansible_collections.community.general.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
executed_commands = []
# ===========================================
# MySQL module specific support methods.
#
def db_exists(cursor, db):
res = 0
for each_db in db:
res += cursor.execute("SHOW DATABASES LIKE %s", (each_db.replace("_", r"\_"),))
return res == len(db)
def db_delete(cursor, db):
if not db:
return False
for each_db in db:
query = "DROP DATABASE %s" % mysql_quote_identifier(each_db, 'database')
executed_commands.append(query)
cursor.execute(query)
return True
def db_dump(module, host, user, password, db_name, target, all_databases, port,
config_file, socket=None, ssl_cert=None, ssl_key=None, ssl_ca=None,
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):
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 += " --defaults-file=%s" % shlex_quote(config_file)
else:
cmd += " --defaults-extra-file=%s" % shlex_quote(config_file)
if check_implicit_admin:
cmd += " --user=root --password=''"
else:
if user is not None:
cmd += " --user=%s" % shlex_quote(user)
if password is not None:
if not unsafe_password:
cmd += " --password=%s" % shlex_quote(password)
else:
cmd += " --password=%s" % password
if ssl_cert is not None:
cmd += " --ssl-cert=%s" % shlex_quote(ssl_cert)
if ssl_key is not None:
cmd += " --ssl-key=%s" % shlex_quote(ssl_key)
if ssl_ca is not None:
cmd += " --ssl-ca=%s" % shlex_quote(ssl_ca)
if force:
cmd += " --force"
if socket is not None:
cmd += " --socket=%s" % shlex_quote(socket)
else:
cmd += " --host=%s --port=%i" % (shlex_quote(host), port)
if all_databases:
cmd += " --all-databases"
elif len(db_name) > 1:
cmd += " --databases {0}".format(' '.join(db_name))
else:
cmd += " %s" % shlex_quote(' '.join(db_name))
if skip_lock_tables:
cmd += " --skip-lock-tables"
if (encoding is not None) and (encoding != ""):
cmd += " --default-character-set=%s" % shlex_quote(encoding)
if single_transaction:
cmd += " --single-transaction=true"
if quick:
cmd += " --quick"
if ignore_tables:
for an_ignored_table in ignore_tables:
cmd += " --ignore-table={0}".format(an_ignored_table)
if hex_blob:
cmd += " --hex-blob"
if master_data:
cmd += " --master-data=%s" % master_data
if dump_extra_args is not None:
cmd += " " + dump_extra_args
path = None
if os.path.splitext(target)[-1] == '.gz':
path = module.get_bin_path('gzip', True)
elif os.path.splitext(target)[-1] == '.bz2':
path = module.get_bin_path('bzip2', True)
elif os.path.splitext(target)[-1] == '.xz':
path = module.get_bin_path('xz', True)
if path:
cmd = '%s | %s > %s' % (cmd, path, shlex_quote(target))
else:
cmd += " > %s" % shlex_quote(target)
executed_commands.append(cmd)
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,
socket=None, ssl_cert=None, ssl_key=None, ssl_ca=None, encoding=None, force=False,
use_shell=False, unsafe_password=False, restrict_config_file=False,
check_implicit_admin=False):
if not os.path.exists(target):
return module.fail_json(msg="target %s does not exist on the host" % target)
cmd = [module.get_bin_path('mysql', True)]
# --defaults-file must go first, or errors out
if config_file:
if restrict_config_file:
cmd.append("--defaults-file=%s" % shlex_quote(config_file))
else:
cmd.append("--defaults-extra-file=%s" % shlex_quote(config_file))
if check_implicit_admin:
cmd += " --user=root --password=''"
else:
if user:
cmd.append("--user=%s" % shlex_quote(user))
if password:
if not unsafe_password:
cmd.append("--password=%s" % shlex_quote(password))
else:
cmd.append("--password=%s" % password)
if ssl_cert is not None:
cmd.append("--ssl-cert=%s" % shlex_quote(ssl_cert))
if ssl_key is not None:
cmd.append("--ssl-key=%s" % shlex_quote(ssl_key))
if ssl_ca is not None:
cmd.append("--ssl-ca=%s" % shlex_quote(ssl_ca))
if force:
cmd.append("-f")
if socket is not None:
cmd.append("--socket=%s" % shlex_quote(socket))
else:
cmd.append("--host=%s" % shlex_quote(host))
cmd.append("--port=%i" % port)
if (encoding is not None) and (encoding != ""):
cmd.append("--default-character-set=%s" % shlex_quote(encoding))
if not all_databases:
cmd.append("--one-database")
cmd.append(shlex_quote(''.join(db_name)))
comp_prog_path = None
if os.path.splitext(target)[-1] == '.gz':
comp_prog_path = module.get_bin_path('gzip', required=True)
elif os.path.splitext(target)[-1] == '.bz2':
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)
if comp_prog_path:
# The line below is for returned data only:
executed_commands.append('%s -dc %s | %s' % (comp_prog_path, target, cmd))
if not use_shell:
p1 = subprocess.Popen([comp_prog_path, '-dc', target], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
p2 = subprocess.Popen(cmd, stdin=p1.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(stdout2, stderr2) = p2.communicate()
p1.stdout.close()
p1.wait()
if p1.returncode != 0:
stderr1 = p1.stderr.read()
return p1.returncode, '', stderr1
else:
return p2.returncode, stdout2, stderr2
else:
# Used to prevent 'Broken pipe' errors that
# occasionaly occur when target files are compressed.
# FYI: passing the `shell=True` argument to p2 = subprocess.Popen()
# doesn't solve the problem.
cmd = " ".join(cmd)
cmd = "%s -dc %s | %s" % (comp_prog_path, shlex_quote(target), cmd)
rc, stdout, stderr = module.run_command(cmd, use_unsafe_shell=True)
return rc, stdout, stderr
else:
cmd = ' '.join(cmd)
cmd += " < %s" % shlex_quote(target)
executed_commands.append(cmd)
rc, stdout, stderr = module.run_command(cmd, use_unsafe_shell=True)
return rc, stdout, stderr
def db_create(cursor, db, encoding, collation):
if not db:
return False
query_params = dict(enc=encoding, collate=collation)
res = 0
for each_db in db:
query = ['CREATE DATABASE %s' % mysql_quote_identifier(each_db, 'database')]
if encoding:
query.append("CHARACTER SET %(enc)s")
if collation:
query.append("COLLATE %(collate)s")
query = ' '.join(query)
res += cursor.execute(query, query_params)
try:
executed_commands.append(cursor.mogrify(query, query_params))
except AttributeError:
executed_commands.append(cursor._executed)
except Exception:
executed_commands.append(query)
return res > 0
# ===========================================
# Module execution.
#
def main():
module = AnsibleModule(
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),
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,
)
if mysql_driver is None:
module.fail_json(msg=mysql_driver_fail_msg)
db = module.params["name"]
if not db:
module.exit_json(changed=False, db=db, db_list=[])
db = [each_db.strip() for each_db in db]
encoding = module.params["encoding"]
collation = module.params["collation"]
state = module.params["state"]
target = module.params["target"]
socket = module.params["login_unix_socket"]
login_port = module.params["login_port"]
if login_port < 0 or login_port > 65535:
module.fail_json(msg="login_port must be a valid unix port number (0-65535)")
ssl_cert = module.params["client_cert"]
ssl_key = module.params["client_key"]
ssl_ca = module.params["ca_cert"]
connect_timeout = module.params['connect_timeout']
config_file = module.params['config_file']
login_password = module.params["login_password"]
unsafe_login_password = module.params["unsafe_login_password"]
login_user = module.params["login_user"]
login_host = module.params["login_host"]
ignore_tables = module.params["ignore_tables"]
for a_table in ignore_tables:
if a_table == "":
module.fail_json(msg="Name of ignored table cannot be empty")
single_transaction = module.params["single_transaction"]
quick = module.params["quick"]
hex_blob = module.params["hex_blob"]
force = module.params["force"]
master_data = module.params["master_data"]
skip_lock_tables = module.params["skip_lock_tables"]
dump_extra_args = module.params["dump_extra_args"]
use_shell = module.params["use_shell"]
restrict_config_file = module.params["restrict_config_file"]
check_implicit_admin = module.params['check_implicit_admin']
config_overrides_defaults = module.params['config_overrides_defaults']
if len(db) > 1 and state == 'import':
module.fail_json(msg="Multiple databases are not supported with state=import")
db_name = ' '.join(db)
all_databases = False
if state in ['dump', 'import']:
if target is None:
module.fail_json(msg="with state=%s target is required" % state)
if db == ['all']:
all_databases = True
else:
if db == ['all']:
module.fail_json(msg="name is not allowed to equal 'all' unless state equals import, or dump.")
try:
cursor = None
if check_implicit_admin:
try:
cursor, db_conn = mysql_connect(module, 'root', '', config_file, ssl_cert, ssl_key, ssl_ca,
connect_timeout=connect_timeout,
config_overrides_defaults=config_overrides_defaults)
except Exception as e:
check_implicit_admin = False
pass
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)
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. "
"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)))
changed = False
if not os.path.exists(config_file):
config_file = None
existence_list = []
non_existence_list = []
if not all_databases:
for each_database in db:
if db_exists(cursor, [each_database]):
existence_list.append(each_database)
else:
non_existence_list.append(each_database)
if state == "absent":
if module.check_mode:
module.exit_json(changed=bool(existence_list), db=db_name, db_list=db)
try:
changed = db_delete(cursor, existence_list)
except Exception as e:
module.fail_json(msg="error deleting database: %s" % to_native(e))
module.exit_json(changed=changed, db=db_name, db_list=db, executed_commands=executed_commands)
elif state == "present":
if module.check_mode:
module.exit_json(changed=bool(non_existence_list), db=db_name, db_list=db)
changed = False
if non_existence_list:
try:
changed = db_create(cursor, non_existence_list, encoding, collation)
except Exception as e:
module.fail_json(msg="error creating database: %s" % to_native(e),
exception=traceback.format_exc())
module.exit_json(changed=changed, db=db_name, db_list=db, executed_commands=executed_commands)
elif state == "dump":
if non_existence_list and not all_databases:
module.fail_json(msg="Cannot dump database(s) %r - not found" % (', '.join(non_existence_list)))
if module.check_mode:
module.exit_json(changed=True, db=db_name, db_list=db)
rc, stdout, stderr = db_dump(module, login_host, login_user,
login_password, db, target, all_databases,
login_port, config_file, socket, ssl_cert, ssl_key,
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)
if rc != 0:
module.fail_json(msg="%s" % stderr)
module.exit_json(changed=True, db=db_name, db_list=db, msg=stdout,
executed_commands=executed_commands)
elif state == "import":
if module.check_mode:
module.exit_json(changed=True, db=db_name, db_list=db)
if non_existence_list and not all_databases:
try:
db_create(cursor, non_existence_list, encoding, collation)
except Exception as e:
module.fail_json(msg="error creating database: %s" % to_native(e),
exception=traceback.format_exc())
rc, stdout, stderr = db_import(module, login_host, login_user,
login_password, db, target,
all_databases,
login_port, config_file,
socket, ssl_cert, ssl_key, ssl_ca,
encoding, force, use_shell, unsafe_login_password,
restrict_config_file, 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,
executed_commands=executed_commands)
if __name__ == '__main__':
main()

View file

@ -1,543 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# 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 servers
description:
- 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(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),
the excluding values, C(!settings) in this case, will be ignored.
type: list
elements: str
login_db:
description:
- Database name to connect to.
- It makes sense if I(login_user) is allowed to connect to a specific database only.
type: str
exclude_fields:
description:
- List of fields which are not needed to collect.
- "Supports elements: C(db_size). Unsupported elements will be ignored"
type: list
elements: str
version_added: '0.2.0'
return_empty_dbs:
description:
- Includes names of empty databases to returned dictionary.
type: bool
default: no
notes:
- Calculating the size of a database might be slow, depending on the number and size of tables in it.
To avoid this, use I(exclude_fields=db_size).
seealso:
- module: community.general.mysql_variables
- module: community.general.mysql_db
- module: community.general.mysql_user
- module: community.general.mysql_replication
author:
- Andrew Klychkov (@Andersson007)
extends_documentation_fragment:
- community.general.mysql
'''
EXAMPLES = r'''
# Display info from mysql-hosts group (using creds from ~/.my.cnf to connect):
# ansible mysql-hosts -m mysql_info
# Display only databases and users info:
# ansible mysql-hosts -m mysql_info -a 'filter=databases,users'
# 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'
- name: Collect all possible information using passwordless root access
mysql_info:
login_user: root
- name: Get MySQL version with non-default credentials
mysql_info:
login_user: mysuperuser
login_password: mysuperpass
filter: version
- name: Collect all info except settings and users by root
mysql_info:
login_user: root
login_password: rootpass
filter: "!settings,!users"
- name: Collect info about databases and version using ~/.my.cnf as a credential file
become: yes
mysql_info:
filter:
- databases
- version
- name: Collect info about databases and version using ~alice/.my.cnf as a credential file
become: yes
mysql_info:
config_file: /home/alice/.my.cnf
filter:
- databases
- version
- name: Collect info about databases including empty and excluding their sizes
become: yes
mysql_info:
config_file: /home/alice/.my.cnf
filter:
- databases
exclude_fields: db_size
return_empty_dbs: yes
'''
RETURN = r'''
version:
description: Database server version.
returned: if not excluded by filter
type: dict
sample: { "version": { "major": 5, "minor": 5, "release": 60 } }
contains:
major:
description: Major server version.
returned: if not excluded by filter
type: int
sample: 5
minor:
description: Minor server version.
returned: if not excluded by filter
type: int
sample: 5
release:
description: Release server version.
returned: if not excluded by filter
type: int
sample: 60
databases:
description: Information about databases.
returned: if not excluded by filter
type: dict
sample:
- { "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 }
settings:
description: Global settings (variables) information.
returned: if not excluded by filter
type: dict
sample:
- { "innodb_open_files": 300, innodb_page_size": 16384 }
global_status:
description: Global status information.
returned: if not excluded by filter
type: dict
sample:
- { "Innodb_buffer_pool_read_requests": 123, "Innodb_buffer_pool_reads": 32 }
users:
description: Users information.
returned: if not excluded by filter
type: dict
sample:
- { "localhost": { "root": { "Alter_priv": "Y", "Alter_routine_priv": "Y" } } }
engines:
description: Information about the server's storage engines.
returned: if not excluded by filter
type: dict
sample:
- { "CSV": { "Comment": "CSV storage engine", "Savepoints": "NO", "Support": "YES", "Transactions": "NO", "XA": "NO" } }
master_status:
description: Master status information.
returned: if master
type: dict
sample:
- { "Binlog_Do_DB": "", "Binlog_Ignore_DB": "mysql", "File": "mysql-bin.000001", "Position": 769 }
slave_status:
description: Slave status information.
returned: if standby
type: dict
sample:
- { "192.168.1.101": { "3306": { "replication_user": { "Connect_Retry": 60, "Exec_Master_Log_Pos": 769, "Last_Errno": 0 } } } }
slave_hosts:
description: Slave status information.
returned: if master
type: dict
sample:
- { "2": { "Host": "", "Master_id": 1, "Port": 3306 } }
'''
from decimal import Decimal
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.mysql import (
mysql_connect,
mysql_common_argument_spec,
mysql_driver,
mysql_driver_fail_msg,
)
from ansible.module_utils.six import iteritems
from ansible.module_utils._text import to_native
# ===========================================
# MySQL module specific support methods.
#
class MySQL_Info(object):
"""Class for collection MySQL instance information.
Arguments:
module (AnsibleModule): Object of AnsibleModule class.
cursor (pymysql/mysql-python): Cursor class for interaction with
the database.
Note:
If you need to add a new subset:
1. add a new key with the same name to self.info attr in self.__init__()
2. add a new private method to get the information
3. add invocation of the new method to self.__collect()
4. add info about the new subset to the DOCUMENTATION block
5. add info about the new subset with an example to RETURN block
"""
def __init__(self, module, cursor):
self.module = module
self.cursor = cursor
self.info = {
'version': {},
'databases': {},
'settings': {},
'global_status': {},
'engines': {},
'users': {},
'master_status': {},
'slave_hosts': {},
'slave_status': {},
}
def get_info(self, filter_, exclude_fields, return_empty_dbs):
"""Get MySQL instance information based on filter_.
Arguments:
filter_ (list): List of collected subsets (e.g., databases, users, etc.),
when it is empty, return all available information.
"""
inc_list = []
exc_list = []
if filter_:
partial_info = {}
for fi in filter_:
if fi.lstrip('!') not in self.info:
self.module.warn('filter element: %s is not allowable, ignored' % fi)
continue
if fi[0] == '!':
exc_list.append(fi.lstrip('!'))
else:
inc_list.append(fi)
if inc_list:
self.__collect(exclude_fields, return_empty_dbs, set(inc_list))
for i in self.info:
if i in inc_list:
partial_info[i] = self.info[i]
else:
not_in_exc_list = list(set(self.info) - set(exc_list))
self.__collect(exclude_fields, return_empty_dbs, set(not_in_exc_list))
for i in self.info:
if i not in exc_list:
partial_info[i] = self.info[i]
return partial_info
else:
self.__collect(exclude_fields, return_empty_dbs, set(self.info))
return self.info
def __collect(self, exclude_fields, return_empty_dbs, wanted):
"""Collect all possible subsets."""
if 'version' in wanted or 'settings' in wanted:
self.__get_global_variables()
if 'databases' in wanted:
self.__get_databases(exclude_fields, return_empty_dbs)
if 'global_status' in wanted:
self.__get_global_status()
if 'engines' in wanted:
self.__get_engines()
if 'users' in wanted:
self.__get_users()
if 'master_status' in wanted:
self.__get_master_status()
if 'slave_status' in wanted:
self.__get_slave_status()
if 'slave_hosts' in wanted:
self.__get_slaves()
def __get_engines(self):
"""Get storage engines info."""
res = self.__exec_sql('SHOW ENGINES')
if res:
for line in res:
engine = line['Engine']
self.info['engines'][engine] = {}
for vname, val in iteritems(line):
if vname != 'Engine':
self.info['engines'][engine][vname] = val
def __convert(self, val):
"""Convert unserializable data."""
try:
if isinstance(val, Decimal):
val = float(val)
else:
val = int(val)
except ValueError:
pass
except TypeError:
pass
return val
def __get_global_variables(self):
"""Get global variables (instance settings)."""
res = self.__exec_sql('SHOW GLOBAL VARIABLES')
if res:
for var in res:
self.info['settings'][var['Variable_name']] = self.__convert(var['Value'])
ver = self.info['settings']['version'].split('.')
release = ver[2].split('-')[0]
self.info['version'] = dict(
major=int(ver[0]),
minor=int(ver[1]),
release=int(release),
)
def __get_global_status(self):
"""Get global status."""
res = self.__exec_sql('SHOW GLOBAL STATUS')
if res:
for var in res:
self.info['global_status'][var['Variable_name']] = self.__convert(var['Value'])
def __get_master_status(self):
"""Get master status if the instance is a master."""
res = self.__exec_sql('SHOW MASTER STATUS')
if res:
for line in res:
for vname, val in iteritems(line):
self.info['master_status'][vname] = self.__convert(val)
def __get_slave_status(self):
"""Get slave status if the instance is a slave."""
res = self.__exec_sql('SHOW SLAVE STATUS')
if res:
for line in res:
host = line['Master_Host']
if host not in self.info['slave_status']:
self.info['slave_status'][host] = {}
port = line['Master_Port']
if port not in self.info['slave_status'][host]:
self.info['slave_status'][host][port] = {}
user = line['Master_User']
if user not in self.info['slave_status'][host][port]:
self.info['slave_status'][host][port][user] = {}
for vname, val in iteritems(line):
if vname not in ('Master_Host', 'Master_Port', 'Master_User'):
self.info['slave_status'][host][port][user][vname] = self.__convert(val)
def __get_slaves(self):
"""Get slave hosts info if the instance is a master."""
res = self.__exec_sql('SHOW SLAVE HOSTS')
if res:
for line in res:
srv_id = line['Server_id']
if srv_id not in self.info['slave_hosts']:
self.info['slave_hosts'][srv_id] = {}
for vname, val in iteritems(line):
if vname != 'Server_id':
self.info['slave_hosts'][srv_id][vname] = self.__convert(val)
def __get_users(self):
"""Get user info."""
res = self.__exec_sql('SELECT * FROM mysql.user')
if res:
for line in res:
host = line['Host']
if host not in self.info['users']:
self.info['users'][host] = {}
user = line['User']
self.info['users'][host][user] = {}
for vname, val in iteritems(line):
if vname not in ('Host', 'User'):
self.info['users'][host][user][vname] = self.__convert(val)
def __get_databases(self, exclude_fields, return_empty_dbs):
"""Get info about databases."""
if not exclude_fields:
query = ('SELECT table_schema AS "name", '
'SUM(data_length + index_length) AS "size" '
'FROM information_schema.TABLES GROUP BY table_schema')
else:
if 'db_size' in exclude_fields:
query = ('SELECT table_schema AS "name" '
'FROM information_schema.TABLES GROUP BY table_schema')
res = self.__exec_sql(query)
if res:
for db in res:
self.info['databases'][db['name']] = {}
if not exclude_fields or 'db_size' not in exclude_fields:
self.info['databases'][db['name']]['size'] = int(db['size'])
# If empty dbs are not needed in the returned dict, exit from the method
if not return_empty_dbs:
return None
# Add info about empty databases (issue #65727):
res = self.__exec_sql('SHOW DATABASES')
if res:
for db in res:
if db['Database'] not in self.info['databases']:
self.info['databases'][db['Database']] = {}
if not exclude_fields or 'db_size' not in exclude_fields:
self.info['databases'][db['Database']]['size'] = 0
def __exec_sql(self, query, ddl=False):
"""Execute SQL.
Arguments:
ddl (bool): If True, return True or False.
Used for queries that don't return any rows
(mainly for DDL queries) (default False).
"""
try:
self.cursor.execute(query)
if not ddl:
res = self.cursor.fetchall()
return res
return True
except Exception as e:
self.module.fail_json(msg="Cannot execute SQL '%s': %s" % (query, to_native(e)))
return False
# ===========================================
# Module execution.
#
def main():
argument_spec = mysql_common_argument_spec()
argument_spec.update(
login_db=dict(type='str'),
filter=dict(type='list'),
exclude_fields=dict(type='list'),
return_empty_dbs=dict(type='bool', default=False),
)
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True,
)
db = module.params['login_db']
connect_timeout = module.params['connect_timeout']
login_user = module.params['login_user']
login_password = module.params['login_password']
ssl_cert = module.params['client_cert']
ssl_key = module.params['client_key']
ssl_ca = module.params['ca_cert']
config_file = module.params['config_file']
filter_ = module.params['filter']
exclude_fields = module.params['exclude_fields']
return_empty_dbs = module.params['return_empty_dbs']
if filter_:
filter_ = [f.strip() for f in filter_]
if exclude_fields:
exclude_fields = set([f.strip() for f in exclude_fields])
if mysql_driver is None:
module.fail_json(msg=mysql_driver_fail_msg)
try:
cursor, db_conn = mysql_connect(module, login_user, login_password,
config_file, ssl_cert, ssl_key, ssl_ca, db,
connect_timeout=connect_timeout, cursor_class='DictCursor')
except Exception as e:
module.fail_json(msg="unable to connect to database, check login_user and login_password are correct or %s has the credentials. "
"Exception message: %s" % (config_file, to_native(e)))
###############################
# Create object and do main job
mysql = MySQL_Info(module, cursor)
module.exit_json(changed=False, **mysql.get_info(filter_, exclude_fields, return_empty_dbs))
if __name__ == '__main__':
main()

View file

@ -1,233 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# 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)
__metaclass__ = type
DOCUMENTATION = r'''
---
module: mysql_query
short_description: Run MySQL queries
description:
- Runs arbitrary MySQL queries.
- Pay attention, the module does not support check mode!
All queries will be executed in autocommit mode.
version_added: '0.2.0'
options:
query:
description:
- SQL query to run. Multiple queries can be passed using YAML list syntax.
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
named_args:
description:
- Dictionary of key-value arguments to pass to the query.
- Mutually exclusive with I(positional_args).
type: dict
login_db:
description:
- Name of database to connect to and run queries against.
type: str
single_transaction:
description:
- Where passed queries run in a single transaction (C(yes)) or commit them one-by-one (C(no)).
type: bool
default: no
notes:
- To pass a query containing commas, use YAML list notation with hyphen (see EXAMPLES block).
author:
- Andrew Klychkov (@Andersson007)
extends_documentation_fragment:
- community.general.mysql
'''
EXAMPLES = r'''
- name: Simple select query to acme db
mysql_query:
login_db: acme
query: SELECT * FROM orders
- name: Select query to db acme with positional arguments
mysql_query:
login_db: acme
query: SELECT * FROM acme WHERE id = %s AND story = %s
positional_args:
- 1
- test
- name: Select query to test_db with named_args
mysql_query:
login_db: test_db
query: SELECT * FROM test WHERE id = %(id_val)s AND story = %(story_val)s
named_args:
id_val: 1
story_val: test
- name: Run several insert queries against db test_db in single transaction
mysql_query:
login_db: test_db
query:
- INSERT INTO articles (id, story) VALUES (2, 'my_long_story')
- INSERT INTO prices (id, price) VALUES (123, '100.00')
single_transaction: yes
'''
RETURN = r'''
executed_queries:
description: List of executed queries.
returned: always
type: list
sample: ['SELECT * FROM bar', 'UPDATE bar SET id = 1 WHERE id = 2']
query_result:
description:
- List of lists (sublist for each query) containing dictionaries
in column:value form representing returned rows.
returned: changed
type: list
sample: [[{"Column": "Value1"},{"Column": "Value2"}], [{"ID": 1}, {"ID": 2}]]
rowcount:
description: Number of affected rows for each subquery.
returned: changed
type: list
sample: [5, 1]
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.mysql import (
mysql_connect,
mysql_common_argument_spec,
mysql_driver,
mysql_driver_fail_msg,
)
from ansible.module_utils._text import to_native
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')
# ===========================================
# Module execution.
#
def main():
argument_spec = mysql_common_argument_spec()
argument_spec.update(
query=dict(type='list', elements='str', required=True),
login_db=dict(type='str'),
positional_args=dict(type='list'),
named_args=dict(type='dict'),
single_transaction=dict(type='bool', default=False),
)
module = AnsibleModule(
argument_spec=argument_spec,
mutually_exclusive=(
('positional_args', 'named_args'),
),
)
db = module.params['login_db']
connect_timeout = module.params['connect_timeout']
login_user = module.params['login_user']
login_password = module.params['login_password']
ssl_cert = module.params['client_cert']
ssl_key = module.params['client_key']
ssl_ca = module.params['ca_cert']
config_file = module.params['config_file']
query = module.params["query"]
if module.params["single_transaction"]:
autocommit = False
else:
autocommit = True
# Prepare args:
if module.params.get("positional_args"):
arguments = module.params["positional_args"]
elif module.params.get("named_args"):
arguments = module.params["named_args"]
else:
arguments = None
if mysql_driver is None:
module.fail_json(msg=mysql_driver_fail_msg)
# Connect to DB:
try:
cursor, db_connection = mysql_connect(module, login_user, login_password,
config_file, ssl_cert, ssl_key, ssl_ca, db,
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
max_keyword_len = len(max(DML_QUERY_KEYWORDS + DDL_QUERY_KEYWORDS, key=len))
# Execute query:
query_result = []
executed_queries = []
rowcount = []
for q in query:
try:
cursor.execute(q, arguments)
except Exception as e:
if not autocommit:
db_connection.rollback()
cursor.close()
module.fail_json(msg="Cannot execute SQL '%s' args [%s]: %s" % (q, arguments, to_native(e)))
try:
query_result.append([dict(row) for row in cursor.fetchall()])
except Exception as e:
if not autocommit:
db_connection.rollback()
module.fail_json(msg="Cannot fetch rows from cursor: %s" % to_native(e))
# Check DML or DDL keywords in query and set changed accordingly:
q = q.lstrip()[0:max_keyword_len].upper()
for keyword in DML_QUERY_KEYWORDS:
if keyword in q and cursor.rowcount > 0:
changed = True
for keyword in DDL_QUERY_KEYWORDS:
if keyword in q:
changed = True
executed_queries.append(cursor._last_executed)
rowcount.append(cursor.rowcount)
# When the module run with the single_transaction == True:
if not autocommit:
db_connection.commit()
# Create dict with returned values:
kw = {
'changed': changed,
'executed_queries': executed_queries,
'query_result': query_result,
'rowcount': rowcount,
}
# Exit:
module.exit_json(**kw)
if __name__ == '__main__':
main()

View file

@ -1,573 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2013, Balazs Pocze <banyek@gawker.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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r'''
---
module: mysql_replication
short_description: Manage MySQL replication
description:
- Manages MySQL server replication, slave, master status, get and change master host.
author:
- Balazs Pocze (@banyek)
- Andrew Klychkov (@Andersson007)
options:
mode:
description:
- Module operating mode. Could be
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.general 0.2.0,
C(resetslave) (RESET SLAVE),
C(resetslaveall) (RESET SLAVE ALL).
type: str
choices:
- changemaster
- getmaster
- getslave
- startslave
- stopslave
- resetmaster
- resetslave
- resetslaveall
default: getslave
master_host:
description:
- Same as mysql variable.
type: str
master_user:
description:
- Same as mysql variable.
type: str
master_password:
description:
- Same as mysql variable.
type: str
master_port:
description:
- Same as mysql variable.
type: int
master_connect_retry:
description:
- Same as mysql variable.
type: int
master_log_file:
description:
- Same as mysql variable.
type: str
master_log_pos:
description:
- Same as mysql variable.
type: int
relay_log_file:
description:
- Same as mysql variable.
type: str
relay_log_pos:
description:
- Same as mysql variable.
type: int
master_ssl:
description:
- Same as mysql variable.
type: bool
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.
type: bool
master_use_gtid:
description:
- 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, slave_pos, disabled]
type: str
version_added: '0.2.0'
master_delay:
description:
- 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.2.0'
connection_name:
description:
- 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/).
type: str
version_added: '0.2.0'
channel:
description:
- Name of replication channel.
- Multi-source replication is supported from MySQL 5.7.
- Mutually exclusive with I(connection_name).
- For more information see U(https://dev.mysql.com/doc/refman/8.0/en/replication-multi-source.html).
type: str
version_added: '0.2.0'
fail_on_error:
description:
- Fails on error when calling mysql.
type: bool
default: False
version_added: '0.2.0'
notes:
- If an empty value for the parameter of string type is needed, use an empty string.
extends_documentation_fragment:
- community.general.mysql
seealso:
- module: community.general.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: MariaDB replication reference
description: Complete reference of the MariaDB replication documentation.
link: https://mariadb.com/kb/en/library/setting-up-replication/
'''
EXAMPLES = r'''
- name: Stop mysql slave thread
mysql_replication:
mode: stopslave
- name: Get master binlog file name and binlog position
mysql_replication:
mode: getmaster
- name: Change master to master server 192.0.2.1 and use binary log 'mysql-bin.000009' with position 4578
mysql_replication:
mode: changemaster
master_host: 192.0.2.1
master_log_file: mysql-bin.000009
master_log_pos: 4578
- name: Check slave status using port 3308
mysql_replication:
mode: getslave
login_host: ansible.example.com
login_port: 3308
- name: On MariaDB change master to use GTID current_pos
mysql_replication:
mode: changemaster
master_use_gtid: current_pos
- name: Change master to use replication delay 3600 seconds
mysql_replication:
mode: changemaster
master_host: 192.0.2.1
master_delay: 3600
- name: Start MariaDB standby with connection name master-1
mysql_replication:
mode: startslave
connection_name: master-1
- name: Stop replication in channel master-1
mysql_replication:
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 master
mysql_replication:
mode: resetmaster
- name: Run start slave and fail the task on errors
mysql_replication:
mode: startslave
connection_name: master-1
fail_on_error: yes
- name: Change master and fail on error (like when slave thread is running)
mysql_replication:
mode: changemaster
fail_on_error: yes
'''
RETURN = r'''
queries:
description: List of executed queries which modified DB's state.
returned: always
type: list
sample: ["CHANGE MASTER TO MASTER_HOST='master2.example.com',MASTER_PORT=3306"]
version_added: '0.2.0'
'''
import os
import warnings
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.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_master_status(cursor):
cursor.execute("SHOW MASTER STATUS")
masterstatus = cursor.fetchone()
return masterstatus
def get_slave_status(cursor, connection_name='', channel=''):
if connection_name:
query = "SHOW SLAVE '%s' STATUS" % connection_name
else:
query = "SHOW SLAVE STATUS"
if channel:
query += " FOR CHANNEL '%s'" % channel
cursor.execute(query)
slavestatus = cursor.fetchone()
return slavestatus
def stop_slave(module, cursor, connection_name='', channel='', fail_on_error=False):
if connection_name:
query = "STOP SLAVE '%s'" % connection_name
else:
query = 'STOP SLAVE'
if channel:
query += " FOR CHANNEL '%s'" % channel
try:
executed_queries.append(query)
cursor.execute(query)
stopped = True
except mysql_driver.Warning as e:
stopped = False
except Exception as e:
if fail_on_error:
module.fail_json(msg="STOP SLAVE failed: %s" % to_native(e))
stopped = False
return stopped
def reset_slave(module, cursor, connection_name='', channel='', fail_on_error=False):
if connection_name:
query = "RESET SLAVE '%s'" % connection_name
else:
query = 'RESET SLAVE'
if channel:
query += " FOR CHANNEL '%s'" % channel
try:
executed_queries.append(query)
cursor.execute(query)
reset = True
except mysql_driver.Warning as e:
reset = False
except Exception as e:
if fail_on_error:
module.fail_json(msg="RESET SLAVE failed: %s" % to_native(e))
reset = False
return reset
def reset_slave_all(module, cursor, connection_name='', channel='', fail_on_error=False):
if connection_name:
query = "RESET SLAVE '%s' ALL" % connection_name
else:
query = 'RESET SLAVE ALL'
if channel:
query += " FOR CHANNEL '%s'" % channel
try:
executed_queries.append(query)
cursor.execute(query)
reset = True
except mysql_driver.Warning as e:
reset = False
except Exception as e:
if fail_on_error:
module.fail_json(msg="RESET SLAVE ALL failed: %s" % to_native(e))
reset = False
return reset
def reset_master(module, cursor, fail_on_error=False):
query = 'RESET MASTER'
try:
executed_queries.append(query)
cursor.execute(query)
reset = True
except mysql_driver.Warning as e:
reset = False
except Exception as e:
if fail_on_error:
module.fail_json(msg="RESET MASTER failed: %s" % to_native(e))
reset = False
return reset
def start_slave(module, cursor, connection_name='', channel='', fail_on_error=False):
if connection_name:
query = "START SLAVE '%s'" % connection_name
else:
query = 'START SLAVE'
if channel:
query += " FOR CHANNEL '%s'" % channel
try:
executed_queries.append(query)
cursor.execute(query)
started = True
except mysql_driver.Warning as e:
started = False
except Exception as e:
if fail_on_error:
module.fail_json(msg="START SLAVE failed: %s" % to_native(e))
started = False
return started
def changemaster(cursor, chm, connection_name='', channel=''):
if connection_name:
query = "CHANGE MASTER '%s' TO %s" % (connection_name, ','.join(chm))
else:
query = 'CHANGE MASTER TO %s' % ','.join(chm)
if channel:
query += " FOR CHANNEL '%s'" % channel
executed_queries.append(query)
cursor.execute(query)
def main():
module = AnsibleModule(
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"]
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"]
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"]
connect_timeout = module.params['connect_timeout']
config_file = module.params['config_file']
master_delay = module.params['master_delay']
if module.params.get("master_use_gtid") == 'disabled':
master_use_gtid = 'no'
else:
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']
if mysql_driver is None:
module.fail_json(msg=mysql_driver_fail_msg)
else:
warnings.filterwarnings('error', category=mysql_driver.Warning)
login_password = module.params["login_password"]
login_user = module.params["login_user"]
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)
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. "
"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)))
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['Is_Master'] = True
module.exit_json(queries=executed_queries, **status)
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['Is_Slave'] = True
module.exit_json(queries=executed_queries, **status)
elif mode in "changemaster":
chm = []
result = {}
if master_host is not None:
chm.append("MASTER_HOST='%s'" % master_host)
if master_user is not None:
chm.append("MASTER_USER='%s'" % master_user)
if master_password is not None:
chm.append("MASTER_PASSWORD='%s'" % master_password)
if master_port is not None:
chm.append("MASTER_PORT=%s" % master_port)
if master_connect_retry is not None:
chm.append("MASTER_CONNECT_RETRY=%s" % master_connect_retry)
if master_log_file is not None:
chm.append("MASTER_LOG_FILE='%s'" % master_log_file)
if master_log_pos is not None:
chm.append("MASTER_LOG_POS=%s" % master_log_pos)
if master_delay is not None:
chm.append("MASTER_DELAY=%s" % master_delay)
if relay_log_file is not None:
chm.append("RELAY_LOG_FILE='%s'" % relay_log_file)
if relay_log_pos is not None:
chm.append("RELAY_LOG_POS=%s" % relay_log_pos)
if master_ssl:
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:
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 == CHANGE MASTER TO %s' % (to_native(e), chm))
result['changed'] = True
module.exit_json(queries=executed_queries, **result)
elif mode in "startslave":
started = start_slave(module, cursor, connection_name, channel, fail_on_error)
if started is True:
module.exit_json(msg="Slave started ", changed=True, queries=executed_queries)
else:
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="Slave stopped", changed=True, queries=executed_queries)
else:
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="Master reset", changed=True, queries=executed_queries)
else:
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="Slave reset", changed=True, queries=executed_queries)
else:
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="Slave reset", changed=True, queries=executed_queries)
else:
module.exit_json(msg="Slave already reset", changed=False, queries=executed_queries)
warnings.simplefilter("ignore")
if __name__ == '__main__':
main()

View file

@ -1,991 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2012, Mark Theunissen <mark.theunissen@gmail.com>
# Sponsored by Four Kitchens http://fourkitchens.com.
# 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_user
short_description: Adds or removes a user from a MySQL database
description:
- Adds or removes a user from a MySQL database.
options:
name:
description:
- Name of the user (role) to add or remove.
type: str
required: true
password:
description:
- Set the user's password..
type: str
encrypted:
description:
- Indicate that the 'password' field is a `mysql_native_password` hash.
type: bool
default: no
host:
description:
- The 'host' part of the MySQL username.
type: str
default: localhost
host_all:
description:
- Override the host option, making ansible apply changes to all hostnames for a given user.
- This option cannot be used when creating users.
type: bool
default: no
priv:
description:
- "MySQL privileges string in the format: C(db.table:priv1,priv2)."
- "Multiple privileges can be specified by separating each one using
a forward slash: C(db.table:priv/db.table:priv)."
- The format is based on MySQL C(GRANT) statement.
- Database and table names can be quoted, MySQL-style.
- If column privileges are used, the C(priv1,priv2) part must be
exactly as returned by a C(SHOW GRANT) statement. If not followed,
the module will always report changes. It includes grouping columns
by permission (C(SELECT(col1,col2)) instead of C(SELECT(col1),SELECT(col2))).
- Can be passed as a dictionary (see the examples).
type: raw
append_privs:
description:
- Append the privileges defined by priv to the existing ones for this
user instead of overwriting existing ones.
type: bool
default: no
sql_log_bin:
description:
- Whether binary logging should be enabled or disabled for the connection.
type: bool
default: yes
state:
description:
- Whether the user should exist.
- When C(absent), removes the user.
type: str
choices: [ absent, present ]
default: present
check_implicit_admin:
description:
- Check if mysql allows login as root/nopassword before trying supplied credentials.
- If success, passed I(login_user)/I(login_password) will be ignored.
type: bool
default: no
update_password:
description:
- C(always) will update passwords if they differ.
- C(on_create) will only set the password for newly created users.
type: str
choices: [ always, on_create ]
default: always
plugin:
description:
- User's plugin to authenticate (``CREATE USER user IDENTIFIED WITH plugin``).
type: str
version_added: '0.2.0'
plugin_hash_string:
description:
- User's plugin hash string (``CREATE USER user IDENTIFIED WITH plugin AS plugin_hash_string``).
type: str
version_added: '0.2.0'
plugin_auth_string:
description:
- User's plugin auth_string (``CREATE USER user IDENTIFIED WITH plugin BY plugin_auth_string``).
type: str
version_added: '0.2.0'
resource_limits:
description:
- Limit the user for certain server resources. Provided since MySQL 5.6 / MariaDB 10.2.
- "Available options are C(MAX_QUERIES_PER_HOUR: num), C(MAX_UPDATES_PER_HOUR: num),
C(MAX_CONNECTIONS_PER_HOUR: num), C(MAX_USER_CONNECTIONS: num)."
- Used when I(state=present), ignored otherwise.
type: dict
version_added: '0.2.0'
notes:
- "MySQL server installs with default login_user of 'root' and no password. To secure this user
as part of an idempotent playbook, you must create at least two tasks: the first must change the root user's password,
without providing any login_user/login_password details. The second must drop a ~/.my.cnf file containing
the new root credentials. Subsequent runs of the playbook will then succeed by reading the new credentials from
the file."
- Currently, there is only support for the `mysql_native_password` encrypted password hash module.
seealso:
- module: community.general.mysql_info
- name: MySQL access control and account management reference
description: Complete reference of the MySQL access control and account management documentation.
link: https://dev.mysql.com/doc/refman/8.0/en/access-control.html
- name: MySQL provided privileges reference
description: Complete reference of the MySQL provided privileges documentation.
link: https://dev.mysql.com/doc/refman/8.0/en/privileges-provided.html
author:
- Jonathan Mainguy (@Jmainguy)
- Benjamin Malynovytch (@bmalynovytch)
- Lukasz Tomaszkiewicz (@tomaszkiewicz)
extends_documentation_fragment:
- community.general.mysql
'''
EXAMPLES = r'''
- name: Removes anonymous user account for localhost
mysql_user:
name: ''
host: localhost
state: absent
- name: Removes all anonymous user accounts
mysql_user:
name: ''
host_all: yes
state: absent
- name: Create database user with name 'bob' and password '12345' with all database privileges
mysql_user:
name: bob
password: 12345
priv: '*.*:ALL'
state: present
- name: Create database user using hashed password with all database privileges
mysql_user:
name: bob
password: '*EE0D72C1085C46C5278932678FBE2C6A782821B4'
encrypted: yes
priv: '*.*:ALL'
state: present
- name: Create database user with password and all database privileges and 'WITH GRANT OPTION'
mysql_user:
name: bob
password: 12345
priv: '*.*:ALL,GRANT'
state: present
- name: Create user with password, all database privileges and 'WITH GRANT OPTION' in db1 and db2
mysql_user:
state: present
name: bob
password: 12345dd
priv:
'db1.*': 'ALL,GRANT'
'db2.*': 'ALL,GRANT'
# Note that REQUIRESSL is a special privilege that should only apply to *.* by itself.
- name: Modify user to require SSL connections.
mysql_user:
name: bob
append_privs: yes
priv: '*.*:REQUIRESSL'
state: present
- name: Ensure no user named 'sally'@'localhost' exists, also passing in the auth credentials.
mysql_user:
login_user: root
login_password: 123456
name: sally
state: absent
# check_implicit_admin example
- name: >
Ensure no user named 'sally'@'localhost' exists, also passing in the auth credentials.
If mysql allows root/nopassword login, try it without the credentials first.
If it's not allowed, pass the credentials.
mysql_user:
check_implicit_admin: yes
login_user: root
login_password: 123456
name: sally
state: absent
- name: Ensure no user named 'sally' exists at all
mysql_user:
name: sally
host_all: yes
state: absent
- name: Specify grants composed of more than one word
mysql_user:
name: replication
password: 12345
priv: "*.*:REPLICATION CLIENT"
state: present
- name: Revoke all privileges for user 'bob' and password '12345'
mysql_user:
name: bob
password: 12345
priv: "*.*:USAGE"
state: present
# Example privileges string format
# mydb.*:INSERT,UPDATE/anotherdb.*:SELECT/yetanotherdb.*:ALL
- name: Example using login_unix_socket to connect to server
mysql_user:
name: root
password: abc123
login_unix_socket: /var/run/mysqld/mysqld.sock
- name: Example of skipping binary logging while adding user 'bob'
mysql_user:
name: bob
password: 12345
priv: "*.*:USAGE"
state: present
sql_log_bin: no
- name: Create user 'bob' authenticated with plugin 'AWSAuthenticationPlugin'
mysql_user:
name: bob
plugin: AWSAuthenticationPlugin
plugin_hash_string: RDS
priv: '*.*:ALL'
state: present
- name: Limit bob's resources to 10 queries per hour and 5 connections per hour
mysql_user:
name: bob
resource_limits:
MAX_QUERIES_PER_HOUR: 10
MAX_CONNECTIONS_PER_HOUR: 5
# Example .my.cnf file for setting the root password
# [client]
# user=root
# password=n<_665{vS43y
'''
import re
import string
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.database import SQLParseError
from ansible_collections.community.general.plugins.module_utils.mysql import mysql_connect, mysql_driver, mysql_driver_fail_msg
from ansible.module_utils.six import iteritems
from ansible.module_utils._text import to_native
VALID_PRIVS = frozenset(('CREATE', 'DROP', 'GRANT', 'GRANT OPTION',
'LOCK TABLES', 'REFERENCES', 'EVENT', 'ALTER',
'DELETE', 'INDEX', 'INSERT', 'SELECT', 'UPDATE',
'CREATE TEMPORARY TABLES', 'TRIGGER', 'CREATE VIEW',
'SHOW VIEW', 'ALTER ROUTINE', 'CREATE ROUTINE',
'EXECUTE', 'FILE', 'CREATE TABLESPACE', 'CREATE USER',
'PROCESS', 'PROXY', 'RELOAD', 'REPLICATION CLIENT',
'REPLICATION SLAVE', 'SHOW DATABASES', 'SHUTDOWN',
'SUPER', 'ALL', 'ALL PRIVILEGES', 'USAGE', 'REQUIRESSL',
'CREATE ROLE', 'DROP ROLE', 'APPLICATION_PASSWORD_ADMIN',
'AUDIT_ADMIN', 'BACKUP_ADMIN', 'BINLOG_ADMIN',
'BINLOG_ENCRYPTION_ADMIN', 'CLONE_ADMIN', 'CONNECTION_ADMIN',
'ENCRYPTION_KEY_ADMIN', 'FIREWALL_ADMIN', 'FIREWALL_USER',
'GROUP_REPLICATION_ADMIN', 'INNODB_REDO_LOG_ARCHIVE',
'NDB_STORED_USER', 'PERSIST_RO_VARIABLES_ADMIN',
'REPLICATION_APPLIER', 'REPLICATION_SLAVE_ADMIN',
'RESOURCE_GROUP_ADMIN', 'RESOURCE_GROUP_USER',
'ROLE_ADMIN', 'SESSION_VARIABLES_ADMIN', 'SET_USER_ID',
'SYSTEM_USER', 'SYSTEM_VARIABLES_ADMIN', 'SYSTEM_USER',
'TABLE_ENCRYPTION_ADMIN', 'VERSION_TOKEN_ADMIN',
'XA_RECOVER_ADMIN', 'LOAD FROM S3', 'SELECT INTO S3',
'INVOKE LAMBDA',
'ALTER ROUTINE',
'BINLOG ADMIN',
'BINLOG MONITOR',
'BINLOG REPLAY',
'CONNECTION ADMIN',
'READ_ONLY ADMIN',
'REPLICATION MASTER ADMIN',
'REPLICATION SLAVE',
'REPLICATION SLAVE ADMIN',
'SET USER',))
class InvalidPrivsError(Exception):
pass
# ===========================================
# MySQL module specific support methods.
#
# User Authentication Management changed in MySQL 5.7 and MariaDB 10.2.0
def use_old_user_mgmt(cursor):
cursor.execute("SELECT VERSION()")
result = cursor.fetchone()
version_str = result[0]
version = version_str.split('.')
if 'mariadb' in version_str.lower():
# Prior to MariaDB 10.2
if int(version[0]) * 1000 + int(version[1]) < 10002:
return True
else:
return False
else:
# Prior to MySQL 5.7
if int(version[0]) * 1000 + int(version[1]) < 5007:
return True
else:
return False
def get_mode(cursor):
cursor.execute('SELECT @@GLOBAL.sql_mode')
result = cursor.fetchone()
mode_str = result[0]
if 'ANSI' in mode_str:
mode = 'ANSI'
else:
mode = 'NOTANSI'
return mode
def user_exists(cursor, user, host, host_all):
if host_all:
cursor.execute("SELECT count(*) FROM mysql.user WHERE user = %s", (user,))
else:
cursor.execute("SELECT count(*) FROM mysql.user WHERE user = %s AND host = %s", (user, host))
count = cursor.fetchone()
return count[0] > 0
def user_add(cursor, user, host, host_all, password, encrypted,
plugin, plugin_hash_string, plugin_auth_string, new_priv, check_mode):
# we cannot create users without a proper hostname
if host_all:
return False
if check_mode:
return True
# Determine what user management method server uses
old_user_mgmt = use_old_user_mgmt(cursor)
if password and encrypted:
cursor.execute("CREATE USER %s@%s IDENTIFIED BY PASSWORD %s", (user, host, password))
elif password and not encrypted:
if old_user_mgmt:
cursor.execute("CREATE USER %s@%s IDENTIFIED BY %s", (user, host, password))
else:
cursor.execute("SELECT CONCAT('*', UCASE(SHA1(UNHEX(SHA1(%s)))))", (password,))
encrypted_password = cursor.fetchone()[0]
cursor.execute("CREATE USER %s@%s IDENTIFIED WITH mysql_native_password AS %s", (user, host, encrypted_password))
elif plugin and plugin_hash_string:
cursor.execute("CREATE USER %s@%s IDENTIFIED WITH %s AS %s", (user, host, plugin, plugin_hash_string))
elif plugin and plugin_auth_string:
cursor.execute("CREATE USER %s@%s IDENTIFIED WITH %s BY %s", (user, host, plugin, plugin_auth_string))
elif plugin:
cursor.execute("CREATE USER %s@%s IDENTIFIED WITH %s", (user, host, plugin))
else:
cursor.execute("CREATE USER %s@%s", (user, host))
if new_priv is not None:
for db_table, priv in iteritems(new_priv):
privileges_grant(cursor, user, host, db_table, priv)
return True
def is_hash(password):
ishash = False
if len(password) == 41 and password[0] == '*':
if frozenset(password[1:]).issubset(string.hexdigits):
ishash = True
return ishash
def user_mod(cursor, user, host, host_all, password, encrypted,
plugin, plugin_hash_string, plugin_auth_string, new_priv, append_privs, module):
changed = False
msg = "User unchanged"
grant_option = False
if host_all:
hostnames = user_get_hostnames(cursor, [user])
else:
hostnames = [host]
for host in hostnames:
# Handle clear text and hashed passwords.
if bool(password):
# Determine what user management method server uses
old_user_mgmt = use_old_user_mgmt(cursor)
# Get a list of valid columns in mysql.user table to check if Password and/or authentication_string exist
cursor.execute("""
SELECT COLUMN_NAME FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = 'mysql' AND TABLE_NAME = 'user' AND COLUMN_NAME IN ('Password', 'authentication_string')
ORDER BY COLUMN_NAME DESC LIMIT 1
""")
colA = cursor.fetchone()
cursor.execute("""
SELECT COLUMN_NAME FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = 'mysql' AND TABLE_NAME = 'user' AND COLUMN_NAME IN ('Password', 'authentication_string')
ORDER BY COLUMN_NAME ASC LIMIT 1
""")
colB = cursor.fetchone()
# Select hash from either Password or authentication_string, depending which one exists and/or is filled
cursor.execute("""
SELECT COALESCE(
CASE WHEN %s = '' THEN NULL ELSE %s END,
CASE WHEN %s = '' THEN NULL ELSE %s END
)
FROM mysql.user WHERE user = %%s AND host = %%s
""" % (colA[0], colA[0], colB[0], colB[0]), (user, host))
current_pass_hash = cursor.fetchone()[0]
if isinstance(current_pass_hash, bytes):
current_pass_hash = current_pass_hash.decode('ascii')
if encrypted:
encrypted_password = password
if not is_hash(encrypted_password):
module.fail_json(msg="encrypted was specified however it does not appear to be a valid hash expecting: *SHA1(SHA1(your_password))")
else:
if old_user_mgmt:
cursor.execute("SELECT PASSWORD(%s)", (password,))
else:
cursor.execute("SELECT CONCAT('*', UCASE(SHA1(UNHEX(SHA1(%s)))))", (password,))
encrypted_password = cursor.fetchone()[0]
if current_pass_hash != encrypted_password:
msg = "Password updated"
if module.check_mode:
return (True, msg)
if old_user_mgmt:
cursor.execute("SET PASSWORD FOR %s@%s = %s", (user, host, encrypted_password))
msg = "Password updated (old style)"
else:
try:
cursor.execute("ALTER USER %s@%s IDENTIFIED WITH mysql_native_password AS %s", (user, host, encrypted_password))
msg = "Password updated (new style)"
except (mysql_driver.Error) as e:
# https://stackoverflow.com/questions/51600000/authentication-string-of-root-user-on-mysql
# Replacing empty root password with new authentication mechanisms fails with error 1396
if e.args[0] == 1396:
cursor.execute(
"UPDATE mysql.user SET plugin = %s, authentication_string = %s, Password = '' WHERE User = %s AND Host = %s",
('mysql_native_password', encrypted_password, user, host)
)
cursor.execute("FLUSH PRIVILEGES")
msg = "Password forced update"
else:
raise e
changed = True
# Handle plugin authentication
if plugin:
cursor.execute("SELECT plugin, authentication_string FROM mysql.user "
"WHERE user = %s AND host = %s", (user, host))
current_plugin = cursor.fetchone()
update = False
if current_plugin[0] != plugin:
update = True
if plugin_hash_string and current_plugin[1] != plugin_hash_string:
update = True
if plugin_auth_string and current_plugin[1] != plugin_auth_string:
# this case can cause more updates than expected,
# as plugin can hash auth_string in any way it wants
# and there's no way to figure it out for
# a check, so I prefer to update more often than never
update = True
if update:
if plugin_hash_string:
cursor.execute("ALTER USER %s@%s IDENTIFIED WITH %s AS %s", (user, host, plugin, plugin_hash_string))
elif plugin_auth_string:
cursor.execute("ALTER USER %s@%s IDENTIFIED WITH %s BY %s", (user, host, plugin, plugin_auth_string))
else:
cursor.execute("ALTER USER %s@%s IDENTIFIED WITH %s", (user, host, plugin))
changed = True
# Handle privileges
if new_priv is not None:
curr_priv = privileges_get(cursor, user, host)
# If the user has privileges on a db.table that doesn't appear at all in
# the new specification, then revoke all privileges on it.
for db_table, priv in iteritems(curr_priv):
# If the user has the GRANT OPTION on a db.table, revoke it first.
if "GRANT" in priv:
grant_option = True
if db_table not in new_priv:
if user != "root" and "PROXY" not in priv and not append_privs:
msg = "Privileges updated"
if module.check_mode:
return (True, msg)
privileges_revoke(cursor, user, host, db_table, priv, grant_option)
changed = True
# If the user doesn't currently have any privileges on a db.table, then
# we can perform a straight grant operation.
for db_table, priv in iteritems(new_priv):
if db_table not in curr_priv:
msg = "New privileges granted"
if module.check_mode:
return (True, msg)
privileges_grant(cursor, user, host, db_table, priv)
changed = True
# If the db.table specification exists in both the user's current privileges
# and in the new privileges, then we need to see if there's a difference.
db_table_intersect = set(new_priv.keys()) & set(curr_priv.keys())
for db_table in db_table_intersect:
priv_diff = set(new_priv[db_table]) ^ set(curr_priv[db_table])
if len(priv_diff) > 0:
msg = "Privileges updated"
if module.check_mode:
return (True, msg)
if not append_privs:
privileges_revoke(cursor, user, host, db_table, curr_priv[db_table], grant_option)
privileges_grant(cursor, user, host, db_table, new_priv[db_table])
changed = True
return (changed, msg)
def user_delete(cursor, user, host, host_all, check_mode):
if check_mode:
return True
if host_all:
hostnames = user_get_hostnames(cursor, user)
for hostname in hostnames:
cursor.execute("DROP USER %s@%s", (user, hostname))
else:
cursor.execute("DROP USER %s@%s", (user, host))
return True
def user_get_hostnames(cursor, user):
cursor.execute("SELECT Host FROM mysql.user WHERE user = %s", (user,))
hostnames_raw = cursor.fetchall()
hostnames = []
for hostname_raw in hostnames_raw:
hostnames.append(hostname_raw[0])
return hostnames
def privileges_get(cursor, user, host):
""" MySQL doesn't have a better method of getting privileges aside from the
SHOW GRANTS query syntax, which requires us to then parse the returned string.
Here's an example of the string that is returned from MySQL:
GRANT USAGE ON *.* TO 'user'@'localhost' IDENTIFIED BY 'pass';
This function makes the query and returns a dictionary containing the results.
The dictionary format is the same as that returned by privileges_unpack() below.
"""
output = {}
cursor.execute("SHOW GRANTS FOR %s@%s", (user, host))
grants = cursor.fetchall()
def pick(x):
if x == 'ALL PRIVILEGES':
return 'ALL'
else:
return x
for grant in grants:
res = re.match("""GRANT (.+) ON (.+) TO (['`"]).*\\3@(['`"]).*\\4( IDENTIFIED BY PASSWORD (['`"]).+\\6)? ?(.*)""", grant[0])
if res is None:
raise InvalidPrivsError('unable to parse the MySQL grant string: %s' % grant[0])
privileges = res.group(1).split(",")
privileges = [pick(x.strip()) for x in privileges]
if "WITH GRANT OPTION" in res.group(7):
privileges.append('GRANT')
if "REQUIRE SSL" in res.group(7):
privileges.append('REQUIRESSL')
db = res.group(2)
output.setdefault(db, []).extend(privileges)
return output
def privileges_unpack(priv, mode):
""" Take a privileges string, typically passed as a parameter, and unserialize
it into a dictionary, the same format as privileges_get() above. We have this
custom format to avoid using YAML/JSON strings inside YAML playbooks. Example
of a privileges string:
mydb.*:INSERT,UPDATE/anotherdb.*:SELECT/yetanother.*:ALL
The privilege USAGE stands for no privileges, so we add that in on *.* if it's
not specified in the string, as MySQL will always provide this by default.
"""
if mode == 'ANSI':
quote = '"'
else:
quote = '`'
output = {}
privs = []
for item in priv.strip().split('/'):
pieces = item.strip().rsplit(':', 1)
dbpriv = pieces[0].rsplit(".", 1)
# Check for FUNCTION or PROCEDURE object types
parts = dbpriv[0].split(" ", 1)
object_type = ''
if len(parts) > 1 and (parts[0] == 'FUNCTION' or parts[0] == 'PROCEDURE'):
object_type = parts[0] + ' '
dbpriv[0] = parts[1]
# Do not escape if privilege is for database or table, i.e.
# neither quote *. nor .*
for i, side in enumerate(dbpriv):
if side.strip('`') != '*':
dbpriv[i] = '%s%s%s' % (quote, side.strip('`'), quote)
pieces[0] = object_type + '.'.join(dbpriv)
if '(' in pieces[1]:
output[pieces[0]] = re.split(r',\s*(?=[^)]*(?:\(|$))', pieces[1].upper())
for i in output[pieces[0]]:
privs.append(re.sub(r'\s*\(.*\)', '', i))
else:
output[pieces[0]] = pieces[1].upper().split(',')
privs = output[pieces[0]]
new_privs = frozenset(privs)
if not new_privs.issubset(VALID_PRIVS):
raise InvalidPrivsError('Invalid privileges specified: %s' % new_privs.difference(VALID_PRIVS))
if '*.*' not in output:
output['*.*'] = ['USAGE']
# if we are only specifying something like REQUIRESSL and/or GRANT (=WITH GRANT OPTION) in *.*
# we still need to add USAGE as a privilege to avoid syntax errors
if 'REQUIRESSL' in priv and not set(output['*.*']).difference(set(['GRANT', 'REQUIRESSL'])):
output['*.*'].append('USAGE')
return output
def privileges_revoke(cursor, user, host, db_table, priv, grant_option):
# Escape '%' since mysql db.execute() uses a format string
db_table = db_table.replace('%', '%%')
if grant_option:
query = ["REVOKE GRANT OPTION ON %s" % db_table]
query.append("FROM %s@%s")
query = ' '.join(query)
cursor.execute(query, (user, host))
priv_string = ",".join([p for p in priv if p not in ('GRANT', 'REQUIRESSL')])
query = ["REVOKE %s ON %s" % (priv_string, db_table)]
query.append("FROM %s@%s")
query = ' '.join(query)
cursor.execute(query, (user, host))
def privileges_grant(cursor, user, host, db_table, priv):
# Escape '%' since mysql db.execute uses a format string and the
# specification of db and table often use a % (SQL wildcard)
db_table = db_table.replace('%', '%%')
priv_string = ",".join([p for p in priv if p not in ('GRANT', 'REQUIRESSL')])
query = ["GRANT %s ON %s" % (priv_string, db_table)]
query.append("TO %s@%s")
if 'REQUIRESSL' in priv:
query.append("REQUIRE SSL")
if 'GRANT' in priv:
query.append("WITH GRANT OPTION")
query = ' '.join(query)
cursor.execute(query, (user, host))
def convert_priv_dict_to_str(priv):
"""Converts privs dictionary to string of certain format.
Args:
priv (dict): Dict of privileges that needs to be converted to string.
Returns:
priv (str): String representation of input argument.
"""
priv_list = ['%s:%s' % (key, val) for key, val in iteritems(priv)]
return '/'.join(priv_list)
# Alter user is supported since MySQL 5.6 and MariaDB 10.2.0
def server_supports_alter_user(cursor):
"""Check if the server supports ALTER USER statement or doesn't.
Args:
cursor (cursor): DB driver cursor object.
Returns: True if supports, False otherwise.
"""
cursor.execute("SELECT VERSION()")
version_str = cursor.fetchone()[0]
version = version_str.split('.')
if 'mariadb' in version_str.lower():
# MariaDB 10.2 and later
if int(version[0]) * 1000 + int(version[1]) >= 10002:
return True
else:
return False
else:
# MySQL 5.6 and later
if int(version[0]) * 1000 + int(version[1]) >= 5006:
return True
else:
return False
def get_resource_limits(cursor, user, host):
"""Get user resource limits.
Args:
cursor (cursor): DB driver cursor object.
user (str): User name.
host (str): User host name.
Returns: Dictionary containing current resource limits.
"""
query = ('SELECT max_questions AS MAX_QUERIES_PER_HOUR, '
'max_updates AS MAX_UPDATES_PER_HOUR, '
'max_connections AS MAX_CONNECTIONS_PER_HOUR, '
'max_user_connections AS MAX_USER_CONNECTIONS '
'FROM mysql.user WHERE User = %s AND Host = %s')
cursor.execute(query, (user, host))
res = cursor.fetchone()
if not res:
return None
current_limits = {
'MAX_QUERIES_PER_HOUR': res[0],
'MAX_UPDATES_PER_HOUR': res[1],
'MAX_CONNECTIONS_PER_HOUR': res[2],
'MAX_USER_CONNECTIONS': res[3],
}
return current_limits
def match_resource_limits(module, current, desired):
"""Check and match limits.
Args:
module (AnsibleModule): Ansible module object.
current (dict): Dictionary with current limits.
desired (dict): Dictionary with desired limits.
Returns: Dictionary containing parameters that need to change.
"""
if not current:
# It means the user does not exists, so we need
# to set all limits after its creation
return desired
needs_to_change = {}
for key, val in iteritems(desired):
if key not in current:
# Supported keys are listed in the documentation
# and must be determined in the get_resource_limits function
# (follow 'AS' keyword)
module.fail_json(msg="resource_limits: key '%s' is unsupported." % key)
try:
val = int(val)
except Exception:
module.fail_json(msg="Can't convert value '%s' to integer." % val)
if val != current.get(key):
needs_to_change[key] = val
return needs_to_change
def limit_resources(module, cursor, user, host, resource_limits, check_mode):
"""Limit user resources.
Args:
module (AnsibleModule): Ansible module object.
cursor (cursor): DB driver cursor object.
user (str): User name.
host (str): User host name.
resource_limit (dict): Dictionary with desired limits.
check_mode (bool): Run the function in check mode or not.
Returns: True, if changed, False otherwise.
"""
if not server_supports_alter_user(cursor):
module.fail_json(msg="The server version does not match the requirements "
"for resource_limits parameter. See module's documentation.")
current_limits = get_resource_limits(cursor, user, host)
needs_to_change = match_resource_limits(module, current_limits, resource_limits)
if not needs_to_change:
return False
if needs_to_change and check_mode:
return True
# If not check_mode
tmp = []
for key, val in iteritems(needs_to_change):
tmp.append('%s %s' % (key, val))
query = "ALTER USER %s@%s"
query += ' WITH %s' % ' '.join(tmp)
cursor.execute(query, (user, host))
return True
# ===========================================
# Module execution.
#
def main():
module = AnsibleModule(
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'),
user=dict(type='str', required=True, aliases=['name']),
password=dict(type='str', no_log=True),
encrypted=dict(type='bool', default=False),
host=dict(type='str', default='localhost'),
host_all=dict(type="bool", default=False),
state=dict(type='str', default='present', choices=['absent', 'present']),
priv=dict(type='raw'),
append_privs=dict(type='bool', default=False),
check_implicit_admin=dict(type='bool', default=False),
update_password=dict(type='str', default='always', choices=['always', 'on_create'], no_log=False),
connect_timeout=dict(type='int', default=30),
config_file=dict(type='path', default='~/.my.cnf'),
sql_log_bin=dict(type='bool', default=True),
client_cert=dict(type='path', aliases=['ssl_cert']),
client_key=dict(type='path', aliases=['ssl_key']),
ca_cert=dict(type='path', aliases=['ssl_ca']),
plugin=dict(default=None, type='str'),
plugin_hash_string=dict(default=None, type='str'),
plugin_auth_string=dict(default=None, type='str'),
resource_limits=dict(type='dict'),
),
supports_check_mode=True,
)
login_user = module.params["login_user"]
login_password = module.params["login_password"]
user = module.params["user"]
password = module.params["password"]
encrypted = module.boolean(module.params["encrypted"])
host = module.params["host"].lower()
host_all = module.params["host_all"]
state = module.params["state"]
priv = module.params["priv"]
check_implicit_admin = module.params['check_implicit_admin']
connect_timeout = module.params['connect_timeout']
config_file = module.params['config_file']
append_privs = module.boolean(module.params["append_privs"])
update_password = module.params['update_password']
ssl_cert = module.params["client_cert"]
ssl_key = module.params["client_key"]
ssl_ca = module.params["ca_cert"]
db = ''
sql_log_bin = module.params["sql_log_bin"]
plugin = module.params["plugin"]
plugin_hash_string = module.params["plugin_hash_string"]
plugin_auth_string = module.params["plugin_auth_string"]
resource_limits = module.params["resource_limits"]
if priv and not (isinstance(priv, str) or isinstance(priv, dict)):
module.fail_json(msg="priv parameter must be str or dict but %s was passed" % type(priv))
if priv and isinstance(priv, dict):
priv = convert_priv_dict_to_str(priv)
if mysql_driver is None:
module.fail_json(msg=mysql_driver_fail_msg)
cursor = None
try:
if check_implicit_admin:
try:
cursor, db_conn = mysql_connect(module, 'root', '', config_file, ssl_cert, ssl_key, ssl_ca, db,
connect_timeout=connect_timeout)
except Exception:
pass
if not cursor:
cursor, db_conn = mysql_connect(module, login_user, login_password, config_file, ssl_cert, ssl_key, ssl_ca, db,
connect_timeout=connect_timeout)
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)))
if not sql_log_bin:
cursor.execute("SET SQL_LOG_BIN=0;")
if priv is not None:
try:
mode = get_mode(cursor)
except Exception as e:
module.fail_json(msg=to_native(e))
try:
priv = privileges_unpack(priv, mode)
except Exception as e:
module.fail_json(msg="invalid privileges string: %s" % to_native(e))
if state == "present":
if user_exists(cursor, user, host, host_all):
try:
if update_password == 'always':
changed, msg = user_mod(cursor, user, host, host_all, password, encrypted,
plugin, plugin_hash_string, plugin_auth_string,
priv, append_privs, module)
else:
changed, msg = user_mod(cursor, user, host, host_all, None, encrypted,
plugin, plugin_hash_string, plugin_auth_string,
priv, append_privs, module)
except (SQLParseError, InvalidPrivsError, mysql_driver.Error) as e:
module.fail_json(msg=to_native(e))
else:
if host_all:
module.fail_json(msg="host_all parameter cannot be used when adding a user")
try:
changed = user_add(cursor, user, host, host_all, password, encrypted,
plugin, plugin_hash_string, plugin_auth_string,
priv, module.check_mode)
if changed:
msg = "User added"
except (SQLParseError, InvalidPrivsError, mysql_driver.Error) as e:
module.fail_json(msg=to_native(e))
if resource_limits:
changed = limit_resources(module, cursor, user, host, resource_limits, module.check_mode) or changed
elif state == "absent":
if user_exists(cursor, user, host, host_all):
changed = user_delete(cursor, user, host, host_all, module.check_mode)
msg = "User deleted"
else:
changed = False
msg = "User doesn't exist"
module.exit_json(changed=changed, user=user, msg=msg)
if __name__ == '__main__':
main()

View file

@ -1,272 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2013, Balazs Pocze <banyek@gawker.com>
# 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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r'''
---
module: mysql_variables
short_description: Manage MySQL global variables
description:
- Query / Set MySQL variables.
author:
- Balazs Pocze (@banyek)
options:
variable:
description:
- Variable name to operate
type: str
required: yes
value:
description:
- If set, then sets variable value to this
type: str
mode:
description:
- C(global) assigns C(value) to a global system variable which will be changed at runtime
but won't persist across server restarts.
- C(persist) assigns C(value) to a global system variable and persists it to
the mysqld-auto.cnf option file in the data directory
(the variable will survive service restarts).
- C(persist_only) persists C(value) to the mysqld-auto.cnf option file in the data directory
but without setting the global variable runtime value
(the value will be changed after the next service restart).
- Supported by MySQL 8.0 or later.
- For more information see U(https://dev.mysql.com/doc/refman/8.0/en/set-variable.html).
type: str
choices: ['global', 'persist', 'persist_only']
default: global
version_added: '0.2.0'
seealso:
- module: community.general.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
extends_documentation_fragment:
- community.general.mysql
'''
EXAMPLES = r'''
- name: Check for sync_binlog setting
mysql_variables:
variable: sync_binlog
- name: Set read_only variable to 1 persistently
mysql_variables:
variable: read_only
value: 1
mode: persist
'''
RETURN = r'''
queries:
description: List of executed queries which modified DB's state.
returned: if executed
type: list
sample: ["SET GLOBAL `read_only` = 1"]
version_added: '0.2.0'
'''
import os
import warnings
from re import match
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.database import SQLParseError, mysql_quote_identifier
from ansible_collections.community.general.plugins.module_utils.mysql import mysql_connect, mysql_driver, mysql_driver_fail_msg
from ansible.module_utils._text import to_native
executed_queries = []
def check_mysqld_auto(module, cursor, mysqlvar):
"""Check variable's value in mysqld-auto.cnf."""
query = ("SELECT VARIABLE_VALUE "
"FROM performance_schema.persisted_variables "
"WHERE VARIABLE_NAME = %s")
try:
cursor.execute(query, (mysqlvar,))
res = cursor.fetchone()
except Exception as e:
if "Table 'performance_schema.persisted_variables' doesn't exist" in str(e):
module.fail_json(msg='Server version must be 8.0 or greater.')
if res:
return res[0]
else:
return None
def typedvalue(value):
"""
Convert value to number whenever possible, return same value
otherwise.
>>> typedvalue('3')
3
>>> typedvalue('3.0')
3.0
>>> typedvalue('foobar')
'foobar'
"""
try:
return int(value)
except ValueError:
pass
try:
return float(value)
except ValueError:
pass
return value
def getvariable(cursor, mysqlvar):
cursor.execute("SHOW VARIABLES WHERE Variable_name = %s", (mysqlvar,))
mysqlvar_val = cursor.fetchall()
if len(mysqlvar_val) == 1:
return mysqlvar_val[0][1]
else:
return None
def setvariable(cursor, mysqlvar, value, mode='global'):
""" Set a global mysql variable to a given value
The DB driver will handle quoting of the given value based on its
type, thus numeric strings like '3.0' or '8' are illegal, they
should be passed as numeric literals.
"""
if mode == 'persist':
query = "SET PERSIST %s = " % mysql_quote_identifier(mysqlvar, 'vars')
elif mode == 'global':
query = "SET GLOBAL %s = " % mysql_quote_identifier(mysqlvar, 'vars')
elif mode == 'persist_only':
query = "SET PERSIST_ONLY %s = " % mysql_quote_identifier(mysqlvar, 'vars')
try:
cursor.execute(query + "%s", (value,))
executed_queries.append(query + "%s" % value)
cursor.fetchall()
result = True
except Exception as e:
result = to_native(e)
return result
def main():
module = AnsibleModule(
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"]
connect_timeout = module.params['connect_timeout']
ssl_cert = module.params["client_cert"]
ssl_key = module.params["client_key"]
ssl_ca = module.params["ca_cert"]
config_file = module.params['config_file']
db = 'mysql'
mysqlvar = module.params["variable"]
value = module.params["value"]
mode = module.params["mode"]
if mysqlvar is None:
module.fail_json(msg="Cannot run without variable to operate with")
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)
else:
warnings.filterwarnings('error', category=mysql_driver.Warning)
try:
cursor, db_conn = mysql_connect(module, user, password, config_file, ssl_cert, ssl_key, ssl_ca, db,
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. "
"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)))
mysqlvar_val = None
var_in_mysqld_auto_cnf = None
mysqlvar_val = getvariable(cursor, mysqlvar)
if mysqlvar_val is None:
module.fail_json(msg="Variable not available \"%s\"" % mysqlvar, changed=False)
if value is None:
module.exit_json(msg=mysqlvar_val)
if mode in ('persist', 'persist_only'):
var_in_mysqld_auto_cnf = check_mysqld_auto(module, cursor, mysqlvar)
if mode == 'persist_only':
if var_in_mysqld_auto_cnf is None:
mysqlvar_val = False
else:
mysqlvar_val = var_in_mysqld_auto_cnf
# Type values before using them
value_wanted = typedvalue(value)
value_actual = typedvalue(mysqlvar_val)
value_in_auto_cnf = None
if var_in_mysqld_auto_cnf is not None:
value_in_auto_cnf = typedvalue(var_in_mysqld_auto_cnf)
if value_wanted == value_actual and mode in ('global', 'persist'):
if mode == 'persist' and value_wanted == value_in_auto_cnf:
module.exit_json(msg="Variable is already set to requested value globally"
"and stored into mysqld-auto.cnf file.", changed=False)
elif mode == 'global':
module.exit_json(msg="Variable is already set to requested value.", changed=False)
if mode == 'persist_only' and value_wanted == value_in_auto_cnf:
module.exit_json(msg="Variable is already stored into mysqld-auto.cnf "
"with requested value.", changed=False)
try:
result = setvariable(cursor, mysqlvar, value_wanted, mode)
except SQLParseError as e:
result = to_native(e)
if result is True:
module.exit_json(msg="Variable change succeeded prev_value=%s" % value_actual,
changed=True, queries=executed_queries)
else:
module.fail_json(msg=result, changed=False)
if __name__ == '__main__':
main()

View file

@ -1 +0,0 @@
./database/mysql/mysql_db.py

View file

@ -1 +0,0 @@
./database/mysql/mysql_info.py

View file

@ -1 +0,0 @@
./database/mysql/mysql_query.py

View file

@ -1 +0,0 @@
./database/mysql/mysql_replication.py

View file

@ -1 +0,0 @@
./database/mysql/mysql_user.py

View file

@ -1 +0,0 @@
./database/mysql/mysql_variables.py

View file

@ -1,7 +0,0 @@
destructive
unsupported # these tests conflict with mysql_replication tests and do not run on changes to the mysql_replication module
skip/aix
skip/osx
skip/freebsd
skip/rhel
needs/root

View file

@ -1,7 +0,0 @@
master_port: 3306
standby_port: 3307
test_db: test_db
replication_user: replication_user
replication_pass: replication_pass
dump_path: /tmp/dump.sql
conn_name: master-1

View file

@ -1,3 +0,0 @@
---
dependencies:
- setup_mariadb

View file

@ -1,21 +0,0 @@
# 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: mariadb_replication_initial.yml
when:
- ansible_facts.distribution == 'CentOS'
- ansible_facts.distribution_major_version is version('7', '>=')
# Tests of master_use_gtid parameter
# https://github.com/ansible/ansible/pull/62648
- import_tasks: mariadb_master_use_gtid.yml
when:
- ansible_facts.distribution == 'CentOS'
- ansible_facts.distribution_major_version is version('7', '>=')
# Tests of connection_name parameter
- import_tasks: mariadb_replication_connection_name.yml
when:
- ansible_facts.distribution == 'CentOS'
- ansible_facts.distribution_major_version is version('7', '>=')

View file

@ -1,173 +0,0 @@
# 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.
# https://github.com/ansible/ansible/pull/62648
#############################
# master_use_gtid: "disabled"
#############################
# Auxiliary step:
- name: Get master status
mysql_replication:
login_host: 127.0.0.1
login_port: "{{ primary_db.port }}"
mode: getmaster
register: primary_status
# Set master_use_gtid disabled:
- name: Run replication
mysql_replication:
login_host: 127.0.0.1
login_port: "{{ replica_db.port }}"
mode: changemaster
master_host: 127.0.0.1
master_port: "{{ primary_db.port }}"
master_user: "{{ replication_user }}"
master_password: "{{ replication_pass }}"
master_log_file: mysql-bin.000001
master_log_pos: '{{ primary_status.Position }}'
master_use_gtid: disabled
register: result
- assert:
that:
- result is changed
# Start standby for further tests:
- name: Start standby
mysql_replication:
login_host: 127.0.0.1
login_port: "{{ primary_db.port }}"
mode: startslave
- name: Get standby status
mysql_replication:
login_host: 127.0.0.1
login_port: "{{ replica_db.port }}"
mode: getslave
register: slave_status
- assert:
that:
- slave_status.Using_Gtid == 'No'
# Stop standby for further tests:
- name: Stop standby
mysql_replication:
login_host: 127.0.0.1
login_port: "{{ replica_db.port }}"
mode: stopslave
################################
# master_use_gtid: "current_pos"
################################
# Auxiliary step:
- name: Get master status
mysql_replication:
login_host: 127.0.0.1
login_port: "{{ primary_db.port }}"
mode: getmaster
register: primary_status
# Set master_use_gtid current_pos:
- name: Run replication
mysql_replication:
login_host: 127.0.0.1
login_port: "{{ replica_db.port }}"
mode: changemaster
master_host: 127.0.0.1
master_port: "{{ primary_db.port }}"
master_user: "{{ replication_user }}"
master_password: "{{ replication_pass }}"
master_log_file: mysql-bin.000001
master_log_pos: '{{ primary_status.Position }}'
master_use_gtid: current_pos
register: result
- assert:
that:
- result is changed
# Start standby for further tests:
- name: Start standby
mysql_replication:
login_host: 127.0.0.1
login_port: "{{ primary_db.port }}"
mode: startslave
- name: Get standby status
mysql_replication:
login_host: 127.0.0.1
login_port: "{{ replica_db.port }}"
mode: getslave
register: slave_status
- assert:
that:
- slave_status.Using_Gtid == 'Current_Pos'
# Stop standby for further tests:
- name: Stop standby
mysql_replication:
login_host: 127.0.0.1
login_port: "{{ replica_db.port }}"
mode: stopslave
##############################
# master_use_gtid: "slave_pos"
##############################
# Auxiliary step:
- name: Get master status
mysql_replication:
login_host: 127.0.0.1
login_port: "{{ primary_db.port }}"
mode: getmaster
register: primary_status
# Set master_use_gtid slave_pos:
- name: Run replication
mysql_replication:
login_host: 127.0.0.1
login_port: "{{ replica_db.port }}"
mode: changemaster
master_host: 127.0.0.1
master_port: "{{ primary_db.port }}"
master_user: "{{ replication_user }}"
master_password: "{{ replication_pass }}"
master_log_file: mysql-bin.000001
master_log_pos: '{{ primary_status.Position }}'
master_use_gtid: slave_pos
register: result
- assert:
that:
- result is changed
# Start standby for further tests:
- name: Start standby
mysql_replication:
login_host: 127.0.0.1
login_port: "{{ primary_db.port }}"
mode: startslave
- name: Get standby status
mysql_replication:
login_host: 127.0.0.1
login_port: "{{ replica_db.port }}"
mode: getslave
register: slave_status
- assert:
that:
- slave_status.Using_Gtid == 'Slave_Pos'
# Stop standby for further tests:
- name: Stop standby
mysql_replication:
login_host: 127.0.0.1
login_port: "{{ replica_db.port }}"
mode: stopslave

View file

@ -1,118 +0,0 @@
# 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: 127.0.0.1
login_port: "{{ replica_db.port }}"
mode: stopslave
- name: Reset slave all
mysql_replication:
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: 127.0.0.1
login_port: "{{ primary_db.port }}"
mode: getmaster
register: primary_status
# Test changemaster mode:
- name: Run replication with connection_name
mysql_replication:
login_host: 127.0.0.1
login_port: "{{ replica_db.port }}"
mode: changemaster
master_host: 127.0.0.1
master_port: "{{ primary_db.port }}"
master_user: "{{ replication_user }}"
master_password: "{{ replication_pass }}"
master_log_file: mysql-bin.000001
master_log_pos: '{{ primary_status.Position }}'
connection_name: '{{ conn_name }}'
register: result
- assert:
that:
- result is changed
- result.queries[0] is match("CHANGE MASTER ('\S+' )?TO MASTER_HOST='[0-9.]+',MASTER_USER='\w+',MASTER_PASSWORD='[*]{8}',MASTER_PORT=\d+,MASTER_LOG_FILE='mysql-bin.000001',MASTER_LOG_POS=\d+")
# Test startslave mode:
- name: Start slave with connection_name
mysql_replication:
login_host: 127.0.0.1
login_port: "{{ replica_db.port }}"
mode: startslave
connection_name: "{{ conn_name }}"
register: result
- assert:
that:
- result is changed
- result.queries == ["START SLAVE \'{{ conn_name }}\'"]
# Test getslave mode:
- name: Get standby statu with connection_name
mysql_replication:
login_host: 127.0.0.1
login_port: "{{ replica_db.port }}"
mode: getslave
connection_name: "{{ conn_name }}"
register: slave_status
- assert:
that:
- slave_status.Is_Slave == true
- 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
- slave_status.Last_IO_Error == ''
- slave_status is not changed
# Test stopslave mode:
- name: Stop slave with connection_name
mysql_replication:
login_host: 127.0.0.1
login_port: "{{ replica_db.port }}"
mode: stopslave
connection_name: "{{ conn_name }}"
register: result
- assert:
that:
- result is changed
- result.queries == ["STOP SLAVE \'{{ conn_name }}\'"]
# Test reset
- name: Reset slave with connection_name
mysql_replication:
login_host: 127.0.0.1
login_port: "{{ replica_db.port }}"
mode: resetslave
connection_name: "{{ conn_name }}"
register: result
- assert:
that:
- result is changed
- result.queries == ["RESET SLAVE \'{{ conn_name }}\'"]
# Test reset all
- name: Reset slave all with connection_name
mysql_replication:
login_host: 127.0.0.1
login_port: "{{ replica_db.port }}"
mode: resetslaveall
connection_name: "{{ conn_name }}"
register: result
- assert:
that:
- result is changed
- result.queries == ["RESET SLAVE \'{{ conn_name }}\' ALL"]

View file

@ -1,96 +0,0 @@
# 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 127.0.0.1"
- name: Create test database
mysql_db:
login_host: 127.0.0.1
login_port: '{{ primary_db.port }}'
state: present
name: '{{ test_db }}'
- name: Dump all databases from the master
shell: 'mysqldump -P {{ primary_db.port }} -h 127.0.01 --all-databases --master-data=2 > {{ dump_path }}'
- name: Restore the dump to the replica
shell: 'mysql -P {{ replica_db.port }} -h 127.0.0.1 < {{ dump_path }}'
# Test getmaster mode:
- name: Get master status
mysql_replication:
login_host: 127.0.0.1
login_port: "{{ primary_db.port }}"
mode: getmaster
register: master_status
- assert:
that:
- master_status.Is_Master == true
- master_status.Position != 0
- master_status is not changed
# Test changemaster mode:
- name: Run replication
mysql_replication:
login_host: 127.0.0.1
login_port: "{{ replica_db.port }}"
mode: changemaster
master_host: 127.0.0.1
master_port: "{{ primary_db.port }}"
master_user: "{{ replication_user }}"
master_password: "{{ replication_pass }}"
master_log_file: mysql-bin.000001
master_log_pos: '{{ master_status.Position }}'
register: result
- assert:
that:
- result is changed
- result.queries[0] is match("CHANGE MASTER ('\S+' )?TO MASTER_HOST='[0-9.]+',MASTER_USER='\w+',MASTER_PASSWORD='[*]{8}',MASTER_PORT=\d+,MASTER_LOG_FILE='mysql-bin.000001',MASTER_LOG_POS=\d+")
# Test startslave mode:
- name: Start slave
mysql_replication:
login_host: 127.0.0.1
login_port: "{{ replica_db.port }}"
mode: startslave
register: result
- assert:
that:
- result is changed
- result.queries == ["START SLAVE"]
# Test getslave mode:
- name: Get replica status
mysql_replication:
login_host: 127.0.0.1
login_port: "{{ replica_db.port }}"
mode: getslave
register: slave_status
- assert:
that:
- slave_status.Is_Slave == true
- 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
- slave_status.Last_IO_Error == ''
- slave_status is not changed
# Test stopslave mode:
- name: Stop slave
mysql_replication:
login_host: 127.0.0.1
login_port: "{{ replica_db.port }}"
mode: stopslave
register: result
- assert:
that:
- result is changed
- result.queries == ["STOP SLAVE"]

View file

@ -1,5 +0,0 @@
destructive
shippable/posix/group5
skip/aix
skip/osx
skip/freebsd

View file

@ -1,10 +0,0 @@
---
# defaults file for test_mysql_db
db_name: 'data'
db_name2: 'data2'
db_user1: 'datauser1'
db_user2: 'datauser2'
tmp_dir: '/tmp'
db_latin1_name: 'db_latin1'
file4: 'latin1_file'

View file

@ -1,2 +0,0 @@
dependencies:
- setup_mysql_db

View file

@ -1,71 +0,0 @@
- set_fact:
db_to_create=testdb1
config_file="/root/.my1.cnf"
fake_port=9999
fake_host="blahblah.local"
- name: Create custom config file
shell: 'echo "[client]" > {{ config_file }}'
- name: Add fake port to config file
shell: 'echo "port = {{ fake_port }}" >> {{ config_file }}'
- name: Create database using fake port to connect to, must fail
mysql_db:
name: '{{ db_to_create }}'
state: present
check_implicit_admin: yes
config_file: '{{ config_file }}'
config_overrides_defaults: yes
ignore_errors: yes
register: result
- 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")
- name: Create database using default port
mysql_db:
name: '{{ db_to_create }}'
state: present
check_implicit_admin: yes
config_file: '{{ config_file }}'
config_overrides_defaults: no
login_unix_socket: '{{ mysql_socket }}'
register: result
- name: Must not fail because of the default of login_port is correct
assert:
that:
- result is changed
- name: Reinit custom config file
shell: 'echo "[client]" > {{ config_file }}'
- name: Add fake host to config file
shell: 'echo "host = {{ fake_host }}" >> {{ config_file }}'
- name: Remove database using fake login_host
mysql_db:
name: '{{ db_to_create }}'
state: absent
config_file: '{{ config_file }}'
config_overrides_defaults: yes
register: result
ignore_errors: yes
- 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:
name: '{{ db_to_create }}'
state: absent
check_implicit_admin: yes
login_unix_socket: '{{ mysql_socket }}'

View file

@ -1,78 +0,0 @@
---
- set_fact:
latin1_file1="{{tmp_dir}}/{{file}}"
- name: Deleting Latin1 encoded Database
mysql_db:
name: '{{ db_latin1_name }}'
state: absent
login_unix_socket: '{{ mysql_socket }}'
- name: create Latin1 encoded database
mysql_db:
name: '{{ db_latin1_name }}'
state: present
encoding: latin1
login_unix_socket: '{{ mysql_socket }}'
- name: create a table in Latin1 database
command: mysql {{ db_latin1_name }} -e 'create table testlatin1(id int, name varchar(100));'
# Inserting a string in latin1 into table, , this string be tested later,
# so report any change of content in the test too
- name: inserting data into Latin1 database
command: mysql {{ db_latin1_name }} -e "insert into testlatin1 value(47,'Amédée Bôlüt');"
- name: selecting table
command: mysql {{ db_latin1_name }} -e "select * from testlatin1;"
register: output
- name: Dumping a table in Latin1 database
mysql_db:
name: "{{ db_latin1_name }}"
encoding: latin1
target: "{{ latin1_file1 }}"
state: dump
login_unix_socket: '{{ mysql_socket }}'
register: dump_result
- assert:
that:
- result is changed
- 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
shell: grep -a 47 {{ latin1_file1 }} | od -c |grep "A m 351 d 351 e B 364\|A m 303 251 d 303 251 e B 303"
- name: Dropping {{ db_latin1_name }} database
mysql_db:
name: '{{ db_latin1_name }}'
state: absent
login_unix_socket: '{{ mysql_socket }}'
- name: Importing the latin1 mysql script
mysql_db:
state: import
encoding: latin1
name: '{{ db_latin1_name }}'
target: "{{ latin1_file1 }}"
login_unix_socket: '{{ mysql_socket }}'
- assert:
that:
- result is changed
- name: check encoding of table
shell: mysql {{ db_latin1_name }} -e "SHOW FULL COLUMNS FROM testlatin1;"
register: output
failed_when: '"latin1_swedish_ci" not in output.stdout'
- name: remove database
mysql_db:
name: '{{ db_latin1_name }}'
state: absent
login_unix_socket: '{{ mysql_socket }}'

View file

@ -1,263 +0,0 @@
# test code for the mysql_db module
# (c) 2014, Wayne Rosario <wrosario@ansible.com>
# 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: remove database if it exists
command: >
mysql -sse "drop database {{db_name}};"
ignore_errors: True
- name: make sure the test database is not there
command: mysql {{db_name}}
register: mysql_db_check
failed_when: "'1049' not in mysql_db_check.stderr"
- name: test state=present for a database name (expect changed=true)
mysql_db:
name: '{{ db_name }}'
state: present
login_unix_socket: '{{ mysql_socket }}'
register: result
- name: assert output message that database exist
assert:
that:
- result is changed
- result.db == '{{ db_name }}'
- result.executed_commands == ["CREATE DATABASE `{{ db_name }}`"]
- name: run command to test state=present for a database name (expect db_name in stdout)
command: mysql "-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:
name: '{{ db_name }}'
state: absent
login_unix_socket: '{{ mysql_socket }}'
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 "-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:
name: datanotvalid
state: present
encoding: notvalid
login_unix_socket: '{{ mysql_socket }}'
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:
name: 'en{{ db_name }}'
state: present
encoding: utf8
login_unix_socket: '{{ mysql_socket }}'
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 "-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:
name: 'en{{ db_name }}'
state: absent
login_unix_socket: '{{ mysql_socket }}'
# ============================================================
- name: test mysql_db using valid encoding binary (expect changed=true)
mysql_db:
name: 'en{{ db_name }}'
state: present
encoding: binary
login_unix_socket: '{{ mysql_socket }}'
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 "-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:
name: 'en{{ db_name }}'
state: absent
login_unix_socket: '{{ mysql_socket }}'
# ============================================================
- name: create user1 to access database dbuser1
mysql_user:
name: user1
password: 'Hfd6fds^dfA8Ga'
priv: '*.*:ALL'
state: present
login_unix_socket: '{{ mysql_socket }}'
- name: create database dbuser1 using user1
mysql_db:
name: '{{ db_user1 }}'
state: present
login_user: user1
login_password: 'Hfd6fds^dfA8Ga'
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 "-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:
name: user2
password: 'kjsfd&F7safjad'
priv: '*.*:SELECT'
state: present
login_unix_socket: '{{ mysql_socket }}'
- name: create database dbuser2 using user2 with no privilege to create (expect failed=true)
mysql_db:
name: '{{ db_user2 }}'
state: present
login_user: user2
login_password: 'kjsfd&F7safjad'
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 "-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:
name: '{{ db_user1 }}'
state: absent
login_user: user2
login_password: 'kjsfd&F7safjad'
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 "-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:
name: '{{ db_user1 }}'
state: absent
login_user: user1
login_password: 'Hfd6fds^dfA8Ga'
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 "-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

View file

@ -1,557 +0,0 @@
# Copyright (c) 2019, Pratik Gadiya <pratikgadiya1@gmail.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
- set_fact:
db1_name="database1"
db2_name="database2"
db3_name="database3"
db4_name="database4"
db5_name="database5"
dump1_file="/tmp/dump1_file.sql"
dump2_file="/tmp/all.sql"
# ============================== CREATE TEST ===============================
#
# ==========================================================================
# Initial check - To confirm that database does not exist before executing check mode tasks
- name: run command to list databases like specified database name
command: mysql "-e show databases like 'database%';"
register: mysql_result
- name: assert that databases does not exist
assert:
that:
- "'{{ db1_name }}' not in mysql_result.stdout"
- "'{{ db2_name }}' not in mysql_result.stdout"
- "'{{ db3_name }}' not in mysql_result.stdout"
# ==========================================================================
# Create multiple databases that does not exists (check mode)
- name: Create multiple databases that does not exists (check mode)
mysql_db:
name:
- '{{ db1_name }}'
- '{{ db2_name }}'
- '{{ db3_name }}'
state: present
login_unix_socket: '{{ mysql_socket }}'
register: check_mode_result
check_mode: yes
- name: assert successful completion of create database using check_mode since databases does not exist prior
assert:
that:
- check_mode_result.changed == true
- name: run command to list databases like specified database name
command: mysql "-e show databases like 'database%';"
register: mysql_result
- name: assert that databases does not exist (since created via check mode)
assert:
that:
- "'{{ db1_name }}' not in mysql_result.stdout"
- "'{{ db2_name }}' not in mysql_result.stdout"
- "'{{ db3_name }}' not in mysql_result.stdout"
# ==========================================================================
# Create multiple databases
- name: Create multiple databases
mysql_db:
name:
- '{{ db1_name }}'
- '{{ db2_name }}'
- '{{ db3_name }}'
state: present
login_unix_socket: '{{ mysql_socket }}'
register: result
- name: assert successful completion of create database
assert:
that:
- result.changed == true
- result.db_list == ['{{ db1_name }}', '{{ db2_name }}', '{{ db3_name }}']
- name: run command to list databases like specified database name
command: mysql "-e show databases like 'database%';"
register: mysql_result
- name: assert that databases exist after creation
assert:
that:
- "'{{ db1_name }}' in mysql_result.stdout"
- "'{{ db2_name }}' in mysql_result.stdout"
- "'{{ db3_name }}' in mysql_result.stdout"
# =========================================================================
# Recreate already existing databases (check mode)
- name: Recreate already existing databases (check mode)
mysql_db:
name:
- '{{ db1_name }}'
- '{{ db2_name }}'
- '{{ db3_name }}'
state: present
login_unix_socket: '{{ mysql_socket }}'
register: check_mode_result
check_mode: yes
- name: assert that recreation of existing databases does not make change (since recreated using check mode)
assert:
that:
- check_mode_result.changed == false
- name: run command to list databases like specified database name
command: mysql "-e show databases like 'database%';"
register: mysql_result
- name: assert that databases exist (since performed recreation of existing databases via check mode)
assert:
that:
- "'{{ db1_name }}' in mysql_result.stdout"
- "'{{ db2_name }}' in mysql_result.stdout"
- "'{{ db3_name }}' in mysql_result.stdout"
# ==========================================================================
# Recreate same databases
- name: Recreate multiple databases
mysql_db:
name:
- '{{ db1_name }}'
- '{{ db2_name }}'
- '{{ db3_name }}'
state: present
login_unix_socket: '{{ mysql_socket }}'
register: result
- name: assert that recreation of existing databases does not make change
assert:
that:
- result.changed == false
- name: run command to list databases like specified database name
command: mysql "-e show databases like 'database%';"
register: mysql_result
- name: assert that databases does priorly exist
assert:
that:
- "'{{ db1_name }}' in mysql_result.stdout"
- "'{{ db2_name }}' in mysql_result.stdout"
- "'{{ db3_name }}' in mysql_result.stdout"
# ==========================================================================
# Delete one of the databases (db2 here)
- name: Delete db2 database
mysql_db:
name:
- '{{ db2_name }}'
state: absent
login_unix_socket: '{{ mysql_socket }}'
register: result
- name: assert successful completion of deleting database
assert:
that:
- result.changed == true
- name: run command to list databases like specified database name
command: mysql "-e show databases like 'database%';"
register: mysql_result
- name: assert that only db2 database does not exist
assert:
that:
- "'{{ db1_name }}' in mysql_result.stdout"
- "'{{ db2_name }}' not in mysql_result.stdout"
- "'{{ db3_name }}' in mysql_result.stdout"
# =========================================================================
# Recreate multiple databases in which few databases does not exists (check mode)
- name: Recreate multiple databases in which few databases does not exists (check mode)
mysql_db:
name:
- '{{ db1_name }}'
- '{{ db2_name }}'
- '{{ db3_name }}'
state: present
login_unix_socket: '{{ mysql_socket }}'
register: check_mode_result
check_mode: yes
- name: assert successful completion of recreation of partially existing database using check mode
assert:
that:
- check_mode_result.changed == true
- name: run command to list databases like specified database name
command: mysql "-e show databases like 'database%';"
register: mysql_result
- name: assert that recreated non existing databases does not exist (since created via check mode)
assert:
that:
- "'{{ db1_name }}' in mysql_result.stdout"
- "'{{ db2_name }}' not in mysql_result.stdout"
- "'{{ db3_name }}' in mysql_result.stdout"
# ==========================================================================
# Create multiple databases
- name: Create multiple databases
mysql_db:
name:
- '{{ db1_name }}'
- '{{ db2_name }}'
- '{{ db3_name }}'
state: present
login_unix_socket: '{{ mysql_socket }}'
register: result
- name: assert successful completion of create database
assert:
that:
- result.changed == true
- name: run command to list databases like specified database name
command: mysql "-e show databases like 'database%';"
register: mysql_result
- name: assert that databases exist
assert:
that:
- "'{{ db1_name }}' in mysql_result.stdout"
- "'{{ db2_name }}' in mysql_result.stdout"
- "'{{ db3_name }}' in mysql_result.stdout"
# ============================== DUMP TEST =================================
#
# ==========================================================================
# Check that dump file does not exist
- name: Dump file does not exist
file: name={{ dump1_file }} state=absent
# ==========================================================================
# Dump existing databases (check mode)
- name: Dump existing databases (check mode)
mysql_db:
name:
- '{{ db1_name }}'
- '{{ db3_name }}'
state: dump
target: '{{ dump1_file }}'
login_unix_socket: '{{ mysql_socket }}'
register: check_mode_dump_result
check_mode: yes
- name: assert successful completion of dump operation using check mode
assert:
that:
- check_mode_dump_result.changed == true
- name: run command to list databases like specified database name
command: mysql "-e show databases like 'database%';"
register: mysql_result
- name: assert that databases exist (check mode)
assert:
that:
- "'{{ db1_name }}' in mysql_result.stdout"
- "'{{ db2_name }}' in mysql_result.stdout"
- "'{{ db3_name }}' in mysql_result.stdout"
- name: state dump - file name should not exist (since dumped via check mode)
file: name={{ dump1_file }} state=absent
# ==========================================================================
# Dump existing and non-existing databases (check mode)
- name: Dump existing and non-existing databases (check mode)
mysql_db:
name:
- "{{ db1_name }}"
- "{{ db4_name }}"
- "{{ db3_name }}"
state: dump
login_unix_socket: '{{ mysql_socket }}'
target: "{{ dump1_file }}"
register: check_mode_dump_result
ignore_errors: True
check_mode: yes
- name: assert that dump operation of existing and non existing databases does not make change (using check mode)
assert:
that:
- "'Cannot dump database' in check_mode_dump_result['msg']"
- name: run command to list databases like specified database name
command: mysql "-e show databases like 'database%';"
register: mysql_result
- name: assert that databases exist (since check mode)
assert:
that:
- "'{{ db1_name }}' in mysql_result.stdout"
- "'{{ db2_name }}' in mysql_result.stdout"
- "'{{ db3_name }}' in mysql_result.stdout"
- "'{{ db4_name }}' not in mysql_result.stdout"
- name: state dump - file name should not exist (since prior dump operation performed via check mode)
file: name={{ dump1_file }} state=absent
# ==========================================================================
# Dump non-existing databases (check mode)
- name: Dump non-existing databases (check mode)
mysql_db:
name:
- "{{ db4_name }}"
- "{{ db5_name }}"
state: dump
target: "{{ dump1_file }}"
login_unix_socket: '{{ mysql_socket }}'
register: check_mode_dump_result
ignore_errors: True
check_mode: yes
- name: assert successful completion of dump operation using check mode
assert:
that:
- "'Cannot dump database' in check_mode_dump_result['msg']"
- name: run command to list databases like specified database name
command: mysql "-e show databases like 'database%';"
register: mysql_result
- name: assert that databases exist (since delete via check mode)
assert:
that:
- "'{{ db1_name }}' in mysql_result.stdout"
- "'{{ db2_name }}' in mysql_result.stdout"
- "'{{ db3_name }}' in mysql_result.stdout"
- "'{{ db4_name }}' not in mysql_result.stdout"
- "'{{ db5_name }}' not in mysql_result.stdout"
- name: state dump - file name should not exist (since prior dump operation performed via check mode)
file: name={{ dump1_file }} state=absent
# ==========================================================================
# Dump existing databases
- name: Dump existing databases
mysql_db:
name:
- '{{ db1_name }}'
- '{{ db2_name }}'
- '{{ db3_name }}'
state: dump
target: '{{ dump1_file }}'
login_unix_socket: '{{ mysql_socket }}'
register: dump_result
- name: assert successful completion of dump operation
assert:
that:
- dump_result.changed == true
- dump_result.db_list == ['{{ db1_name }}', '{{ db2_name }}', '{{ db3_name }}']
- name: run command to list databases like specified database name
command: mysql "-e show databases like 'database%';"
register: mysql_result
- name: assert that databases exist
assert:
that:
- "'{{ db1_name }}' in mysql_result.stdout"
- "'{{ db2_name }}' in mysql_result.stdout"
- "'{{ db3_name }}' in mysql_result.stdout"
- name: state dump - file name should exist
file: name={{ dump1_file }} state=file
- name: Check if db1 database create command is present in the dumped file
shell: "grep -i 'CREATE DATABASE.*`{{ db1_name }}`' {{ dump1_file }}"
- name: Check if db2 database create command is present in the dumped file
shell: "grep -i 'CREATE DATABASE.*`{{ db2_name }}`' {{ dump1_file }}"
- name: Check if db3 database create command is present in the dumped file
shell: "grep -i 'CREATE DATABASE.*`{{ db3_name }}`' {{ dump1_file }}"
# ==========================================================================
# Dump all databases
- name: state dump - dump2 file name should not exist
file: name={{ dump2_file }} state=absent
- name: Dump existing databases
mysql_db:
name: all
state: dump
target: '{{ dump2_file }}'
login_unix_socket: '{{ mysql_socket }}'
register: dump_result
- name: assert successful completion of dump operation
assert:
that:
- dump_result.changed == true
- name: run command to list databases like specified database name
command: mysql "-e show databases like 'database%';"
register: mysql_result
- name: assert that databases exist
assert:
that:
- "'{{ db1_name }}' in mysql_result.stdout"
- "'{{ db2_name }}' in mysql_result.stdout"
- "'{{ db3_name }}' in mysql_result.stdout"
- "'{{ db4_name }}' not in mysql_result.stdout"
- "'{{ db5_name }}' not in mysql_result.stdout"
- name: state dump - file name should exist
file: name={{ dump2_file }} state=file
# ============================ DELETE TEST =================================
#
# ==========================================================================
# Delete multiple databases which already exists (check mode)
- name: Delete multiple databases which already exists (check mode)
mysql_db:
name:
- '{{ db2_name }}'
- '{{ db3_name }}'
state: absent
login_unix_socket: '{{ mysql_socket }}'
register: check_mode_result
check_mode: yes
- name: assert successful completion of delete databases which already exists using check mode
assert:
that:
- check_mode_result.changed == true
- name: run command to test state=absent for a database name
command: mysql "-e show databases like 'database%';"
register: mysql_result
- name: assert that databases exist even after deleting (since deleted via check mode)
assert:
that:
- "'{{ db2_name }}' in mysql_result.stdout"
- "'{{ db3_name }}' in mysql_result.stdout"
# ==========================================================================
# Delete multiple databases
- name: Delete multiple databases
mysql_db:
name:
- '{{ db2_name }}'
- '{{ db3_name }}'
state: absent
login_unix_socket: '{{ mysql_socket }}'
register: result
- name: assert successful completion of deleting database
assert:
that:
- result.changed == true
- result.db_list == ['{{ db2_name }}', '{{ db3_name }}']
- name: run command to list databases like specified database name
command: mysql "-e show databases like 'database%';"
register: mysql_result
- name: assert that databases does not exist
assert:
that:
- "'{{ db2_name }}' not in mysql_result.stdout"
- "'{{ db3_name }}' not in mysql_result.stdout"
# ==========================================================================
# Delete non existing databases (check mode)
- name: Delete non existing databases (check mode)
mysql_db:
name:
- '{{ db2_name }}'
- '{{ db4_name }}'
state: absent
login_unix_socket: '{{ mysql_socket }}'
register: check_mode_result
check_mode: yes
- name: assert that deletion of non existing databases does not make change (using check mode)
assert:
that:
- check_mode_result.changed == false
- name: run command to test state=absent for a database name
command: mysql "-e show databases like 'database%';"
register: mysql_result
- name: assert that databases does not exist since were deleted priorly (check mode)
assert:
that:
- "'{{ db2_name }}' not in mysql_result.stdout"
- "'{{ db4_name }}' not in mysql_result.stdout"
# ==========================================================================
# Delete already deleted databases
- name: Delete already deleted databases
mysql_db:
name:
- '{{ db2_name }}'
- '{{ db4_name }}'
state: absent
login_unix_socket: '{{ mysql_socket }}'
register: result
- name: assert that deletion of non existing databases does not make change
assert:
that:
- result.changed == false
- name: run command to list databases like specified database name
command: mysql "-e show databases like 'database%';"
register: mysql_result
- name: assert that databases does not exists
assert:
that:
- "'{{ db2_name }}' not in mysql_result.stdout"
- "'{{ db4_name }}' not in mysql_result.stdout"
# ==========================================================================
# Delete all databases
- name: Delete all databases
mysql_db:
name:
- '{{ db1_name }}'
- '{{ db2_name }}'
- '{{ db3_name }}'
- '{{ db4_name }}'
- '{{ db5_name }}'
state: absent
login_unix_socket: '{{ mysql_socket }}'
register: result
- name: assert successful completion of deleting database
assert:
that:
- result.changed == true
- name: run command to list databases like specified database name
command: mysql "-e show databases like 'database%';"
register: mysql_result
- name: assert that specific databases does not exist
assert:
that:
- "'{{ db1_name }}' not in mysql_result.stdout"
- "'{{ db2_name }}' not in mysql_result.stdout"
- "'{{ db3_name }}' not in mysql_result.stdout"
- "'{{ db4_name }}' not in mysql_result.stdout"
- "'{{ db5_name }}' not in mysql_result.stdout"
- name: state dump - dump 1 file name should be removed
file: name={{ dump1_file }} state=absent
- name: state dump - dump 2 file name should be removed
file: name={{ dump2_file }} state=absent

View file

@ -1,375 +0,0 @@
# test code for state dump and import for mysql_db module
# (c) 2014, Wayne Rosario <wrosario@ansible.com>
# 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/>.
# ============================================================
- set_fact:
db_file_name="{{tmp_dir}}/{{file}}"
wrong_sql_file="{{tmp_dir}}/wrong.sql"
dump_file1="{{tmp_dir}}/{{file2}}"
dump_file2="{{tmp_dir}}/{{file3}}"
db_user="test"
db_user_unsafe_password="pass!word"
config_file="/root/.my.cnf"
- name: create custom config file
shell: 'echo "[client]" > {{ config_file }}'
- name: create user for test unsafe_login_password parameter
mysql_user:
name: '{{ db_user }}'
password: '{{ db_user_unsafe_password }}'
priv: '*.*:ALL'
state: present
login_unix_socket: '{{ mysql_socket }}'
- name: state dump/import - create database
mysql_db:
name: '{{ db_name }}'
state: present
check_implicit_admin: yes
login_unix_socket: '{{ mysql_socket }}'
- name: create database
mysql_db:
name: '{{ db_name2 }}'
state: present
check_implicit_admin: no
login_unix_socket: '{{ mysql_socket }}'
- name: state dump/import - create table department
command: mysql {{ db_name }} '-e create table department(id int, name varchar(100));'
- name: state dump/import - create table employee
command: mysql {{ db_name }} '-e create table employee(id int, name varchar(100));'
- name: state dump/import - insert data into table employee
command: mysql {{ db_name }} "-e insert into employee value(47,'Joe Smith');"
- name: state dump/import - insert data into table department
command: mysql {{ db_name }} "-e insert into department value(2,'Engineering');"
- name: state dump/import - file name should not exist
file: name={{ db_file_name }} state=absent
- name: database dump file1 should not exist
file: name={{ dump_file1 }} state=absent
- name: database dump file2 should not exist
file: name={{ dump_file2 }} state=absent
- name: state dump without department table.
mysql_db:
login_user: '{{ db_user }}'
login_password: '{{ db_user_unsafe_password }}'
unsafe_login_password: yes
name: "{{ db_name }}"
state: dump
target: "{{ db_file_name }}"
ignore_tables:
- "{{ db_name }}.department"
login_unix_socket: '{{ mysql_socket }}'
force: yes
master_data: 1
skip_lock_tables: yes
dump_extra_args: --skip-triggers
config_file: '{{ config_file }}'
restrict_config_file: yes
check_implicit_admin: no
register: result
- name: assert successful completion of dump operation
assert:
that:
- result is changed
- result.executed_commands[0] is search("mysqldump --defaults-file={{ config_file }} --user={{ db_user }} --password=\*\*\*\*\*\*\*\* --force --socket={{ mysql_socket }} {{ db_name }} --skip-lock-tables --quick --ignore-table={{ db_name }}.department --master-data=1 --skip-triggers")
- name: state dump/import - file name should exist
file: name={{ db_file_name }} state=file
- name: state dump with multiple databases in comma separated form.
mysql_db:
name: "{{ db_name }},{{ db_name2 }}"
state: dump
target: "{{ dump_file1 }}"
login_unix_socket: '{{ mysql_socket }}'
check_implicit_admin: yes
register: dump_result1
- 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: state dump - dump file1 should exist
file: name={{ dump_file1 }} state=file
- name: state dump with multiple databases in list form via check_mode
mysql_db:
name:
- "{{ db_name }}"
- "{{ db_name2 }}"
state: dump
target: "{{ dump_file2 }}"
login_unix_socket: '{{ mysql_socket }}'
register: dump_result
check_mode: yes
- name: assert successful completion of dump operation (with multiple databases in list form) via check mode
assert:
that:
- "dump_result.changed == true"
- name: database dump file2 should not exist
stat:
path: "{{ dump_file2 }}"
register: stat_result
- name: assert that check_mode does not create dump file for databases
assert:
that:
- stat_result.stat.exists is defined and not stat_result.stat.exists
- name: state dump with multiple databases in list form.
mysql_db:
name:
- "{{ db_name }}"
- "{{ db_name2 }}"
state: dump
target: "{{ dump_file2 }}"
login_unix_socket: '{{ mysql_socket }}'
register: dump_result2
- name: assert successful completion of dump operation (with multiple databases in list form)
assert:
that:
- "dump_result2.changed == true"
- name: state dump - dump file2 should exist
file: name={{ dump_file2 }} state=file
- name: state dump/import - remove database
mysql_db:
name: '{{ db_name }}'
state: absent
login_unix_socket: '{{ mysql_socket }}'
- name: remove database
mysql_db:
name: '{{ db_name2 }}'
state: absent
login_unix_socket: '{{ mysql_socket }}'
- 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 }}'
unsafe_login_password: yes
name: '{{ db_name }}'
state: import
target: '{{ db_file_name }}'
login_unix_socket: '{{ mysql_socket }}'
use_shell: yes
register: result
- name: show the tables
command: mysql {{ db_name }} "-e show tables;"
register: result
- name: assert that the department table is absent.
assert:
that:
- "'department' not in result.stdout"
- name: test state=import to restore a database from multiple database dumped file1
mysql_db:
name: '{{ db_name2 }}'
state: import
target: '{{ dump_file1 }}'
login_unix_socket: '{{ mysql_socket }}'
use_shell: no
register: import_result
- name: assert output message restored a database from dump file1
assert: { that: "import_result.changed == true" }
- name: remove database
mysql_db:
name: '{{ db_name2 }}'
state: absent
login_unix_socket: '{{ mysql_socket }}'
- name: run command to list databases
command: mysql "-e show databases like 'data%';"
register: mysql_result
- name: assert that db_name2 database does not exist
assert:
that:
- "'{{ db_name2 }}' not in mysql_result.stdout"
- name: test state=import to restore a database from dumped file2 (check mode)
mysql_db:
name: '{{ db_name2 }}'
state: import
target: '{{ dump_file2 }}'
login_unix_socket: '{{ mysql_socket }}'
register: check_import_result
check_mode: yes
- name: assert output message restored a database from dump file2 (check mode)
assert: { that: "check_import_result.changed == true" }
- name: run command to list databases
command: mysql "-e show databases like 'data%';"
register: mysql_result
- name: assert that db_name2 database does not exist (check mode)
assert:
that:
- "'{{ db_name2 }}' not in mysql_result.stdout"
- name: test state=import to restore a database from multiple database dumped file2
mysql_db:
name: '{{ db_name2 }}'
state: import
target: '{{ dump_file2 }}'
login_unix_socket: '{{ mysql_socket }}'
register: import_result2
- name: assert output message restored a database from dump file2
assert:
that:
- import_result2.changed == true
- import_result2.db_list == ['{{ db_name2 }}']
- name: run command to list databases
command: mysql "-e show databases like 'data%';"
register: mysql_result
- name: assert that db_name2 database does exist after import
assert:
that:
- "'{{ db_name2 }}' in mysql_result.stdout"
- name: test state=dump to backup the database of type {{ format_type }} (expect changed=true)
mysql_db:
name: '{{ db_name }}'
state: dump
target: '{{ db_file_name }}'
login_unix_socket: '{{ mysql_socket }}'
register: result
- name: assert output message backup the database
assert:
that:
- "result.changed == true"
- "result.db =='{{ db_name }}'"
- name: assert database was backed up successfully
command: file {{ db_file_name }}
register: result
- name: assert file format type
assert: { that: "'{{format_msg_type}}' in result.stdout" }
- name: update database table employee
command: mysql {{ db_name }} "-e update employee set name='John Doe' where id=47;"
- name: test state=import to restore the database of type {{ format_type }} (expect changed=true)
mysql_db:
name: '{{ db_name }}'
state: import
target: '{{ db_file_name }}'
login_unix_socket: '{{ mysql_socket }}'
register: result
- name: assert output message restore the database
assert: { that: "result.changed == true" }
- name: select data from table employee
command: mysql {{ db_name }} "-e select * from employee;"
register: result
- name: assert data in database is from the restore database
assert:
that:
- "'47' in result.stdout"
- "'Joe Smith' in result.stdout"
##########################
# Test ``force`` parameter
##########################
- name: create wrong sql file
shell: echo 'CREATE TABLE hello (id int); CREATE ELBAT ehlo (int id);' >> '{{ wrong_sql_file }}'
- name: try to import without force parameter, must fail
mysql_db:
name: '{{ db_name }}'
state: import
target: '{{ wrong_sql_file }}'
login_unix_socket: '{{ mysql_socket }}'
force: no
register: result
ignore_errors: yes
- assert:
that:
- result.failed == true
- name: try to import with force parameter
mysql_db:
name: '{{ db_name }}'
state: import
target: '{{ wrong_sql_file }}'
login_unix_socket: '{{ mysql_socket }}'
force: yes
register: result
- assert:
that:
- result is changed
##########
# Clean up
##########
- name: remove database name
mysql_db:
name: '{{ db_name }}'
state: absent
login_unix_socket: '{{ mysql_socket }}'
- name: remove database
mysql_db:
name: '{{ db_name2 }}'
state: absent
login_unix_socket: '{{ mysql_socket }}'
- 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

View file

@ -1,6 +0,0 @@
destructive
shippable/posix/group1
skip/aix
skip/osx
skip/freebsd
skip/rhel

View file

@ -1,5 +0,0 @@
---
# defaults file for test_mysql_info
db_name: data
user_name: alice
user_pass: alice

View file

@ -1,3 +0,0 @@
dependencies:
- setup_mysql_db
- setup_remote_tmp_dir

View file

@ -1,190 +0,0 @@
# Test code for mysql_info module
# 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)
###################
# Prepare for tests
#
# Create role for tests
- name: mysql_info - create mysql user {{ user_name }}
mysql_user:
name: '{{ user_name }}'
password: '{{ user_pass }}'
state: present
priv: '*.*:ALL'
login_unix_socket: '{{ mysql_socket }}'
# Create default MySQL config file with credentials
- name: mysql_info - create default config file
template:
src: my.cnf.j2
dest: /root/.my.cnf
mode: '0400'
# Create non-default MySQL config file with credentials
- name: mysql_info - create non-default config file
template:
src: my.cnf.j2
dest: /root/non-default_my.cnf
mode: '0400'
###############
# Do tests
# Access by default cred file
- name: mysql_info - collect default cred file
mysql_info:
login_user: '{{ user_name }}'
register: result
- assert:
that:
- 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
mysql_info:
login_user: '{{ user_name }}'
config_file: /root/non-default_my.cnf
register: result
- assert:
that:
- result.changed == false
- result.version != {}
# Remove cred files
- name: mysql_info - remove cred files
file:
path: '{{ item }}'
state: absent
with_items:
- /root/.my.cnf
- /root/non-default_my.cnf
# Access with password
- name: mysql_info - check access with password
mysql_info:
login_user: '{{ user_name }}'
login_password: '{{ user_pass }}'
register: result
- assert:
that:
- result.changed == false
- result.version != {}
# Test excluding
- name: Collect all info except settings and users
mysql_info:
login_user: '{{ user_name }}'
login_password: '{{ user_pass }}'
filter: '!settings,!users'
register: result
- assert:
that:
- 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
mysql_info:
login_user: '{{ user_name }}'
login_password: '{{ user_pass }}'
filter:
- version
- databases
register: result
- assert:
that:
- result.changed == false
- result.version != {}
- result.databases != {}
- result.engines is not defined
- result.settings is not defined
- result.global_status is not defined
- result.users is not defined
# 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:
login_user: '{{ user_name }}'
login_password: '{{ user_pass }}'
filter:
- databases
exclude_fields:
- db_size
- unsupported
register: result
- assert:
that:
- result.changed == false
- result.databases != {}
- result.databases.mysql == {}
########################################################
# Issue #65727, empty databases must be in returned dict
#
- name: Create empty database acme
mysql_db:
login_user: '{{ user_name }}'
login_password: '{{ user_pass }}'
name: acme
- name: Collect info about databases
mysql_info:
login_user: '{{ user_name }}'
login_password: '{{ user_pass }}'
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:
login_user: '{{ user_name }}'
login_password: '{{ user_pass }}'
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:
login_user: '{{ user_name }}'
login_password: '{{ user_pass }}'
name: acme
state: absent

View file

@ -1,3 +0,0 @@
[client]
user={{ user_name }}
password={{ user_pass }}

View file

@ -1,6 +0,0 @@
destructive
shippable/posix/group3
skip/aix
skip/osx
skip/freebsd
skip/rhel

View file

@ -1,6 +0,0 @@
root_user: root
db_name: data
test_db: testdb
test_table1: test1
test_table2: test2
test_script_path: /tmp/test.sql

View file

@ -1,2 +0,0 @@
dependencies:
- setup_mysql8

View file

@ -1,5 +0,0 @@
# mysql_query module initial CI tests
- import_tasks: mysql_query_initial.yml
when:
- ansible_distribution == 'CentOS'
- ansible_distribution_major_version >= '7'

View file

@ -1,249 +0,0 @@
# Test code for mysql_query module
# 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_unix_socket: '{{ mysql_socket }}'
login_user: '{{ root_user }}'
login_password: '{{ root_pass }}'
block:
- name: Create db {{ test_db }}
mysql_query:
<<: *mysql_params
query: 'CREATE DATABASE {{ test_db }}'
register: result
- assert:
that:
- result is changed
- result.executed_queries == ['CREATE DATABASE {{ test_db }}']
- name: Create {{ test_table1 }}
mysql_query:
<<: *mysql_params
login_db: '{{ test_db }}'
query: 'CREATE TABLE {{ test_table1 }} (id int)'
register: result
- assert:
that:
- 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)'
single_transaction: yes
register: result
- 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)']
- name: Check data in {{ test_table1 }}
mysql_query:
<<: *mysql_params
login_db: '{{ test_db }}'
query: 'SELECT * FROM {{ test_table1 }}'
register: result
- 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
- name: Check data in {{ test_table1 }} using positional args
mysql_query:
<<: *mysql_params
login_db: '{{ test_db }}'
query: 'SELECT * FROM {{ test_table1 }} WHERE id = %s'
positional_args:
- 1
register: result
- 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
- name: Check data in {{ test_table1 }} using named args
mysql_query:
<<: *mysql_params
login_db: '{{ test_db }}'
query: 'SELECT * FROM {{ test_table1 }} WHERE id = %(some_id)s'
named_args:
some_id: 1
register: result
- 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
- name: Update data in {{ test_table1 }}
mysql_query:
<<: *mysql_params
login_db: '{{ test_db }}'
query: 'UPDATE {{ test_table1 }} SET id = %(new_id)s WHERE id = %(current_id)s'
named_args:
current_id: 1
new_id: 0
register: result
- assert:
that:
- 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:
<<: *mysql_params
login_db: '{{ test_db }}'
query: 'SELECT * FROM {{ test_table1 }} WHERE id = %(some_id)s'
named_args:
some_id: 1
register: result
- assert:
that:
- 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:
<<: *mysql_params
login_db: '{{ test_db }}'
query: 'SELECT * FROM {{ test_table1 }} WHERE id = %(some_id)s'
named_args:
some_id: 0
register: result
- assert:
that:
- 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:
<<: *mysql_params
login_db: '{{ test_db }}'
query: 'UPDATE {{ test_table1 }} SET id = %(new_id)s WHERE id = %(current_id)s'
named_args:
current_id: 1
new_id: 0
register: result
- assert:
that:
- 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:
<<: *mysql_params
login_db: '{{ test_db }}'
query:
- 'DELETE FROM {{ test_table1 }} WHERE id = 0'
- 'SELECT * FROM {{ test_table1 }} WHERE id = 0'
register: result
- 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]
- name: Delete data from {{ test_table1 }} again
mysql_query:
<<: *mysql_params
login_db: '{{ test_db }}'
query: 'DELETE FROM {{ test_table1 }} WHERE id = 0'
register: result
- assert:
that:
- 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 }}'
register: result
- assert:
that:
- result is changed
- result.executed_queries == ['TRUNCATE {{ test_table1 }}', 'SELECT * FROM {{ test_table1 }}']
- result.rowcount == [0, 0]
- name: Rename {{ test_table1 }}
mysql_query:
<<: *mysql_params
login_db: '{{ test_db }}'
query: 'RENAME TABLE {{ test_table1 }} TO {{ test_table2 }}'
register: result
- assert:
that:
- result is changed
- result.executed_queries == ['RENAME TABLE {{ test_table1 }} TO {{ test_table2 }}']
- result.rowcount == [0]
- name: Check the prev rename
mysql_query:
<<: *mysql_params
login_db: '{{ test_db }}'
query: 'SELECT * FROM {{ test_table1 }}'
register: result
ignore_errors: yes
- assert:
that:
- result.failed == true
- name: Check the prev rename
mysql_query:
<<: *mysql_params
login_db: '{{ test_db }}'
query: 'SELECT * FROM {{ test_table2 }}'
register: result
- assert:
that:
- result.rowcount == [0]
- name: Drop db {{ test_db }}
mysql_query:
<<: *mysql_params
query: 'DROP DATABASE {{ test_db }}'
register: result
- assert:
that:
- result is changed
- result.executed_queries == ['DROP DATABASE {{ test_db }}']

View file

@ -1,8 +0,0 @@
destructive
shippable/posix/group4
# Make sure that this test runs in a different group than mariadb_replication!
skip/aix
skip/osx
skip/freebsd
skip/rhel
needs/root

View file

@ -1,9 +0,0 @@
master_port: 3306
standby_port: 3307
test_db: test_db
test_table: test_table
test_master_delay: 60
replication_user: replication_user
replication_pass: replication_pass
dump_path: /tmp/dump.sql
test_channel: test_channel-1

View file

@ -1,3 +0,0 @@
---
dependencies:
- setup_mysql_replication

View file

@ -1,26 +0,0 @@
# 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
when:
- ansible_facts.distribution == 'CentOS'
- ansible_facts.distribution_major_version is version('7', '==')
# Tests of master_delay parameter:
- import_tasks: mysql_replication_master_delay.yml
when:
- ansible_facts.distribution == 'CentOS'
- ansible_facts.distribution_major_version is version('7', '==')
# Tests of channel parameter:
- import_tasks: mysql_replication_channel.yml
when:
- ansible_facts.distribution == 'CentOS'
- ansible_facts.distribution_major_version is version('7', '==')
# Tests of resetmaster mode:
- import_tasks: mysql_replication_resetmaster_mode.yml
when:
- ansible_facts.distribution == 'CentOS'
- ansible_facts.distribution_major_version is version('7', '==')

View file

@ -1,119 +0,0 @@
# 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: 127.0.0.1
login_port: "{{ standby_port }}"
mode: stopslave
- name: Reset slave all
mysql_replication:
login_host: 127.0.0.1
login_port: "{{ standby_port }}"
mode: resetslaveall
# Get master log file and log pos:
- name: Get master status
mysql_replication:
login_host: 127.0.0.1
login_port: "{{ master_port }}"
mode: getmaster
register: master_status
# Test changemaster mode:
- name: Run replication with channel
mysql_replication:
login_host: 127.0.0.1
login_port: "{{ standby_port }}"
mode: changemaster
master_host: 127.0.0.1
master_port: "{{ master_port }}"
master_user: "{{ replication_user }}"
master_password: "{{ replication_pass }}"
master_log_file: "{{ master_status.File }}"
master_log_pos: "{{ master_status.Position }}"
channel: "{{ test_channel }}"
register: result
- assert:
that:
- result is changed
- result.queries == ["CHANGE MASTER TO MASTER_HOST='127.0.0.1',MASTER_USER='replication_user',MASTER_PASSWORD='********',MASTER_PORT=3306,MASTER_LOG_FILE='{{ master_status.File }}',MASTER_LOG_POS={{ master_status.Position }} FOR CHANNEL '{{ test_channel }}'"]
# Test startslave mode:
- name: Start slave with channel
mysql_replication:
login_host: 127.0.0.1
login_port: "{{ standby_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:
login_host: 127.0.0.1
login_port: "{{ standby_port }}"
mode: getslave
channel: '{{ test_channel }}'
register: slave_status
- assert:
that:
- slave_status.Is_Slave == true
- slave_status.Master_Host == '127.0.0.1'
- slave_status.Exec_Master_Log_Pos == master_status.Position
- slave_status.Master_Port == {{ master_port }}
- slave_status.Last_IO_Errno == 0
- slave_status.Last_IO_Error == ''
- slave_status.Channel_Name == '{{ test_channel }}'
- slave_status is not changed
# Test stopslave mode:
- name: Stop slave with channel
mysql_replication:
login_host: 127.0.0.1
login_port: "{{ standby_port }}"
mode: stopslave
channel: '{{ test_channel }}'
register: result
- assert:
that:
- result is changed
- result.queries == ["STOP SLAVE FOR CHANNEL '{{ test_channel }}'"]
# Test reset
- name: Reset slave with channel
mysql_replication:
login_host: 127.0.0.1
login_port: "{{ standby_port }}"
mode: resetslave
channel: '{{ test_channel }}'
register: result
- assert:
that:
- result is changed
- result.queries == ["RESET SLAVE FOR CHANNEL '{{ test_channel }}'"]
# Test reset all
- name: Reset slave all with channel
mysql_replication:
login_host: 127.0.0.1
login_port: "{{ standby_port }}"
mode: resetslaveall
channel: '{{ test_channel }}'
register: result
- assert:
that:
- result is changed
- result.queries == ["RESET SLAVE ALL FOR CHANNEL '{{ test_channel }}'"]

View file

@ -1,191 +0,0 @@
# 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 {{ master_port }} -h 127.0.0.1"
- name: Create test database
mysql_db:
login_host: 127.0.0.1
login_port: '{{ master_port }}'
state: present
name: '{{ test_db }}'
- name: Dump all databases from the master
shell: 'mysqldump -P {{ master_port }} -h 127.0.0.1 --all-databases --master-data=2 > {{ dump_path }}'
- name: Restore the dump to the standby
shell: 'mysql -P {{ standby_port }} -h 127.0.0.1 < {{ dump_path }}'
# Test getmaster mode:
- name: Get master status
mysql_replication:
login_host: 127.0.0.1
login_port: "{{ master_port }}"
mode: getmaster
register: master_status
- assert:
that:
- master_status.Is_Master == true
- master_status.Position != 0
- master_status is not changed
# 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:
login_host: 127.0.0.1
login_port: "{{ standby_port }}"
mode: startslave
fail_on_error: yes
register: result
ignore_errors: yes
- assert:
that:
- result is failed
# 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:
login_host: 127.0.0.1
login_port: "{{ standby_port }}"
mode: startslave
fail_on_error: no
register: result
- assert:
that:
- result is not failed
# 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 slave and fail without propagating it to ansible as previous versions did not fail on error
mysql_replication:
login_host: 127.0.0.1
login_port: "{{ standby_port }}"
mode: startslave
register: result
- assert:
that:
- result is not failed
# 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: Run replication
mysql_replication:
login_host: 127.0.0.1
login_port: "{{ standby_port }}"
mode: changemaster
master_host: 127.0.0.1
master_port: "{{ master_port }}"
master_user: "{{ replication_user }}"
master_password: "{{ replication_pass }}"
master_log_file: "{{ master_status.File }}"
master_log_pos: "{{ master_status.Position }}"
master_ssl_ca: ''
register: result
- assert:
that:
- result is changed
- result.queries == ["CHANGE MASTER TO MASTER_HOST='127.0.0.1',MASTER_USER='replication_user',MASTER_PASSWORD='********',MASTER_PORT=3306,MASTER_LOG_FILE='{{ master_status.File }}',MASTER_LOG_POS={{ master_status.Position }},MASTER_SSL_CA=''"]
# Test startslave mode:
- name: Start slave
mysql_replication:
login_host: 127.0.0.1
login_port: "{{ standby_port }}"
mode: startslave
register: result
- assert:
that:
- result is changed
- result.queries == ["START SLAVE"]
# Test getslave mode:
- name: Get standby status
mysql_replication:
login_host: 127.0.0.1
login_port: "{{ standby_port }}"
mode: getslave
register: slave_status
- assert:
that:
- slave_status.Is_Slave == true
- slave_status.Master_Host == '127.0.0.1'
- slave_status.Exec_Master_Log_Pos == master_status.Position
- slave_status.Master_Port == {{ master_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 -P {{ master_port }} -h 127.0.0.1 {{ test_db }}"
- name: Insert data
shell: >
echo "INSERT INTO {{ test_table }} (id) VALUES (1), (2), (3); FLUSH LOGS;" |
mysql -P {{ master_port }} -h 127.0.0.1 {{ test_db }}
- name: Small pause to be sure the bin log, which was flushed previously, reached the slave
pause:
seconds: 2
# Test master log pos has been changed:
- name: Get standby status
mysql_replication:
login_host: 127.0.0.1
login_port: "{{ standby_port }}"
mode: getslave
register: slave_status
# master_status.Position is not actual and it has been changed by the prev step,
# so slave_status.Exec_Master_Log_Pos must be different:
- assert:
that:
- slave_status.Exec_Master_Log_Pos != master_status.Position
- name: Start slave that is already running
mysql_replication:
login_host: 127.0.0.1
login_port: "{{ standby_port }}"
mode: startslave
fail_on_error: true
register: result
- assert:
that:
- result is not changed
# Test stopslave mode:
- name: Stop slave
mysql_replication:
login_host: 127.0.0.1
login_port: "{{ standby_port }}"
mode: stopslave
register: result
- assert:
that:
- result is changed
- result.queries == ["STOP SLAVE"]
# Test stopslave mode:
- name: Stop slave that is no longer running
mysql_replication:
login_host: 127.0.0.1
login_port: "{{ standby_port }}"
mode: stopslave
fail_on_error: true
register: result
- assert:
that:
- result is not changed

View file

@ -1,44 +0,0 @@
# 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)
# Test master_delay mode:
- name: Run replication
mysql_replication:
login_host: 127.0.0.1
login_port: "{{ standby_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:
login_host: 127.0.0.1
login_port: "{{ standby_port }}"
mode: startslave
register: result
# Check master_delay:
- name: Get standby status
mysql_replication:
login_host: 127.0.0.1
login_port: "{{ standby_port }}"
mode: getslave
register: slave_status
- assert:
that:
- slave_status.SQL_Delay == {{ test_master_delay }}
- slave_status is not changed
# Stop standby for further tests:
- name: Stop slave
mysql_replication:
login_host: 127.0.0.1
login_port: "{{ standby_port }}"
mode: stopslave

View file

@ -1,48 +0,0 @@
# 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: 127.0.0.1
login_port: "{{ standby_port }}"
mode: stopslave
- name: Reset slave all
mysql_replication:
login_host: 127.0.0.1
login_port: "{{ standby_port }}"
mode: resetslaveall
# Get master initial status:
- name: Get master status
mysql_replication:
login_host: 127.0.0.1
login_port: "{{ master_port }}"
mode: getmaster
register: master_initial_status
# Test resetmaster mode:
- name: Reset master
mysql_replication:
login_host: 127.0.0.1
login_port: "{{ master_port }}"
mode: resetmaster
register: result
- assert:
that:
- result is changed
- result.queries == ["RESET MASTER"]
# Get master final status:
- name: Get master status
mysql_replication:
login_host: 127.0.0.1
login_port: "{{ master_port }}"
mode: getmaster
register: master_final_status
- assert:
that:
- master_initial_status.File != master_final_status.File

View file

@ -1,5 +0,0 @@
destructive
shippable/posix/group1
skip/aix
skip/osx
skip/freebsd

View file

@ -1,19 +0,0 @@
---
# defaults file for test_mysql_user
db_name: 'data'
user_name_1: 'db_user1'
user_name_2: 'db_user2'
user_name_3: 'db_user3'
user_password_1: 'gadfFDSdtTU^Sdfuj'
user_password_2: 'jkFKUdfhdso78yi&td'
user_password_3: 'jkFKUdfhdso78yi&tk'
root_password: 'zevuR6oPh7'
db_names:
- clientdb
- employeedb
- providerdb
tmp_dir: '/tmp'

View file

@ -1,8 +0,0 @@
USE foo;
DELIMITER ;;
CREATE FUNCTION `function` () RETURNS tinyint(4)
BEGIN
DECLARE NAME_FOUND tinyint DEFAULT 0;
RETURN NAME_FOUND;
END;;
DELIMITER ;

View file

@ -1,5 +0,0 @@
USE bar;
DELIMITER ;;
CREATE PROCEDURE `procedure` ()
SELECT * FROM bar;;
DELIMITER ;

View file

@ -1,3 +0,0 @@
dependencies:
- setup_mysql_db
- setup_remote_tmp_dir

View file

@ -1,25 +0,0 @@
# test code to assert no mysql user
# (c) 2014, Wayne Rosario <wrosario@ansible.com>
# 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: run command to query for mysql user
command: mysql "-e SELECT User FROM mysql.user where user='{{ user_name }}';"
register: result
- name: assert mysql user is not present
assert: { that: "'{{ user_name }}' not in result.stdout" }

View file

@ -1,34 +0,0 @@
# test code to assert mysql user
# (c) 2014, Wayne Rosario <wrosario@ansible.com>
# 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: run command to query for mysql user
command: mysql "-e SELECT User FROM mysql.user where user='{{ user_name }}';"
register: result
- name: assert mysql user is present
assert: { that: "'{{ user_name }}' in result.stdout" }
- name: run command to show privileges for user (expect privileges in stdout)
command: mysql "-e SHOW GRANTS FOR '{{ user_name }}'@'localhost';"
register: result
when: priv is defined
- name: assert user has giving privileges
assert: { that: "'GRANT {{priv}} ON *.*' in result.stdout" }
when: priv is defined

View file

@ -1,29 +0,0 @@
# test code to create mysql user
# (c) 2014, Wayne Rosario <wrosario@ansible.com>
# 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: create mysql user {{user_name}}
mysql_user:
name: '{{user_name}}'
password: '{{user_password}}'
state: present
login_unix_socket: '{{ mysql_socket }}'
register: result
- name: assert output message mysql user was created
assert: { that: "result.changed == true" }

View file

@ -1,78 +0,0 @@
---
- name: Issue test setup - drop database
mysql_db:
name: "{{ item }}"
state: absent
login_unix_socket: '{{ mysql_socket }}'
loop:
- foo
- bar
- name: Issue test setup - create database
mysql_db:
name: "{{ item }}"
state: present
login_unix_socket: '{{ mysql_socket }}'
loop:
- foo
- bar
- name: Copy SQL scripts to remote
copy:
src: "{{ item }}"
dest: "{{ remote_tmp_dir }}/{{ item | basename }}"
with_items:
- create-function.sql
- create-procedure.sql
- name: Create function for test
shell: "mysql < {{ remote_tmp_dir }}/create-function.sql"
- name: Create procedure for test
shell: "mysql < {{ remote_tmp_dir }}/create-procedure.sql"
- name: Create user with FUNCTION and PROCEDURE privileges
mysql_user:
name: '{{ user_name_2 }}'
password: '{{ user_password_2 }}'
state: present
priv: 'FUNCTION foo.function:EXECUTE/foo.*:SELECT/PROCEDURE bar.procedure:EXECUTE'
login_unix_socket: '{{ mysql_socket }}'
register: result
- name: Assert Create user with FUNCTION and PROCEDURE privileges
assert:
that:
- result is success
- result is changed
- name: Create user with FUNCTION and PROCEDURE privileges - Idempotent check
mysql_user:
name: '{{ user_name_2 }}'
password: '{{ user_password_2 }}'
state: present
priv: 'FUNCTION foo.function:EXECUTE/foo.*:SELECT/PROCEDURE bar.procedure:EXECUTE'
login_unix_socket: '{{ mysql_socket }}'
register: result
- name: Assert Create user with FUNCTION and PROCEDURE privileges
assert:
that:
- result is success
- result is not changed
- name: Remove user
mysql_user:
name: '{{ user_name_2 }}'
state: absent
login_unix_socket: '{{ mysql_socket }}'
- name: Issue test teardown - cleanup databases
mysql_db:
name: "{{ item }}"
state: absent
login_unix_socket: '{{ mysql_socket }}'
loop:
- foo
- bar

View file

@ -1,27 +0,0 @@
---
- name: Set root password
mysql_user:
name: root
password: '{{ root_password }}'
login_user: root
login_password: '{{ root_password }}'
check_implicit_admin: yes
login_unix_socket: '{{ mysql_socket }}'
register: result
- name: assert root password is changed
assert: { that: "result.changed == true" }
- name: Set root password again
mysql_user:
name: root
password: '{{ root_password }}'
login_user: root
login_password: '{{ root_password }}'
check_implicit_admin: yes
login_unix_socket: '{{ mysql_socket }}'
register: result
- name: Assert root password is not changed
assert: { that: "result.changed == false" }

View file

@ -1,222 +0,0 @@
# test code for the mysql_user module
# (c) 2014, Wayne Rosario <wrosario@ansible.com>
# 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 dof 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/>.
# ============================================================
# create mysql user and verify user is added to mysql database
#
- include: create_user.yml user_name={{user_name_1}} user_password={{ user_password_1 }}
- include: resource_limits.yml
- include: assert_user.yml user_name={{user_name_1}}
- include: remove_user.yml user_name={{user_name_1}} user_password={{ user_password_1 }}
- include: assert_no_user.yml user_name={{user_name_1}}
# ============================================================
# Create mysql user that already exist on mysql database
#
- include: create_user.yml user_name={{user_name_1}} user_password={{ user_password_1 }}
- name: create mysql user that already exist (expect changed=false)
mysql_user:
name: '{{user_name_1}}'
password: '{{user_password_1}}'
state: present
login_unix_socket: '{{ mysql_socket }}'
register: result
- name: assert output message mysql user was not created
assert: { that: "result.changed == false" }
# ============================================================
# remove mysql user and verify user is removed from mysql database
#
- name: remove mysql user state=absent (expect changed=true)
mysql_user:
name: '{{ user_name_1 }}'
password: '{{ user_password_1 }}'
state: absent
login_unix_socket: '{{ mysql_socket }}'
register: result
- name: assert output message mysql user was removed
assert: { that: "result.changed == true" }
- include: assert_no_user.yml user_name={{user_name_1}}
# ============================================================
# remove mysql user that does not exist on mysql database
#
- name: remove mysql user that does not exist state=absent (expect changed=false)
mysql_user:
name: '{{ user_name_1 }}'
password: '{{ user_password_1 }}'
state: absent
login_unix_socket: '{{ mysql_socket }}'
register: result
- name: assert output message mysql user that does not exist
assert: { that: "result.changed == false" }
- include: assert_no_user.yml user_name={{user_name_1}}
# ============================================================
# Create user with no privileges and verify default privileges are assign
#
- name: create user with select privilege state=present (expect changed=true)
mysql_user:
name: '{{ user_name_1 }}'
password: '{{ user_password_1 }}'
state: present
login_unix_socket: '{{ mysql_socket }}'
register: result
- include: assert_user.yml user_name={{user_name_1}} priv=USAGE
- include: remove_user.yml user_name={{user_name_1}} user_password={{ user_password_1 }}
- include: assert_no_user.yml user_name={{user_name_1}}
# ============================================================
# Create user with select privileges and verify select privileges are assign
#
- name: create user with select privilege state=present (expect changed=true)
mysql_user:
name: '{{ user_name_2 }}'
password: '{{ user_password_2 }}'
state: present
priv: '*.*:SELECT'
login_unix_socket: '{{ mysql_socket }}'
register: result
- include: assert_user.yml user_name={{user_name_2}} priv=SELECT
- include: remove_user.yml user_name={{user_name_2}} user_password={{ user_password_2 }}
- include: assert_no_user.yml user_name={{user_name_2}}
# ============================================================
# Assert user has access to multiple databases
#
- name: give users access to multiple databases
mysql_user:
name: '{{ item[0] }}'
priv: '{{ item[1] }}.*:ALL'
append_privs: yes
password: '{{ user_password_1 }}'
login_unix_socket: '{{ mysql_socket }}'
with_nested:
- [ '{{ user_name_1 }}', '{{ user_name_2 }}']
- "{{db_names}}"
- name: show grants access for user1 on multiple database
command: mysql "-e SHOW GRANTS FOR '{{ user_name_1 }}'@'localhost';"
register: result
- name: assert grant access for user1 on multiple database
assert: { that: "'{{ item }}' in result.stdout" }
with_items: "{{db_names}}"
- name: show grants access for user2 on multiple database
command: mysql "-e SHOW GRANTS FOR '{{ user_name_2 }}'@'localhost';"
register: result
- name: assert grant access for user2 on multiple database
assert: { that: "'{{ item }}' in result.stdout" }
with_items: "{{db_names}}"
- include: remove_user.yml user_name={{user_name_1}} user_password={{ user_password_1 }}
- include: remove_user.yml user_name={{user_name_2}} user_password={{ user_password_1 }}
- name: give user access to database via wildcard
mysql_user:
name: '{{ user_name_1 }}'
priv: '%db.*:SELECT'
append_privs: yes
password: '{{ user_password_1 }}'
login_unix_socket: '{{ mysql_socket }}'
- name: show grants access for user1 on multiple database
command: mysql "-e SHOW GRANTS FOR '{{ user_name_1 }}'@'localhost';"
register: result
- name: assert grant access for user1 on multiple database
assert:
that:
- "'%db' in result.stdout"
- "'SELECT' in result.stdout"
- name: change user access to database via wildcard
mysql_user:
name: '{{ user_name_1 }}'
priv: '%db.*:INSERT'
append_privs: yes
password: '{{ user_password_1 }}'
login_unix_socket: '{{ mysql_socket }}'
- name: show grants access for user1 on multiple database
command: mysql "-e SHOW GRANTS FOR '{{ user_name_1 }}'@'localhost';"
register: result
- name: assert grant access for user1 on multiple database
assert:
that:
- "'%db' in result.stdout"
- "'INSERT' in result.stdout"
- include: remove_user.yml user_name={{user_name_1}} user_password={{ user_password_1 }}
# ============================================================
# Update user password for a user.
# Assert the user password is updated and old password can no longer be used.
#
#- include: user_password_update_test.yml
# ============================================================
# Assert create user with SELECT privileges, attempt to create database and update privileges to create database
#
- include: test_privs.yml current_privilege=SELECT current_append_privs=no
# ============================================================
# Assert creating user with SELECT privileges, attempt to create database and append privileges to create database
#
- include: test_privs.yml current_privilege=DROP current_append_privs=yes
# ============================================================
# Assert create user with SELECT privileges, attempt to create database and update privileges to create database
#
- include: test_privs.yml current_privilege='UPDATE,ALTER' current_append_privs=no
# ============================================================
# Assert creating user with SELECT privileges, attempt to create database and append privileges to create database
#
- include: test_privs.yml current_privilege='INSERT,DELETE' current_append_privs=yes
# Tests for the priv parameter with dict value (https://github.com/ansible/ansible/issues/57533)
- include: test_priv_dict.yml
- import_tasks: issue-29511.yaml
tags:
- issue-29511
- import_tasks: issue-64560.yaml
tags:
- issue-64560

View file

@ -1,59 +0,0 @@
# test code to remove mysql user
# (c) 2014, Wayne Rosario <wrosario@ansible.com>
# 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: remove mysql user {{user_name}}
mysql_user:
name: '{{user_name}}'
password: '{{user_password}}'
state: absent
login_unix_socket: '{{ mysql_socket }}'
register: result
- name: assert output message mysql user was removed
assert: { that: "result.changed == true" }
# ============================================================
- name: create blank mysql user to be removed later
mysql_user:
name: ""
state: present
password: 'KJFDY&D*Sfuydsgf'
login_unix_socket: '{{ mysql_socket }}'
- name: remove blank mysql user with hosts=all (expect changed)
mysql_user:
user: ""
host_all: true
state: absent
login_unix_socket: '{{ mysql_socket }}'
register: result
- name: assert changed is true for removing all blank users
assert: { that: "result.changed == true" }
- name: remove blank mysql user with hosts=all (expect ok)
mysql_user:
user: ""
host_all: true
state: absent
login_unix_socket: '{{ mysql_socket }}'
register: result
- name: assert changed is true for removing all blank users
assert: { that: "result.changed == false" }

View file

@ -1,112 +0,0 @@
# test code for resource_limits parameter
- block:
- name: Drop mysql user {{ user_name_1 }} if exists
mysql_user:
name: '{{ user_name_1 }}'
state: absent
login_unix_socket: '{{ mysql_socket }}'
- name: Create mysql user {{ user_name_1 }} with resource limits in check_mode
mysql_user:
name: '{{ user_name_1 }}'
password: '{{ user_password_1 }}'
state: present
login_unix_socket: '{{ mysql_socket }}'
resource_limits:
MAX_QUERIES_PER_HOUR: 10
MAX_CONNECTIONS_PER_HOUR: 5
check_mode: yes
register: result
- assert:
that:
- result is changed
- name: Create mysql user {{ user_name_1 }} with resource limits in actual mode
mysql_user:
name: '{{ user_name_1 }}'
password: '{{ user_password_1 }}'
state: present
login_unix_socket: '{{ mysql_socket }}'
resource_limits:
MAX_QUERIES_PER_HOUR: 10
MAX_CONNECTIONS_PER_HOUR: 5
register: result
- assert:
that:
- result is changed
- name: Check
mysql_query:
query: >
SELECT User FROM mysql.user WHERE User = '{{ user_name_1 }}' AND Host = 'localhost'
AND max_questions = 10 AND max_connections = 5
login_unix_socket: '{{ mysql_socket }}'
register: result
- assert:
that:
- result.rowcount[0] == 1
- name: Try to set the same limits again in check mode
mysql_user:
name: '{{ user_name_1 }}'
password: '{{ user_password_1 }}'
state: present
login_unix_socket: '{{ mysql_socket }}'
resource_limits:
MAX_QUERIES_PER_HOUR: 10
MAX_CONNECTIONS_PER_HOUR: 5
check_mode: yes
register: result
- assert:
that:
- result is not changed
- name: Try to set the same limits again in actual mode
mysql_user:
name: '{{ user_name_1 }}'
password: '{{ user_password_1 }}'
state: present
login_unix_socket: '{{ mysql_socket }}'
resource_limits:
MAX_QUERIES_PER_HOUR: 10
MAX_CONNECTIONS_PER_HOUR: 5
register: result
- assert:
that:
- result is not changed
- name: Change limits
mysql_user:
name: '{{ user_name_1 }}'
password: '{{ user_password_1 }}'
state: present
login_unix_socket: '{{ mysql_socket }}'
resource_limits:
MAX_QUERIES_PER_HOUR: 5
MAX_CONNECTIONS_PER_HOUR: 5
register: result
- assert:
that:
- result is changed
- name: Check
mysql_query:
query: >
SELECT User FROM mysql.user WHERE User = '{{ user_name_1 }}' AND Host = 'localhost'
AND max_questions = 5 AND max_connections = 5
login_unix_socket: '{{ mysql_socket }}'
register: result
- assert:
that:
- result.rowcount[0] == 1
when: (ansible_distribution == 'Ubuntu' and ansible_distribution_major_version >= '18') or (ansible_distribution == 'CentOS' and ansible_distribution_major_version >= '8')

View file

@ -1,46 +0,0 @@
# Tests for priv parameter value passed as a dict
- name: Create test databases
mysql_db:
name: '{{ item }}'
state: present
login_unix_socket: '{{ mysql_socket }}'
loop:
- data1
- data2
- name: Create user with privileges
mysql_user:
name: '{{ user_name_3 }}'
password: '{{ user_password_3 }}'
priv:
"data1.*": "SELECT"
"data2.*": "SELECT"
state: present
login_unix_socket: '{{ mysql_socket }}'
- name: Run command to show privileges for user (expect privileges in stdout)
command: mysql "-e SHOW GRANTS FOR '{{ user_name_3 }}'@'localhost';"
register: result
- name: Assert user has giving privileges
assert:
that:
- "'GRANT SELECT ON `data1`.*' in result.stdout"
- "'GRANT SELECT ON `data2`.*' in result.stdout"
##########
# Clean up
- name: Drop test databases
mysql_db:
name: '{{ item }}'
state: present
login_unix_socket: '{{ mysql_socket }}'
loop:
- data1
- data2
- name: Drop test user
mysql_user:
name: '{{ user_name_3 }}'
state: absent
login_unix_socket: '{{ mysql_socket }}'

View file

@ -1,155 +0,0 @@
# test code for privileges for mysql_user module
# (c) 2014, Wayne Rosario <wrosario@ansible.com>
# 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: create user with basic select privileges
mysql_user:
name: '{{ user_name_2 }}'
password: '{{ user_password_2 }}'
priv: '*.*:SELECT'
state: present
login_unix_socket: '{{ mysql_socket }}'
when: current_append_privs == "yes"
- include: assert_user.yml user_name={{user_name_2}} priv='SELECT'
when: current_append_privs == "yes"
- name: create user with current privileges (expect changed=true)
mysql_user:
name: '{{ user_name_2 }}'
password: '{{ user_password_2 }}'
priv: '*.*:{{current_privilege}}'
append_privs: '{{current_append_privs}}'
state: present
login_unix_socket: '{{ mysql_socket }}'
register: result
- name: assert output message for current privileges
assert: { that: "result.changed == true" }
- name: run command to show privileges for user (expect privileges in stdout)
command: mysql "-e SHOW GRANTS FOR '{{user_name_2}}'@'localhost';"
register: result
- name: assert user has correct privileges
assert: { that: "'GRANT {{current_privilege | replace(',', ', ')}} ON *.*' in result.stdout" }
when: current_append_privs == "no"
- name: assert user has correct privileges
assert: { that: "'GRANT SELECT, {{current_privilege | replace(',', ', ')}} ON *.*' in result.stdout" }
when: current_append_privs == "yes"
- name: create database using user current privileges
mysql_db:
name: '{{ db_name }}'
state: present
login_user: '{{ user_name_2 }}'
login_password: '{{ user_password_2 }}'
ignore_errors: true
- name: run command to test that database was not created
command: mysql "-e show databases like '{{ db_name }}';"
register: result
- name: assert database was not created
assert: { that: "'{{ db_name }}' not in result.stdout" }
# ============================================================
- name: Add privs to a specific table (expect changed)
mysql_user:
name: '{{ user_name_2 }}'
password: '{{ user_password_2 }}'
priv: 'jmainguy.jmainguy:ALL'
state: present
login_unix_socket: '{{ mysql_socket }}'
register: result
- name: Assert that priv changed
assert: { that: "result.changed == true" }
- name: Add privs to a specific table (expect ok)
mysql_user:
name: '{{ user_name_2 }}'
password: '{{ user_password_2 }}'
priv: 'jmainguy.jmainguy:ALL'
state: present
login_unix_socket: '{{ mysql_socket }}'
register: result
- name: Assert that priv did not change
assert: { that: "result.changed == false" }
# ============================================================
- name: update user with all privileges
mysql_user:
name: '{{ user_name_2 }}'
password: '{{ user_password_2 }}'
priv: '*.*:ALL'
state: present
login_unix_socket: '{{ mysql_socket }}'
- include: assert_user.yml user_name={{user_name_2}} priv='ALL PRIVILEGES'
- name: create database using user
mysql_db:
name: '{{ db_name }}'
state: present
login_user: '{{ user_name_2 }}'
login_password: '{{ user_password_2 }}'
- name: run command to test database was created using user new privileges
command: mysql "-e SHOW CREATE DATABASE {{ db_name }};"
- name: drop database using user
mysql_db:
name: '{{ db_name }}'
state: absent
login_user: '{{ user_name_2 }}'
login_password: '{{ user_password_2 }}'
# ============================================================
- name: update user with a long privileges list (mysql has a special multiline grant output)
mysql_user:
name: '{{ user_name_2 }}'
password: '{{ user_password_2 }}'
priv: '*.*:CREATE USER,FILE,PROCESS,RELOAD,REPLICATION CLIENT,REPLICATION SLAVE,SHOW DATABASES,SHUTDOWN,SUPER,CREATE,DROP,EVENT,LOCK TABLES,INSERT,UPDATE,DELETE,SELECT,SHOW VIEW,GRANT'
state: present
login_unix_socket: '{{ mysql_socket }}'
register: result
- name: Assert that priv changed
assert: { that: "result.changed == true" }
- name: Test idempotency (expect ok)
mysql_user:
name: '{{ user_name_2 }}'
password: '{{ user_password_2 }}'
priv: '*.*:CREATE USER,FILE,PROCESS,RELOAD,REPLICATION CLIENT,REPLICATION SLAVE,SHOW DATABASES,SHUTDOWN,SUPER,CREATE,DROP,EVENT,LOCK TABLES,INSERT,UPDATE,DELETE,SELECT,SHOW VIEW,GRANT'
state: present
login_unix_socket: '{{ mysql_socket }}'
register: result
- name: Assert that priv did not change
assert: { that: "result.changed == false" }
- name: remove username
mysql_user:
name: '{{ user_name_2 }}'
password: '{{ user_password_2 }}'
state: absent
login_unix_socket: '{{ mysql_socket }}'

View file

@ -1,155 +0,0 @@
# test code update password for the mysql_user module
# (c) 2014, Wayne Rosario <wrosario@ansible.com>
# 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 dof 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/>.
# ============================================================
# Update user password for a user.
# Assert the user password is updated and old password can no longer be used.
#
- name: create user1 state=present with a password
mysql_user:
name: '{{ user_name_1 }}'
password: '{{ user_password_1 }}'
priv: '*.*:ALL'
state: present
login_unix_socket: '{{ mysql_socket }}'
- name: create user2 state=present with a password
mysql_user:
name: '{{ user_name_2 }}'
password: '{{ user_password_2 }}'
priv: '*.*:ALL'
state: present
login_unix_socket: '{{ mysql_socket }}'
- name: store user2 grants with old password (mysql 5.7.6 and newer)
command: mysql "-e SHOW CREATE USER '{{ user_name_2 }}'@'localhost';"
register: user_password_old_create
ignore_errors: yes
- name: store user2 grants with old password (mysql 5.7.5 and older)
command: mysql "-e SHOW GRANTS FOR '{{ user_name_2 }}'@'localhost';"
register: user_password_old
when: user_password_old_create is failed
- name: update user2 state=present with same password (expect changed=false)
mysql_user:
name: '{{ user_name_2 }}'
password: '{{ user_password_2 }}'
priv: '*.*:ALL'
state: present
login_unix_socket: '{{ mysql_socket }}'
register: result
- name: assert output user2 was not updated
assert: { that: "result.changed == false" }
- include: assert_user.yml user_name={{user_name_2}} priv='ALL PRIVILEGES'
- name: update user2 state=present with a new password (expect changed=true)
mysql_user:
name: '{{ user_name_2 }}'
password: '{{ user_password_1 }}'
state: present
login_unix_socket: '{{ mysql_socket }}'
register: result
- include: assert_user.yml user_name={{user_name_2}} priv='ALL PRIVILEGES'
- name: store user2 grants with old password (mysql 5.7.6 and newer)
command: mysql "-e SHOW CREATE USER '{{ user_name_2 }}'@'localhost';"
register: user_password_new_create
ignore_errors: yes
- name: store user2 grants with new password
command: mysql "-e SHOW GRANTS FOR '{{ user_name_2 }}'@'localhost';"
register: user_password_new
when: user_password_new_create is failed
- name: assert output message password was update for user2 (mysql 5.7.6 and newer)
assert: { that: "user_password_old_create.stdout != user_password_new_create.stdout" }
when: user_password_new_create is not failed
- name: assert output message password was update for user2 (mysql 5.7.5 and older)
assert: { that: "user_password_old.stdout != user_password_new.stdout" }
when: user_password_new_create is failed
- name: create database using user2 and old password
mysql_db:
name: '{{ db_name }}'
state: present
login_user: '{{ user_name_2 }}'
login_password: '{{ user_password_2 }}'
ignore_errors: true
register: result
- debug: var=result.msg
- name: assert output message that database not create with old password
assert:
that:
- "result.failed == true"
- name: create database using user2 and new password
mysql_db:
name: '{{ db_name }}'
state: present
login_user: '{{ user_name_2 }}'
login_password: '{{ user_password_1 }}'
register: result
- name: assert output message that database is created with new password
assert: { that: "result.changed == true" }
- name: remove database
mysql_db:
name: '{{ db_name }}'
state: absent
login_unix_socket: '{{ mysql_socket }}'
- include: remove_user.yml user_name={{user_name_1}} user_password={{ user_password_1 }}
- include: remove_user.yml user_name={{user_name_2}} user_password={{ user_password_1 }}
- name: Create user with Fdt8fd^34ds using hash. (expect changed=true)
mysql_user:
name: jmainguy
password: '*0cb5b86f23fdc24db19a29b8854eb860cbc47793'
encrypted: yes
login_unix_socket: '{{ mysql_socket }}'
register: encrypt_result
- name: Check that the module made a change
assert:
that:
- "encrypt_result.changed == True"
- name: See if the password needs to be updated. (expect changed=false)
mysql_user:
name: jmainguy
password: 'Fdt8fd^34ds'
login_unix_socket: '{{ mysql_socket }}'
register: plain_result
- name: Check that the module did not change the password
assert:
that:
- "plain_result.changed == False"
- name: Remove user (cleanup)
mysql_user:
name: jmainguy
state: absent

View file

@ -1,6 +0,0 @@
destructive
shippable/posix/group3
skip/aix
skip/osx
skip/freebsd
skip/rhel

View file

@ -1,4 +0,0 @@
---
# defaults file for test_mysql_variables
user: 'user1'
password: 'khH&DYigjg1#'

View file

@ -1,2 +0,0 @@
dependencies:
- setup_mysql8

View file

@ -1,25 +0,0 @@
# test code to assert message in mysql_variables module
# (c) 2014, Wayne Rosario <wrosario@ansible.com>
# 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/>.
# ============================================================
# Assert message failure and confirm failed=true
#
- name: assert message failure (expect failed=true)
assert:
that:
- "output.failed == true"

View file

@ -1,34 +0,0 @@
# test code to assert variables in mysql_variables module
# (c) 2014, Wayne Rosario <wrosario@ansible.com>
# 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/>.
# ============================================================
# Assert mysql variable name and value from mysql database
#
- name: assert output message changed value
assert: { that: "output.changed == {{changed}}" }
- name: run mysql command to show variable
command: "mysql -uroot -p{{ root_pass }} \"-e show variables like '{{var_name}}';\""
register: result
- name: assert output mysql variable name and value
assert:
that:
- "result.changed == true"
- "'{{var_name}}' in result.stdout"
- "'{{var_value}}' in result.stdout"

View file

@ -1,38 +0,0 @@
# test code to assert variables in mysql_variables module
# (c) 2014, Wayne Rosario <wrosario@ansible.com>
# 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/>.
# ============================================================
# Assert output variable name/value match mysql variable name/value
#
- name: assert output message changed value
assert: { that: "output.changed == {{changed}}" }
- set_fact:
key_name: "{{var_name}}"
key_value: "{{output.msg[0][0]}}"
- name: run mysql command to show variable
command: "mysql -uroot -p{{ root_pass }} \"-e show variables like '{{var_name}}';\""
register: result
- name: assert output variable info match mysql variable info
assert:
that:
- "result.changed == true"
- "key_name in result.stdout"
- "key_value in result.stdout"

View file

@ -1,2 +0,0 @@
- import_tasks: mysql_variables.yml
when: ansible_distribution == 'CentOS' and ansible_distribution_major_version >= '7'

View file

@ -1,412 +0,0 @@
# test code for the mysql_variables module
# (c) 2014, Wayne Rosario <wrosario@ansible.com>
# 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/>.
# ============================================================
# Verify mysql_variable successfully queries a variable
#
- set_fact: set_name='version'
- name: read mysql variables (expect changed=false)
mysql_variables:
variable: '{{set_name}}'
login_unix_socket: '{{ mysql_socket }}'
login_user: root
login_password: '{{ root_pass }}'
register: result
- include: assert_var_output.yml changed=false output={{result}} var_name={{set_name}}
# ============================================================
# Verify mysql_variable successfully updates a variable (issue:4568)
#
- set_fact:
set_name: 'delay_key_write'
set_value: 'ON'
- name: set mysql variable
mysql_variables:
variable: '{{set_name}}'
value: '{{set_value}}'
login_user: root
login_password: '{{ root_pass }}'
login_unix_socket: '{{ mysql_socket }}'
- name: update mysql variable to same value (expect changed=false)
mysql_variables:
variable: '{{set_name}}'
value: '{{set_value}}'
login_unix_socket: '{{ mysql_socket }}'
login_user: root
login_password: '{{ root_pass }}'
register: result
- include: assert_var.yml changed=false output={{result}} var_name={{set_name}} var_value={{set_value}}
# ============================================================
# Verify mysql_variable successfully updates a variable using single quotes
#
- set_fact:
set_name: 'wait_timeout'
set_value: '300'
- name: set mysql variable to a temp value
mysql_variables:
variable: '{{set_name}}'
value: '200'
login_unix_socket: '{{ mysql_socket }}'
login_user: root
login_password: '{{ root_pass }}'
- name: update mysql variable value (expect changed=true)
mysql_variables:
variable: '{{set_name}}'
value: '{{set_value}}'
login_unix_socket: '{{ mysql_socket }}'
login_user: root
login_password: '{{ root_pass }}'
register: result
- assert:
that:
- result.queries == ["SET GLOBAL `{{ set_name }}` = {{ set_value }}"]
- include: assert_var.yml changed=true output={{result}} var_name={{set_name}} var_value='{{set_value}}'
# ============================================================
# Verify mysql_variable successfully updates a variable using double quotes
#
- set_fact:
set_name: "wait_timeout"
set_value: "400"
- name: set mysql variable to a temp value
mysql_variables:
variable: '{{set_name}}'
value: "200"
login_unix_socket: '{{ mysql_socket }}'
login_user: root
login_password: '{{ root_pass }}'
- name: update mysql variable value (expect changed=true)
mysql_variables:
variable: '{{set_name}}'
value: '{{set_value}}'
login_unix_socket: '{{ mysql_socket }}'
login_user: root
login_password: '{{ root_pass }}'
register: result
- include: assert_var.yml changed=true output={{result}} var_name={{set_name}} var_value='{{set_value}}'
# ============================================================
# Verify mysql_variable successfully updates a variable using no quotes
#
- set_fact:
set_name: wait_timeout
set_value: 500
- name: set mysql variable to a temp value
mysql_variables:
variable: '{{set_name}}'
value: 200
login_unix_socket: '{{ mysql_socket }}'
login_user: root
login_password: '{{ root_pass }}'
- name: update mysql variable value (expect changed=true)
mysql_variables:
variable: '{{set_name}}'
value: '{{set_value}}'
login_unix_socket: '{{ mysql_socket }}'
login_user: root
login_password: '{{ root_pass }}'
register: result
- include: assert_var.yml changed=true output={{result}} var_name={{set_name}} var_value='{{set_value}}'
# ============================================================
# Verify mysql_variable successfully updates a variable using an expression (e.g. 1024*4)
#
- name: set mysql variable value to an expression
mysql_variables:
variable: max_tmp_tables
value: "1024*4"
login_unix_socket: '{{ mysql_socket }}'
login_user: root
login_password: '{{ root_pass }}'
register: result
ignore_errors: true
- include: assert_fail_msg.yml output={{result}} msg='Incorrect argument type to variable'
# ============================================================
# Verify mysql_variable fails when setting an incorrect value (out of range)
#
- name: set mysql variable value to a number out of range
mysql_variables:
variable: max_tmp_tables
value: -1
login_unix_socket: '{{ mysql_socket }}'
login_user: root
login_password: '{{ root_pass }}'
register: result
ignore_errors: true
- include: assert_fail_msg.yml output={{result}} msg='Truncated incorrect'
# ============================================================
# Verify mysql_variable fails when setting an incorrect value (incorrect type)
#
- name: set mysql variable value to a non-valid value number
mysql_variables:
variable: max_tmp_tables
value: TEST
login_unix_socket: '{{ mysql_socket }}'
login_user: root
login_password: '{{ root_pass }}'
register: result
ignore_errors: true
- include: assert_fail_msg.yml output={{result}} msg='Incorrect argument type to variable'
# ============================================================
# Verify mysql_variable fails when setting an unknown variable
#
- name: set a non mysql variable
mysql_variables:
variable: my_sql_variable
value: ON
login_unix_socket: '{{ mysql_socket }}'
login_user: root
login_password: '{{ root_pass }}'
register: result
ignore_errors: true
- include: assert_fail_msg.yml output={{result}} msg='Variable not available'
# ============================================================
# Verify mysql_variable fails when setting a read-only variable
#
- name: set value of a read only mysql variable
mysql_variables:
variable: character_set_system
value: utf16
login_unix_socket: '{{ mysql_socket }}'
login_user: root
login_password: '{{ root_pass }}'
register: result
ignore_errors: true
- include: assert_fail_msg.yml output={{result}} msg='read only variable'
#=============================================================
# Verify mysql_variable works with the login_user and login_password parameters
#
- name: create mysql user
mysql_user:
name: '{{user}}'
password: '{{password}}'
state: present
priv: '*.*:ALL'
login_unix_socket: '{{ mysql_socket }}'
login_user: root
login_password: '{{ root_pass }}'
- set_fact:
set_name: wait_timeout
set_value: 77
- name: query mysql_variable using login_user and password_password
mysql_variables:
variable: '{{set_name}}'
login_user: '{{user}}'
login_password: '{{password}}'
register: result
- include: assert_var_output.yml changed=false output={{result}} var_name={{set_name}}
- name: set mysql variable to temp value using user login and password (expect changed=true)
mysql_variables:
variable: '{{set_name}}'
value: 20
login_user: '{{user}}'
login_password: '{{password}}'
register: result
- name: update mysql variable value using user login and password (expect changed=true)
mysql_variables:
variable: '{{set_name}}'
value: '{{set_value}}'
login_user: '{{user}}'
login_password: '{{password}}'
register: result
- include: assert_var.yml changed=true output={{result}} var_name={{set_name}} var_value='{{set_value}}'
#============================================================
# Verify mysql_variable fails with an incorrect login_password parameter
#
- set_fact:
set_name: connect_timeout
set_value: 10
- name: query mysql_variable using incorrect login_password
mysql_variables:
variable: '{{set_name}}'
login_user: '{{user}}'
login_password: 'wrongpassword'
register: result
ignore_errors: true
- include: assert_fail_msg.yml output={{result}} msg='unable to connect to database'
- name: update mysql variable value using incorrect login_password (expect failed=true)
mysql_variables:
variable: '{{set_name}}'
value: '{{set_value}}'
login_user: '{{user}}'
login_password: 'this is an incorrect password'
register: result
ignore_errors: true
- include: assert_fail_msg.yml output={{result}} msg='unable to connect to database'
#============================================================
# Verify mysql_variable fails with an incorrect login_host parameter
#
- name: query mysql_variable using incorrect login_host
mysql_variables:
variable: wait_timeout
login_host: '12.0.0.9'
login_user: '{{user}}'
login_password: '{{password}}'
connect_timeout: 5
register: result
ignore_errors: true
- include: assert_fail_msg.yml output={{result}} msg='unable to connect to database'
- name: remove mysql_user {{user}}
mysql_user:
name: '{{user}}'
state: absent
login_unix_socket: '{{ mysql_socket }}'
login_user: root
login_password: '{{ root_pass }}'
#=========================================
# Check mode 'persist' and 'persist_only':
#
- name: update mysql variable value (expect changed=true) in persist mode
mysql_variables:
variable: '{{ set_name }}'
value: '{{ set_value }}'
login_unix_socket: '{{ mysql_socket }}'
login_user: root
login_password: '{{ root_pass }}'
mode: persist
register: result
- assert:
that:
- result.queries == ["SET PERSIST `{{ set_name }}` = {{ set_value }}"]
- include: assert_var.yml changed=true output={{result}} var_name={{set_name}} var_value='{{set_value}}'
- name: try to update mysql variable value (expect changed=false) in persist mode again
mysql_variables:
variable: '{{ set_name }}'
value: '{{ set_value }}'
login_unix_socket: '{{ mysql_socket }}'
login_user: root
login_password: '{{ root_pass }}'
mode: persist
register: result
- include: assert_var.yml changed=false output={{result}} var_name={{set_name}} var_value='{{set_value}}'
- name: set mysql variable to a temp value
mysql_variables:
variable: '{{ set_name }}'
value: '200'
login_unix_socket: '{{ mysql_socket }}'
login_user: root
login_password: '{{ root_pass }}'
mode: persist
- name: update mysql variable value (expect changed=true) in persist_only mode
mysql_variables:
variable: '{{ set_name }}'
value: '{{ set_value }}'
login_unix_socket: '{{ mysql_socket }}'
login_user: root
login_password: '{{ root_pass }}'
mode: persist_only
register: result
- assert:
that:
- result is changed
- result.queries == ["SET PERSIST_ONLY `{{ set_name }}` = {{ set_value }}"]
- name: try to update mysql variable value (expect changed=false) in persist_only mode again
mysql_variables:
variable: '{{ set_name }}'
value: '{{ set_value }}'
login_unix_socket: '{{ mysql_socket }}'
login_user: root
login_password: '{{ root_pass }}'
mode: persist_only
register: result
- assert:
that:
- result is not changed
- set_fact:
set_name: max_connections
set_value: 105
def_val: 151
- name: update mysql variable value (expect changed=true) in persist_only mode
mysql_variables:
variable: '{{ set_name }}'
value: '{{ set_value }}'
login_unix_socket: '{{ mysql_socket }}'
login_user: root
login_password: '{{ root_pass }}'
mode: persist_only
register: result
- include: assert_var.yml changed=true output={{result}} var_name={{set_name}} var_value='{{def_val}}'
# Bugfix of https://github.com/ansible/ansible/issues/54239
- name: set variable containing dot
mysql_variables:
login_unix_socket: '{{ mysql_socket }}'
login_user: root
login_password: '{{ root_pass }}'
variable: validate_password.policy
value: LOW
mode: persist_only
register: result
- assert:
that:
- result is changed
- result.queries == ["SET PERSIST_ONLY `validate_password`.`policy` = LOW"]

View file

@ -1,21 +0,0 @@
repo_link: https://repo.mysql.com/mysql80-community-release-el{{ ansible_facts.distribution_major_version }}.rpm
my_cnf: /etc/my.cnf
mysql_data_dirs:
- /var/lib/mysql
- /usr/mysql
mysql_support_packages:
- MySQL-python
mysql_server_packages:
- mysql-community-server
mysql_cleanup_packages:
- mysql-community-client
- mysql-community-common
- mysql-community-libs
- mysql-community-libs-compat
- mysql-community-server
- mysql80-community-release
- python3-PyMySQL

View file

@ -1,7 +0,0 @@
[mysqld]
datadir=/var/lib/mysql
socket=/var/lib/mysql/mysql.sock
log-error=/var/log/mysqld.log
pid-file=/var/run/mysqld/mysqld.pid
default_authentication_plugin=mysql_native_password
skip-grant-tables

View file

@ -1,29 +0,0 @@
- name: stop mysql service
service:
name: mysqld
state: stopped
listen: cleanup mysql8
- name: remove repo
# yum:
# name: mysql80-community-release
# state: absent
# Work around for a bug in the dnf module. Use the module once that gets fixed.
# https://github.com/ansible/ansible/issues/64294
command: "{{ ansible_facts.pkg_mgr}} -y erase mysql80-community-release"
args:
warn: no
listen: cleanup mysql8
- name: remove mysql packages
yum:
name: '{{ mysql_support_packages | union(mysql_server_packages) | union(mysql_cleanup_packages) }}'
state: absent
listen: cleanup mysql8
- name: remove mysql data
file:
path: "{{ item }}"
state: absent
loop: "{{ mysql_data_dirs }}"
listen: cleanup mysql8

View file

@ -1,18 +0,0 @@
# 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)
# Setup MySQL 8:
- name: Include distribution specific variables
include_vars: "{{ lookup('first_found', params) }}"
vars:
params:
files:
- '{{ ansible_facts.distribution }}-{{ ansible_facts.distribution_major_version }}.yml'
- '{{ ansible_facts.os_family }}.yml'
- 'default.yml'
paths: vars
- import_tasks: setup_mysql8.yml
when:
- ansible_facts.distribution == 'CentOS'
- ansible_facts.distribution_major_version is version_compare('7', '>=')

View file

@ -1,71 +0,0 @@
# 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)
- name: Install PyMySQL package via pip
pip:
name: PyMySQL
state: present
- name: Install MySQL repo
yum:
name: '{{ repo_link }}'
notify: cleanup mysql8
# These packages come from AppStream in RHEL 8, so they need to be done in a separate task
- name: Install MySQL support packages
yum:
name: "{{ mysql_support_packages }}"
notify: cleanup mysql8
- name: Install MySQL community server
yum:
name: '{{ mysql_server_packages }}'
disablerepo: '{{ mysql_disablerepo | default(omit) }}'
notify: cleanup mysql8
- name: Copy my.cnf
copy:
src: my.cnf
dest: '{{ my_cnf }}'
- name: Start MySQL
service:
name: mysqld
state: started
### Debug #######################
#- name: Debug
# shell: cat /var/log/mysqld.log
#################################
- name: Check connection to the server
shell: 'echo "SHOW DATABASES;" | mysql'
- name: Check connection to the server
shell: "echo \"SHOW VARIABLES LIKE '%version%';\" | mysql"
- name: Detect socket path
shell: 'echo "show variables like ''socket''\G" | mysql | grep ''Value: '' | sed ''s/[ ]\+Value: //'''
register: _socket_path
- name: Set socket path
set_fact:
mysql_socket: '{{ _socket_path["stdout"] }}'
- name: Set root pass
set_fact:
root_pass: "dlsafjlkjdsaK1#"
- name: Set root password
shell: 'echo "flush privileges; ALTER USER ''root''@''localhost'' IDENTIFIED WITH mysql_native_password BY ''{{ root_pass }}'';" | mysql'
- name: Change configuration
lineinfile:
path: '{{ my_cnf }}'
line: skip-grant-tables
state: absent
- name: Restart MySQL
service:
name: mysqld
state: restarted

View file

@ -1,4 +0,0 @@
mysql_support_packages:
- python3-PyMySQL
mysql_disablerepo: AppStream

View file

@ -1,3 +0,0 @@
mysql_data_dirs:
- /var/lib/mysql
- /usr/share/mysql

View file

@ -1,4 +0,0 @@
mysql_support_packages:
- python3-PyMySQL
mysql_disablerepo: rhel-8-for-x86_64-appstream-rpms

View file

@ -1,18 +0,0 @@
mysql_service: mysqld
mysql_packages:
- mysql-server
- MySQL-python
- bzip2
mysql_cleanup_packages:
- mysql-community-client
- mysql-community-common
- mysql-community-libs
- mysql-community-libs-compat
- mysql-community-server
- mysql80-community-release
mysql_data_dirs:
- /var/lib/mysql
- /usr/mysql

View file

@ -1,25 +0,0 @@
- name: stop mysql service
service:
name: "{{ mysql_service }}"
state: stopped
listen: cleanup mysql
- name: remove mysql packages
action: '{{ ansible_facts.pkg_mgr }}'
args:
name: "{{ item }}"
state: absent
loop: "{{ mysql_packages | union(mysql_cleanup_packages) }}"
listen: cleanup mysql
- name: remove mysql data
file:
path: "{{ item }}"
state: absent
loop: "{{ mysql_data_dirs }}"
listen: cleanup mysql
- name: remove pip packages
pip:
name: mysql-python
state: absent

View file

@ -1,2 +0,0 @@
dependencies:
- setup_pkg_mgr

View file

@ -1,112 +0,0 @@
# setup code for the mysql_db module
# (c) 2014, Wayne Rosario <wrosario@ansible.com>
# 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: python 2
set_fact:
python_suffix: ""
when: ansible_python_version is version('3', '<')
- name: python 3
set_fact:
python_suffix: "-py3"
when: ansible_python_version is version('3', '>=')
- name: Include distribution specific variables
include_vars: "{{ lookup('first_found', params) }}"
vars:
params:
files:
- '{{ ansible_facts.distribution }}-{{ ansible_facts.distribution_major_version }}{{ python_suffix }}.yml'
- '{{ ansible_facts.distribution }}-{{ ansible_facts.distribution_major_version }}.yml'
- '{{ ansible_facts.os_family }}-{{ ansible_facts.distribution_major_version }}{{ python_suffix }}.yml'
- '{{ ansible_facts.os_family }}-{{ ansible_facts.distribution_major_version }}.yml'
- '{{ ansible_facts.distribution }}{{ python_suffix }}.yml'
- '{{ ansible_facts.os_family }}{{ python_suffix }}.yml'
- 'default{{ python_suffix }}.yml'
paths: "{{ role_path }}/vars"
- name: install mysqldb_test rpm dependencies
yum:
name: "{{ mysql_packages }}"
state: latest
when: ansible_facts.pkg_mgr == 'yum'
notify: cleanup mysql
- name: install mysqldb_test rpm dependencies
dnf:
name: '{{ mysql_packages }}'
state: latest
install_weak_deps: False # mariadb-server has a weak dep on python2 which break Python 3 test environments
when: ansible_facts.pkg_mgr == 'dnf'
notify: cleanup mysql
- name: install mysqldb_test debian dependencies
apt:
name: "{{ mysql_packages }}"
state: latest
when: ansible_facts.pkg_mgr == 'apt'
notify: cleanup mysql
- name: install mysqldb_test opensuse dependencies
zypper:
name: "{{ mysql_packages }}"
state: latest
when: ansible_facts.pkg_mgr in ['zypper', 'community.general.zypper']
notify: cleanup mysql
- name: install mysqldb_test FreeBSD dependencies
package:
name: "{{ mysql_packages }}"
state: present
when: ansible_os_family == "FreeBSD"
notify: cleanup mysql
- name: install mysql-python package via pip (FreeBSD)
pip:
name: mysql-python
state: present
when: ansible_os_family == "FreeBSD"
notify:
- cleanup mysql
- remove pip packages
- name: enable mysql-server service (FreeBSD)
lineinfile:
path: /etc/rc.conf
line: 'mysql_server_enable="YES"'
when: ansible_os_family == "FreeBSD"
- name: apply work-around for OverlayFS issue
# https://github.com/docker/for-linux/issues/72#issuecomment-319904698
command: find {{ mysql_data_dirs[0] }} -type f -exec touch {} ;
# find will fail if mysql has never been started, as the directory won't exist
ignore_errors: yes
- name: restart mysql_db service
service:
name: "{{ mysql_service }}"
state: restarted
- name: Detect socket path
shell: 'echo "show variables like ''socket''\G" | mysql | grep ''Value: '' | sed ''s/[ ]\+Value: //'''
register: _socket_path
- name: Set socket path
set_fact:
mysql_socket: '{{ _socket_path["stdout"] }}'

View file

@ -1,16 +0,0 @@
mysql_service: 'mysql'
mysql_packages:
- mysql-server
- python-mysqldb
- bzip2
mysql_cleanup_packages:
- mysql-client*
- mysql-server*
- mysql-common
- mysql-sandbox
mysql_data_dirs:
- /var/lib/mysql
- /usr/share/mysql

View file

@ -1,6 +0,0 @@
mysql_service: 'mariadb'
mysql_packages:
- mariadb-server
- python3-PyMySQL
- bzip2

View file

@ -1,6 +0,0 @@
mysql_service: 'mariadb'
mysql_packages:
- mariadb-server
- python-PyMySQL
- bzip2

View file

@ -1,5 +0,0 @@
mysql_service: 'mysql-server'
mysql_packages:
- mariadb101-server
- py-pymysql

View file

@ -1,6 +0,0 @@
mysql_service: 'mariadb'
mysql_packages:
- mariadb-server
- MySQL-python
- bzip2

View file

@ -1,6 +0,0 @@
mysql_service: 'mariadb'
mysql_packages:
- mariadb-server
- python3-PyMySQL
- bzip2

View file

@ -1,6 +0,0 @@
mysql_service: 'mysqld'
mysql_packages:
- mysql-server
- MySQL-python
- bzip2

View file

@ -1,6 +0,0 @@
mysql_service: 'mysql'
mysql_packages:
- mariadb
- python3-PyMySQL
- bzip2

View file

@ -1,6 +0,0 @@
mysql_service: 'mysql'
mysql_packages:
- mariadb
- python-PyMySQL
- bzip2

View file

@ -1,16 +0,0 @@
mysql_service: 'mysql'
mysql_packages:
- mysql-server
- python3-pymysql
- bzip2
mysql_cleanup_packages:
- mysql-client*
- mysql-server*
- mysql-common
- mysql-sandbox
mysql_data_dirs:
- /var/lib/mysql
- /usr/share/mysql

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