Browse Source

initial commit (#1)

* Migrated existing modules (and docs fragment) from community.general
* Added basic role
* Added integration tests for modules
* Implemented git actions workflows
local_integrationtest
Ben Mildren 2 years ago committed by GitHub
parent
commit
6d17e8cbc4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 94
      .github/workflows/ansible-test-plugins.yml
  2. 56
      .github/workflows/ansible-test-roles.yml
  3. 59
      .github/workflows/ansible-test.yml
  4. 3
      .gitignore
  5. 68
      README.md
  6. 16
      galaxy.yml
  7. 31
      plugins/README.md
  8. 61
      plugins/doc_fragments/proxysql.py
  9. 110
      plugins/module_utils/mysql.py
  10. 518
      plugins/modules/proxysql_backend_servers.py
  11. 269
      plugins/modules/proxysql_global_variables.py
  12. 218
      plugins/modules/proxysql_manage_config.py
  13. 481
      plugins/modules/proxysql_mysql_users.py
  14. 632
      plugins/modules/proxysql_query_rules.py
  15. 381
      plugins/modules/proxysql_replication_hostgroups.py
  16. 424
      plugins/modules/proxysql_scheduler.py
  17. 11
      roles/proxysql/.yamllint
  18. 43
      roles/proxysql/README.md
  19. 171
      roles/proxysql/defaults/main.yml
  20. 33
      roles/proxysql/handlers/main.yml
  21. 20
      roles/proxysql/meta/main.yml
  22. 5
      roles/proxysql/molecule/default/converge.yml
  23. 46
      roles/proxysql/molecule/default/molecule.yml
  24. 20
      roles/proxysql/molecule/default/prepare.yml
  25. 81
      roles/proxysql/molecule/default/tests/test_default.py
  26. 32
      roles/proxysql/tasks/config.yml
  27. 90
      roles/proxysql/tasks/install.yml
  28. 22
      roles/proxysql/tasks/main.yml
  29. 8
      roles/proxysql/tasks/setvars.yml
  30. 16
      roles/proxysql/tasks/users.yml
  31. 6
      roles/proxysql/templates/client.my.cnf.j2
  32. 21
      roles/proxysql/templates/proxysql.cnf.j2
  33. 385
      roles/proxysql/vars/main.yml
  34. 5
      tests/integration/targets/setup_proxysql/defaults/main.yml
  35. 12
      tests/integration/targets/setup_proxysql/tasks/config.yml
  36. 28
      tests/integration/targets/setup_proxysql/tasks/install.yml
  37. 3
      tests/integration/targets/setup_proxysql/tasks/main.yml
  38. 13
      tests/integration/targets/setup_proxysql/vars/main.yml
  39. 8
      tests/integration/targets/test_proxysql_backend_servers/defaults/main.yml
  40. 3
      tests/integration/targets/test_proxysql_backend_servers/meta/main.yml
  41. 57
      tests/integration/targets/test_proxysql_backend_servers/tasks/base_test.yml
  42. 12
      tests/integration/targets/test_proxysql_backend_servers/tasks/cleanup_test_servers.yml
  43. 83
      tests/integration/targets/test_proxysql_backend_servers/tasks/main.yml
  44. 12
      tests/integration/targets/test_proxysql_backend_servers/tasks/setup_test_server.yml
  45. 6
      tests/integration/targets/test_proxysql_backend_servers/tasks/teardown.yml
  46. 31
      tests/integration/targets/test_proxysql_backend_servers/tasks/test_create_backend_server.yml
  47. 31
      tests/integration/targets/test_proxysql_backend_servers/tasks/test_create_backend_server_in_memory_only.yml
  48. 31
      tests/integration/targets/test_proxysql_backend_servers/tasks/test_create_backend_server_with_delayed_persist.yml
  49. 30
      tests/integration/targets/test_proxysql_backend_servers/tasks/test_create_using_check_mode.yml
  50. 31
      tests/integration/targets/test_proxysql_backend_servers/tasks/test_delete_backend_server.yml
  51. 31
      tests/integration/targets/test_proxysql_backend_servers/tasks/test_delete_backend_server_in_memory_only.yml
  52. 31
      tests/integration/targets/test_proxysql_backend_servers/tasks/test_delete_backend_server_with_delayed_persist.yml
  53. 30
      tests/integration/targets/test_proxysql_backend_servers/tasks/test_delete_using_check_mode.yml
  54. 8
      tests/integration/targets/test_proxysql_global_variables/defaults/main.yml
  55. 3
      tests/integration/targets/test_proxysql_global_variables/meta/main.yml
  56. 54
      tests/integration/targets/test_proxysql_global_variables/tasks/base_test.yml
  57. 12
      tests/integration/targets/test_proxysql_global_variables/tasks/cleanup_global_variables.yml
  58. 48
      tests/integration/targets/test_proxysql_global_variables/tasks/main.yml
  59. 12
      tests/integration/targets/test_proxysql_global_variables/tasks/setup_global_variables.yml
  60. 12
      tests/integration/targets/test_proxysql_global_variables/tasks/setvars.yml
  61. 6
      tests/integration/targets/test_proxysql_global_variables/tasks/teardown.yml
  62. 31
      tests/integration/targets/test_proxysql_global_variables/tasks/test_update_variable_value.yml
  63. 31
      tests/integration/targets/test_proxysql_global_variables/tasks/test_update_variable_value_in_memory_only.yml
  64. 30
      tests/integration/targets/test_proxysql_global_variables/tasks/test_update_variable_value_using_check_mode.yml
  65. 31
      tests/integration/targets/test_proxysql_global_variables/tasks/test_update_variable_value_with_delayed_persist.yml
  66. 8
      tests/integration/targets/test_proxysql_mysql_users/defaults/main.yml
  67. 3
      tests/integration/targets/test_proxysql_mysql_users/meta/main.yml
  68. 57
      tests/integration/targets/test_proxysql_mysql_users/tasks/base_test.yml
  69. 12
      tests/integration/targets/test_proxysql_mysql_users/tasks/cleanup_test_users.yml
  70. 83
      tests/integration/targets/test_proxysql_mysql_users/tasks/main.yml
  71. 12
      tests/integration/targets/test_proxysql_mysql_users/tasks/setup_test_user.yml
  72. 6
      tests/integration/targets/test_proxysql_mysql_users/tasks/teardown.yml
  73. 31
      tests/integration/targets/test_proxysql_mysql_users/tasks/test_create_mysql_user.yml
  74. 31
      tests/integration/targets/test_proxysql_mysql_users/tasks/test_create_mysql_user_in_memory_only.yml
  75. 31
      tests/integration/targets/test_proxysql_mysql_users/tasks/test_create_mysql_user_with_delayed_persist.yml
  76. 30
      tests/integration/targets/test_proxysql_mysql_users/tasks/test_create_using_check_mode.yml
  77. 31
      tests/integration/targets/test_proxysql_mysql_users/tasks/test_delete_mysql_user.yml
  78. 31
      tests/integration/targets/test_proxysql_mysql_users/tasks/test_delete_mysql_user_in_memory_only.yml
  79. 31
      tests/integration/targets/test_proxysql_mysql_users/tasks/test_delete_mysql_user_with_delayed_persist.yml
  80. 30
      tests/integration/targets/test_proxysql_mysql_users/tasks/test_delete_using_check_mode.yml
  81. 12
      tests/integration/targets/test_proxysql_query_rules/defaults/main.yml
  82. 3
      tests/integration/targets/test_proxysql_query_rules/meta/main.yml
  83. 61
      tests/integration/targets/test_proxysql_query_rules/tasks/base_test.yml
  84. 12
      tests/integration/targets/test_proxysql_query_rules/tasks/cleanup_test_query_rules.yml
  85. 83
      tests/integration/targets/test_proxysql_query_rules/tasks/main.yml
  86. 12
      tests/integration/targets/test_proxysql_query_rules/tasks/setup_test_query_rule.yml
  87. 6
      tests/integration/targets/test_proxysql_query_rules/tasks/teardown.yml
  88. 31
      tests/integration/targets/test_proxysql_query_rules/tasks/test_create_query_rule.yml
  89. 31
      tests/integration/targets/test_proxysql_query_rules/tasks/test_create_query_rule_in_memory_only.yml
  90. 31
      tests/integration/targets/test_proxysql_query_rules/tasks/test_create_query_rule_with_delayed_persist.yml
  91. 30
      tests/integration/targets/test_proxysql_query_rules/tasks/test_create_using_check_mode.yml
  92. 31
      tests/integration/targets/test_proxysql_query_rules/tasks/test_delete_query_rule.yml
  93. 30
      tests/integration/targets/test_proxysql_query_rules/tasks/test_delete_query_rule_in_memory_only.yml
  94. 31
      tests/integration/targets/test_proxysql_query_rules/tasks/test_delete_query_rule_with_delayed_persist.yml
  95. 30
      tests/integration/targets/test_proxysql_query_rules/tasks/test_delete_using_check_mode.yml
  96. 9
      tests/integration/targets/test_proxysql_replication_hostgroups/defaults/main.yml
  97. 3
      tests/integration/targets/test_proxysql_replication_hostgroups/meta/main.yml
  98. 58
      tests/integration/targets/test_proxysql_replication_hostgroups/tasks/base_test.yml
  99. 12
      tests/integration/targets/test_proxysql_replication_hostgroups/tasks/cleanup_test_replication_hostgroups.yml
  100. 83
      tests/integration/targets/test_proxysql_replication_hostgroups/tasks/main.yml
  101. Some files were not shown because too many files have changed in this diff Show More

94
.github/workflows/ansible-test-plugins.yml

@ -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:
proxysql_version_file: "./ansible_collections/community/proxysql/tests/integration/targets/setup_proxysql/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/proxysql
- 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/proxysql
integration:
name: "Integration (Python: ${{ matrix.python }}, Ansible: ${{ matrix.ansible }}, ProxySQL: ${{ matrix.proxysql }})"
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
proxysql:
- 2.0.12
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/proxysql
- 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 ProxySQL version (${{ matrix.proxysql }})
run: "sed -i 's/^proxysql_version:.*/proxysql_version: \"${{ matrix.proxysql }}\"/g' ${{ env.proxysql_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/proxysql
- name: Generate coverage report.
run: ansible-test coverage xml -v --requirements --group-by command --group-by version
working-directory: ./ansible_collections/community/proxysql
- uses: codecov/codecov-action@v1
with:
fail_ci_if_error: false

56
.github/workflows/ansible-test-roles.yml

@ -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 }}, ProxySQL: ${{ matrix.proxysql }})"
runs-on: ubuntu-latest
env:
PY_COLORS: 1
ANSIBLE_FORCE_COLOR: 1
strategy:
matrix:
proxysql:
- 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/proxysql
- 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/proxysql

