mirror of
https://github.com/ansible-collections/community.mysql.git
synced 2025-04-01 08:10:32 -07:00
initial commit (#1)
* initial commit * removed remaining references to community.general * enabled integration pipeline * switched from preconfigured replication topology to simple multinode install * updated version from 1.0.0 to 0.1.0
This commit is contained in:
parent
9fcbbaad81
commit
c26bc095ad
89 changed files with 8774 additions and 135 deletions
94
.github/workflows/ansible-test-plugins.yml
vendored
Normal file
94
.github/workflows/ansible-test-plugins.yml
vendored
Normal file
|
@ -0,0 +1,94 @@
|
|||
name: Plugins CI
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'plugins/**'
|
||||
- 'tests/**'
|
||||
- '.github/workflows/ansible-test.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'plugins/**'
|
||||
- 'tests/**'
|
||||
- '.github/workflows/ansible-test.yml'
|
||||
schedule:
|
||||
- cron: '0 6 * * *'
|
||||
|
||||
|
||||
env:
|
||||
mysql_version_file: "./ansible_collections/community/mysql/tests/integration/targets/setup_mysql/defaults/main.yml"
|
||||
|
||||
jobs:
|
||||
sanity:
|
||||
name: "Sanity (Python: ${{ matrix.python }}, Ansible: ${{ matrix.ansible }})"
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
ansible:
|
||||
- stable-2.9
|
||||
- stable-2.10
|
||||
- devel
|
||||
python:
|
||||
- 2.7
|
||||
- 3.8
|
||||
steps:
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
path: ansible_collections/community/mysql
|
||||
|
||||
- name: Set up Python ${{ matrix.python }}
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: ${{ matrix.python }}
|
||||
|
||||
- name: Install ansible-base (${{ matrix.ansible }})
|
||||
run: pip install https://github.com/ansible/ansible/archive/${{ matrix.ansible }}.tar.gz --disable-pip-version-check
|
||||
|
||||
- name: Run sanity tests
|
||||
run: ansible-test sanity --docker -v --color
|
||||
working-directory: ./ansible_collections/community/mysql
|
||||
|
||||
integration:
|
||||
name: "Integration (Python: ${{ matrix.python }}, Ansible: ${{ matrix.ansible }}, MySQL: ${{ matrix.mysql }})"
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
mysql:
|
||||
- 8.0.20
|
||||
ansible:
|
||||
- stable-2.9
|
||||
- stable-2.10
|
||||
- devel
|
||||
python:
|
||||
- 3.6
|
||||
steps:
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
path: ansible_collections/community/mysql
|
||||
|
||||
- name: Set up Python ${{ matrix.python }}
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: ${{ matrix.python }}
|
||||
|
||||
- name: Install ansible-base (${{ matrix.ansible }})
|
||||
run: pip install https://github.com/ansible/ansible/archive/${{ matrix.ansible }}.tar.gz --disable-pip-version-check
|
||||
|
||||
- name: Set MySQL version (${{ matrix.mysql }})
|
||||
run: "sed -i 's/^mysql_version:.*/mysql_version: \"${{ matrix.mysql }}\"/g' ${{ env.mysql_version_file }}"
|
||||
|
||||
- name: Run integration tests
|
||||
run: ansible-test integration --docker -v --color --retry-on-error --continue-on-error --python ${{ matrix.python }} --diff --coverage
|
||||
working-directory: ./ansible_collections/community/mysql
|
||||
|
||||
- name: Generate coverage report.
|
||||
run: ansible-test coverage xml -v --requirements --group-by command --group-by version
|
||||
working-directory: ./ansible_collections/community/mysql
|
||||
|
||||
- uses: codecov/codecov-action@v1
|
||||
with:
|
||||
fail_ci_if_error: false
|
56
.github/workflows/ansible-test-roles.yml
vendored
Normal file
56
.github/workflows/ansible-test-roles.yml
vendored
Normal file
|
@ -0,0 +1,56 @@
|
|||
name: Roles CI
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'roles/**'
|
||||
- '.github/workflows/ansible-test-roles.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'roles/**'
|
||||
- '.github/workflows/ansible-test-roles.yml'
|
||||
schedule:
|
||||
- cron: '0 6 * * *'
|
||||
|
||||
jobs:
|
||||
molecule:
|
||||
name: "Molecule (Python: ${{ matrix.python }}, Ansible: ${{ matrix.ansible }}, MySQL: ${{ matrix.mysql }})"
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
PY_COLORS: 1
|
||||
ANSIBLE_FORCE_COLOR: 1
|
||||
strategy:
|
||||
matrix:
|
||||
mysql:
|
||||
- 2.0.12
|
||||
ansible:
|
||||
- stable-2.9
|
||||
### it looks like there's errors for 2.10+ with ansible-lint (https://github.com/ansible/ansible-lint/pull/878)
|
||||
### and molecule (_maybe_ relating to https://github.com/ansible-community/molecule/pull/2547)
|
||||
# - stable-2.10
|
||||
# - devel
|
||||
python:
|
||||
- 2.7
|
||||
- 3.8
|
||||
|
||||
steps:
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
path: ansible_collections/community/mysql
|
||||
|
||||
- name: Set up Python ${{ matrix.python }}
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: ${{ matrix.python }}
|
||||
|
||||
- name: Install ansible-base (${{ matrix.ansible }})
|
||||
run: pip install https://github.com/ansible/ansible/archive/${{ matrix.ansible }}.tar.gz --disable-pip-version-check
|
||||
|
||||
- name: Install molecule and related dependencies
|
||||
run: |
|
||||
pip install ansible-lint docker flake8 molecule testinfra yamllint
|
||||
|
||||
# - name: Run molecule default test scenario
|
||||
# run: for d in roles/*/; do (cd "$d" && molecule --version && molecule test) done
|
||||
# working-directory: ./ansible_collections/community/mysql
|
93
.github/workflows/ansible-test.yml
vendored
93
.github/workflows/ansible-test.yml
vendored
|
@ -1,93 +0,0 @@
|
|||
name: CI
|
||||
on:
|
||||
- pull_request
|
||||
|
||||
jobs:
|
||||
sanity:
|
||||
name: Sanity (${{ matrix.ansible }})
|
||||
strategy:
|
||||
matrix:
|
||||
ansible:
|
||||
- stable-2.10
|
||||
- devel
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v1
|
||||
with:
|
||||
# FIXME ansible_collections/NAMESPACE/COLLECTION
|
||||
path: ansible_collections/community/FIXME
|
||||
|
||||
- name: Set up Python 3.6
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: 3.6
|
||||
|
||||
- name: Install ansible-base (${{ matrix.ansible }})
|
||||
run: pip install https://github.com/ansible/ansible/archive/${{ matrix.ansible }}.tar.gz --disable-pip-version-check
|
||||
|
||||
- name: Run sanity tests
|
||||
run: ansible-test sanity --docker -v --color --python 3.6
|
||||
|
||||
# If you don't have unit tests, you can delete this section
|
||||
units:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v1
|
||||
with:
|
||||
# FIXME ansible_collections/NAMESPACE/COLLECTION
|
||||
path: ansible_collections/community/grafana
|
||||
|
||||
- name: Set up Python 3.6
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: 3.6
|
||||
|
||||
- name: Install ansible-base (devel)
|
||||
run: pip install https://github.com/ansible/ansible/archive/devel.tar.gz --disable-pip-version-check
|
||||
|
||||
- name: Run unit tests
|
||||
run: ansible-test units --docker -v --color --python 3.6 --coverage
|
||||
|
||||
- name: Generate coverage report.
|
||||
run: ansible-test coverage xml -v --requirements --group-by command --group-by version
|
||||
|
||||
- uses: codecov/codecov-action@v1
|
||||
with:
|
||||
fail_ci_if_error: false
|
||||
|
||||
# If you don't have unit tests, you can delete this section
|
||||
# Though you should consider creating some
|
||||
|
||||
integration:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python_version: ["3.6"]
|
||||
container:
|
||||
image: python:${{ matrix.python_version }}-alpine
|
||||
steps:
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v1
|
||||
with:
|
||||
# FIXME ansible_collections/NAMESPACE/COLLECTION
|
||||
path: ansible_collections/community/FIXME
|
||||
|
||||
- name: Install ansible-base (stable-2.10)
|
||||
run: pip install https://github.com/ansible/ansible/archive/stable-2.10.tar.gz --disable-pip-version-check
|
||||
|
||||
|
||||
- name: Run integration tests on Python ${{ matrix.python_version }}
|
||||
run: ansible-test integration --docker -v --color --retry-on-error --python ${{ matrix.python_version }} --continue-on-error --diff --coverage
|
||||
|
||||
- name: Generate coverage report.
|
||||
run: ansible-test coverage xml -v --requirements --group-by command --group-by version
|
||||
# FIXME ansible_collections/NAMESPACE/COLLECTION
|
||||
working-directory: ./ansible_collections/community/FIXME
|
||||
|
||||
- uses: codecov/codecov-action@v1
|
||||
with:
|
||||
fail_ci_if_error: false
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -129,3 +129,6 @@ dmypy.json
|
|||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# MacOS
|
||||
.DS_Store
|
||||
|
|
81
README.md
81
README.md
|
@ -1,54 +1,51 @@
|
|||
# collection_template
|
||||
You can build a new repository for an Ansible Collection using this template by following [Creating a repository from a template](https://help.github.com/en/github/creating-cloning-and-archiving-repositories/creating-a-repository-from-a-template). This README.md contains recommended headings for your collection README.md, with comments describing what each section should contain. Once you have created your collection repository, delete this paragraph and the title above it from your README.md.
|
||||
|
||||
# Foo Collection
|
||||
<!-- Add CI and code coverage badges here. Samples included below. -->
|
||||
[](https://github.com/ansible-collections/REPONAMEHERE/actions) [](https://codecov.io/gh/ansible-collections/REPONAMEHERE)
|
||||
|
||||
<!-- Describe the collection and why a user would want to use it. What does the collection do? -->
|
||||
|
||||
## Tested with Ansible
|
||||
|
||||
<!-- List the versions of Ansible the collection has been tested with. Must match what is in galaxy.yml. -->
|
||||
|
||||
## External requirements
|
||||
|
||||
<!-- List any external resources the collection depends on, for example minimum versions of an OS, libraries, or utilities. Do not list other Ansible collections here. -->
|
||||
|
||||
### Supported connections
|
||||
<!-- Optional. If your collection supports only specific connection types (such as HTTPAPI, netconf, or others), list them here. -->
|
||||
# MySQL collection for Ansible
|
||||
[](https://github.com/ansible-collections/community.mysql/actions?query=workflow%3A"Plugins+CI") [](https://github.com/ansible-collections/community.mysql/actions?query=workflow%3A"Roles+CI") [](https://codecov.io/gh/ansible-collections/community.mysql)
|
||||
|
||||
## Included content
|
||||
|
||||
<!-- Galaxy will eventually list the module docs within the UI, but until that is ready, you may need to either describe your plugins etc here, or point to an external docsite to cover that information. -->
|
||||
- **Modules**:
|
||||
- [mysql_db](https://docs.ansible.com/ansible/latest/modules/mysql_db_module.html)
|
||||
- [mysql_info](https://docs.ansible.com/ansible/latest/modules/mysql_info_module.html)
|
||||
- [mysql_query](https://docs.ansible.com/ansible/latest/modules/mysql_query_module.html)
|
||||
- [mysql_replication](https://docs.ansible.com/ansible/latest/modules/mysql_replication_module.html)
|
||||
- [mysql_user](https://docs.ansible.com/ansible/latest/modules/mysql_user_module.html)
|
||||
- [mysql_variables](https://docs.ansible.com/ansible/latest/modules/mysql_variables_module.html)
|
||||
|
||||
## Tested with Ansible
|
||||
|
||||
- 2.9
|
||||
- 2.10
|
||||
- devel
|
||||
|
||||
## External requirements
|
||||
|
||||
The MySQL modules rely on a MySQL connector. The list of supported drivers is below:
|
||||
|
||||
- [PyMySQL](https://github.com/PyMySQL/PyMySQL)
|
||||
- [MySQLdb](https://github.com/PyMySQL/mysqlclient-python)
|
||||
- Support for other Python MySQL connectors may be added in a future release.
|
||||
|
||||
## Using this collection
|
||||
|
||||
<!--Include some quick examples that cover the most common use cases for your collection content. -->
|
||||
### Installing the Collection from Ansible Galaxy
|
||||
|
||||
Before using the MySQL collection, you need to install it with the Ansible Galaxy CLI:
|
||||
|
||||
```bash
|
||||
ansible-galaxy collection install community.mysql
|
||||
```
|
||||
|
||||
You can also include it in a `requirements.yml` file and install it via `ansible-galaxy collection install -r requirements.yml`, using the format:
|
||||
|
||||
```yaml
|
||||
---
|
||||
collections:
|
||||
- name: community.mysql
|
||||
version: v0.1.0
|
||||
```
|
||||
|
||||
See [Ansible Using collections](https://docs.ansible.com/ansible/latest/user_guide/collections_using.html) for more details.
|
||||
|
||||
## Contributing to this collection
|
||||
|
||||
<!--Describe how the community can contribute to your collection. At a minimum, include how and where users can create issues to report problems or request features for this collection. List contribution requirements, including preferred workflows and necessary testing, so you can benefit from community PRs. If you are following general Ansible contributor guidelines, you can link to - [Ansible Community Guide](https://docs.ansible.com/ansible/latest/community/index.html). -->
|
||||
|
||||
|
||||
## Release notes
|
||||
<!--Add a link to a changelog.md file or an external docsite to cover this information. -->
|
||||
|
||||
## Roadmap
|
||||
|
||||
<!-- Optional. Include the roadmap for this collection, and the proposed release/versioning strategy so users can anticipate the upgrade/update cycle. -->
|
||||
|
||||
## More information
|
||||
|
||||
<!-- List out where the user can find additional information, such as working group meeting times, slack/IRC channels, or documentation for the product this collection automates. At a minimum, link to: -->
|
||||
|
||||
- [Ansible Collection overview](https://github.com/ansible-collections/overview)
|
||||
- [Ansible User guide](https://docs.ansible.com/ansible/latest/user_guide/index.html)
|
||||
- [Ansible Developer guide](https://docs.ansible.com/ansible/latest/dev_guide/index.html)
|
||||
- [Ansible Community code of conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html)
|
||||
|
||||
## Licensing
|
||||
|
||||
<!-- Include the appropriate license information here and a pointer to the full licensing details. If the collection contains modules migrated from the ansible/ansible repo, you must use the same license that existed in the ansible/ansible repo. See the GNU license example below. -->
|
||||
|
|
29
changelogs/config.yaml
Normal file
29
changelogs/config.yaml
Normal file
|
@ -0,0 +1,29 @@
|
|||
changelog_filename_template: CHANGELOG.rst
|
||||
changelog_filename_version_depth: 0
|
||||
changes_file: changelog.yaml
|
||||
changes_format: combined
|
||||
keep_fragments: false
|
||||
mention_ancestor: true
|
||||
new_plugins_after_name: removed_features
|
||||
notesdir: fragments
|
||||
prelude_section_name: release_summary
|
||||
prelude_section_title: Release Summary
|
||||
sections:
|
||||
- - major_changes
|
||||
- Major Changes
|
||||
- - minor_changes
|
||||
- Minor Changes
|
||||
- - breaking_changes
|
||||
- Breaking Changes / Porting Guide
|
||||
- - deprecated_features
|
||||
- Deprecated Features
|
||||
- - removed_features
|
||||
- Removed Features (previously deprecated)
|
||||
- - security_fixes
|
||||
- Security Fixes
|
||||
- - bugfixes
|
||||
- Bugfixes
|
||||
- - known_issues
|
||||
- Known Issues
|
||||
title: Community.Mysql
|
||||
trivial_section_name: trivial
|
2
codecov.yml
Normal file
2
codecov.yml
Normal file
|
@ -0,0 +1,2 @@
|
|||
fixes:
|
||||
- "/ansible_collections/community/mysql/::"
|
16
galaxy.yml
Normal file
16
galaxy.yml
Normal file
|
@ -0,0 +1,16 @@
|
|||
namespace: community
|
||||
name: mysql
|
||||
version: 0.1.0
|
||||
readme: README.md
|
||||
authors:
|
||||
- MySQL Working Group (https://github.com/ansible/community/wiki/MySQL)
|
||||
description: MySQL collection for Ansible
|
||||
license_file: COPYING
|
||||
tags:
|
||||
- database
|
||||
- mysql
|
||||
- mariadb
|
||||
repository: https://github.com/ansible-collections/community.mysql
|
||||
documentation: https://github.com/ansible-collections/community.mysql
|
||||
homepage: https://github.com/ansible-collections/community.mysql
|
||||
issues: https://github.com/ansible-collections/community.mysql/issues
|
2
meta/runtime.yml
Normal file
2
meta/runtime.yml
Normal file
|
@ -0,0 +1,2 @@
|
|||
---
|
||||
requires_ansible: '>=2.9.10'
|
31
plugins/README.md
Normal file
31
plugins/README.md
Normal file
|
@ -0,0 +1,31 @@
|
|||
# Collections Plugins Directory
|
||||
|
||||
This directory can be used to ship various plugins inside an Ansible collection. Each plugin is placed in a folder that
|
||||
is named after the type of plugin it is in. It can also include the `module_utils` and `modules` directory that
|
||||
would contain module utils and modules respectively.
|
||||
|
||||
Here is an example directory of the majority of plugins currently supported by Ansible:
|
||||
|
||||
```
|
||||
└── plugins
|
||||
├── action
|
||||
├── become
|
||||
├── cache
|
||||
├── callback
|
||||
├── cliconf
|
||||
├── connection
|
||||
├── filter
|
||||
├── httpapi
|
||||
├── inventory
|
||||
├── lookup
|
||||
├── module_utils
|
||||
├── modules
|
||||
├── netconf
|
||||
├── shell
|
||||
├── strategy
|
||||
├── terminal
|
||||
├── test
|
||||
└── vars
|
||||
```
|
||||
|
||||
A full list of plugin types can be found at [Working With Plugins](https://docs.ansible.com/ansible/2.9/plugins/plugins.html).
|
82
plugins/doc_fragments/mysql.py
Normal file
82
plugins/doc_fragments/mysql.py
Normal file
|
@ -0,0 +1,82 @@
|
|||
# -*- 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``.
|
||||
'''
|
189
plugins/module_utils/database.py
Normal file
189
plugins/module_utils/database.py
Normal file
|
@ -0,0 +1,189 @@
|
|||
# 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) 2014, Toshio Kuratomi <tkuratomi@ansible.com>
|
||||
#
|
||||
# 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 re
|
||||
|
||||
|
||||
# Input patterns for is_input_dangerous function:
|
||||
#
|
||||
# 1. '"' in string and '--' in string or
|
||||
# "'" in string and '--' in string
|
||||
PATTERN_1 = re.compile(r'(\'|\").*--')
|
||||
|
||||
# 2. union \ intersect \ except + select
|
||||
PATTERN_2 = re.compile(r'(UNION|INTERSECT|EXCEPT).*SELECT', re.IGNORECASE)
|
||||
|
||||
# 3. ';' and any KEY_WORDS
|
||||
PATTERN_3 = re.compile(r';.*(SELECT|UPDATE|INSERT|DELETE|DROP|TRUNCATE|ALTER)', re.IGNORECASE)
|
||||
|
||||
|
||||
class SQLParseError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class UnclosedQuoteError(SQLParseError):
|
||||
pass
|
||||
|
||||
|
||||
# maps a type of identifier to the maximum number of dot levels that are
|
||||
# allowed to specify that identifier. For example, a database column can be
|
||||
# specified by up to 4 levels: database.schema.table.column
|
||||
_PG_IDENTIFIER_TO_DOT_LEVEL = dict(
|
||||
database=1,
|
||||
schema=2,
|
||||
table=3,
|
||||
column=4,
|
||||
role=1,
|
||||
tablespace=1,
|
||||
sequence=3,
|
||||
publication=1,
|
||||
)
|
||||
_MYSQL_IDENTIFIER_TO_DOT_LEVEL = dict(database=1, table=2, column=3, role=1, vars=1)
|
||||
|
||||
|
||||
def _find_end_quote(identifier, quote_char):
|
||||
accumulate = 0
|
||||
while True:
|
||||
try:
|
||||
quote = identifier.index(quote_char)
|
||||
except ValueError:
|
||||
raise UnclosedQuoteError
|
||||
accumulate = accumulate + quote
|
||||
try:
|
||||
next_char = identifier[quote + 1]
|
||||
except IndexError:
|
||||
return accumulate
|
||||
if next_char == quote_char:
|
||||
try:
|
||||
identifier = identifier[quote + 2:]
|
||||
accumulate = accumulate + 2
|
||||
except IndexError:
|
||||
raise UnclosedQuoteError
|
||||
else:
|
||||
return accumulate
|
||||
|
||||
|
||||
def _identifier_parse(identifier, quote_char):
|
||||
if not identifier:
|
||||
raise SQLParseError('Identifier name unspecified or unquoted trailing dot')
|
||||
|
||||
already_quoted = False
|
||||
if identifier.startswith(quote_char):
|
||||
already_quoted = True
|
||||
try:
|
||||
end_quote = _find_end_quote(identifier[1:], quote_char=quote_char) + 1
|
||||
except UnclosedQuoteError:
|
||||
already_quoted = False
|
||||
else:
|
||||
if end_quote < len(identifier) - 1:
|
||||
if identifier[end_quote + 1] == '.':
|
||||
dot = end_quote + 1
|
||||
first_identifier = identifier[:dot]
|
||||
next_identifier = identifier[dot + 1:]
|
||||
further_identifiers = _identifier_parse(next_identifier, quote_char)
|
||||
further_identifiers.insert(0, first_identifier)
|
||||
else:
|
||||
raise SQLParseError('User escaped identifiers must escape extra quotes')
|
||||
else:
|
||||
further_identifiers = [identifier]
|
||||
|
||||
if not already_quoted:
|
||||
try:
|
||||
dot = identifier.index('.')
|
||||
except ValueError:
|
||||
identifier = identifier.replace(quote_char, quote_char * 2)
|
||||
identifier = ''.join((quote_char, identifier, quote_char))
|
||||
further_identifiers = [identifier]
|
||||
else:
|
||||
if dot == 0 or dot >= len(identifier) - 1:
|
||||
identifier = identifier.replace(quote_char, quote_char * 2)
|
||||
identifier = ''.join((quote_char, identifier, quote_char))
|
||||
further_identifiers = [identifier]
|
||||
else:
|
||||
first_identifier = identifier[:dot]
|
||||
next_identifier = identifier[dot + 1:]
|
||||
further_identifiers = _identifier_parse(next_identifier, quote_char)
|
||||
first_identifier = first_identifier.replace(quote_char, quote_char * 2)
|
||||
first_identifier = ''.join((quote_char, first_identifier, quote_char))
|
||||
further_identifiers.insert(0, first_identifier)
|
||||
|
||||
return further_identifiers
|
||||
|
||||
|
||||
def pg_quote_identifier(identifier, id_type):
|
||||
identifier_fragments = _identifier_parse(identifier, quote_char='"')
|
||||
if len(identifier_fragments) > _PG_IDENTIFIER_TO_DOT_LEVEL[id_type]:
|
||||
raise SQLParseError('PostgreSQL does not support %s with more than %i dots' % (id_type, _PG_IDENTIFIER_TO_DOT_LEVEL[id_type]))
|
||||
return '.'.join(identifier_fragments)
|
||||
|
||||
|
||||
def mysql_quote_identifier(identifier, id_type):
|
||||
identifier_fragments = _identifier_parse(identifier, quote_char='`')
|
||||
if (len(identifier_fragments) - 1) > _MYSQL_IDENTIFIER_TO_DOT_LEVEL[id_type]:
|
||||
raise SQLParseError('MySQL does not support %s with more than %i dots' % (id_type, _MYSQL_IDENTIFIER_TO_DOT_LEVEL[id_type]))
|
||||
|
||||
special_cased_fragments = []
|
||||
for fragment in identifier_fragments:
|
||||
if fragment == '`*`':
|
||||
special_cased_fragments.append('*')
|
||||
else:
|
||||
special_cased_fragments.append(fragment)
|
||||
|
||||
return '.'.join(special_cased_fragments)
|
||||
|
||||
|
||||
def is_input_dangerous(string):
|
||||
"""Check if the passed string is potentially dangerous.
|
||||
Can be used to prevent SQL injections.
|
||||
|
||||
Note: use this function only when you can't use
|
||||
psycopg2's cursor.execute method parametrized
|
||||
(typically with DDL queries).
|
||||
"""
|
||||
if not string:
|
||||
return False
|
||||
|
||||
for pattern in (PATTERN_1, PATTERN_2, PATTERN_3):
|
||||
if re.search(pattern, string):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def check_input(module, *args):
|
||||
"""Wrapper for is_input_dangerous function."""
|
||||
needs_to_check = args
|
||||
|
||||
dangerous_elements = []
|
||||
|
||||
for elem in needs_to_check:
|
||||
if isinstance(elem, str):
|
||||
if is_input_dangerous(elem):
|
||||
dangerous_elements.append(elem)
|
||||
|
||||
elif isinstance(elem, list):
|
||||
for e in elem:
|
||||
if is_input_dangerous(e):
|
||||
dangerous_elements.append(e)
|
||||
|
||||
elif elem is None or isinstance(elem, bool):
|
||||
pass
|
||||
|
||||
else:
|
||||
elem = str(elem)
|
||||
if is_input_dangerous(elem):
|
||||
dangerous_elements.append(elem)
|
||||
|
||||
if dangerous_elements:
|
||||
module.fail_json(msg="Passed input '%s' is "
|
||||
"potentially dangerous" % ', '.join(dangerous_elements))
|
110
plugins/module_utils/mysql.py
Normal file
110
plugins/module_utils/mysql.py
Normal file
|
@ -0,0 +1,110 @@
|
|||
# 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']),
|
||||
)
|
727
plugins/modules/mysql_db.py
Normal file
727
plugins/modules/mysql_db.py
Normal file
|
@ -0,0 +1,727 @@
|
|||
#!/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.mysql.mysql_info
|
||||
- module: community.mysql.mysql_variables
|
||||
- module: community.mysql.mysql_user
|
||||
- module: community.mysql.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.mysql.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.mysql.plugins.module_utils.database import mysql_quote_identifier
|
||||
from ansible_collections.community.mysql.plugins.module_utils.mysql import mysql_connect, mysql_driver, mysql_driver_fail_msg
|
||||
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()
|
543
plugins/modules/mysql_info.py
Normal file
543
plugins/modules/mysql_info.py
Normal file
|
@ -0,0 +1,543 @@
|
|||
#!/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.mysql.mysql_variables
|
||||
- module: community.mysql.mysql_db
|
||||
- module: community.mysql.mysql_user
|
||||
- module: community.mysql.mysql_replication
|
||||
|
||||
author:
|
||||
- Andrew Klychkov (@Andersson007)
|
||||
|
||||
extends_documentation_fragment:
|
||||
- community.mysql.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.mysql.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()
|
233
plugins/modules/mysql_query.py
Normal file
233
plugins/modules/mysql_query.py
Normal file
|
@ -0,0 +1,233 @@
|
|||
#!/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.mysql.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.mysql.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()
|
573
plugins/modules/mysql_replication.py
Normal file
573
plugins/modules/mysql_replication.py
Normal file
|
@ -0,0 +1,573 @@
|
|||
#!/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.mysql 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.mysql.mysql
|
||||
|
||||
|
||||
seealso:
|
||||
- module: community.mysql.mysql_info
|
||||
- name: MySQL replication reference
|
||||
description: Complete reference of the MySQL replication documentation.
|
||||
link: https://dev.mysql.com/doc/refman/8.0/en/replication.html
|
||||
- name: 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.mysql.plugins.module_utils.mysql import mysql_connect, mysql_driver, mysql_driver_fail_msg
|
||||
from ansible.module_utils._text import to_native
|
||||
|
||||
executed_queries = []
|
||||
|
||||
|
||||
def get_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()
|
991
plugins/modules/mysql_user.py
Normal file
991
plugins/modules/mysql_user.py
Normal file
|
@ -0,0 +1,991 @@
|
|||
#!/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.mysql.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.mysql.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.mysql.plugins.module_utils.database import SQLParseError
|
||||
from ansible_collections.community.mysql.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()
|
272
plugins/modules/mysql_variables.py
Normal file
272
plugins/modules/mysql_variables.py
Normal file
|
@ -0,0 +1,272 @@
|
|||
#!/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.mysql.mysql_info
|
||||
- name: MySQL SET command reference
|
||||
description: Complete reference of the MySQL SET command documentation.
|
||||
link: https://dev.mysql.com/doc/refman/8.0/en/set-statement.html
|
||||
|
||||
extends_documentation_fragment:
|
||||
- community.mysql.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.mysql.plugins.module_utils.database import SQLParseError, mysql_quote_identifier
|
||||
from ansible_collections.community.mysql.plugins.module_utils.mysql import mysql_connect, mysql_driver, mysql_driver_fail_msg
|
||||
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()
|
7
tests/integration/old_mariadb_replication/aliases
Normal file
7
tests/integration/old_mariadb_replication/aliases
Normal file
|
@ -0,0 +1,7 @@
|
|||
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
|
|
@ -0,0 +1,7 @@
|
|||
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
|
3
tests/integration/old_mariadb_replication/meta/main.yml
Normal file
3
tests/integration/old_mariadb_replication/meta/main.yml
Normal file
|
@ -0,0 +1,3 @@
|
|||
---
|
||||
dependencies:
|
||||
- setup_mariadb
|
21
tests/integration/old_mariadb_replication/tasks/main.yml
Normal file
21
tests/integration/old_mariadb_replication/tasks/main.yml
Normal file
|
@ -0,0 +1,21 @@
|
|||
# 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', '>=')
|
|
@ -0,0 +1,173 @@
|
|||
# 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
|
|
@ -0,0 +1,118 @@
|
|||
# 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"]
|
|
@ -0,0 +1,96 @@
|
|||
# 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"]
|
8
tests/integration/old_mysql_replication/aliases
Normal file
8
tests/integration/old_mysql_replication/aliases
Normal file
|
@ -0,0 +1,8 @@
|
|||
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
|
|
@ -0,0 +1,9 @@
|
|||
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
|
3
tests/integration/old_mysql_replication/meta/main.yml
Normal file
3
tests/integration/old_mysql_replication/meta/main.yml
Normal file
|
@ -0,0 +1,3 @@
|
|||
---
|
||||
dependencies:
|
||||
- setup_mysql_replication
|
26
tests/integration/old_mysql_replication/tasks/main.yml
Normal file
26
tests/integration/old_mysql_replication/tasks/main.yml
Normal file
|
@ -0,0 +1,26 @@
|
|||
# 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', '==')
|
|
@ -0,0 +1,119 @@
|
|||
# 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 }}'"]
|
|
@ -0,0 +1,191 @@
|
|||
# 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
|
|
@ -0,0 +1,44 @@
|
|||
# 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
|
|
@ -0,0 +1,48 @@
|
|||
# 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
|
13
tests/integration/targets/setup_mysql/defaults/main.yml
Normal file
13
tests/integration/targets/setup_mysql/defaults/main.yml
Normal file
|
@ -0,0 +1,13 @@
|
|||
dbdeployer_version: 1.52.0
|
||||
dbdeployer_home_dir: /opt/dbdeployer
|
||||
|
||||
home_dir: /root
|
||||
|
||||
percona_client_version: 5.7
|
||||
|
||||
mariadb_install: false
|
||||
|
||||
mysql_version: 8.0.20
|
||||
mariadb_version: 10.5.4
|
||||
|
||||
mysql_base_port: 3306
|
6
tests/integration/targets/setup_mysql/handlers/main.yml
Normal file
6
tests/integration/targets/setup_mysql/handlers/main.yml
Normal file
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
- name: "{{ role_name }} | handler | create dbdeployer installed file"
|
||||
template:
|
||||
src: installed_file.j2
|
||||
dest: "{{ dbdeployer_installed_file }}"
|
||||
listen: create zookeeper installed file
|
15
tests/integration/targets/setup_mysql/tasks/config.yml
Normal file
15
tests/integration/targets/setup_mysql/tasks/config.yml
Normal file
|
@ -0,0 +1,15 @@
|
|||
---
|
||||
- name: "{{ role_name }} | config | download mysql tarball"
|
||||
get_url:
|
||||
url: "{{ install_src }}"
|
||||
dest: "{{ dbdeployer_sandbox_download_dir }}/{{ install_tarball }}"
|
||||
|
||||
- name: "{{ role_name }} | config | run unpack tarball"
|
||||
shell:
|
||||
cmd: "dbdeployer unpack {{ dbdeployer_sandbox_download_dir }}/{{ install_tarball }}"
|
||||
creates: "{{ dbdeployer_sandbox_binary_dir }}/{{ install_version }}"
|
||||
|
||||
- name: "{{ role_name }} | config | setup replication topology"
|
||||
shell:
|
||||
cmd: "dbdeployer deploy multiple {{ install_version }} --base-port {{ mysql_base_port }}"
|
||||
creates: "{{ dbdeployer_sandbox_home_dir }}/multi_msb_{{ install_version|replace('.','_') }}"
|
11
tests/integration/targets/setup_mysql/tasks/dir.yml
Normal file
11
tests/integration/targets/setup_mysql/tasks/dir.yml
Normal file
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
- name: "{{ role_name }} | dir | create dbdeployer directories"
|
||||
file:
|
||||
state: directory
|
||||
path: "{{ item }}"
|
||||
loop:
|
||||
- "{{ dbdeployer_home_dir }}"
|
||||
- "{{ dbdeployer_install_dir }}"
|
||||
- "{{ dbdeployer_sandbox_download_dir }}"
|
||||
- "{{ dbdeployer_sandbox_binary_dir }}"
|
||||
- "{{ dbdeployer_sandbox_home_dir }}"
|
52
tests/integration/targets/setup_mysql/tasks/install.yml
Normal file
52
tests/integration/targets/setup_mysql/tasks/install.yml
Normal file
|
@ -0,0 +1,52 @@
|
|||
---
|
||||
- name: "{{ role_name }} | install | add apt signing key for percona"
|
||||
apt_key:
|
||||
keyserver: keyserver.ubuntu.com
|
||||
id: 4D1BB29D63D98E422B2113B19334A25F8507EFA5
|
||||
state: present
|
||||
|
||||
- name: "{{ role_name }} | install | add percona repositories"
|
||||
apt_repository:
|
||||
repo: "{{ item }}"
|
||||
state: present
|
||||
loop: "{{ percona_mysql_repos }}"
|
||||
|
||||
- name: "{{ role_name }} | install | install packages required by percona"
|
||||
apt:
|
||||
name: "{{ percona_mysql_packages }}"
|
||||
state: present
|
||||
environment:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
|
||||
- name: "{{ role_name }} | install | install python packages"
|
||||
pip:
|
||||
name: "{{ python_packages }}"
|
||||
|
||||
- name: "{{ role_name }} | install | install packages required by mysql"
|
||||
apt:
|
||||
name: "{{ install_prereqs }}"
|
||||
state: present
|
||||
environment:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
|
||||
- name: "{{ role_name }} | install | download and unpack dbdeployer"
|
||||
unarchive:
|
||||
remote_src: true
|
||||
src: "{{ dbdeployer_src }}"
|
||||
dest: "{{ dbdeployer_install_dir }}"
|
||||
creates: "{{ dbdeployer_installed_file }}"
|
||||
register: dbdeployer_tarball_install
|
||||
notify:
|
||||
- create zookeeper installed file
|
||||
until: dbdeployer_tarball_install is not failed
|
||||
retries: 6
|
||||
delay: 5
|
||||
|
||||
- name: "{{ role_name }} | install | create symlink"
|
||||
file:
|
||||
src: "{{ dbdeployer_install_dir }}/dbdeployer-{{ dbdeployer_version }}.linux"
|
||||
dest: /usr/local/bin/dbdeployer
|
||||
follow: false
|
||||
state: link
|
||||
|
||||
- meta: flush_handlers
|
6
tests/integration/targets/setup_mysql/tasks/main.yml
Normal file
6
tests/integration/targets/setup_mysql/tasks/main.yml
Normal file
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
- import_tasks: setvars.yml
|
||||
- import_tasks: dir.yml
|
||||
- import_tasks: install.yml
|
||||
- import_tasks: config.yml
|
||||
- import_tasks: verify.yml
|
28
tests/integration/targets/setup_mysql/tasks/setvars.yml
Normal file
28
tests/integration/targets/setup_mysql/tasks/setvars.yml
Normal file
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
- name: "{{ role_name }} | setvars | split mysql version in parts"
|
||||
set_fact:
|
||||
mysql_version_parts: "{{ mysql_version.split('.') }}"
|
||||
|
||||
- name: "{{ role_name }} | setvars | get mysql major version"
|
||||
set_fact:
|
||||
mysql_major_version: "{{ mysql_version_parts[0] + '.' + mysql_version_parts[1] }}"
|
||||
|
||||
- name: "{{ role_name }} | setvars | set the appropriate extension dependent on the mysql version"
|
||||
set_fact:
|
||||
mysql_compression_extension: "{{ mysql_version is version('8.0.0', '<') | ternary('gz', 'xz') }}"
|
||||
|
||||
- name: "{{ role_name }} | setvars | set the install type"
|
||||
set_fact:
|
||||
install_type: "{{ mariadb_install | ternary('mariadb', 'mysql') }}"
|
||||
|
||||
- name: "{{ role_name }} | setvars | set install_version"
|
||||
set_fact:
|
||||
install_version: "{{ lookup('vars', install_type + '_version') }}"
|
||||
|
||||
- name: "{{ role_name }} | setvars | set install_tarball"
|
||||
set_fact:
|
||||
install_tarball: "{{ lookup('vars', install_type + '_tarball') }}"
|
||||
|
||||
- name: "{{ role_name }} | setvars | set install_src"
|
||||
set_fact:
|
||||
install_src: "{{ lookup('vars', install_type + '_src') }}"
|
27
tests/integration/targets/setup_mysql/tasks/verify.yml
Normal file
27
tests/integration/targets/setup_mysql/tasks/verify.yml
Normal file
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
- name: "{{ role_name }} | verify | confirm primary is running and get the port"
|
||||
shell: "{{ dbdeployer_sandbox_home_dir }}/multi_msb_{{ install_version|replace('.','_') }}/n1 -BNe'select @@port'"
|
||||
register: primary_port
|
||||
|
||||
- name: "{{ role_name }} | verify | confirm replica1 is running and get the port"
|
||||
shell: "{{ dbdeployer_sandbox_home_dir }}/multi_msb_{{ install_version|replace('.','_') }}/n2 -BNe'select @@port'"
|
||||
register: replica1_port
|
||||
|
||||
- name: "{{ role_name }} | verify | confirm replica2 is running and get the port"
|
||||
shell: "{{ dbdeployer_sandbox_home_dir }}/multi_msb_{{ install_version|replace('.','_') }}/n3 -BNe'select @@port'"
|
||||
register: replica2_port
|
||||
|
||||
- name: "{{ role_name }} | verify | confirm primary is running on expected port"
|
||||
assert:
|
||||
that:
|
||||
- primary_port.stdout|int == 3307
|
||||
|
||||
- name: "{{ role_name }} | verify | confirm replica1 is running on expected port"
|
||||
assert:
|
||||
that:
|
||||
- replica1_port.stdout|int == 3308
|
||||
|
||||
- name: "{{ role_name }} | verify | confirm replica2 is running on expected port"
|
||||
assert:
|
||||
that:
|
||||
- replica2_port.stdout|int == 3309
|
|
@ -0,0 +1 @@
|
|||
{{ dbdeployer_version }}
|
27
tests/integration/targets/setup_mysql/vars/main.yml
Normal file
27
tests/integration/targets/setup_mysql/vars/main.yml
Normal file
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
dbdeployer_install_dir: "{{ dbdeployer_home_dir }}/dbdeployer_{{ dbdeployer_version }}"
|
||||
dbdeployer_src: "https://github.com/datacharmer/dbdeployer/releases/download/v{{ dbdeployer_version }}/dbdeployer-{{ dbdeployer_version }}.linux.tar.gz"
|
||||
dbdeployer_installed_file: "{{ dbdeployer_home_dir }}/dbdeployer_installed"
|
||||
|
||||
dbdeployer_sandbox_download_dir: "{{ home_dir }}/downloads"
|
||||
dbdeployer_sandbox_binary_dir: "{{ home_dir }}/opt/mysql"
|
||||
dbdeployer_sandbox_home_dir: "{{ home_dir }}/sandboxes"
|
||||
|
||||
percona_mysql_repos:
|
||||
- deb http://repo.percona.com/apt {{ ansible_lsb.codename }} main
|
||||
- deb-src http://repo.percona.com/apt {{ ansible_lsb.codename }} main
|
||||
|
||||
percona_mysql_packages:
|
||||
- percona-server-client-{{ percona_client_version }}
|
||||
|
||||
python_packages:
|
||||
- pymysql
|
||||
|
||||
install_prereqs:
|
||||
- libaio1
|
||||
- libnuma1
|
||||
|
||||
mysql_tarball: "mysql-{{ mysql_version }}-linux-glibc2.12-x86_64.tar.{{ mysql_compression_extension }}"
|
||||
mysql_src: "https://dev.mysql.com/get/Downloads/MySQL-{{ mysql_major_version }}/{{ mysql_tarball }}"
|
||||
mariadb_tarball: "mariadb-{{ mariadb_version }}-linux-x86_64.tar.gz"
|
||||
mariadb_src: "https://downloads.mariadb.com/MariaDB/mariadb-{{ mariadb_version }}/bintar-linux-x86_64/{{ mariadb_tarball }}"
|
|
@ -0,0 +1,5 @@
|
|||
- name: delete temporary directory
|
||||
include_tasks: default-cleanup.yml
|
||||
|
||||
- name: delete temporary directory (windows)
|
||||
include_tasks: windows-cleanup.yml
|
|
@ -0,0 +1,5 @@
|
|||
- name: delete temporary directory
|
||||
file:
|
||||
path: "{{ remote_tmp_dir }}"
|
||||
state: absent
|
||||
no_log: yes
|
|
@ -0,0 +1,11 @@
|
|||
- name: create temporary directory
|
||||
tempfile:
|
||||
state: directory
|
||||
suffix: .test
|
||||
register: remote_tmp_dir
|
||||
notify:
|
||||
- delete temporary directory
|
||||
|
||||
- name: record temporary directory
|
||||
set_fact:
|
||||
remote_tmp_dir: "{{ remote_tmp_dir.path }}"
|
|
@ -0,0 +1,10 @@
|
|||
- name: make sure we have the ansible_os_family and ansible_distribution_version facts
|
||||
setup:
|
||||
gather_subset: distribution
|
||||
when: ansible_facts == {}
|
||||
|
||||
- include_tasks: "{{ lookup('first_found', files)}}"
|
||||
vars:
|
||||
files:
|
||||
- "{{ ansible_os_family | lower }}.yml"
|
||||
- "default.yml"
|
14
tests/integration/targets/test_mysql_db/defaults/main.yml
Normal file
14
tests/integration/targets/test_mysql_db/defaults/main.yml
Normal file
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
# defaults file for test_mysql_db
|
||||
mysql_user: root
|
||||
mysql_password: msandbox
|
||||
mysql_primary_port: 3307
|
||||
|
||||
db_name: 'data'
|
||||
db_name2: 'data2'
|
||||
db_user1: 'datauser1'
|
||||
db_user2: 'datauser2'
|
||||
|
||||
tmp_dir: '/tmp'
|
||||
db_latin1_name: 'db_latin1'
|
||||
file4: 'latin1_file'
|
2
tests/integration/targets/test_mysql_db/meta/main.yml
Normal file
2
tests/integration/targets/test_mysql_db/meta/main.yml
Normal file
|
@ -0,0 +1,2 @@
|
|||
dependencies:
|
||||
- setup_mysql
|
|
@ -0,0 +1,85 @@
|
|||
- 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:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
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:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name: '{{ db_to_create }}'
|
||||
state: present
|
||||
check_implicit_admin: yes
|
||||
config_file: '{{ config_file }}'
|
||||
config_overrides_defaults: no
|
||||
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:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
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:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name: '{{ db_to_create }}'
|
||||
state: absent
|
||||
check_implicit_admin: yes
|
|
@ -0,0 +1,98 @@
|
|||
---
|
||||
|
||||
- set_fact:
|
||||
latin1_file1: "{{tmp_dir}}/{{file}}"
|
||||
|
||||
- name: Deleting Latin1 encoded Database
|
||||
mysql_db:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name: '{{ db_latin1_name }}'
|
||||
state: absent
|
||||
|
||||
- name: create Latin1 encoded database
|
||||
mysql_db:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name: '{{ db_latin1_name }}'
|
||||
state: present
|
||||
encoding: latin1
|
||||
|
||||
- name: create a table in Latin1 database
|
||||
command: "{{ mysql_command }} {{ db_latin1_name }} -e \"create table testlatin1(id int, name varchar(100))\""
|
||||
|
||||
|
||||
# Inserting a string in latin1 into table, , this string be tested later,
|
||||
# so report any change of content in the test too
|
||||
- name: inserting data into Latin1 database
|
||||
command: "{{ mysql_command }} {{ db_latin1_name }} -e \"insert into testlatin1 value(47,'Amédée Bôlüt')\""
|
||||
|
||||
- name: selecting table
|
||||
command: "{{ mysql_command }} {{ db_latin1_name }} -e \"select * from testlatin1\""
|
||||
register: output
|
||||
|
||||
- name: Dumping a table in Latin1 database
|
||||
mysql_db:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name: "{{ db_latin1_name }}"
|
||||
encoding: latin1
|
||||
target: "{{ latin1_file1 }}"
|
||||
state: dump
|
||||
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:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name: '{{ db_latin1_name }}'
|
||||
state: absent
|
||||
|
||||
- name: Importing the latin1 mysql script
|
||||
mysql_db:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
state: import
|
||||
encoding: latin1
|
||||
name: '{{ db_latin1_name }}'
|
||||
target: "{{ latin1_file1 }}"
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result is changed
|
||||
|
||||
- name: check encoding of table
|
||||
shell: "{{ mysql_command }} {{ db_latin1_name }} -e \"SHOW FULL COLUMNS FROM testlatin1\""
|
||||
register: output
|
||||
failed_when: '"latin1_swedish_ci" not in output.stdout'
|
||||
|
||||
- name: remove database
|
||||
mysql_db:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name: '{{ db_latin1_name }}'
|
||||
state: absent
|
319
tests/integration/targets/test_mysql_db/tasks/main.yml
Normal file
319
tests/integration/targets/test_mysql_db/tasks/main.yml
Normal file
|
@ -0,0 +1,319 @@
|
|||
# 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: alias mysql command to include default options
|
||||
set_fact:
|
||||
mysql_command: "mysql -u{{ mysql_user }} -p{{ mysql_password }} -P{{ mysql_primary_port }} --protocol=tcp"
|
||||
|
||||
- name: remove database if it exists
|
||||
command: >
|
||||
"{{ mysql_command }} -sse 'drop database {{ db_name }}'"
|
||||
ignore_errors: True
|
||||
|
||||
- name: make sure the test database is not there
|
||||
command: "{{ mysql_command }} {{ db_name }}"
|
||||
register: mysql_db_check
|
||||
failed_when: "'1049' not in mysql_db_check.stderr"
|
||||
|
||||
- name: test state=present for a database name (expect changed=true)
|
||||
mysql_db:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name: '{{ db_name }}'
|
||||
state: present
|
||||
register: result
|
||||
|
||||
- name: assert output message that database exist
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
- result.db == '{{ db_name }}'
|
||||
- result.executed_commands == ["CREATE DATABASE `{{ db_name }}`"]
|
||||
|
||||
- name: run command to test state=present for a database name (expect db_name in stdout)
|
||||
command: "{{ mysql_command }} -e \"show databases like '{{ db_name }}'\""
|
||||
register: result
|
||||
|
||||
- name: assert database exist
|
||||
assert:
|
||||
that:
|
||||
- "'{{ db_name }}' in result.stdout"
|
||||
|
||||
# ============================================================
|
||||
- name: test state=absent for a database name (expect changed=true)
|
||||
mysql_db:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name: '{{ db_name }}'
|
||||
state: absent
|
||||
register: result
|
||||
|
||||
- name: assert output message that database does not exist
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
- result.db == '{{ db_name }}'
|
||||
- result.executed_commands == ["DROP DATABASE `{{ db_name }}`"]
|
||||
|
||||
- name: run command to test state=absent for a database name (expect db_name not in stdout)
|
||||
command: "{{ mysql_command }} -e \"show databases like '{{ db_name }}'\""
|
||||
register: result
|
||||
|
||||
- name: assert database does not exist
|
||||
assert:
|
||||
that:
|
||||
- "'{{ db_name }}' not in result.stdout"
|
||||
|
||||
# ============================================================
|
||||
- name: test mysql_db encoding param not valid - issue 8075
|
||||
mysql_db:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name: datanotvalid
|
||||
state: present
|
||||
encoding: notvalid
|
||||
register: result
|
||||
ignore_errors: true
|
||||
|
||||
- name: assert test mysql_db encoding param not valid - issue 8075 (failed=true)
|
||||
assert:
|
||||
that:
|
||||
- "result.failed == true"
|
||||
- "'Traceback' not in result.msg"
|
||||
- "'Unknown character set' in result.msg"
|
||||
|
||||
# ============================================================
|
||||
- name: test mysql_db using a valid encoding utf8 (expect changed=true)
|
||||
mysql_db:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name: 'en{{ db_name }}'
|
||||
state: present
|
||||
encoding: utf8
|
||||
register: result
|
||||
|
||||
- name: assert output message created a database
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
- result.executed_commands == ["CREATE DATABASE `en{{ db_name }}` CHARACTER SET 'utf8'"]
|
||||
|
||||
- name: test database was created
|
||||
command: "{{ mysql_command }} -e \"SHOW CREATE DATABASE en{{ db_name }}\""
|
||||
register: result
|
||||
|
||||
- name: assert created database is of encoding utf8
|
||||
assert:
|
||||
that:
|
||||
- "'utf8' in result.stdout"
|
||||
|
||||
- name: remove database
|
||||
mysql_db:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name: 'en{{ db_name }}'
|
||||
state: absent
|
||||
|
||||
# ============================================================
|
||||
- name: test mysql_db using valid encoding binary (expect changed=true)
|
||||
mysql_db:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name: 'en{{ db_name }}'
|
||||
state: present
|
||||
encoding: binary
|
||||
register: result
|
||||
|
||||
- name: assert output message that database was created
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
- result.executed_commands == ["CREATE DATABASE `en{{ db_name }}` CHARACTER SET 'binary'"]
|
||||
|
||||
- name: run command to test database was created
|
||||
command: "{{ mysql_command }} -e \"SHOW CREATE DATABASE en{{ db_name }}\""
|
||||
register: result
|
||||
|
||||
- name: assert created database is of encoding binary
|
||||
assert:
|
||||
that:
|
||||
- "'binary' in result.stdout"
|
||||
|
||||
- name: remove database
|
||||
mysql_db:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name: 'en{{ db_name }}'
|
||||
state: absent
|
||||
|
||||
# ============================================================
|
||||
- name: create user1 to access database dbuser1
|
||||
mysql_user:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name: user1
|
||||
password: 'Hfd6fds^dfA8Ga'
|
||||
priv: '*.*:ALL'
|
||||
state: present
|
||||
|
||||
- name: create database dbuser1 using user1
|
||||
mysql_db:
|
||||
login_user: user1
|
||||
login_password: 'Hfd6fds^dfA8Ga'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name: '{{ db_user1 }}'
|
||||
state: present
|
||||
register: result
|
||||
|
||||
- name: assert output message that database was created
|
||||
assert:
|
||||
that:
|
||||
- "result.changed == true"
|
||||
|
||||
- name: run command to test database was created using user1
|
||||
command: "{{ mysql_command }} -e \"show databases like '{{ db_user1 }}'\""
|
||||
register: result
|
||||
|
||||
- name: assert database exist
|
||||
assert:
|
||||
that:
|
||||
- "'{{ db_user1 }}' in result.stdout"
|
||||
|
||||
# ============================================================
|
||||
- name: create user2 to access database with privilege select only
|
||||
mysql_user:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name: user2
|
||||
password: 'kjsfd&F7safjad'
|
||||
priv: '*.*:SELECT'
|
||||
state: present
|
||||
|
||||
- name: create database dbuser2 using user2 with no privilege to create (expect failed=true)
|
||||
mysql_db:
|
||||
login_user: user2
|
||||
login_password: 'kjsfd&F7safjad'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name: '{{ db_user2 }}'
|
||||
state: present
|
||||
register: result
|
||||
ignore_errors: true
|
||||
|
||||
- name: assert output message that database was not created using dbuser2
|
||||
assert:
|
||||
that:
|
||||
- "result.failed == true"
|
||||
- "'Access denied' in result.msg"
|
||||
|
||||
- name: run command to test that database was not created
|
||||
command: "{{ mysql_command }} -e \"show databases like '{{ db_user2 }}'\""
|
||||
register: result
|
||||
|
||||
- name: assert database does not exist
|
||||
assert:
|
||||
that:
|
||||
- "'{{ db_user2 }}' not in result.stdout"
|
||||
|
||||
# ============================================================
|
||||
- name: delete database using user2 with no privilege to delete (expect failed=true)
|
||||
mysql_db:
|
||||
login_user: user2
|
||||
login_password: 'kjsfd&F7safjad'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name: '{{ db_user1 }}'
|
||||
state: absent
|
||||
register: result
|
||||
ignore_errors: true
|
||||
|
||||
- name: assert output message that database was not deleted using dbuser2
|
||||
assert:
|
||||
that:
|
||||
- "result.failed == true"
|
||||
- "'Access denied' in result.msg"
|
||||
|
||||
- name: run command to test database was not deleted
|
||||
command: "{{ mysql_command }} -e \"show databases like '{{ db_user1 }}'\""
|
||||
register: result
|
||||
|
||||
- name: assert database still exist
|
||||
assert:
|
||||
that:
|
||||
- "'{{ db_user1 }}' in result.stdout"
|
||||
|
||||
# ============================================================
|
||||
- name: delete database using user1 with all privilege to delete a database (expect changed=true)
|
||||
mysql_db:
|
||||
login_user: user1
|
||||
login_password: 'Hfd6fds^dfA8Ga'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name: '{{ db_user1 }}'
|
||||
state: absent
|
||||
register: result
|
||||
ignore_errors: true
|
||||
|
||||
- name: assert output message that database was deleted using user1
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
- result.executed_commands == ["DROP DATABASE `{{ db_user1 }}`"]
|
||||
|
||||
- name: run command to test database was deleted using user1
|
||||
command: "{{ mysql_command }} -e \"show databases like '{{ db_name }}'\""
|
||||
register: result
|
||||
|
||||
- name: assert database does not exist
|
||||
assert:
|
||||
that:
|
||||
- "'{{ db_user1 }}' not in result.stdout"
|
||||
|
||||
# ============================================================
|
||||
- include: state_dump_import.yml format_type=sql file=dbdata.sql format_msg_type=ASCII file2=dump2.sql file3=dump3.sql file4=dump4.sql
|
||||
|
||||
- include: state_dump_import.yml format_type=gz file=dbdata.gz format_msg_type=gzip file2=dump2.gz file3=dump3.gz file4=dump4.gz
|
||||
|
||||
- include: state_dump_import.yml format_type=bz2 file=dbdata.bz2 format_msg_type=bzip2 file2=dump2.bz2 file3=dump3.bz2 file4=dump4.bz2
|
||||
|
||||
- include: multi_db_create_delete.yml
|
||||
|
||||
- include: encoding_dump_import.yml file=latin1.sql format_msg_type=ASCII
|
||||
|
||||
- include: config_overrides_defaults.yml
|
||||
when: ansible_python.version_info[0] >= 3
|
|
@ -0,0 +1,626 @@
|
|||
# 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_command }} \"-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:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name:
|
||||
- '{{ db1_name }}'
|
||||
- '{{ db2_name }}'
|
||||
- '{{ db3_name }}'
|
||||
state: present
|
||||
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_command }} \"-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:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name:
|
||||
- '{{ db1_name }}'
|
||||
- '{{ db2_name }}'
|
||||
- '{{ db3_name }}'
|
||||
state: present
|
||||
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_command }} \"-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:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name:
|
||||
- '{{ db1_name }}'
|
||||
- '{{ db2_name }}'
|
||||
- '{{ db3_name }}'
|
||||
state: present
|
||||
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_command }} \"-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:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name:
|
||||
- '{{ db1_name }}'
|
||||
- '{{ db2_name }}'
|
||||
- '{{ db3_name }}'
|
||||
state: present
|
||||
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_command }} \"-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:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name:
|
||||
- '{{ db2_name }}'
|
||||
state: absent
|
||||
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_command }} \"-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:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name:
|
||||
- '{{ db1_name }}'
|
||||
- '{{ db2_name }}'
|
||||
- '{{ db3_name }}'
|
||||
state: present
|
||||
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_command }} \"-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:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name:
|
||||
- '{{ db1_name }}'
|
||||
- '{{ db2_name }}'
|
||||
- '{{ db3_name }}'
|
||||
state: present
|
||||
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_command }} \"-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:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name:
|
||||
- '{{ db1_name }}'
|
||||
- '{{ db3_name }}'
|
||||
state: dump
|
||||
target: '{{ dump1_file }}'
|
||||
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_command }} \"-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:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name:
|
||||
- "{{ db1_name }}"
|
||||
- "{{ db4_name }}"
|
||||
- "{{ db3_name }}"
|
||||
state: dump
|
||||
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_command }} \"-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:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name:
|
||||
- "{{ db4_name }}"
|
||||
- "{{ db5_name }}"
|
||||
state: dump
|
||||
target: "{{ dump1_file }}"
|
||||
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_command }} \"-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:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name:
|
||||
- '{{ db1_name }}'
|
||||
- '{{ db2_name }}'
|
||||
- '{{ db3_name }}'
|
||||
state: dump
|
||||
target: '{{ dump1_file }}'
|
||||
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_command }} \"-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:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name: all
|
||||
state: dump
|
||||
target: '{{ dump2_file }}'
|
||||
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_command }} \"-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:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name:
|
||||
- '{{ db2_name }}'
|
||||
- '{{ db3_name }}'
|
||||
state: absent
|
||||
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_command }} \"-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:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name:
|
||||
- '{{ db2_name }}'
|
||||
- '{{ db3_name }}'
|
||||
state: absent
|
||||
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_command }} \"-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:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name:
|
||||
- '{{ db2_name }}'
|
||||
- '{{ db4_name }}'
|
||||
state: absent
|
||||
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_command }} \"-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:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name:
|
||||
- '{{ db2_name }}'
|
||||
- '{{ db4_name }}'
|
||||
state: absent
|
||||
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_command }} \"-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:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name:
|
||||
- '{{ db1_name }}'
|
||||
- '{{ db2_name }}'
|
||||
- '{{ db3_name }}'
|
||||
- '{{ db4_name }}'
|
||||
- '{{ db5_name }}'
|
||||
state: absent
|
||||
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_command }} \"-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
|
|
@ -0,0 +1,459 @@
|
|||
# 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:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name: '{{ db_user }}'
|
||||
password: '{{ db_user_unsafe_password }}'
|
||||
priv: '*.*:ALL'
|
||||
state: present
|
||||
|
||||
- name: state dump/import - create database
|
||||
mysql_db:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name: '{{ db_name }}'
|
||||
state: present
|
||||
check_implicit_admin: yes
|
||||
|
||||
- name: create database
|
||||
mysql_db:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name: '{{ db_name2 }}'
|
||||
state: present
|
||||
check_implicit_admin: no
|
||||
|
||||
- name: state dump/import - create table department
|
||||
command: "{{ mysql_command }} {{ db_name }} \"-e create table department(id int, name varchar(100))\""
|
||||
|
||||
- name: state dump/import - create table employee
|
||||
command: "{{ mysql_command }} {{ db_name }} \"-e create table employee(id int, name varchar(100))\""
|
||||
|
||||
- name: state dump/import - insert data into table employee
|
||||
command: "{{ mysql_command }} {{ db_name }} \"-e insert into employee value(47,'Joe Smith')\""
|
||||
|
||||
- name: state dump/import - insert data into table department
|
||||
command: "{{ mysql_command }} {{ 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 }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
unsafe_login_password: yes
|
||||
name: '{{ db_name }}'
|
||||
state: dump
|
||||
target: '{{ db_file_name }}'
|
||||
ignore_tables:
|
||||
- "{{ db_name }}.department"
|
||||
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 --host=127.0.0.1 --port={{ mysql_primary_port }} {{ db_name }} --skip-lock-tables --quick --ignore-table={{ db_name }}.department --master-data=1 --skip-triggers")
|
||||
|
||||
- name: 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:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name: "{{ db_name }},{{ db_name2 }}"
|
||||
state: dump
|
||||
target: '{{ dump_file1 }}'
|
||||
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:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name:
|
||||
- '{{ db_name }}'
|
||||
- '{{ db_name2 }}'
|
||||
state: dump
|
||||
target: '{{ dump_file2 }}'
|
||||
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:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name:
|
||||
- '{{ db_name }}'
|
||||
- '{{ db_name2 }}'
|
||||
state: dump
|
||||
target: '{{ dump_file2 }}'
|
||||
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:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name: '{{ db_name }}'
|
||||
state: absent
|
||||
|
||||
- name: remove database
|
||||
mysql_db:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name: '{{ db_name2 }}'
|
||||
state: absent
|
||||
|
||||
- name: test state=import to restore the database of type {{ format_type }} (expect changed=true)
|
||||
mysql_db:
|
||||
login_user: '{{ db_user }}'
|
||||
login_password: '{{ db_user_unsafe_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
unsafe_login_password: yes
|
||||
name: '{{ db_name }}'
|
||||
state: import
|
||||
target: '{{ db_file_name }}'
|
||||
use_shell: yes
|
||||
register: result
|
||||
|
||||
- name: show the tables
|
||||
command: "{{ mysql_command }} {{ 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:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name: '{{ db_name2 }}'
|
||||
state: import
|
||||
target: '{{ dump_file1 }}'
|
||||
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:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name: '{{ db_name2 }}'
|
||||
state: absent
|
||||
|
||||
- name: run command to list databases
|
||||
command: "{{ mysql_command }} \"-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:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name: '{{ db_name2 }}'
|
||||
state: import
|
||||
target: '{{ dump_file2 }}'
|
||||
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_command }} \"-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:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name: '{{ db_name2 }}'
|
||||
state: import
|
||||
target: '{{ dump_file2 }}'
|
||||
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_command }} \"-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:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name: '{{ db_name }}'
|
||||
state: dump
|
||||
target: '{{ db_file_name }}'
|
||||
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_command }} {{ 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:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name: '{{ db_name }}'
|
||||
state: import
|
||||
target: '{{ db_file_name }}'
|
||||
register: result
|
||||
|
||||
- name: assert output message restore the database
|
||||
assert:
|
||||
that:
|
||||
- "result.changed == true"
|
||||
|
||||
- name: select data from table employee
|
||||
command: "{{ mysql_command }} {{ 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:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name: '{{ db_name }}'
|
||||
state: import
|
||||
target: '{{ wrong_sql_file }}'
|
||||
force: no
|
||||
register: result
|
||||
ignore_errors: yes
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result.failed == true
|
||||
|
||||
- name: try to import with force parameter
|
||||
mysql_db:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name: '{{ db_name }}'
|
||||
state: import
|
||||
target: '{{ wrong_sql_file }}'
|
||||
force: yes
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result is changed
|
||||
|
||||
##########
|
||||
# Clean up
|
||||
##########
|
||||
|
||||
- name: remove database name
|
||||
mysql_db:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name: '{{ db_name }}'
|
||||
state: absent
|
||||
|
||||
- name: remove database
|
||||
mysql_db:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name: '{{ db_name2 }}'
|
||||
state: absent
|
||||
|
||||
- name: remove file name
|
||||
file:
|
||||
name: '{{ db_file_name }}'
|
||||
state: absent
|
||||
|
||||
- name: remove file name
|
||||
file:
|
||||
name: '{{ wrong_sql_file }}'
|
||||
state: absent
|
||||
|
||||
- name: remove dump file1
|
||||
file:
|
||||
name: '{{ dump_file1 }}'
|
||||
state: absent
|
||||
|
||||
- name: remove dump file2
|
||||
file:
|
||||
name: '{{ dump_file2 }}'
|
||||
state: absent
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
# defaults file for test_mysql_info
|
||||
mysql_user: root
|
||||
mysql_password: msandbox
|
||||
mysql_host: 127.0.0.1
|
||||
mysql_primary_port: 3307
|
||||
|
||||
db_name: data
|
3
tests/integration/targets/test_mysql_info/meta/main.yml
Normal file
3
tests/integration/targets/test_mysql_info/meta/main.yml
Normal file
|
@ -0,0 +1,3 @@
|
|||
dependencies:
|
||||
- setup_mysql
|
||||
- setup_remote_tmp_dir
|
186
tests/integration/targets/test_mysql_info/tasks/main.yml
Normal file
186
tests/integration/targets/test_mysql_info/tasks/main.yml
Normal file
|
@ -0,0 +1,186 @@
|
|||
# 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
|
||||
#
|
||||
|
||||
- vars:
|
||||
mysql_parameters: &mysql_params
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: '{{ mysql_host }}'
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
|
||||
block:
|
||||
|
||||
# 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: '{{ mysql_user }}'
|
||||
login_host: '{{ mysql_host }}'
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
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: '{{ mysql_user }}'
|
||||
login_host: '{{ mysql_host }}'
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
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:
|
||||
<<: *mysql_params
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result.changed == false
|
||||
- result.version != {}
|
||||
|
||||
# Test excluding
|
||||
- name: Collect all info except settings and users
|
||||
mysql_info:
|
||||
<<: *mysql_params
|
||||
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:
|
||||
<<: *mysql_params
|
||||
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:
|
||||
<<: *mysql_params
|
||||
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:
|
||||
<<: *mysql_params
|
||||
name: acme
|
||||
|
||||
- name: Collect info about databases
|
||||
mysql_info:
|
||||
<<: *mysql_params
|
||||
filter:
|
||||
- databases
|
||||
return_empty_dbs: true
|
||||
register: result
|
||||
|
||||
# Check acme is in returned dict
|
||||
- assert:
|
||||
that:
|
||||
- result.changed == false
|
||||
- result.databases.acme.size == 0
|
||||
- result.databases.mysql != {}
|
||||
|
||||
- name: Collect info about databases excluding their sizes
|
||||
mysql_info:
|
||||
<<: *mysql_params
|
||||
filter:
|
||||
- databases
|
||||
exclude_fields:
|
||||
- db_size
|
||||
return_empty_dbs: true
|
||||
register: result
|
||||
|
||||
# Check acme is in returned dict
|
||||
- assert:
|
||||
that:
|
||||
- result.changed == false
|
||||
- result.databases.acme == {}
|
||||
- result.databases.mysql == {}
|
||||
|
||||
- name: Remove acme database
|
||||
mysql_db:
|
||||
<<: *mysql_params
|
||||
name: acme
|
||||
state: absent
|
|
@ -0,0 +1,5 @@
|
|||
[client]
|
||||
user={{ mysql_user }}
|
||||
password={{ mysql_password }}
|
||||
host={{ mysql_host }}
|
||||
port={{ mysql_primary_port }}
|
|
@ -0,0 +1,9 @@
|
|||
mysql_user: root
|
||||
mysql_password: msandbox
|
||||
mysql_primary_port: 3307
|
||||
|
||||
db_name: data
|
||||
test_db: testdb
|
||||
test_table1: test1
|
||||
test_table2: test2
|
||||
test_script_path: /tmp/test.sql
|
2
tests/integration/targets/test_mysql_query/meta/main.yml
Normal file
2
tests/integration/targets/test_mysql_query/meta/main.yml
Normal file
|
@ -0,0 +1,2 @@
|
|||
dependencies:
|
||||
- setup_mysql
|
|
@ -0,0 +1,2 @@
|
|||
# mysql_query module initial CI tests
|
||||
- import_tasks: mysql_query_initial.yml
|
|
@ -0,0 +1,250 @@
|
|||
# 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_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
|
||||
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 }}']
|
24
tests/integration/targets/test_mysql_user/defaults/main.yml
Normal file
24
tests/integration/targets/test_mysql_user/defaults/main.yml
Normal file
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
# defaults file for test_mysql_user
|
||||
mysql_user: root
|
||||
mysql_password: msandbox
|
||||
mysql_host: 127.0.0.1
|
||||
mysql_primary_port: 3307
|
||||
|
||||
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'
|
|
@ -0,0 +1,8 @@
|
|||
USE foo;
|
||||
DELIMITER ;;
|
||||
CREATE FUNCTION `function` () RETURNS tinyint(4) DETERMINISTIC
|
||||
BEGIN
|
||||
DECLARE NAME_FOUND tinyint DEFAULT 0;
|
||||
RETURN NAME_FOUND;
|
||||
END;;
|
||||
DELIMITER ;
|
|
@ -0,0 +1,5 @@
|
|||
USE bar;
|
||||
DELIMITER ;;
|
||||
CREATE PROCEDURE `procedure` ()
|
||||
SELECT * FROM bar;;
|
||||
DELIMITER ;
|
3
tests/integration/targets/test_mysql_user/meta/main.yml
Normal file
3
tests/integration/targets/test_mysql_user/meta/main.yml
Normal file
|
@ -0,0 +1,3 @@
|
|||
dependencies:
|
||||
- setup_mysql
|
||||
- setup_remote_tmp_dir
|
|
@ -0,0 +1,25 @@
|
|||
# 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_command }} -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" }
|
|
@ -0,0 +1,38 @@
|
|||
# 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_command }} -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_command }} -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
|
|
@ -0,0 +1,40 @@
|
|||
# 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/>.
|
||||
|
||||
- vars:
|
||||
mysql_parameters: &mysql_params
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
|
||||
block:
|
||||
|
||||
# ============================================================
|
||||
- name: create mysql user {{user_name}}
|
||||
mysql_user:
|
||||
<<: *mysql_params
|
||||
name: '{{ user_name }}'
|
||||
password: '{{ user_password }}'
|
||||
state: present
|
||||
register: result
|
||||
|
||||
- name: assert output message mysql user was created
|
||||
assert:
|
||||
that:
|
||||
- "result.changed == true"
|
|
@ -0,0 +1,86 @@
|
|||
---
|
||||
- vars:
|
||||
mysql_parameters: &mysql_params
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
|
||||
block:
|
||||
|
||||
- name: Issue test setup - drop database
|
||||
mysql_db:
|
||||
<<: *mysql_params
|
||||
name: "{{ item }}"
|
||||
state: absent
|
||||
loop:
|
||||
- foo
|
||||
- bar
|
||||
|
||||
- name: Issue test setup - create database
|
||||
mysql_db:
|
||||
<<: *mysql_params
|
||||
name: "{{ item }}"
|
||||
state: present
|
||||
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_command }} < {{ remote_tmp_dir }}/create-function.sql"
|
||||
|
||||
- name: Create procedure for test
|
||||
shell: "{{ mysql_command }} < {{ remote_tmp_dir }}/create-procedure.sql"
|
||||
|
||||
- name: Create user with FUNCTION and PROCEDURE privileges
|
||||
mysql_user:
|
||||
<<: *mysql_params
|
||||
name: '{{ user_name_2 }}'
|
||||
password: '{{ user_password_2 }}'
|
||||
state: present
|
||||
priv: 'FUNCTION foo.function:EXECUTE/foo.*:SELECT/PROCEDURE bar.procedure:EXECUTE'
|
||||
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:
|
||||
<<: *mysql_params
|
||||
name: '{{ user_name_2 }}'
|
||||
password: '{{ user_password_2 }}'
|
||||
state: present
|
||||
priv: 'FUNCTION foo.function:EXECUTE/foo.*:SELECT/PROCEDURE bar.procedure:EXECUTE'
|
||||
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:
|
||||
<<: *mysql_params
|
||||
name: '{{ user_name_2 }}'
|
||||
state: absent
|
||||
|
||||
- name: Issue test teardown - cleanup databases
|
||||
mysql_db:
|
||||
<<: *mysql_params
|
||||
name: "{{ item }}"
|
||||
state: absent
|
||||
loop:
|
||||
- foo
|
||||
- bar
|
|
@ -0,0 +1,45 @@
|
|||
---
|
||||
- vars:
|
||||
mysql_parameters: &mysql_params
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
|
||||
block:
|
||||
|
||||
- name: Set root password
|
||||
mysql_user:
|
||||
<<: *mysql_params
|
||||
name: root
|
||||
password: '{{ root_password }}'
|
||||
check_implicit_admin: yes
|
||||
register: result
|
||||
|
||||
- name: assert root password is changed
|
||||
assert: { that: "result.changed == true" }
|
||||
|
||||
- name: Set root password again
|
||||
mysql_user:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ root_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name: root
|
||||
password: '{{ root_password }}'
|
||||
check_implicit_admin: yes
|
||||
register: result
|
||||
|
||||
- name: Assert root password is not changed
|
||||
assert: { that: "result.changed == false" }
|
||||
|
||||
- name: Set root password again
|
||||
mysql_user:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ root_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name: root
|
||||
password: '{{ mysql_password }}'
|
||||
check_implicit_admin: yes
|
||||
register: result
|
243
tests/integration/targets/test_mysql_user/tasks/main.yml
Normal file
243
tests/integration/targets/test_mysql_user/tasks/main.yml
Normal file
|
@ -0,0 +1,243 @@
|
|||
# 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
|
||||
#
|
||||
- name: alias mysql command to include default options
|
||||
set_fact:
|
||||
mysql_command: "mysql -u{{ mysql_user }} -p{{ mysql_password }} -P{{ mysql_primary_port }} --protocol=tcp"
|
||||
|
||||
- vars:
|
||||
mysql_parameters: &mysql_params
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
|
||||
block:
|
||||
|
||||
- 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:
|
||||
<<: *mysql_params
|
||||
name: '{{user_name_1}}'
|
||||
password: '{{user_password_1}}'
|
||||
state: present
|
||||
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:
|
||||
<<: *mysql_params
|
||||
name: '{{ user_name_1 }}'
|
||||
password: '{{ user_password_1 }}'
|
||||
state: absent
|
||||
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:
|
||||
<<: *mysql_params
|
||||
name: '{{ user_name_1 }}'
|
||||
password: '{{ user_password_1 }}'
|
||||
state: absent
|
||||
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:
|
||||
<<: *mysql_params
|
||||
name: '{{ user_name_1 }}'
|
||||
password: '{{ user_password_1 }}'
|
||||
state: present
|
||||
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:
|
||||
<<: *mysql_params
|
||||
name: '{{ user_name_2 }}'
|
||||
password: '{{ user_password_2 }}'
|
||||
state: present
|
||||
priv: '*.*:SELECT'
|
||||
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:
|
||||
<<: *mysql_params
|
||||
name: '{{ item[0] }}'
|
||||
priv: '{{ item[1] }}.*:ALL'
|
||||
append_privs: yes
|
||||
password: '{{ user_password_1 }}'
|
||||
with_nested:
|
||||
- [ '{{ user_name_1 }}', '{{ user_name_2 }}']
|
||||
- "{{db_names}}"
|
||||
|
||||
- name: show grants access for user1 on multiple database
|
||||
command: "{{ mysql_command }} -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_command }} -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:
|
||||
<<: *mysql_params
|
||||
name: '{{ user_name_1 }}'
|
||||
priv: '%db.*:SELECT'
|
||||
append_privs: yes
|
||||
password: '{{ user_password_1 }}'
|
||||
|
||||
- name: show grants access for user1 on multiple database
|
||||
command: "{{ mysql_command }} -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:
|
||||
<<: *mysql_params
|
||||
name: '{{ user_name_1 }}'
|
||||
priv: '%db.*:INSERT'
|
||||
append_privs: yes
|
||||
password: '{{ user_password_1 }}'
|
||||
|
||||
- name: show grants access for user1 on multiple database
|
||||
command: "{{ mysql_command }} -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
|
|
@ -0,0 +1,74 @@
|
|||
# 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/>.
|
||||
|
||||
- vars:
|
||||
mysql_parameters: &mysql_params
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
|
||||
block:
|
||||
|
||||
# ============================================================
|
||||
- name: remove mysql user {{user_name}}
|
||||
mysql_user:
|
||||
<<: *mysql_params
|
||||
name: '{{user_name}}'
|
||||
password: '{{user_password}}'
|
||||
state: absent
|
||||
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:
|
||||
<<: *mysql_params
|
||||
name: ""
|
||||
state: present
|
||||
password: 'KJFDY&D*Sfuydsgf'
|
||||
|
||||
- name: remove blank mysql user with hosts=all (expect changed)
|
||||
mysql_user:
|
||||
<<: *mysql_params
|
||||
user: ""
|
||||
host_all: true
|
||||
state: absent
|
||||
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:
|
||||
<<: *mysql_params
|
||||
user: ""
|
||||
host_all: true
|
||||
state: absent
|
||||
register: result
|
||||
|
||||
- name: assert changed is true for removing all blank users
|
||||
assert:
|
||||
that:
|
||||
- "result.changed == false"
|
|
@ -0,0 +1,118 @@
|
|||
# test code for resource_limits parameter
|
||||
- vars:
|
||||
mysql_parameters: &mysql_params
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
|
||||
block:
|
||||
|
||||
- name: Drop mysql user {{ user_name_1 }} if exists
|
||||
mysql_user:
|
||||
<<: *mysql_params
|
||||
name: '{{ user_name_1 }}'
|
||||
state: absent
|
||||
|
||||
- name: Create mysql user {{ user_name_1 }} with resource limits in check_mode
|
||||
mysql_user:
|
||||
<<: *mysql_params
|
||||
name: '{{ user_name_1 }}'
|
||||
password: '{{ user_password_1 }}'
|
||||
state: present
|
||||
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:
|
||||
<<: *mysql_params
|
||||
name: '{{ user_name_1 }}'
|
||||
password: '{{ user_password_1 }}'
|
||||
state: present
|
||||
resource_limits:
|
||||
MAX_QUERIES_PER_HOUR: 10
|
||||
MAX_CONNECTIONS_PER_HOUR: 5
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result is changed
|
||||
|
||||
- name: Check
|
||||
mysql_query:
|
||||
<<: *mysql_params
|
||||
query: >
|
||||
SELECT User FROM mysql.user WHERE User = '{{ user_name_1 }}' AND Host = 'localhost'
|
||||
AND max_questions = 10 AND max_connections = 5
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result.rowcount[0] == 1
|
||||
|
||||
- name: Try to set the same limits again in check mode
|
||||
mysql_user:
|
||||
<<: *mysql_params
|
||||
name: '{{ user_name_1 }}'
|
||||
password: '{{ user_password_1 }}'
|
||||
state: present
|
||||
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:
|
||||
<<: *mysql_params
|
||||
name: '{{ user_name_1 }}'
|
||||
password: '{{ user_password_1 }}'
|
||||
state: present
|
||||
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:
|
||||
<<: *mysql_params
|
||||
name: '{{ user_name_1 }}'
|
||||
password: '{{ user_password_1 }}'
|
||||
state: present
|
||||
resource_limits:
|
||||
MAX_QUERIES_PER_HOUR: 5
|
||||
MAX_CONNECTIONS_PER_HOUR: 5
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result is changed
|
||||
|
||||
- name: Check
|
||||
mysql_query:
|
||||
<<: *mysql_params
|
||||
query: >
|
||||
SELECT User FROM mysql.user WHERE User = '{{ user_name_1 }}' AND Host = 'localhost'
|
||||
AND max_questions = 5 AND max_connections = 5
|
||||
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')
|
|
@ -0,0 +1,55 @@
|
|||
- vars:
|
||||
mysql_parameters: &mysql_params
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
|
||||
block:
|
||||
|
||||
# Tests for priv parameter value passed as a dict
|
||||
- name: Create test databases
|
||||
mysql_db:
|
||||
<<: *mysql_params
|
||||
name: '{{ item }}'
|
||||
state: present
|
||||
loop:
|
||||
- data1
|
||||
- data2
|
||||
|
||||
- name: Create user with privileges
|
||||
mysql_user:
|
||||
<<: *mysql_params
|
||||
name: '{{ user_name_3 }}'
|
||||
password: '{{ user_password_3 }}'
|
||||
priv:
|
||||
"data1.*": "SELECT"
|
||||
"data2.*": "SELECT"
|
||||
state: present
|
||||
|
||||
- name: Run command to show privileges for user (expect privileges in stdout)
|
||||
command: "{{ mysql_command }} -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:
|
||||
<<: *mysql_params
|
||||
name: '{{ item }}'
|
||||
state: present
|
||||
loop:
|
||||
- data1
|
||||
- data2
|
||||
|
||||
- name: Drop test user
|
||||
mysql_user:
|
||||
<<: *mysql_params
|
||||
name: '{{ user_name_3 }}'
|
||||
state: absent
|
186
tests/integration/targets/test_mysql_user/tasks/test_privs.yml
Normal file
186
tests/integration/targets/test_mysql_user/tasks/test_privs.yml
Normal file
|
@ -0,0 +1,186 @@
|
|||
# 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/>.
|
||||
|
||||
- vars:
|
||||
mysql_parameters: &mysql_params
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
|
||||
block:
|
||||
|
||||
# ============================================================
|
||||
- name: create user with basic select privileges
|
||||
mysql_user:
|
||||
<<: *mysql_params
|
||||
name: '{{ user_name_2 }}'
|
||||
password: '{{ user_password_2 }}'
|
||||
priv: '*.*:SELECT'
|
||||
state: present
|
||||
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:
|
||||
<<: *mysql_params
|
||||
name: '{{ user_name_2 }}'
|
||||
password: '{{ user_password_2 }}'
|
||||
priv: '*.*:{{current_privilege}}'
|
||||
append_privs: '{{current_append_privs}}'
|
||||
state: present
|
||||
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_command }} -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:
|
||||
login_user: '{{ user_name_2 }}'
|
||||
login_password: '{{ user_password_2 }}'
|
||||
login_host: '{{ mysql_host }}'
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name: '{{ db_name }}'
|
||||
state: present
|
||||
ignore_errors: true
|
||||
|
||||
- name: run command to test that database was not created
|
||||
command: "{{ mysql_command }} -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:
|
||||
<<: *mysql_params
|
||||
name: '{{ user_name_2 }}'
|
||||
password: '{{ user_password_2 }}'
|
||||
priv: 'jmainguy.jmainguy:ALL'
|
||||
state: present
|
||||
register: result
|
||||
|
||||
- name: Assert that priv changed
|
||||
assert:
|
||||
that:
|
||||
- "result.changed == true"
|
||||
|
||||
- name: Add privs to a specific table (expect ok)
|
||||
mysql_user:
|
||||
<<: *mysql_params
|
||||
name: '{{ user_name_2 }}'
|
||||
password: '{{ user_password_2 }}'
|
||||
priv: 'jmainguy.jmainguy:ALL'
|
||||
state: present
|
||||
register: result
|
||||
|
||||
- name: Assert that priv did not change
|
||||
assert:
|
||||
that:
|
||||
- "result.changed == false"
|
||||
|
||||
# ============================================================
|
||||
- name: update user with all privileges
|
||||
mysql_user:
|
||||
<<: *mysql_params
|
||||
name: '{{ user_name_2 }}'
|
||||
password: '{{ user_password_2 }}'
|
||||
priv: '*.*:ALL'
|
||||
state: present
|
||||
|
||||
# - include: assert_user.yml user_name={{user_name_2}} priv='ALL PRIVILEGES'
|
||||
|
||||
- name: create database using user
|
||||
mysql_db:
|
||||
login_user: '{{ user_name_2 }}'
|
||||
login_password: '{{ user_password_2 }}'
|
||||
login_host: '{{ mysql_host }}'
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name: '{{ db_name }}'
|
||||
state: present
|
||||
|
||||
- name: run command to test database was created using user new privileges
|
||||
command: "{{ mysql_command }} -e \"SHOW CREATE DATABASE {{ db_name }}\""
|
||||
|
||||
- name: drop database using user
|
||||
mysql_db:
|
||||
login_user: '{{ user_name_2 }}'
|
||||
login_password: '{{ user_password_2 }}'
|
||||
login_host: '{{ mysql_host }}'
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name: '{{ db_name }}'
|
||||
state: absent
|
||||
|
||||
# ============================================================
|
||||
- name: update user with a long privileges list (mysql has a special multiline grant output)
|
||||
mysql_user:
|
||||
<<: *mysql_params
|
||||
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
|
||||
register: result
|
||||
|
||||
- name: Assert that priv changed
|
||||
assert:
|
||||
that:
|
||||
- "result.changed == true"
|
||||
|
||||
- name: Test idempotency (expect ok)
|
||||
mysql_user:
|
||||
<<: *mysql_params
|
||||
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
|
||||
register: result
|
||||
|
||||
- name: Assert that priv did not change
|
||||
assert:
|
||||
that:
|
||||
- "result.changed == false"
|
||||
|
||||
- name: remove username
|
||||
mysql_user:
|
||||
<<: *mysql_params
|
||||
name: '{{ user_name_2 }}'
|
||||
password: '{{ user_password_2 }}'
|
||||
state: absent
|
|
@ -0,0 +1,178 @@
|
|||
# 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/>.
|
||||
|
||||
- vars:
|
||||
mysql_parameters: &mysql_params
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
|
||||
block:
|
||||
|
||||
# ============================================================
|
||||
# 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:
|
||||
<<: *mysql_params
|
||||
name: '{{ user_name_1 }}'
|
||||
password: '{{ user_password_1 }}'
|
||||
priv: '*.*:ALL'
|
||||
state: present
|
||||
|
||||
- name: create user2 state=present with a password
|
||||
mysql_user:
|
||||
<<: *mysql_params
|
||||
name: '{{ user_name_2 }}'
|
||||
password: '{{ user_password_2 }}'
|
||||
priv: '*.*:ALL'
|
||||
state: present
|
||||
|
||||
- name: store user2 grants with old password (mysql 5.7.6 and newer)
|
||||
command: "{{ mysql_command }} -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_command }} -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:
|
||||
<<: *mysql_params
|
||||
name: '{{ user_name_2 }}'
|
||||
password: '{{ user_password_2 }}'
|
||||
priv: '*.*:ALL'
|
||||
state: present
|
||||
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:
|
||||
<<: *mysql_params
|
||||
name: '{{ user_name_2 }}'
|
||||
password: '{{ user_password_1 }}'
|
||||
state: present
|
||||
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_command }} -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_command }} -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:
|
||||
login_user: '{{ user_name_2 }}'
|
||||
login_password: '{{ user_password_2 }}'
|
||||
login_host: '{{ mysql_host }}'
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name: '{{ db_name }}'
|
||||
state: present
|
||||
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:
|
||||
login_user: '{{ user_name_2 }}'
|
||||
login_password: '{{ user_password_1 }}'
|
||||
login_host: '{{ mysql_host }}'
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
name: '{{ db_name }}'
|
||||
state: present
|
||||
register: result
|
||||
|
||||
- name: assert output message that database is created with new password
|
||||
assert:
|
||||
that:
|
||||
- "result.changed == true"
|
||||
|
||||
- name: remove database
|
||||
mysql_db:
|
||||
<<: *mysql_params
|
||||
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:
|
||||
<<: *mysql_params
|
||||
name: jmainguy
|
||||
password: '*0cb5b86f23fdc24db19a29b8854eb860cbc47793'
|
||||
encrypted: yes
|
||||
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:
|
||||
<<: *mysql_params
|
||||
name: jmainguy
|
||||
password: 'Fdt8fd^34ds'
|
||||
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:
|
||||
<<: *mysql_params
|
||||
name: jmainguy
|
||||
state: absent
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
# defaults file for test_mysql_variables
|
||||
mysql_user: root
|
||||
mysql_password: msandbox
|
||||
mysql_primary_port: 3307
|
|
@ -0,0 +1,2 @@
|
|||
dependencies:
|
||||
- setup_mysql
|
|
@ -0,0 +1,25 @@
|
|||
# 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"
|
|
@ -0,0 +1,36 @@
|
|||
# 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_command }} \"-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"
|
|
@ -0,0 +1,40 @@
|
|||
# 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_command }} \"-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"
|
|
@ -0,0 +1 @@
|
|||
- import_tasks: mysql_variables.yml
|
|
@ -0,0 +1,368 @@
|
|||
# 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
|
||||
#
|
||||
- vars:
|
||||
mysql_parameters: &mysql_params
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
|
||||
block:
|
||||
|
||||
- name: alias mysql command to include default options
|
||||
set_fact:
|
||||
mysql_command: "mysql -u{{ mysql_user }} -p{{ mysql_password }} -P{{ mysql_primary_port }} --protocol=tcp"
|
||||
|
||||
- set_fact:
|
||||
set_name: 'version'
|
||||
|
||||
- name: read mysql variables (expect changed=false)
|
||||
mysql_variables:
|
||||
<<: *mysql_params
|
||||
variable: '{{ set_name }}'
|
||||
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:
|
||||
<<: *mysql_params
|
||||
variable: '{{ set_name }}'
|
||||
value: '{{ set_value }}'
|
||||
|
||||
- name: update mysql variable to same value (expect changed=false)
|
||||
mysql_variables:
|
||||
<<: *mysql_params
|
||||
variable: '{{ set_name }}'
|
||||
value: '{{ set_value }}'
|
||||
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:
|
||||
<<: *mysql_params
|
||||
variable: '{{ set_name }}'
|
||||
value: '200'
|
||||
|
||||
- name: update mysql variable value (expect changed=true)
|
||||
mysql_variables:
|
||||
<<: *mysql_params
|
||||
variable: '{{ set_name }}'
|
||||
value: '{{ set_value }}'
|
||||
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:
|
||||
<<: *mysql_params
|
||||
variable: '{{ set_name }}'
|
||||
value: "200"
|
||||
|
||||
- name: update mysql variable value (expect changed=true)
|
||||
mysql_variables:
|
||||
<<: *mysql_params
|
||||
variable: '{{ set_name }}'
|
||||
value: '{{ set_value }}'
|
||||
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:
|
||||
<<: *mysql_params
|
||||
variable: '{{ set_name }}'
|
||||
value: 200
|
||||
|
||||
- name: update mysql variable value (expect changed=true)
|
||||
mysql_variables:
|
||||
<<: *mysql_params
|
||||
variable: '{{ set_name }}'
|
||||
value: '{{ set_value }}'
|
||||
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:
|
||||
<<: *mysql_params
|
||||
variable: max_tmp_tables
|
||||
value: "1024*4"
|
||||
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:
|
||||
<<: *mysql_params
|
||||
variable: max_tmp_tables
|
||||
value: -1
|
||||
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:
|
||||
<<: *mysql_params
|
||||
variable: max_tmp_tables
|
||||
value: TEST
|
||||
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:
|
||||
<<: *mysql_params
|
||||
variable: my_sql_variable
|
||||
value: ON
|
||||
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:
|
||||
<<: *mysql_params
|
||||
variable: character_set_system
|
||||
value: utf16
|
||||
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
|
||||
#
|
||||
- set_fact:
|
||||
set_name: wait_timeout
|
||||
set_value: 77
|
||||
|
||||
- name: query mysql_variable using login_user and password_password
|
||||
mysql_variables:
|
||||
<<: *mysql_params
|
||||
variable: '{{ set_name }}'
|
||||
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:
|
||||
<<: *mysql_params
|
||||
variable: '{{ set_name }}'
|
||||
value: 20
|
||||
register: result
|
||||
|
||||
- name: update mysql variable value using user login and password (expect changed=true)
|
||||
mysql_variables:
|
||||
<<: *mysql_params
|
||||
variable: '{{set_name}}'
|
||||
value: '{{set_value}}'
|
||||
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:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: 'wrongpassword'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
variable: '{{ set_name }}'
|
||||
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:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: 'wrongpassword'
|
||||
login_host: 127.0.0.1
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
variable: '{{ set_name }}'
|
||||
value: '{{ set_value }}'
|
||||
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:
|
||||
login_user: '{{ mysql_user }}'
|
||||
login_password: '{{ mysql_password }}'
|
||||
login_host: '12.0.0.9'
|
||||
login_port: '{{ mysql_primary_port }}'
|
||||
variable: wait_timeout
|
||||
connect_timeout: 5
|
||||
register: result
|
||||
ignore_errors: true
|
||||
|
||||
- include: assert_fail_msg.yml output={{ result }} msg='unable to connect to database'
|
||||
|
||||
#=========================================
|
||||
# Check mode 'persist' and 'persist_only':
|
||||
#
|
||||
- name: update mysql variable value (expect changed=true) in persist mode
|
||||
mysql_variables:
|
||||
<<: *mysql_params
|
||||
variable: '{{ set_name }}'
|
||||
value: '{{ set_value }}'
|
||||
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:
|
||||
<<: *mysql_params
|
||||
variable: '{{ set_name }}'
|
||||
value: '{{ set_value }}'
|
||||
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:
|
||||
<<: *mysql_params
|
||||
variable: '{{ set_name }}'
|
||||
value: '200'
|
||||
mode: persist
|
||||
|
||||
- name: update mysql variable value (expect changed=true) in persist_only mode
|
||||
mysql_variables:
|
||||
<<: *mysql_params
|
||||
variable: '{{ set_name }}'
|
||||
value: '{{ set_value }}'
|
||||
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:
|
||||
<<: *mysql_params
|
||||
variable: '{{ set_name }}'
|
||||
value: '{{ set_value }}'
|
||||
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:
|
||||
<<: *mysql_params
|
||||
variable: '{{ set_name }}'
|
||||
value: '{{ set_value }}'
|
||||
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:
|
||||
# <<: *mysql_params
|
||||
# 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"]
|
8
tests/sanity/ignore-2.10.txt
Normal file
8
tests/sanity/ignore-2.10.txt
Normal file
|
@ -0,0 +1,8 @@
|
|||
plugins/modules/mysql_db.py validate-modules:doc-elements-mismatch
|
||||
plugins/modules/mysql_db.py validate-modules:parameter-list-no-elements
|
||||
plugins/modules/mysql_db.py validate-modules:use-run-command-not-popen
|
||||
plugins/modules/mysql_info.py validate-modules:doc-elements-mismatch
|
||||
plugins/modules/mysql_info.py validate-modules:parameter-list-no-elements
|
||||
plugins/modules/mysql_query.py validate-modules:parameter-list-no-elements
|
||||
plugins/modules/mysql_user.py validate-modules:undocumented-parameter
|
||||
plugins/modules/mysql_variables.py validate-modules:doc-required-mismatch
|
8
tests/sanity/ignore-2.11.txt
Normal file
8
tests/sanity/ignore-2.11.txt
Normal file
|
@ -0,0 +1,8 @@
|
|||
plugins/modules/mysql_db.py validate-modules:doc-elements-mismatch
|
||||
plugins/modules/mysql_db.py validate-modules:parameter-list-no-elements
|
||||
plugins/modules/mysql_db.py validate-modules:use-run-command-not-popen
|
||||
plugins/modules/mysql_info.py validate-modules:doc-elements-mismatch
|
||||
plugins/modules/mysql_info.py validate-modules:parameter-list-no-elements
|
||||
plugins/modules/mysql_query.py validate-modules:parameter-list-no-elements
|
||||
plugins/modules/mysql_user.py validate-modules:undocumented-parameter
|
||||
plugins/modules/mysql_variables.py validate-modules:doc-required-mismatch
|
3
tests/sanity/ignore-2.9.txt
Normal file
3
tests/sanity/ignore-2.9.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
plugins/modules/mysql_db.py validate-modules:use-run-command-not-popen
|
||||
plugins/modules/mysql_user.py validate-modules:parameter-type-not-in-doc
|
||||
plugins/modules/mysql_user.py validate-modules:undocumented-parameter
|
Loading…
Add table
Reference in a new issue