59
.github/workflows/ansible-test.yml

@ -1,59 +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:
path: ansible_collections/community/proxysql
- 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
# 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:
# path: ansible_collections/community/proxysql
#
# - 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

@ -129,3 +129,6 @@ dmypy.json
# Pyre type checker
.pyre/
# MacOS
.DS_Store

68
README.md

@ -1,58 +1,56 @@
# 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.
# ProxySQL collection for Ansible
[![Plugins CI](https://github.com/ansible-collections/community.proxysql/workflows/Plugins%20CI/badge.svg?event=push)](https://github.com/ansible-collections/community.proxysql/actions?query=workflow%3A"Plugins+CI") [![Roles CI](https://github.com/ansible-collections/community.proxysql/workflows/Roles%20CI/badge.svg?event=push)](https://github.com/ansible-collections/community.proxysql/actions?query=workflow%3A"Roles+CI") [![Codecov](https://img.shields.io/codecov/c/github/ansible-collections/community.proxysql)](https://codecov.io/gh/ansible-collections/community.proxysql)
# Foo Collection
<!-- Add CI and code coverage badges here. Samples included below. -->
[![CI](https://github.com/ansible-collections/REPONAMEHERE/workflows/CI/badge.svg?event=push)](https://github.com/ansible-collections/REPONAMEHERE/actions) [![Codecov](https://img.shields.io/codecov/c/github/ansible-collections/REPONAMEHERE)](https://codecov.io/gh/ansible-collections/REPONAMEHERE)
## Included content
<!-- Describe the collection and why a user would want to use it. What does the collection do? -->
- **Modules**:
- [proxysql_backend_servers](https://docs.ansible.com/ansible/latest/modules/proxysql_backend_servers_module.html)
- [proxysql_global_variables](https://docs.ansible.com/ansible/latest/modules/proxysql_global_variables_module.html)
- [proxysql_manage_config](https://docs.ansible.com/ansible/latest/modules/proxysql_manage_config_module.html)
- [proxysql_mysql_users](https://docs.ansible.com/ansible/latest/modules/proxysql_mysql_users_module.html)
- [proxysql_query_rules](https://docs.ansible.com/ansible/latest/modules/proxysql_query_rules_module.html)
- [proxysql_replication_hostgroups](https://docs.ansible.com/ansible/latest/modules/proxysql_replication_hostgroups_module.html)
- [proxysql_scheduler](https://docs.ansible.com/ansible/latest/modules/proxysql_scheduler_module.html)
- **Roles**:
- proxysql
## Tested with Ansible
<!-- List the versions of Ansible the collection has been tested with. Must match what is in galaxy.yml. -->
- 2.9
- 2.10
- devel
## 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. -->
## Included content
The ProxySQL modules rely on a MySQL connector. The list of supported drivers is below:
<!-- 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. -->
- [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. -->
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). -->
### Installing the Collection from Ansible Galaxy
## Release notes
<!--Add a link to a changelog.md file or an external docsite to cover this information. -->
Before using the ProxySQL collection, you need to install it with the Ansible Galaxy CLI:
## Roadmap
```bash
ansible-galaxy collection install community.proxysql
```
<!-- Optional. Include the roadmap for this collection, and the proposed release/versioning strategy so users can anticipate the upgrade/update cycle. -->
You can also include it in a `requirements.yml` file and install it via `ansible-galaxy collection install -r requirements.yml`, using the format:
## More information
```yaml
---
collections:
- name: community.proxysql
version: v0.1.0
```
<!-- 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)
See [Ansible Using collections](https://docs.ansible.com/ansible/latest/user_guide/collections_using.html) for more details.
## 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. -->
GNU General Public License v3.0 or later.
See [LICENSE](https://www.gnu.org/licenses/gpl-3.0.txt) to see the full text.

16
galaxy.yml

@ -0,0 +1,16 @@
namespace: community
name: proxysql
version: 1.0.0
readme: README.md
authors:
- Ben Mildren (@bmildren)
description: ProxySQL collection for Ansible
license_file: LICENSE
tags:
- database
- mysql
- proxysql
repository: https://github.com/ansible-collections/community.proxysql
documentation: https://github.com/ansible-collections/community.proxysql
homepage: https://github.com/ansible-collections/community.proxysql
issues: https://github.com/ansible-collections/community.proxysql/issues

31
plugins/README.md

@ -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).

61
plugins/doc_fragments/proxysql.py

@ -0,0 +1,61 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2017, Ansible Project
# 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):
# Documentation fragment for ProxySQL connectivity
CONNECTIVITY = r'''
options:
login_user:
description:
- The username used to authenticate to ProxySQL admin interface.
type: str
login_password:
description:
- The password used to authenticate to ProxySQL admin interface.
type: str
login_host:
description:
- The host used to connect to ProxySQL admin interface.
type: str
default: '127.0.0.1'
login_unix_socket:
description:
- The socket used to connect to ProxySQL admin interface.
type: str
login_port:
description:
- The port used to connect to ProxySQL admin interface.
type: int
default: 6032
config_file:
description:
- Specify a config file from which I(login_user) and I(login_password)
are to be read.
type: path
default: ''
requirements:
- PyMySQL (Python 2.7 and Python 3.X), or
- MySQLdb (Python 2.x)
'''
# Documentation fragment for managing ProxySQL configuration
MANAGING_CONFIG = r'''
options:
save_to_disk:
description:
- Save config to sqlite db on disk to persist the configuration.
type: bool
default: 'yes'
load_to_runtime:
description:
- Dynamically load config to runtime memory.
type: bool
default: 'yes'
'''

110
plugins/module_utils/mysql.py

@ -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']),
)

518
plugins/modules/proxysql_backend_servers.py

@ -0,0 +1,518 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2017, Ansible Project
# 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 = '''
---
module: proxysql_backend_servers
author: "Ben Mildren (@bmildren)"
short_description: Adds or removes mysql hosts from proxysql admin interface.
description:
- The M(community.general.proxysql_backend_servers) module adds or removes mysql hosts using
the proxysql admin interface.
options:
hostgroup_id:
description:
- The hostgroup in which this mysqld instance is included. An instance
can be part of one or more hostgroups.
type: int
default: 0
hostname:
description:
- The ip address at which the mysqld instance can be contacted.
type: str
required: True
port:
description:
- The port at which the mysqld instance can be contacted.
type: int
default: 3306
status:
description:
- ONLINE - Backend server is fully operational.
OFFLINE_SOFT - When a server is put into C(OFFLINE_SOFT) mode,
connections are kept in use until the current
transaction is completed. This allows to gracefully
detach a backend.
OFFLINE_HARD - When a server is put into C(OFFLINE_HARD) mode, the
existing connections are dropped, while new incoming
connections aren't accepted either.
If omitted the proxysql database default for I(status) is C(ONLINE).
type: str
choices: [ "ONLINE", "OFFLINE_SOFT", "OFFLINE_HARD"]
weight:
description:
- The bigger the weight of a server relative to other weights, the higher
the probability of the server being chosen from the hostgroup. If
omitted the proxysql database default for I(weight) is 1.
type: int
compression:
description:
- If the value of I(compression) is greater than 0, new connections to
that server will use compression. If omitted the proxysql database
default for I(compression) is 0.
type: int
max_connections:
description:
- The maximum number of connections ProxySQL will open to this backend
server. If omitted the proxysql database default for I(max_connections)
is 1000.
type: int
max_replication_lag:
description:
- If greater than 0, ProxySQL will regularly monitor replication lag. If
replication lag goes above I(max_replication_lag), proxysql will
temporarily shun the server until replication catches up. If omitted
the proxysql database default for I(max_replication_lag) is 0.
type: int
use_ssl:
description:
- If I(use_ssl) is set to C(True), connections to this server will be
made using SSL connections. If omitted the proxysql database default
for I(use_ssl) is C(False).
type: bool
max_latency_ms:
description:
- Ping time is monitored regularly. If a host has a ping time greater
than I(max_latency_ms) it is excluded from the connection pool
(although the server stays ONLINE). If omitted the proxysql database
default for I(max_latency_ms) is 0.
type: int
comment:
description:
- Text field that can be used for any purposed defined by the user.
Could be a description of what the host stores, a reminder of when the
host was added or disabled, or a JSON processed by some checker script.
type: str
default: ''
state:
description:
- When C(present) - adds the host, when C(absent) - removes the host.
type: str
choices: [ "present", "absent" ]
default: present
extends_documentation_fragment:
- community.proxysql.proxysql.managing_config
- community.proxysql.proxysql.connectivity
'''
EXAMPLES = '''
---
# This example adds a server, it saves the mysql server config to disk, but
# avoids loading the mysql server config to runtime (this might be because
# several servers are being added and the user wants to push the config to
# runtime in a single batch using the community.general.proxysql_manage_config
# module). It uses supplied credentials to connect to the proxysql admin
# interface.
- name: Add a server
proxysql_backend_servers:
login_user: 'admin'
login_password: 'admin'
hostname: 'mysql01'
state: present
load_to_runtime: False
# This example removes a server, saves the mysql server config to disk, and
# dynamically loads the mysql server config to runtime. It uses credentials
# in a supplied config file to connect to the proxysql admin interface.
- name: Remove a server
proxysql_backend_servers:
config_file: '~/proxysql.cnf'
hostname: 'mysql02'
state: absent
'''
RETURN = '''
stdout:
description: The mysql host modified or removed from proxysql
returned: On create/update will return the newly modified host, on delete
it will return the deleted record.
type: dict
"sample": {
"changed": true,
"hostname": "192.168.52.1",
"msg": "Added server to mysql_hosts",
"server": {
"comment": "",
"compression": "0",
"hostgroup_id": "1",
"hostname": "192.168.52.1",
"max_connections": "1000",
"max_latency_ms": "0",
"max_replication_lag": "0",
"port": "3306",
"status": "ONLINE",
"use_ssl": "0",
"weight": "1"
},
"state": "present"
}
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.proxysql.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
# ===========================================
# proxysql module specific support methods.
#
def perform_checks(module):
if module.params["login_port"] < 0 \
or module.params["login_port"] > 65535:
module.fail_json(
msg="login_port must be a valid unix port number (0-65535)"
)
if module.params["port"] < 0 \
or module.params["port"] > 65535:
module.fail_json(
msg="port must be a valid unix port number (0-65535)"
)
if module.params["compression"]:
if module.params["compression"] < 0 \
or module.params["compression"] > 102400:
module.fail_json(
msg="compression must be set between 0 and 102400"
)
if module.params["max_replication_lag"]:
if module.params["max_replication_lag"] < 0 \
or module.params["max_replication_lag"] > 126144000:
module.fail_json(
msg="max_replication_lag must be set between 0 and 102400"
)
if mysql_driver is None:
module.fail_json(msg=mysql_driver_fail_msg)
def save_config_to_disk(cursor):
cursor.execute("SAVE MYSQL SERVERS TO DISK")
return True
def load_config_to_runtime(cursor):
cursor.execute("LOAD MYSQL SERVERS TO RUNTIME")
return True
class ProxySQLServer(object):
def __init__(self, module):
self.state = module.params["state"]
self.save_to_disk = module.params["save_to_disk"]
self.load_to_runtime = module.params["load_to_runtime"]
self.hostgroup_id = module.params["hostgroup_id"]
self.hostname = module.params["hostname"]
self.port = module.params["port"]
config_data_keys = ["status",
"weight",
"compression",
"max_connections",
"max_replication_lag",
"use_ssl",
"max_latency_ms",
"comment"]
self.config_data = dict((k, module.params[k])
for k in config_data_keys)
def check_server_config_exists(self, cursor):
query_string = \
"""SELECT count(*) AS `host_count`
FROM mysql_servers
WHERE hostgroup_id = %s
AND hostname = %s
AND port = %s"""
query_data = \
[self.hostgroup_id,
self.hostname,
self.port]
cursor.execute(query_string, query_data)
check_count = cursor.fetchone()
return (int(check_count['host_count']) > 0)
def check_server_config(self, cursor):
query_string = \
"""SELECT count(*) AS `host_count`
FROM mysql_servers
WHERE hostgroup_id = %s
AND hostname = %s
AND port = %s"""
query_data = \
[self.hostgroup_id,
self.hostname,
self.port]
for col, val in iteritems(self.config_data):
if val is not None:
query_data.append(val)
query_string += "\n AND " + col + " = %s"
cursor.execute(query_string, query_data)
check_count = cursor.fetchone()
if isinstance(check_count, tuple):
return int(check_count[0]) > 0
return (int(check_count['host_count']) > 0)
def get_server_config(self, cursor):
query_string = \
"""SELECT *
FROM mysql_servers
WHERE hostgroup_id = %s
AND hostname = %s
AND port = %s"""
query_data = \
[self.hostgroup_id,
self.hostname,
self.port]
cursor.execute(query_string, query_data)
server = cursor.fetchone()
return server
def create_server_config(self, cursor):
query_string = \
"""INSERT INTO mysql_servers (
hostgroup_id,
hostname,
port"""
cols = 3
query_data = \
[self.hostgroup_id,
self.hostname,
self.port]
for col, val in iteritems(self.config_data):
if val is not None:
cols += 1
query_data.append(val)
query_string += ",\n" + col
query_string += \
(")\n" +
"VALUES (" +
"%s ," * cols)
query_string = query_string[:-2]
query_string += ")"
cursor.execute(query_string, query_data)
return True
def update_server_config(self, cursor):
query_string = """UPDATE mysql_servers"""
cols = 0
query_data = []
for col, val in iteritems(self.config_data):
if val is not None:
cols += 1
query_data.append(val)
if cols == 1:
query_string += "\nSET " + col + "= %s,"
else:
query_string += "\n " + col + " = %s,"
query_string = query_string[:-1]
query_string += ("\nWHERE hostgroup_id = %s\n AND hostname = %s" +
"\n AND port = %s")
query_data.append(self.hostgroup_id)
query_data.append(self.hostname)
query_data.append(self.port)
cursor.execute(query_string, query_data)
return True
def delete_server_config(self, cursor):
query_string = \
"""DELETE FROM mysql_servers
WHERE hostgroup_id = %s
AND hostname = %s
AND port = %s"""
query_data = \
[self.hostgroup_id,
self.hostname,
self.port]
cursor.execute(query_string, query_data)
return True
def manage_config(self, cursor, state):
if state:
if self.save_to_disk:
save_config_to_disk(cursor)
if self.load_to_runtime:
load_config_to_runtime(cursor)
def create_server(self, check_mode, result, cursor):
if not check_mode:
result['changed'] = \
self.create_server_config(cursor)
result['msg'] = "Added server to mysql_hosts"
result['server'] = \
self.get_server_config(cursor)
self.manage_config(cursor,
result['changed'])
else:
result['changed'] = True
result['msg'] = ("Server would have been added to" +
" mysql_hosts, however check_mode" +
" is enabled.")
def update_server(self, check_mode, result, cursor):
if not check_mode:
result['changed'] = \
self.update_server_config(cursor)
result['msg'] = "Updated server in mysql_hosts"
result['server'] = \
self.get_server_config(cursor)
self.manage_config(cursor,
result['changed'])
else:
result['changed'] = True
result['msg'] = ("Server would have been updated in" +
" mysql_hosts, however check_mode" +
" is enabled.")
def delete_server(self, check_mode, result, cursor):
if not check_mode:
result['server'] = \
self.get_server_config(cursor)
result['changed'] = \
self.delete_server_config(cursor)
result['msg'] = "Deleted server from mysql_hosts"
self.manage_config(cursor,
result['changed'])
else:
result['changed'] = True
result['msg'] = ("Server would have been deleted from" +
" mysql_hosts, however check_mode is" +
" enabled.")
# ===========================================
# Module execution.
#
def main():
module = AnsibleModule(
argument_spec=dict(
login_user=dict(default=None, type='str'),
login_password=dict(default=None, no_log=True, type='str'),
login_host=dict(default='127.0.0.1'),
login_unix_socket=dict(default=None),
login_port=dict(default=6032, type='int'),
config_file=dict(default='', type='path'),
hostgroup_id=dict(default=0, type='int'),
hostname=dict(required=True, type='str'),
port=dict(default=3306, type='int'),
status=dict(choices=['ONLINE',
'OFFLINE_SOFT',
'OFFLINE_HARD']),
weight=dict(type='int'),
compression=dict(type='int'),
max_connections=dict(type='int'),
max_replication_lag=dict(type='int'),
use_ssl=dict(type='bool'),
max_latency_ms=dict(type='int'),
comment=dict(default='', type='str'),
state=dict(default='present', choices=['present',
'absent']),
save_to_disk=dict(default=True, type='bool'),
load_to_runtime=dict(default=True, type='bool')
),
supports_check_mode=True
)
perform_checks(module)
login_user = module.params["login_user"]
login_password = module.params["login_password"]
config_file = module.params["config_file"]
cursor = None
try:
cursor, db_conn = mysql_connect(module,
login_user,
login_password,
config_file,
cursor_class='DictCursor')
except mysql_driver.Error as e:
module.fail_json(
msg="unable to connect to ProxySQL Admin Module.. %s" % to_native(e)
)
proxysql_server = ProxySQLServer(module)
result = {}
result['state'] = proxysql_server.state
if proxysql_server.hostname:
result['hostname'] = proxysql_server.hostname
if proxysql_server.state == "present":
try:
if not proxysql_server.check_server_config(cursor):
if not proxysql_server.check_server_config_exists(cursor):
proxysql_server.create_server(module.check_mode,
result,
cursor)
else:
proxysql_server.update_server(module.check_mode,
result,
cursor)
else:
result['changed'] = False
result['msg'] = ("The server already exists in mysql_hosts" +
" and doesn't need to be updated.")
result['server'] = \
proxysql_server.get_server_config(cursor)
except mysql_driver.Error as e:
module.fail_json(
msg="unable to modify server.. %s" % to_native(e)
)
elif proxysql_server.state == "absent":
try:
if proxysql_server.check_server_config_exists(cursor):
proxysql_server.delete_server(module.check_mode,
result,
cursor)
else:
result['changed'] = False
result['msg'] = ("The server is already absent from the" +
" mysql_hosts memory configuration")
except mysql_driver.Error as e:
module.fail_json(
msg="unable to remove server.. %s" % to_native(e)
)
module.exit_json(**result)
if __name__ == '__main__':
main()

269
plugins/modules/proxysql_global_variables.py

@ -0,0 +1,269 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2017, Ansible Project
# 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 = '''
---
module: proxysql_global_variables
author: "Ben Mildren (@bmildren)"
short_description: Gets or sets the proxysql global variables.
description:
- The M(community.general.proxysql_global_variables) module gets or sets the proxysql global
variables.
options:
variable:
description:
- Defines which variable should be returned, or if I(value) is specified
which variable should be updated.
type: str
required: True
value:
description:
- Defines a value the variable specified using I(variable) should be set
to.
type: str
extends_documentation_fragment:
- community.proxysql.proxysql.managing_config
- community.proxysql.proxysql.connectivity
'''
EXAMPLES = '''
---
# This example sets the value of a variable, saves the mysql admin variables
# config to disk, and dynamically loads the mysql admin variables config to
# runtime. It uses supplied credentials to connect to the proxysql admin
# interface.
- name: Set the value of a variable
proxysql_global_variables:
login_user: 'admin'
login_password: 'admin'
variable: 'mysql-max_connections'
value: 4096
# This example gets the value of a variable. It uses credentials in a
# supplied config file to connect to the proxysql admin interface.
- name: Get the value of a variable
proxysql_global_variables:
config_file: '~/proxysql.cnf'
variable: 'mysql-default_query_delay'
'''
RETURN = '''
stdout:
description: Returns the mysql variable supplied with it's associated value.
returned: Returns the current variable and value, or the newly set value
for the variable supplied..
type: dict
"sample": {
"changed": false,
"msg": "The variable is already been set to the supplied value",
"var": {
"variable_name": "mysql-poll_timeout",
"variable_value": "3000"
}
}
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.proxysql.plugins.module_utils.mysql import mysql_connect, mysql_driver, mysql_driver_fail_msg
from ansible.module_utils._text import to_native
# ===========================================
# proxysql module specific support methods.
#
def perform_checks(module):
if module.params["login_port"] < 0 \
or module.params["login_port"] > 65535:
module.fail_json(
msg="login_port must be a valid unix port number (0-65535)"
)
if mysql_driver is None:
module.fail_json(msg=mysql_driver_fail_msg)
def save_config_to_disk(variable, cursor):
if variable.startswith("admin"):
cursor.execute("SAVE ADMIN VARIABLES TO DISK")
else:
cursor.execute("SAVE MYSQL VARIABLES TO DISK")
return True
def load_config_to_runtime(variable, cursor):
if variable.startswith("admin"):
cursor.execute("LOAD ADMIN VARIABLES TO RUNTIME")
else:
cursor.execute("LOAD MYSQL VARIABLES TO RUNTIME")
return True
def check_config(variable, value, cursor):
query_string = \
"""SELECT count(*) AS `variable_count`
FROM global_variables
WHERE variable_name = %s and variable_value = %s"""
query_data = \
[variable, value]
cursor.execute(query_string, query_data)
check_count = cursor.fetchone()
if isinstance(check_count, tuple):
return int(check_count[0]) > 0
return (int(check_count['variable_count']) > 0)
def get_config(variable, cursor):
query_string = \
"""SELECT *
FROM global_variables
WHERE variable_name = %s"""
query_data = \
[variable, ]
cursor.execute(query_string, query_data)
row_count = cursor.rowcount
resultset = cursor.fetchone()
if row_count > 0:
return resultset
else:
return False
def set_config(variable, value, cursor):
query_string = \
"""UPDATE global_variables
SET variable_value = %s
WHERE variable_name = %s"""
query_data = \
[value, variable]
cursor.execute(query_string, query_data)
return True
def manage_config(variable, save_to_disk, load_to_runtime, cursor, state):
if state:
if save_to_disk:
save_config_to_disk(variable, cursor)
if load_to_runtime:
load_config_to_runtime(variable, cursor)
# ===========================================
# Module execution.
#
def main():
module = AnsibleModule(
argument_spec=dict(
login_user=dict(default=None, type='str'),
login_password=dict(default=None, no_log=True, type='str'),
login_host=dict(default="127.0.0.1"),
login_unix_socket=dict(default=None),
login_port=dict(default=6032, type='int'),
config_file=dict(default="", type='path'),
variable=dict(required=True, type='str'),
value=dict(),
save_to_disk=dict(default=True, type='bool'),
load_to_runtime=dict(default=True, type='bool')
),
supports_check_mode=True
)
perform_checks(module)
login_user = module.params["login_user"]
login_password = module.params["login_password"]
config_file = module.params["config_file"]
variable = module.params["variable"]
value = module.params["value"]
save_to_disk = module.params["save_to_disk"]
load_to_runtime = module.params["load_to_runtime"]
cursor = None
try:
cursor, db_conn = mysql_connect(module,
login_user,
login_password,
config_file,
cursor_class='DictCursor')
except mysql_driver.Error as e:
module.fail_json(
msg="unable to connect to ProxySQL Admin Module.. %s" % to_native(e)
)
result = {}
if not value:
try:
if get_config(variable, cursor):
result['changed'] = False
result['msg'] = \
"Returned the variable and it's current value"
result['var'] = get_config(variable, cursor)
else:
module.fail_json(
msg="The variable \"%s\" was not found" % variable
)
except mysql_driver.Error as e:
module.fail_json(
msg="unable to get config.. %s" % to_native(e)
)
else:
try:
if get_config(variable, cursor):
if not check_config(variable, value, cursor):
if not module.check_mode:
result['changed'] = set_config(variable, value, cursor)
result['msg'] = \
"Set the variable to the supplied value"
result['var'] = get_config(variable, cursor)
manage_config(variable,
save_to_disk,
load_to_runtime,
cursor,
result['changed'])
else:
result['changed'] = True
result['msg'] = ("Variable would have been set to" +
" the supplied value, however" +
" check_mode is enabled.")
else:
result['changed'] = False
result['msg'] = ("The variable is already been set to" +
" the supplied value")
result['var'] = get_config(variable, cursor)
else:
module.fail_json(
msg="The variable \"%s\" was not found" % variable
)
except mysql_driver.Error as e:
module.fail_json(
msg="unable to set config.. %s" % to_native(e)
)
module.exit_json(**result)
if __name__ == '__main__':
main()

218
plugins/modules/proxysql_manage_config.py

@ -0,0 +1,218 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2017, Ansible Project
# 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 = '''
---
module: proxysql_manage_config
author: "Ben Mildren (@bmildren)"
short_description: Writes the proxysql configuration settings between layers.
description:
- The M(community.general.proxysql_global_variables) module writes the proxysql configuration
settings between layers. Currently this module will always report a
changed state, so should typically be used with WHEN however this will
change in a future version when the CHECKSUM table commands are available
for all tables in proxysql.
options:
action:
description:
- The supplied I(action) combines with the supplied I(direction) to
provide the semantics of how we want to move the I(config_settings)
between the I(config_layers).
type: str
choices: [ "LOAD", "SAVE" ]
required: True