summaryrefslogtreecommitdiffstats
path: root/ansible_collections/community/proxysql/plugins
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:04:41 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:04:41 +0000
commit975f66f2eebe9dadba04f275774d4ab83f74cf25 (patch)
tree89bd26a93aaae6a25749145b7e4bca4a1e75b2be /ansible_collections/community/proxysql/plugins
parentInitial commit. (diff)
downloadansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.tar.xz
ansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.zip
Adding upstream version 7.7.0+dfsg.upstream/7.7.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/community/proxysql/plugins')
-rw-r--r--ansible_collections/community/proxysql/plugins/README.md31
-rw-r--r--ansible_collections/community/proxysql/plugins/doc_fragments/proxysql.py61
-rw-r--r--ansible_collections/community/proxysql/plugins/module_utils/mysql.py170
-rw-r--r--ansible_collections/community/proxysql/plugins/modules/proxysql_backend_servers.py503
-rw-r--r--ansible_collections/community/proxysql/plugins/modules/proxysql_global_variables.py244
-rw-r--r--ansible_collections/community/proxysql/plugins/modules/proxysql_info.py159
-rw-r--r--ansible_collections/community/proxysql/plugins/modules/proxysql_manage_config.py212
-rw-r--r--ansible_collections/community/proxysql/plugins/modules/proxysql_mysql_users.py497
-rw-r--r--ansible_collections/community/proxysql/plugins/modules/proxysql_query_rules.py735
-rw-r--r--ansible_collections/community/proxysql/plugins/modules/proxysql_query_rules_fast_routing.py420
-rw-r--r--ansible_collections/community/proxysql/plugins/modules/proxysql_replication_hostgroups.py398
-rw-r--r--ansible_collections/community/proxysql/plugins/modules/proxysql_scheduler.py409
12 files changed, 3839 insertions, 0 deletions
diff --git a/ansible_collections/community/proxysql/plugins/README.md b/ansible_collections/community/proxysql/plugins/README.md
new file mode 100644
index 000000000..6541cf7cf
--- /dev/null
+++ b/ansible_collections/community/proxysql/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). \ No newline at end of file
diff --git a/ansible_collections/community/proxysql/plugins/doc_fragments/proxysql.py b/ansible_collections/community/proxysql/plugins/doc_fragments/proxysql.py
new file mode 100644
index 000000000..81c22815b
--- /dev/null
+++ b/ansible_collections/community/proxysql/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
+ - mysqlclient
+'''
+
+ # 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: true
+ load_to_runtime:
+ description:
+ - Dynamically load config to runtime memory.
+ type: bool
+ default: true
+'''
diff --git a/ansible_collections/community/proxysql/plugins/module_utils/mysql.py b/ansible_collections/community/proxysql/plugins/module_utils/mysql.py
new file mode 100644
index 000000000..ce581dd2f
--- /dev/null
+++ b/ansible_collections/community/proxysql/plugins/module_utils/mysql.py
@@ -0,0 +1,170 @@
+# 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
+from ansible.module_utils.basic import missing_required_lib
+
+MYSQL_IMP_ERR = None
+try:
+ import pymysql as mysql_driver
+ _mysql_cursor_param = 'cursor'
+ HAS_MYSQL_PACKAGE = True
+except ImportError:
+ try:
+ import MySQLdb as mysql_driver
+ import MySQLdb.cursors
+ _mysql_cursor_param = 'cursorclass'
+ HAS_MYSQL_PACKAGE = True
+ except ImportError:
+ MYSQL_IMP_ERR = 'Cannot find PyMySQL or mysqlclient library.'
+ HAS_MYSQL_PACKAGE = False
+ mysql_driver = None
+
+
+def parse_from_mysql_config_file(cnf):
+ cp = configparser.ConfigParser()
+ cp.read(cnf)
+ return cp
+
+
+def _version(cursor):
+ cursor.execute("select version();")
+ res = cursor.fetchone()
+
+ # 2.2.0-72-ge14accd
+ # 2.3.2-percona-1.1
+ raw_version = res.get('version()').split('-', 1)
+ _version = raw_version[0].split('.')
+
+ version = dict()
+ version['full'] = res.get('version()')
+ version['major'] = int(_version[0])
+ version['minor'] = int(_version[1])
+ version['release'] = int(_version[2])
+ version['suffix'] = raw_version[1]
+
+ return version
+
+
+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 not HAS_MYSQL_PACKAGE:
+ module.fail_json(msg=missing_required_lib("pymysql or MySQLdb"), exception=MYSQL_IMP_ERR)
+
+ 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 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)
+
+ version = _version(db_connection.cursor(**{_mysql_cursor_param: mysql_driver.cursors.DictCursor}))
+
+ if cursor_class == 'DictCursor':
+ return (db_connection.cursor(**{_mysql_cursor_param: mysql_driver.cursors.DictCursor}),
+ db_connection,
+ version)
+ else:
+ return (db_connection.cursor(),
+ db_connection,
+ version)
+
+
+def proxysql_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='127.0.0.1'),
+ login_port=dict(type='int', default=6032),
+ login_unix_socket=dict(type='str'),
+ config_file=dict(type='path', default=''),
+ )
+
+
+def save_config_to_disk(cursor, save_what, variable=None):
+ if variable and variable.startswith("admin"):
+ config_type = "ADMIN"
+ elif save_what == "SCHEDULER":
+ config_type = ""
+ else:
+ config_type = "MYSQL"
+
+ cursor.execute("SAVE {0} {1} TO DISK".format(config_type, save_what))
+
+ return True
+
+
+def load_config_to_runtime(cursor, save_what, variable=None):
+ if variable and variable.startswith("admin"):
+ config_type = "ADMIN"
+ elif save_what == "SCHEDULER":
+ config_type = ""
+ else:
+ config_type = "MYSQL"
+
+ cursor.execute("LOAD {0} {1} TO RUNTIME".format(config_type, save_what))
+
+ return True
diff --git a/ansible_collections/community/proxysql/plugins/modules/proxysql_backend_servers.py b/ansible_collections/community/proxysql/plugins/modules/proxysql_backend_servers.py
new file mode 100644
index 000000000..89be2edff
--- /dev/null
+++ b/ansible_collections/community/proxysql/plugins/modules/proxysql_backend_servers.py
@@ -0,0 +1,503 @@
+#!/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.proxysql.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
+notes:
+- Supports C(check_mode).
+'''
+
+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
+ community.proxysql.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
+ community.proxysql.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,
+ proxysql_common_argument_spec,
+ save_config_to_disk,
+ load_config_to_runtime,
+)
+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["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"
+ )
+
+
+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, "SERVERS")
+ if self.load_to_runtime:
+ load_config_to_runtime(cursor, "SERVERS")
+
+ 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():
+ argument_spec = proxysql_common_argument_spec()
+ argument_spec.update(
+ 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')
+ )
+
+ module = AnsibleModule(
+ supports_check_mode=True,
+ argument_spec=argument_spec
+ )
+
+ 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, version = 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()
diff --git a/ansible_collections/community/proxysql/plugins/modules/proxysql_global_variables.py b/ansible_collections/community/proxysql/plugins/modules/proxysql_global_variables.py
new file mode 100644
index 000000000..9996041ed
--- /dev/null
+++ b/ansible_collections/community/proxysql/plugins/modules/proxysql_global_variables.py
@@ -0,0 +1,244 @@
+#!/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.proxysql.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
+notes:
+- Supports C(check_mode).
+'''
+
+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
+ community.proxysql.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
+ community.proxysql.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,
+ proxysql_common_argument_spec,
+ save_config_to_disk,
+ load_config_to_runtime,
+)
+from ansible.module_utils._text import to_native
+
+# ===========================================
+# proxysql module specific support methods.
+#
+
+
+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(cursor, "VARIABLES", variable)
+ if load_to_runtime:
+ load_config_to_runtime(cursor, "VARIABLES", variable)
+
+# ===========================================
+# Module execution.
+#
+
+
+def main():
+ argument_spec = proxysql_common_argument_spec()
+ argument_spec.update(
+ variable=dict(required=True, type='str'),
+ value=dict(),
+ save_to_disk=dict(default=True, type='bool'),
+ load_to_runtime=dict(default=True, type='bool')
+ )
+
+ module = AnsibleModule(
+ supports_check_mode=True,
+ argument_spec=argument_spec
+ )
+
+ 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, version = 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()
diff --git a/ansible_collections/community/proxysql/plugins/modules/proxysql_info.py b/ansible_collections/community/proxysql/plugins/modules/proxysql_info.py
new file mode 100644
index 000000000..a6088b151
--- /dev/null
+++ b/ansible_collections/community/proxysql/plugins/modules/proxysql_info.py
@@ -0,0 +1,159 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright: (c) 2021, 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_info
+author: "Markus Bergholz (@markuman)"
+short_description: Gathers information about proxysql server
+description:
+ - Gathers information about proxysql server.
+ - Caution. The number of tables that returns, depends on the underlying proyxsql server version.
+version_added: '1.2.0'
+extends_documentation_fragment:
+ - community.proxysql.proxysql.connectivity
+notes:
+ - Supports C(check_mode).
+'''
+
+EXAMPLES = '''
+- name: Receive information about proxysql setup
+ community.proxysql.proxysql_info:
+ login_user: admin
+ login_password: admin
+'''
+
+RETURN = '''
+stdout:
+ description: The number of tables that returns, depends on the underlying proyxsql server version.
+ returned: Always
+ type: dict
+ sample:
+ changed: false
+ failed: false
+ version:
+ description: Version of proxysql.
+ sample:
+ full: 2.1.1-40-g1c2b7e4
+ major: 2
+ minor: 1
+ release: 1
+ suffix: 40
+ type: dict
+ returned: Always
+ tables:
+ description: List of tables that exist in the requested proxysql version.
+ sample:
+ - global_variables
+ - mysql_aws_aurora_hostgroups
+ - mysql_collations
+ - mysql_firewall_whitelist_rules
+ - mysql_firewall_whitelist_sqli_fingerprints
+ - mysql_firewall_whitelist_users
+ - mysql_galera_hostgroups
+ - mysql_group_replication_hostgroups
+ - mysql_query_rules
+ - mysql_query_rules_fast_routing
+ - mysql_replication_hostgroups
+ - mysql_servers
+ - mysql_users
+ - proxysql_servers
+ - restapi_routes
+ - runtime_checksums_values
+ - runtime_global_variables
+ - runtime_mysql_aws_aurora_hostgroups
+ - runtime_mysql_firewall_whitelist_rules
+ - runtime_mysql_firewall_whitelist_sqli_fingerprints
+ - runtime_mysql_firewall_whitelist_users
+ - runtime_mysql_galera_hostgroups
+ - runtime_mysql_group_replication_hostgroups
+ - runtime_mysql_query_rules
+ - runtime_mysql_query_rules_fast_routing
+ - runtime_mysql_replication_hostgroups
+ - runtime_mysql_servers
+ - runtime_mysql_users
+ - runtime_proxysql_servers
+ - runtime_restapi_routes
+ - runtime_scheduler
+ - scheduler
+ type: list
+ returned: Always
+ global_variables:
+ description: Global variables of requested proxysql.
+ type: dict
+ returned: Always
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.community.proxysql.plugins.module_utils.mysql import (
+ mysql_connect,
+ mysql_driver,
+ proxysql_common_argument_spec,
+)
+from ansible.module_utils._text import to_native
+
+# ===========================================
+# proxysql module specific support methods.
+#
+
+
+def get_tables(cursor):
+ result = dict()
+ tables = list()
+
+ cursor.execute("show tables")
+
+ for table in cursor.fetchall():
+ tables.append(table.get('tables'))
+ result['tables'] = tables
+
+ for table in result.get('tables'):
+ cursor.execute("select * from {table}".format(table=table))
+
+ if 'global_variables' in table:
+ result[table] = dict()
+ for item in cursor.fetchall():
+ result[table][item.get('variable_name')] = item.get('variable_value')
+
+ else:
+ result[table] = cursor.fetchall()
+
+ return result
+
+
+def main():
+ module = AnsibleModule(
+ supports_check_mode=True,
+ argument_spec=proxysql_common_argument_spec()
+ )
+
+ login_user = module.params["login_user"]
+ login_password = module.params["login_password"]
+ config_file = module.params["config_file"]
+
+ cursor = None
+ try:
+ cursor, db_conn, version = 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 = get_tables(cursor)
+
+ result['version'] = version
+
+ module.exit_json(**result)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/community/proxysql/plugins/modules/proxysql_manage_config.py b/ansible_collections/community/proxysql/plugins/modules/proxysql_manage_config.py
new file mode 100644
index 000000000..ead810e3c
--- /dev/null
+++ b/ansible_collections/community/proxysql/plugins/modules/proxysql_manage_config.py
@@ -0,0 +1,212 @@
+#!/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.proxysql.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
+ config_settings:
+ description:
+ - The I(config_settings) specifies which configuration we're writing.
+ type: str
+ choices: [ "MYSQL USERS", "MYSQL SERVERS", "MYSQL QUERY RULES",
+ "MYSQL VARIABLES", "ADMIN VARIABLES", "SCHEDULER" ]
+ required: true
+ direction:
+ description:
+ - FROM - denotes we're reading values FROM the supplied I(config_layer)
+ and writing to the next layer.
+ TO - denotes we're reading from the previous layer and writing TO the
+ supplied I(config_layer)."
+ type: str
+ choices: [ "FROM", "TO" ]
+ required: true
+ config_layer:
+ description:
+ - RUNTIME - represents the in-memory data structures of ProxySQL used by
+ the threads that are handling the requests.
+ MEMORY - (sometimes also referred as main) represents the in-memory
+ SQLite3 database.
+ DISK - represents the on-disk SQLite3 database.
+ CONFIG - is the classical config file. You can only LOAD FROM the
+ config file.
+ type: str
+ choices: [ "MEMORY", "DISK", "RUNTIME", "CONFIG" ]
+ required: true
+extends_documentation_fragment:
+- community.proxysql.proxysql.connectivity
+notes:
+- Supports C(check_mode).
+'''
+
+EXAMPLES = '''
+---
+# This example saves the mysql users config from memory to disk. It uses
+# supplied credentials to connect to the proxysql admin interface.
+
+- name: Save the mysql users config from memory to disk
+ community.proxysql.proxysql_manage_config:
+ login_user: 'admin'
+ login_password: 'admin'
+ action: "SAVE"
+ config_settings: "MYSQL USERS"
+ direction: "FROM"
+ config_layer: "MEMORY"
+
+# This example loads the mysql query rules config from memory to runtime. It
+# uses supplied credentials to connect to the proxysql admin interface.
+
+- name: Load the mysql query rules config from memory to runtime
+ community.proxysql.proxysql_manage_config:
+ config_file: '~/proxysql.cnf'
+ action: "LOAD"
+ config_settings: "MYSQL QUERY RULES"
+ direction: "TO"
+ config_layer: "RUNTIME"
+'''
+
+RETURN = '''
+stdout:
+ description: Simply reports whether the action reported a change.
+ returned: Currently the returned value with always be changed=True.
+ type: dict
+ "sample": {
+ "changed": true
+ }
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.community.proxysql.plugins.module_utils.mysql import (
+ mysql_connect,
+ mysql_driver,
+ proxysql_common_argument_spec
+)
+from ansible.module_utils._text import to_native
+
+# ===========================================
+# proxysql module specific support methods.
+#
+
+
+def perform_checks(module):
+ if module.params["config_layer"] == 'CONFIG' and \
+ (module.params["action"] != 'LOAD' or
+ module.params["direction"] != 'FROM'):
+
+ if (module.params["action"] != 'LOAD' and
+ module.params["direction"] != 'FROM'):
+ msg_string = ("Neither the action \"%s\" nor the direction" +
+ " \"%s\" are valid combination with the CONFIG" +
+ " config_layer")
+ module.fail_json(msg=msg_string % (module.params["action"],
+ module.params["direction"]))
+
+ elif module.params["action"] != 'LOAD':
+ msg_string = ("The action \"%s\" is not a valid combination" +
+ " with the CONFIG config_layer")
+ module.fail_json(msg=msg_string % module.params["action"])
+
+ else:
+ msg_string = ("The direction \"%s\" is not a valid combination" +
+ " with the CONFIG config_layer")
+ module.fail_json(msg=msg_string % module.params["direction"])
+
+
+def manage_config(manage_config_settings, cursor, check_mode):
+
+ if not check_mode:
+ query_string = "%s" % ' '.join(manage_config_settings)
+ cursor.execute(query_string)
+
+ return True
+
+# ===========================================
+# Module execution.
+#
+
+
+def main():
+ argument_spec = proxysql_common_argument_spec()
+ argument_spec.update(
+ action=dict(required=True, choices=['LOAD',
+ 'SAVE']),
+ config_settings=dict(required=True, choices=['MYSQL USERS',
+ 'MYSQL SERVERS',
+ 'MYSQL QUERY RULES',
+ 'MYSQL VARIABLES',
+ 'ADMIN VARIABLES',
+ 'SCHEDULER']),
+ direction=dict(required=True, choices=['FROM',
+ 'TO']),
+ config_layer=dict(required=True, choices=['MEMORY',
+ 'DISK',
+ 'RUNTIME',
+ 'CONFIG'])
+ )
+
+ module = AnsibleModule(
+ supports_check_mode=True,
+ argument_spec=argument_spec
+ )
+
+ perform_checks(module)
+
+ login_user = module.params["login_user"]
+ login_password = module.params["login_password"]
+ config_file = module.params["config_file"]
+ action = module.params["action"]
+ config_settings = module.params["config_settings"]
+ direction = module.params["direction"]
+ config_layer = module.params["config_layer"]
+
+ cursor = None
+ try:
+ cursor, db_conn, version = mysql_connect(module,
+ login_user,
+ login_password,
+ config_file)
+ except mysql_driver.Error as e:
+ module.fail_json(
+ msg="unable to connect to ProxySQL Admin Module.. %s" % to_native(e)
+ )
+
+ result = {}
+
+ manage_config_settings = \
+ [action, config_settings, direction, config_layer]
+
+ try:
+ result['changed'] = manage_config(manage_config_settings,
+ cursor, module.check_mode)
+ except mysql_driver.Error as e:
+ module.fail_json(
+ msg="unable to manage config.. %s" % to_native(e)
+ )
+
+ module.exit_json(**result)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/community/proxysql/plugins/modules/proxysql_mysql_users.py b/ansible_collections/community/proxysql/plugins/modules/proxysql_mysql_users.py
new file mode 100644
index 000000000..417172d2d
--- /dev/null
+++ b/ansible_collections/community/proxysql/plugins/modules/proxysql_mysql_users.py
@@ -0,0 +1,497 @@
+#!/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_mysql_users
+author: "Ben Mildren (@bmildren)"
+short_description: Adds or removes mysql users from proxysql admin interface
+description:
+ - The M(community.proxysql.proxysql_mysql_users) module adds or removes mysql users using the
+ proxysql admin interface.
+options:
+ username:
+ description:
+ - Name of the user connecting to the mysqld or ProxySQL instance.
+ type: str
+ required: true
+ password:
+ description:
+ - Password of the user connecting to the mysqld or ProxySQL instance.
+ type: str
+ encrypt_password:
+ description:
+ - Encrypt a cleartext password passed in the I(password) option, using
+ the method defined in I(encryption_method).
+ default: false
+ type: bool
+ encryption_method:
+ description:
+ - Encryption method used when I(encrypt_password) is set to C(True).
+ type: str
+ choices: [ "mysql_native_password" ]
+ default: mysql_native_password
+ active:
+ description:
+ - A user with I(active) set to C(False) will be tracked in the database,
+ but will be never loaded in the in-memory data structures. If omitted
+ the proxysql database default for I(active) is C(True).
+ type: bool
+ use_ssl:
+ description:
+ - If I(use_ssl) is set to C(True), connections by this user will be made
+ using SSL connections. If omitted the proxysql database default for
+ I(use_ssl) is C(False).
+ type: bool
+ default_hostgroup:
+ description:
+ - If there is no matching rule for the queries sent by this user, the
+ traffic it generates is sent to the specified hostgroup.
+ If omitted the proxysql database default for I(use_ssl) is 0.
+ type: int
+ default_schema:
+ description:
+ - The schema to which the connection should change to by default.
+ type: str
+ transaction_persistent:
+ description:
+ - If this is set for the user with which the MySQL client is connecting
+ to ProxySQL (thus a "frontend" user), transactions started within a
+ hostgroup will remain within that hostgroup regardless of any other
+ rules.
+ If omitted the proxysql database default for I(transaction_persistent)
+ is C(False).
+ type: bool
+ fast_forward:
+ description:
+ - If I(fast_forward) is set to C(True), I(fast_forward) will bypass the
+ query processing layer (rewriting, caching) and pass through the query
+ directly as is to the backend server. If omitted the proxysql database
+ default for I(fast_forward) is C(False).
+ type: bool
+ backend:
+ description:
+ - If I(backend) is set to C(True), this (username, password) pair is
+ used for authenticating to the ProxySQL instance.
+ default: true
+ type: bool
+ frontend:
+ description:
+ - If I(frontend) is set to C(True), this (username, password) pair is
+ used for authenticating to the mysqld servers against any hostgroup.
+ default: true
+ type: bool
+ max_connections:
+ description:
+ - The maximum number of connections ProxySQL will open to the backend for
+ this user. If omitted the proxysql database default for
+ I(max_connections) is 10000.
+ type: int
+ state:
+ description:
+ - When C(present) - adds the user, when C(absent) - removes the user.
+ type: str
+ choices: [ "present", "absent" ]
+ default: present
+extends_documentation_fragment:
+- community.proxysql.proxysql.managing_config
+- community.proxysql.proxysql.connectivity
+notes:
+- Supports C(check_mode).
+'''
+
+EXAMPLES = '''
+---
+# This example adds a user, it saves the mysql user config to disk, but
+# avoids loading the mysql user config to runtime (this might be because
+# several users 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 user
+ community.proxysql.proxysql_mysql_users:
+ login_user: 'admin'
+ login_password: 'admin'
+ username: 'productiondba'
+ state: present
+ load_to_runtime: false
+
+# This example removes a user, saves the mysql user config to disk, and
+# dynamically loads the mysql user config to runtime. It uses credentials
+# in a supplied config file to connect to the proxysql admin interface.
+
+- name: Remove a user
+ community.proxysql.proxysql_mysql_users:
+ config_file: '~/proxysql.cnf'
+ username: 'mysqlboy'
+ state: absent
+'''
+
+RETURN = '''
+stdout:
+ description: The mysql user modified or removed from proxysql.
+ returned: On create/update will return the newly modified user, on delete
+ it will return the deleted record.
+ type: dict
+ sample:
+ changed: true
+ msg: Added user to mysql_users
+ state: present
+ user:
+ active: 1
+ backend: 1
+ default_hostgroup: 1
+ default_schema: null
+ fast_forward: 0
+ frontend: 1
+ max_connections: 10000
+ password: VALUE_SPECIFIED_IN_NO_LOG_PARAMETER
+ schema_locked: 0
+ transaction_persistent: 0
+ use_ssl: 0
+ username: guest_ro
+ username: guest_ro
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.community.proxysql.plugins.module_utils.mysql import (
+ mysql_connect,
+ mysql_driver,
+ proxysql_common_argument_spec,
+ save_config_to_disk,
+ load_config_to_runtime,
+)
+from ansible.module_utils.six import iteritems
+from ansible.module_utils._text import to_native, to_bytes
+from hashlib import sha1
+
+# ===========================================
+# proxysql module specific support methods.
+#
+
+
+def _mysql_native_password(cleartext_password):
+ mysql_native_encrypted_password = "*" + sha1(sha1(to_bytes(cleartext_password)).digest()).hexdigest().upper()
+ return mysql_native_encrypted_password
+
+
+def encrypt_cleartext_password(password_to_encrypt, encryption_method):
+ encrypted_password = encryption_method(password_to_encrypt)
+ return encrypted_password
+
+
+encryption_method_map = {
+ 'mysql_native_password': _mysql_native_password
+}
+
+
+class ProxySQLUser(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.username = module.params["username"]
+ self.backend = module.params["backend"]
+ self.frontend = module.params["frontend"]
+
+ config_data_keys = ["password",
+ "active",
+ "use_ssl",
+ "default_hostgroup",
+ "default_schema",
+ "transaction_persistent",
+ "fast_forward",
+ "max_connections"]
+
+ self.config_data = dict((k, module.params[k])
+ for k in config_data_keys)
+
+ if module.params["password"] is not None and module.params["encrypt_password"]:
+ encryption_method = encryption_method_map[module.params["encryption_method"]]
+ encrypted_password = encrypt_cleartext_password(module.params["password"], encryption_method)
+ self.config_data["password"] = encrypted_password
+
+ def check_user_config_exists(self, cursor):
+ query_string = \
+ """SELECT count(*) AS `user_count`
+ FROM mysql_users
+ WHERE username = %s
+ AND backend = %s
+ AND frontend = %s"""
+
+ query_data = \
+ [self.username,
+ self.backend,
+ self.frontend]
+
+ cursor.execute(query_string, query_data)
+ check_count = cursor.fetchone()
+ return (int(check_count['user_count']) > 0)
+
+ def check_user_privs(self, cursor):
+ query_string = \
+ """SELECT count(*) AS `user_count`
+ FROM mysql_users
+ WHERE username = %s
+ AND backend = %s
+ AND frontend = %s"""
+
+ query_data = \
+ [self.username,
+ self.backend,
+ self.frontend]
+
+ 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()
+ return (int(check_count['user_count']) > 0)
+
+ def get_user_config(self, cursor):
+ query_string = \
+ """SELECT *
+ FROM mysql_users
+ WHERE username = %s
+ AND backend = %s
+ AND frontend = %s"""
+
+ query_data = \
+ [self.username,
+ self.backend,
+ self.frontend]
+
+ cursor.execute(query_string, query_data)
+ user = cursor.fetchone()
+ return user
+
+ def create_user_config(self, cursor):
+ query_string = \
+ """INSERT INTO mysql_users (
+ username,
+ backend,
+ frontend"""
+
+ cols = 3
+ query_data = \
+ [self.username,
+ self.backend,
+ self.frontend]
+
+ 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_user_config(self, cursor):
+ query_string = """UPDATE mysql_users"""
+
+ 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 username = %s\n AND backend = %s" +
+ "\n AND frontend = %s")
+
+ query_data.append(self.username)
+ query_data.append(self.backend)
+ query_data.append(self.frontend)
+
+ cursor.execute(query_string, query_data)
+ return True
+
+ def delete_user_config(self, cursor):
+ query_string = \
+ """DELETE FROM mysql_users
+ WHERE username = %s
+ AND backend = %s
+ AND frontend = %s"""
+
+ query_data = \
+ [self.username,
+ self.backend,
+ self.frontend]
+
+ 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, "USERS")
+ if self.load_to_runtime:
+ load_config_to_runtime(cursor, "USERS")
+
+ def create_user(self, check_mode, result, cursor):
+ if not check_mode:
+ result['changed'] = \
+ self.create_user_config(cursor)
+ result['msg'] = "Added user to mysql_users"
+ result['user'] = \
+ self.get_user_config(cursor)
+ self.manage_config(cursor,
+ result['changed'])
+ else:
+ result['changed'] = True
+ result['msg'] = ("User would have been added to" +
+ " mysql_users, however check_mode" +
+ " is enabled.")
+
+ def update_user(self, check_mode, result, cursor):
+ if not check_mode:
+ result['changed'] = \
+ self.update_user_config(cursor)
+ result['msg'] = "Updated user in mysql_users"
+ result['user'] = \
+ self.get_user_config(cursor)
+ self.manage_config(cursor,
+ result['changed'])
+ else:
+ result['changed'] = True
+ result['msg'] = ("User would have been updated in" +
+ " mysql_users, however check_mode" +
+ " is enabled.")
+
+ def delete_user(self, check_mode, result, cursor):
+ if not check_mode:
+ result['user'] = \
+ self.get_user_config(cursor)
+ result['changed'] = \
+ self.delete_user_config(cursor)
+ result['msg'] = "Deleted user from mysql_users"
+ self.manage_config(cursor,
+ result['changed'])
+ else:
+ result['changed'] = True
+ result['msg'] = ("User would have been deleted from" +
+ " mysql_users, however check_mode is" +
+ " enabled.")
+
+# ===========================================
+# Module execution.
+#
+
+
+def main():
+ argument_spec = proxysql_common_argument_spec()
+ argument_spec.update(
+ username=dict(required=True, type='str'),
+ password=dict(no_log=True, type='str'),
+ encrypt_password=dict(default=False, type='bool', no_log=False),
+ encryption_method=dict(default='mysql_native_password', choices=list(encryption_method_map.keys())),
+ active=dict(type='bool'),
+ use_ssl=dict(type='bool'),
+ default_hostgroup=dict(type='int'),
+ default_schema=dict(type='str'),
+ transaction_persistent=dict(type='bool'),
+ fast_forward=dict(type='bool'),
+ backend=dict(default=True, type='bool'),
+ frontend=dict(default=True, type='bool'),
+ max_connections=dict(type='int'),
+ state=dict(default='present', choices=['present',
+ 'absent']),
+ save_to_disk=dict(default=True, type='bool'),
+ load_to_runtime=dict(default=True, type='bool')
+ )
+
+ module = AnsibleModule(
+ supports_check_mode=True,
+ argument_spec=argument_spec
+ )
+
+ login_user = module.params["login_user"]
+ login_password = module.params["login_password"]
+ config_file = module.params["config_file"]
+
+ cursor = None
+ try:
+ cursor, db_conn, version = 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_user = ProxySQLUser(module)
+ result = {}
+
+ result['state'] = proxysql_user.state
+ if proxysql_user.username:
+ result['username'] = proxysql_user.username
+
+ if proxysql_user.state == "present":
+ try:
+ if not proxysql_user.check_user_privs(cursor):
+ if not proxysql_user.check_user_config_exists(cursor):
+ proxysql_user.create_user(module.check_mode,
+ result,
+ cursor)
+ else:
+ proxysql_user.update_user(module.check_mode,
+ result,
+ cursor)
+ else:
+ result['changed'] = False
+ result['msg'] = ("The user already exists in mysql_users" +
+ " and doesn't need to be updated.")
+ result['user'] = \
+ proxysql_user.get_user_config(cursor)
+ except mysql_driver.Error as e:
+ module.fail_json(
+ msg="unable to modify user.. %s" % to_native(e)
+ )
+
+ elif proxysql_user.state == "absent":
+ try:
+ if proxysql_user.check_user_config_exists(cursor):
+ proxysql_user.delete_user(module.check_mode,
+ result,
+ cursor)
+ else:
+ result['changed'] = False
+ result['msg'] = ("The user is already absent from the" +
+ " mysql_users memory configuration")
+ except mysql_driver.Error as e:
+ module.fail_json(
+ msg="unable to remove user.. %s" % to_native(e)
+ )
+
+ module.exit_json(**result)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/community/proxysql/plugins/modules/proxysql_query_rules.py b/ansible_collections/community/proxysql/plugins/modules/proxysql_query_rules.py
new file mode 100644
index 000000000..f03456f98
--- /dev/null
+++ b/ansible_collections/community/proxysql/plugins/modules/proxysql_query_rules.py
@@ -0,0 +1,735 @@
+#!/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_query_rules
+author: "Ben Mildren (@bmildren)"
+short_description: Modifies query rules using the proxysql admin interface
+description:
+ - The M(community.proxysql.proxysql_query_rules) module modifies query rules using the
+ proxysql admin interface.
+options:
+ rule_id:
+ description:
+ - The unique id of the rule. Rules are processed in rule_id order.
+ type: int
+ active:
+ description:
+ - A rule with I(active) set to C(False) will be tracked in the database,
+ but will be never loaded in the in-memory data structures.
+ type: bool
+ username:
+ description:
+ - Filtering criteria matching username. If I(username) is non-NULL, a
+ query will match only if the connection is made with the correct
+ username.
+ type: str
+ schemaname:
+ description:
+ - Filtering criteria matching schemaname. If I(schemaname) is non-NULL, a
+ query will match only if the connection uses schemaname as its default
+ schema.
+ type: str
+ flagIN:
+ description:
+ - Used in combination with I(flagOUT) and I(apply) to create chains of
+ rules.
+ type: int
+ client_addr:
+ description:
+ - Match traffic from a specific source.
+ type: str
+ proxy_addr:
+ description:
+ - Match incoming traffic on a specific local IP.
+ type: str
+ proxy_port:
+ description:
+ - Match incoming traffic on a specific local port.
+ type: int
+ digest:
+ description:
+ - Match queries with a specific digest, as returned by
+ stats_mysql_query_digest.digest.
+ type: str
+ match_digest:
+ description:
+ - Regular expression that matches the query digest. The dialect of
+ regular expressions used is that of re2 - U(https://github.com/google/re2).
+ type: str
+ match_pattern:
+ description:
+ - Regular expression that matches the query text. The dialect of regular
+ expressions used is that of re2 - U(https://github.com/google/re2).
+ type: str
+ negate_match_pattern:
+ description:
+ - If I(negate_match_pattern) is set to C(True), only queries not matching
+ the query text will be considered as a match. This acts as a NOT
+ operator in front of the regular expression matching against
+ match_pattern.
+ type: bool
+ re_modifiers:
+ description:
+ - Comma separated list of options to modify the behavior of the RE engine.
+ With C(CASELESS) the match is case insensitive. With C(GLOBAL) the replace
+ is global (replaces all matches and not just the first).
+ For backward compatibility, only C(CASELESS) is the enabled by default.
+ type: str
+ version_added: "1.3.0"
+ flagOUT:
+ description:
+ - Used in combination with I(flagIN) and apply to create chains of rules.
+ When set, I(flagOUT) signifies the I(flagIN) to be used in the next
+ chain of rules.
+ type: int
+ replace_pattern:
+ description:
+ - This is the pattern with which to replace the matched pattern. Note
+ that this is optional, and when omitted, the query processor will only
+ cache, route, or set other parameters without rewriting.
+ type: str
+ destination_hostgroup:
+ description:
+ - Route matched queries to this hostgroup. This happens unless there is a
+ started transaction and the logged in user has
+ I(transaction_persistent) set to C(True) (see M(community.proxysql.proxysql_mysql_users)).
+ type: int
+ cache_ttl:
+ description:
+ - The number of milliseconds for which to cache the result of the query.
+ Note in ProxySQL 1.1 I(cache_ttl) was in seconds.
+ type: int
+ cache_empty_result:
+ description:
+ - Controls if resultset without rows will be cached or not.
+ type: bool
+ version_added: '1.1.0'
+ multiplex:
+ description:
+ - If C(0), multiplex will be disabled.
+ - If C(1), try to enable multiplex. There can be other conditions preventing this (for example, user variables or transactions).
+ - If C(2), multiplexing is not disabled for just the current query.
+ - By default, does not change multiplexing policies.
+ type: int
+ choices: [0, 1, 2]
+ version_added: '1.1.0'
+ timeout:
+ description:
+ - The maximum timeout in milliseconds with which the matched or rewritten
+ query should be executed. If a query run for longer than the specific
+ threshold, the query is automatically killed. If timeout is not
+ specified, the global variable mysql-default_query_timeout applies.
+ type: int
+ retries:
+ description:
+ - The maximum number of times a query needs to be re-executed in case of
+ detected failure during the execution of the query. If retries is not
+ specified, the global variable mysql-query_retries_on_failure applies.
+ type: int
+ delay:
+ description:
+ - Number of milliseconds to delay the execution of the query. This is
+ essentially a throttling mechanism and QoS, and allows a way to give
+ priority to queries over others. This value is added to the
+ mysql-default_query_delay global variable that applies to all queries.
+ type: int
+ next_query_flagIN:
+ description:
+ - When is set, its value will become the I(flagIN) value for the next queries.
+ type: int
+ version_added: "1.3.0"
+ mirror_flagOUT:
+ description:
+ - Enables query mirroring. If set I(mirror_flagOUT) can be used to
+ evaluates the mirrored query against the specified chain of rules.
+ type: int
+ mirror_hostgroup:
+ description:
+ - Enables query mirroring. If set I(mirror_hostgroup) can be used to
+ mirror queries to the same or different hostgroup.
+ type: int
+ OK_msg:
+ description:
+ - The specified message will be returned for a query that uses the
+ defined rule.
+ type: str
+ version_added: '1.1.0'
+ error_msg:
+ description:
+ - Query will be blocked, and the specified error_msg will be returned to
+ the client.
+ type: str
+ log:
+ description:
+ - Query will be logged.
+ type: bool
+ apply:
+ description:
+ - Used in combination with I(flagIN) and I(flagOUT) to create chains of
+ rules. Setting apply to True signifies the last rule to be applied.
+ type: bool
+ comment:
+ description:
+ - Free form text field, usable for a descriptive comment of the query
+ rule.
+ type: str
+ state:
+ description:
+ - When C(present) - adds the rule, when C(absent) - removes the rule.
+ type: str
+ choices: [ "present", "absent" ]
+ default: present
+ force_delete:
+ description:
+ - By default we avoid deleting more than one schedule in a single batch,
+ however if you need this behaviour and you are not concerned about the
+ schedules deleted, you can set I(force_delete) to C(True).
+ type: bool
+ default: false
+extends_documentation_fragment:
+- community.proxysql.proxysql.managing_config
+- community.proxysql.proxysql.connectivity
+notes:
+- Supports C(check_mode).
+'''
+
+EXAMPLES = '''
+---
+# This example adds a rule to redirect queries from a specific user to another
+# hostgroup, it saves the mysql query rule config to disk, but avoids loading
+# the mysql query config config to runtime (this might be because several
+# rules 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 rule
+ community.proxysql.proxysql_query_rules:
+ login_user: admin
+ login_password: admin
+ username: 'guest_ro'
+ match_pattern: "^SELECT.*"
+ destination_hostgroup: 1
+ active: 1
+ retries: 3
+ state: present
+ load_to_runtime: false
+
+# This example demonstrates the situation, if your application tries to set a
+# variable that will disable multiplexing, and you think it can be filtered out,
+# you can create a filter that returns OK without executing the request.
+
+- name: Add a filter rule
+ community.proxysql.proxysql_query_rules:
+ login_user: admin
+ login_password: admin
+ match_digest: '^SET @@wait_timeout = ?'
+ active: 1
+ OK_msg: 'The wait_timeout variable is ignored'
+
+# This example adds a caching rule for a query that matches the digest.
+# The query digest can be obtained from the `stats_mysql_query_digest`
+# table. `cache_ttl` is specified in milliseconds. Empty responses are
+# not cached.
+
+- name: Add a cache rule
+ community.proxysql.proxysql_query_rules:
+ login_user: admin
+ login_password: admin
+ rule_id: 1
+ digest: 0xECA450EA500A9A55
+ cache_ttl: 30000
+ cache_empty_result: false
+ destination_hostgroup: 1
+ active: true
+ state: present
+ save_to_disk: true
+ load_to_runtime: true
+
+# This example demonstrates how to prevent disabling multiplexing for
+# situations where a request contains @.
+
+- name: Add a rule with multiplex
+ community.proxysql.proxysql_query_rules:
+ login_user: admin
+ login_password: admin
+ rule_id: 1
+ active: 1
+ match_digest: '^SELECT @@max_allowed_packet'
+ multiplex: 2
+
+# This example demonstrates how to use next_query_flagIN argument. It allows
+# ProxySQL query rules to be chained. The examples shows how you can have SELECTS
+# immediately follow INSERT/UPDATE/DELETE statements to query the primary hostgroup
+# and avoid replication lag
+
+- name: Add insert query rule
+ proxysql_query_rules:
+ match_digest: "^INSERT"
+ destination_hostgroup: 1,
+ next_query_flagIN: 1
+
+- name: Add update query rule
+ proxysql_query_rules:
+ match_digest: "^UPDATE"
+ destination_hostgroup: 1,
+ next_query_flagIN: 1
+
+- name: Add delete query rules
+ proxysql_query_rules:
+ match_digest: "^DELETE"
+ destination_hostgroup: 1,
+ next_query_flagIN: 1
+
+- name: Add insert query rules
+ proxysql_query_rules:
+ match_digest: ".*"
+ destination_hostgroup: 1,
+ next_query_flagIN: 1
+ comment: Match every queries after an INSERT/UPDATE/DELETE query
+
+# This example removes all rules that use the username 'guest_ro', saves the
+# mysql query rule config to disk, and dynamically loads the mysql query rule
+# config to runtime. It uses credentials in a supplied config file to connect
+# to the proxysql admin interface.
+
+- name: Remove rules
+ community.proxysql.proxysql_query_rules:
+ config_file: '~/proxysql.cnf'
+ username: 'guest_ro'
+ state: absent
+ force_delete: true
+'''
+
+RETURN = '''
+stdout:
+ description: The mysql user modified or removed from proxysql.
+ returned: On create/update will return the newly modified rule, in all
+ other cases will return a list of rules that match the supplied
+ criteria.
+ type: dict
+ "sample": {
+ "changed": true,
+ "msg": "Added rule to mysql_query_rules",
+ "rules": [
+ {
+ "active": "0",
+ "apply": "0",
+ "cache_ttl": null,
+ "cache_empty_result": null,
+ "multiplex": null,
+ "client_addr": null,
+ "comment": null,
+ "delay": null,
+ "destination_hostgroup": 1,
+ "digest": null,
+ "OK_msg": null,
+ "error_msg": null,
+ "flagIN": "0",
+ "flagOUT": null,
+ "log": null,
+ "match_digest": null,
+ "match_pattern": null,
+ "mirror_flagOUT": null,
+ "mirror_hostgroup": null,
+ "negate_match_pattern": "0",
+ "proxy_addr": null,
+ "proxy_port": null,
+ "reconnect": null,
+ "replace_pattern": null,
+ "retries": null,
+ "rule_id": "1",
+ "schemaname": null,
+ "timeout": null,
+ "username": "guest_ro"
+ }
+ ],
+ "state": "present"
+ }
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.community.proxysql.plugins.module_utils.mysql import (
+ mysql_connect,
+ mysql_driver,
+ proxysql_common_argument_spec,
+ save_config_to_disk,
+ load_config_to_runtime,
+)
+from ansible.module_utils.six import iteritems
+from ansible.module_utils._text import to_native
+
+# ===========================================
+# proxysql module specific support methods.
+#
+
+
+class ProxyQueryRule(object):
+
+ def __init__(self, module, version):
+ self.state = module.params["state"]
+ self.force_delete = module.params["force_delete"]
+ self.save_to_disk = module.params["save_to_disk"]
+ self.load_to_runtime = module.params["load_to_runtime"]
+
+ config_data_keys = ["rule_id",
+ "active",
+ "username",
+ "schemaname",
+ "flagIN",
+ "client_addr",
+ "proxy_addr",
+ "proxy_port",
+ "digest",
+ "match_digest",
+ "match_pattern",
+ "negate_match_pattern",
+ "re_modifiers",
+ "flagOUT",
+ "replace_pattern",
+ "destination_hostgroup",
+ "cache_ttl",
+ "multiplex",
+ "timeout",
+ "retries",
+ "delay",
+ "next_query_flagIN",
+ "mirror_flagOUT",
+ "mirror_hostgroup",
+ "error_msg",
+ "OK_msg",
+ "multiplex",
+ "log",
+ "apply",
+ "comment"]
+
+ if version.get('major') >= 2:
+ config_data_keys.append("cache_empty_result")
+
+ self.config_data = dict((k, module.params[k])
+ for k in config_data_keys)
+
+ def check_rule_pk_exists(self, cursor):
+ query_string = \
+ """SELECT count(*) AS `rule_count`
+ FROM mysql_query_rules
+ WHERE rule_id = %s"""
+
+ query_data = \
+ [self.config_data["rule_id"]]
+
+ cursor.execute(query_string, query_data)
+ check_count = cursor.fetchone()
+ return (int(check_count['rule_count']) > 0)
+
+ def check_rule_cfg_exists(self, cursor):
+ query_string = \
+ """SELECT count(*) AS `rule_count`
+ FROM mysql_query_rules"""
+
+ 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 += "\n WHERE " + col + " = %s"
+ else:
+ query_string += "\n AND " + col + " = %s"
+
+ if cols > 0:
+ cursor.execute(query_string, query_data)
+ else:
+ cursor.execute(query_string)
+ check_count = cursor.fetchone()
+ return int(check_count['rule_count'])
+
+ def get_rule_config(self, cursor, created_rule_id=None):
+ query_string = \
+ """SELECT *
+ FROM mysql_query_rules"""
+
+ if created_rule_id:
+ query_data = [created_rule_id, ]
+ query_string += "\nWHERE rule_id = %s"
+
+ cursor.execute(query_string, query_data)
+ rule = cursor.fetchone()
+ else:
+ 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 += "\n WHERE " + col + " = %s"
+ else:
+ query_string += "\n AND " + col + " = %s"
+
+ if cols > 0:
+ cursor.execute(query_string, query_data)
+ else:
+ cursor.execute(query_string)
+ rule = cursor.fetchall()
+
+ return rule
+
+ def create_rule_config(self, cursor):
+ query_string = \
+ """INSERT INTO mysql_query_rules ("""
+
+ cols = 0
+ query_data = []
+
+ 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 = query_string[:-1]
+
+ query_string += \
+ (")\n" +
+ "VALUES (" +
+ "%s ," * cols)
+
+ query_string = query_string[:-2]
+ query_string += ")"
+
+ cursor.execute(query_string, query_data)
+ new_rule_id = cursor.lastrowid
+ return True, new_rule_id
+
+ def update_rule_config(self, cursor):
+ query_string = """UPDATE mysql_query_rules"""
+
+ cols = 0
+ query_data = []
+
+ for col, val in iteritems(self.config_data):
+ if val is not None and col != "rule_id":
+ 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 rule_id = %s"
+
+ query_data.append(self.config_data["rule_id"])
+
+ cursor.execute(query_string, query_data)
+ return True
+
+ def delete_rule_config(self, cursor):
+ query_string = \
+ """DELETE FROM mysql_query_rules"""
+
+ 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 += "\n WHERE " + col + " = %s"
+ else:
+ query_string += "\n AND " + col + " = %s"
+
+ if cols > 0:
+ cursor.execute(query_string, query_data)
+ else:
+ cursor.execute(query_string)
+ check_count = cursor.rowcount
+ return True, int(check_count)
+
+ def manage_config(self, cursor, state):
+ if state:
+ if self.save_to_disk:
+ save_config_to_disk(cursor, "QUERY RULES")
+ if self.load_to_runtime:
+ load_config_to_runtime(cursor, "QUERY RULES")
+
+ def create_rule(self, check_mode, result, cursor):
+ if not check_mode:
+ result['changed'], new_rule_id = \
+ self.create_rule_config(cursor)
+ result['msg'] = "Added rule to mysql_query_rules"
+ self.manage_config(cursor,
+ result['changed'])
+ result['rules'] = \
+ self.get_rule_config(cursor, new_rule_id)
+ else:
+ result['changed'] = True
+ result['msg'] = ("Rule would have been added to" +
+ " mysql_query_rules, however" +
+ " check_mode is enabled.")
+
+ def update_rule(self, check_mode, result, cursor):
+ if not check_mode:
+ result['changed'] = \
+ self.update_rule_config(cursor)
+ result['msg'] = "Updated rule in mysql_query_rules"
+ self.manage_config(cursor,
+ result['changed'])
+ result['rules'] = \
+ self.get_rule_config(cursor)
+ else:
+ result['changed'] = True
+ result['msg'] = ("Rule would have been updated in" +
+ " mysql_query_rules, however" +
+ " check_mode is enabled.")
+
+ def delete_rule(self, check_mode, result, cursor):
+ if not check_mode:
+ result['rules'] = \
+ self.get_rule_config(cursor)
+ result['changed'], result['rows_affected'] = \
+ self.delete_rule_config(cursor)
+ result['msg'] = "Deleted rule from mysql_query_rules"
+ self.manage_config(cursor,
+ result['changed'])
+ else:
+ result['changed'] = True
+ result['msg'] = ("Rule would have been deleted from" +
+ " mysql_query_rules, however" +
+ " check_mode is enabled.")
+
+# ===========================================
+# Module execution.
+#
+
+
+def main():
+ argument_spec = proxysql_common_argument_spec()
+ argument_spec.update(
+ rule_id=dict(type='int'),
+ active=dict(type='bool'),
+ username=dict(type='str'),
+ schemaname=dict(type='str'),
+ flagIN=dict(type='int'),
+ client_addr=dict(type='str'),
+ proxy_addr=dict(type='str'),
+ proxy_port=dict(type='int'),
+ digest=dict(type='str'),
+ match_digest=dict(type='str'),
+ match_pattern=dict(type='str'),
+ negate_match_pattern=dict(type='bool'),
+ re_modifiers=dict(type='str'),
+ flagOUT=dict(type='int'),
+ replace_pattern=dict(type='str'),
+ destination_hostgroup=dict(type='int'),
+ cache_ttl=dict(type='int'),
+ cache_empty_result=dict(type='bool'),
+ multiplex=dict(type='int', choices=[0, 1, 2]),
+ timeout=dict(type='int'),
+ retries=dict(type='int'),
+ delay=dict(type='int'),
+ next_query_flagIN=dict(type='int'),
+ mirror_flagOUT=dict(type='int'),
+ mirror_hostgroup=dict(type='int'),
+ OK_msg=dict(type='str'),
+ error_msg=dict(type='str'),
+ log=dict(type='bool'),
+ apply=dict(type='bool'),
+ comment=dict(type='str'),
+ state=dict(default='present', choices=['present',
+ 'absent']),
+ force_delete=dict(default=False, type='bool'),
+ save_to_disk=dict(default=True, type='bool'),
+ load_to_runtime=dict(default=True, type='bool')
+ )
+
+ module = AnsibleModule(
+ supports_check_mode=True,
+ argument_spec=argument_spec
+ )
+
+ login_user = module.params["login_user"]
+ login_password = module.params["login_password"]
+ config_file = module.params["config_file"]
+
+ cursor = None
+ try:
+ cursor, db_conn, version = 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_query_rule = ProxyQueryRule(module, version)
+ result = {}
+
+ result['state'] = proxysql_query_rule.state
+
+ if proxysql_query_rule.state == "present":
+ try:
+ if not proxysql_query_rule.check_rule_cfg_exists(cursor):
+ if proxysql_query_rule.config_data["rule_id"] and \
+ proxysql_query_rule.check_rule_pk_exists(cursor):
+ proxysql_query_rule.update_rule(module.check_mode,
+ result,
+ cursor)
+ else:
+ proxysql_query_rule.create_rule(module.check_mode,
+ result,
+ cursor)
+ else:
+ result['changed'] = False
+ result['msg'] = ("The rule already exists in" +
+ " mysql_query_rules and doesn't need to be" +
+ " updated.")
+ result['rules'] = \
+ proxysql_query_rule.get_rule_config(cursor)
+
+ except mysql_driver.Error as e:
+ module.fail_json(
+ msg="unable to modify rule.. %s" % to_native(e)
+ )
+
+ elif proxysql_query_rule.state == "absent":
+ try:
+ existing_rules = proxysql_query_rule.check_rule_cfg_exists(cursor)
+ if existing_rules > 0:
+ if existing_rules == 1 or \
+ proxysql_query_rule.force_delete:
+ proxysql_query_rule.delete_rule(module.check_mode,
+ result,
+ cursor)
+ else:
+ module.fail_json(
+ msg=("Operation would delete multiple rules" +
+ " use force_delete to override this")
+ )
+ else:
+ result['changed'] = False
+ result['msg'] = ("The rule is already absent from the" +
+ " mysql_query_rules memory configuration")
+ except mysql_driver.Error as e:
+ module.fail_json(
+ msg="unable to remove rule.. %s" % to_native(e)
+ )
+
+ module.exit_json(**result)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/community/proxysql/plugins/modules/proxysql_query_rules_fast_routing.py b/ansible_collections/community/proxysql/plugins/modules/proxysql_query_rules_fast_routing.py
new file mode 100644
index 000000000..a02f9be7c
--- /dev/null
+++ b/ansible_collections/community/proxysql/plugins/modules/proxysql_query_rules_fast_routing.py
@@ -0,0 +1,420 @@
+#!/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_query_rules_fast_routing
+author: "Akim Lindberg (@akimrx)"
+short_description: Modifies query rules for fast routing policies using the proxysql admin interface
+description:
+ - The M(community.proxysql.proxysql_query_rules_fast_routing) module modifies query rules for fast
+ routing policies and attributes using the proxysql admin interface.
+version_added: '1.1.0'
+options:
+ username:
+ description:
+ - Filtering criteria matching username, a query will match only if the connection is made with
+ the correct username.
+ type: str
+ required: true
+ schemaname:
+ description:
+ - Filtering criteria matching schemaname, a query will match only if the connection uses
+ schemaname as its default schema.
+ type: str
+ required: true
+ flagIN:
+ description:
+ - Evaluated in the same way as I(flagIN) is in B(mysql_query_rules) and correlates to the
+ I(flagOUT/apply) specified in the B(mysql_query_rules) table.
+ (see M(community.proxysql.proxysql_query_rules)).
+ type: int
+ default: 0
+ destination_hostgroup:
+ description:
+ - Route matched queries to this hostgroup. This happens unless there is a
+ started transaction and the logged in user has
+ I(transaction_persistent) set to C(True) (refer to M(community.proxysql.proxysql_mysql_users)).
+ type: int
+ required: true
+ comment:
+ description:
+ - Free form text field, usable for a descriptive comment of the query rule.
+ type: str
+ default: ''
+ state:
+ description:
+ - When C(present), adds the rule. When C(absent), removes the rule.
+ type: str
+ choices: [ "present", "absent" ]
+ default: present
+ force_delete:
+ description:
+ - By default, we avoid deleting more than one schedule in a single batch;
+ however, if you need this behaviour and you are not concerned about the
+ schedules deleted, you can set I(force_delete) to C(True).
+ type: bool
+ default: false
+extends_documentation_fragment:
+- community.proxysql.proxysql.managing_config
+- community.proxysql.proxysql.connectivity
+notes:
+- Supports C(check_mode).
+'''
+
+EXAMPLES = '''
+---
+# This example adds a rule for fast routing
+- name: Add a rule
+ community.proxysql.proxysql_query_rules_fast_routing:
+ login_user: admin
+ login_password: admin
+ username: 'user_ro'
+ schemaname: 'default'
+ destination_hostgroup: 1
+ comment: 'fast route user_ro to default schema'
+ state: present
+ save_to_disk: true
+ load_to_runtime: true
+'''
+
+RETURN = '''
+stdout:
+ description: The mysql user modified or removed from proxysql.
+ returned: On create/update will return the newly modified rule, in all
+ other cases will return a list of rules that match the supplied
+ criteria.
+ type: dict
+ "sample": {
+ "changed": true,
+ "msg": "Added rule to mysql_query_rules_fast_routing",
+ "rules": [
+ {
+ "username": "user_ro",
+ "schemaname": "default",
+ "destination_hostgroup": 1,
+ "flagIN": "0",
+ "comment": ""
+ }
+ ],
+ "state": "present"
+ }
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.community.proxysql.plugins.module_utils.mysql import (
+ mysql_connect,
+ mysql_driver,
+ proxysql_common_argument_spec,
+ save_config_to_disk,
+ load_config_to_runtime,
+)
+from ansible.module_utils.six import iteritems
+from ansible.module_utils._text import to_native
+
+# ===========================================
+# proxysql module specific support methods.
+#
+
+
+class ProxyQueryRuleFastRouting(object):
+
+ def __init__(self, module):
+ self.state = module.params["state"]
+ self.force_delete = module.params["force_delete"]
+ self.save_to_disk = module.params["save_to_disk"]
+ self.load_to_runtime = module.params["load_to_runtime"]
+
+ config_data_keys = [
+ "username",
+ "schemaname",
+ "flagIN",
+ "destination_hostgroup",
+ "comment"
+ ]
+
+ self.config_data = dict(
+ (k, module.params[k])
+ for k in config_data_keys
+ )
+
+ def check_rule_pk_exists(self, cursor):
+ query_string = (
+ "SELECT count(*) AS `rule_count` "
+ "FROM mysql_query_rules_fast_routing "
+ "WHERE username = %s "
+ "AND schemaname = %s "
+ "AND flagIN = %s"
+ )
+
+ query_data = [
+ self.config_data["username"],
+ self.config_data["schemaname"],
+ self.config_data["flagIN"],
+ ]
+
+ cursor.execute(query_string, query_data)
+ check_count = cursor.fetchone()
+ return (int(check_count['rule_count']) > 0)
+
+ def check_rule_cfg_exists(self, cursor):
+ query_string = "SELECT count(*) AS `rule_count` FROM mysql_query_rules_fast_routing"
+
+ 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 += " WHERE " + col + " = %s"
+ else:
+ query_string += " AND " + col + " = %s"
+
+ if cols > 0:
+ cursor.execute(query_string, query_data)
+ else:
+ cursor.execute(query_string)
+ check_count = cursor.fetchone()
+ return int(check_count['rule_count'])
+
+ def get_rule_config(self, cursor):
+ query_string = (
+ "SELECT * "
+ "FROM mysql_query_rules_fast_routing "
+ "WHERE username = %s "
+ "AND schemaname = %s "
+ "AND flagIN = %s"
+ )
+
+ query_data = [
+ self.config_data["username"],
+ self.config_data["schemaname"],
+ self.config_data["flagIN"]
+ ]
+
+ for col, val in iteritems(self.config_data):
+ if val is not None:
+ query_data.append(val)
+ query_string += " AND " + col + " = %s"
+
+ cursor.execute(query_string, query_data)
+ rule = cursor.fetchall()
+ return rule
+
+ def create_rule_config(self, cursor):
+ query_string = "INSERT INTO mysql_query_rules_fast_routing ("
+
+ cols = 0
+ query_data = []
+
+ for col, val in iteritems(self.config_data):
+ if val is not None:
+ cols += 1
+ query_data.append(val)
+ query_string += col + ","
+
+ query_string = query_string[:-1]
+ query_string += ") VALUES (" + "%s, " * cols
+ query_string = query_string[:-2]
+ query_string += ")"
+
+ cursor.execute(query_string, query_data)
+ return True
+
+ def update_rule_config(self, cursor):
+ query_string = "UPDATE mysql_query_rules_fast_routing"
+
+ cols = 0
+ query_data = [
+ self.config_data["username"],
+ self.config_data["schemaname"],
+ self.config_data["flagIN"]
+ ]
+
+ for col, val in iteritems(self.config_data):
+ if val is not None and col not in ("username", "schemaname", "flagIN"):
+ query_data.insert(cols, val)
+ cols += 1
+ if cols == 1:
+ query_string += " SET " + col + "= %s,"
+ else:
+ query_string += " " + col + " = %s,"
+
+ query_string = query_string[:-1]
+ query_string += (
+ "WHERE username = %s "
+ "AND schemaname = %s "
+ "AND flagIN = %s"
+ )
+
+ cursor.execute(query_string, query_data)
+ return True
+
+ def delete_rule_config(self, cursor):
+ query_string = "DELETE FROM mysql_query_rules_fast_routing"
+
+ 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 += " WHERE " + col + " = %s"
+ else:
+ query_string += " AND " + col + " = %s"
+
+ if cols > 0:
+ cursor.execute(query_string, query_data)
+ else:
+ cursor.execute(query_string)
+ check_count = cursor.rowcount
+ return True, int(check_count)
+
+ def manage_config(self, cursor, changed):
+ if not changed:
+ return
+
+ if self.save_to_disk:
+ save_config_to_disk(cursor, "QUERY RULES")
+ if self.load_to_runtime:
+ load_config_to_runtime(cursor, "QUERY RULES")
+
+ def create_rule(self, check_mode, result, cursor):
+ if not check_mode:
+ result['changed'] = self.create_rule_config(cursor)
+ result['msg'] = "Added rule to mysql_query_rules_fast_routing."
+ self.manage_config(cursor, result['changed'])
+ result['rules'] = self.get_rule_config(cursor)
+ else:
+ result['changed'] = True
+ result['msg'] = (
+ "Rule would have been added to "
+ "mysql_query_rules_fast_routing, "
+ "however check_mode is enabled."
+ )
+
+ def update_rule(self, check_mode, result, cursor):
+ if not check_mode:
+ result['changed'] = self.update_rule_config(cursor)
+ result['msg'] = "Updated rule in mysql_query_rules_fast_routing."
+ self.manage_config(cursor, result['changed'])
+ result['rules'] = self.get_rule_config(cursor)
+ else:
+ result['changed'] = True
+ result['msg'] = (
+ "Rule would have been updated in "
+ "mysql_query_rules_fast_routing, "
+ "however check_mode is enabled."
+ )
+
+ def delete_rule(self, check_mode, result, cursor):
+ if not check_mode:
+ result['rules'] = self.get_rule_config(cursor)
+ result['changed'], result['rows_affected'] = self.delete_rule_config(cursor)
+ result['msg'] = "Deleted rule from mysql_query_rules_fast_routing."
+ self.manage_config(cursor, result['changed'])
+ else:
+ result['changed'] = True
+ result['msg'] = (
+ "Rule would have been deleted from "
+ "mysql_query_rules_fast_routing, "
+ "however check_mode is enabled."
+ )
+
+# ===========================================
+# Module execution.
+#
+
+
+def main():
+ argument_spec = proxysql_common_argument_spec()
+ argument_spec.update(
+ username=dict(required=True, type='str'),
+ schemaname=dict(required=True, type='str'),
+ destination_hostgroup=dict(required=True, type='int'),
+ flagIN=dict(default=0, type='int'),
+ comment=dict(default='', type='str'),
+ state=dict(default='present', choices=['present', 'absent']),
+ force_delete=dict(default=False, type='bool'),
+ save_to_disk=dict(default=True, type='bool'),
+ load_to_runtime=dict(default=True, type='bool')
+ )
+
+ module = AnsibleModule(
+ supports_check_mode=True,
+ argument_spec=argument_spec
+ )
+
+ login_user = module.params["login_user"]
+ login_password = module.params["login_password"]
+ config_file = module.params["config_file"]
+
+ cursor = None
+ try:
+ cursor, db_conn, version = 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))
+
+ query_rule = ProxyQueryRuleFastRouting(module)
+ result = {}
+
+ result['state'] = query_rule.state
+
+ if query_rule.state == "present":
+ try:
+ if not query_rule.check_rule_cfg_exists(cursor):
+ if query_rule.config_data["username"] and query_rule.config_data["schemaname"] and \
+ query_rule.check_rule_pk_exists(cursor):
+ query_rule.update_rule(module.check_mode, result, cursor)
+ else:
+ query_rule.create_rule(module.check_mode, result, cursor)
+ else:
+ result['changed'] = False
+ result['msg'] = (
+ "The rule already exists in "
+ "mysql_query_rules_fast_routing "
+ "and doesn't need to be updated."
+ )
+ result['rules'] = query_rule.get_rule_config(cursor)
+
+ except mysql_driver.Error as e:
+ module.fail_json(msg="unable to modify rule: %s" % to_native(e))
+
+ elif query_rule.state == "absent":
+ try:
+ existing_rules = query_rule.check_rule_cfg_exists(cursor)
+ if existing_rules > 0:
+ if existing_rules == 1 or query_rule.force_delete:
+ query_rule.delete_rule(module.check_mode, result, cursor)
+ else:
+ module.fail_json(msg=("Operation would delete multiple rules use force_delete to override this."))
+ else:
+ result['changed'] = False
+ result['msg'] = (
+ "The rule is already absent from the "
+ "mysql_query_rules_fast_routing memory "
+ "configuration"
+ )
+ except mysql_driver.Error as e:
+ module.fail_json(msg="unable to remove rule: %s" % to_native(e))
+
+ module.exit_json(**result)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/community/proxysql/plugins/modules/proxysql_replication_hostgroups.py b/ansible_collections/community/proxysql/plugins/modules/proxysql_replication_hostgroups.py
new file mode 100644
index 000000000..9ea04053f
--- /dev/null
+++ b/ansible_collections/community/proxysql/plugins/modules/proxysql_replication_hostgroups.py
@@ -0,0 +1,398 @@
+#!/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_replication_hostgroups
+author: "Ben Mildren (@bmildren)"
+short_description: Manages replication hostgroups using the proxysql admin
+ interface
+description:
+ - Each row in mysql_replication_hostgroups represent a pair of
+ writer_hostgroup and reader_hostgroup. ProxySQL will monitor the value of
+ read_only for all the servers in specified hostgroups, and based on the
+ value of read_only will assign the server to the writer or reader
+ hostgroups.
+options:
+ writer_hostgroup:
+ description:
+ - Id of the writer hostgroup.
+ type: int
+ required: true
+ reader_hostgroup:
+ description:
+ - Id of the reader hostgroup.
+ type: int
+ required: true
+ comment:
+ description:
+ - Text field that can be used for any purposes defined by the user.
+ type: str
+ default: ""
+ state:
+ description:
+ - When C(present) - adds the replication hostgroup, when C(absent) -
+ removes the replication hostgroup.
+ type: str
+ choices: [ "present", "absent" ]
+ default: present
+ check_type:
+ description:
+ - Which check type to use when detecting that the node is a standby.
+ - Requires proxysql >= 2.0.1. Otherwise it has no effect.
+ - C(read_only|innodb_read_only) and C(read_only&innodb_read_only) requires proxysql >= 2.0.8.
+ type: str
+ choices: [ "read_only", "innodb_read_only", "super_read_only", "read_only|innodb_read_only", "read_only&innodb_read_only" ]
+ default: read_only
+ version_added: 1.3.0
+extends_documentation_fragment:
+- community.proxysql.proxysql.managing_config
+- community.proxysql.proxysql.connectivity
+notes:
+- Supports C(check_mode).
+'''
+
+EXAMPLES = '''
+---
+# This example adds a replication hostgroup, it saves the mysql server config
+# to disk, but avoids loading the mysql server config to runtime (this might be
+# because several replication hostgroup 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 replication hostgroup
+ community.proxysql.proxysql_replication_hostgroups:
+ login_user: 'admin'
+ login_password: 'admin'
+ writer_hostgroup: 1
+ reader_hostgroup: 2
+ state: present
+ load_to_runtime: false
+
+- name: Change check_type
+ community.proxysql.proxysql_replication_hostgroups:
+ login_user: 'admin'
+ login_password: 'admin'
+ writer_hostgroup: 1
+ reader_hostgroup: 2
+ check_type: innodb_read_only
+ state: present
+ load_to_runtime: false
+
+# This example removes a replication hostgroup, 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 replication hostgroup
+ community.proxysql.proxysql_replication_hostgroups:
+ config_file: '~/proxysql.cnf'
+ writer_hostgroup: 3
+ reader_hostgroup: 4
+ state: absent
+'''
+
+RETURN = '''
+stdout:
+ description: The replication hostgroup modified or removed from proxysql.
+ returned: On create/update will return the newly modified group, on delete
+ it will return the deleted record.
+ type: dict
+ "sample": {
+ "changed": true,
+ "msg": "Added server to mysql_hosts",
+ "repl_group": {
+ "comment": "",
+ "reader_hostgroup": "1",
+ "writer_hostgroup": "2",
+ "check_type": "read_only"
+ },
+ "state": "present"
+ }
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.community.proxysql.plugins.module_utils.mysql import (
+ mysql_connect,
+ mysql_driver,
+ proxysql_common_argument_spec,
+ save_config_to_disk,
+ load_config_to_runtime,
+)
+from ansible.module_utils._text import to_native
+
+# ===========================================
+# proxysql module specific support methods.
+#
+
+
+def perform_checks(module):
+ if not module.params["writer_hostgroup"] >= 0:
+ module.fail_json(
+ msg="writer_hostgroup must be a integer greater than or equal to 0"
+ )
+
+ if module.params["reader_hostgroup"] < 0:
+ module.fail_json(
+ msg="reader_hostgroup must be an integer greater than or equal to 0"
+ )
+
+ if module.params["reader_hostgroup"] == module.params["writer_hostgroup"]:
+ module.fail_json(
+ msg="reader_hostgroup and writer_hostgroup must be different integer values")
+
+
+class ProxySQLReplicationHostgroup(object):
+
+ def __init__(self, module, version):
+ self.state = module.params["state"]
+ self.save_to_disk = module.params["save_to_disk"]
+ self.load_to_runtime = module.params["load_to_runtime"]
+ self.writer_hostgroup = module.params["writer_hostgroup"]
+ self.reader_hostgroup = module.params["reader_hostgroup"]
+ self.comment = module.params["comment"]
+ self.check_type = module.params["check_type"]
+ self.check_type_support = version.get('major') >= 2
+ self.check_mode = module.check_mode
+
+ def check_repl_group_config(self, cursor, keys):
+ query_string = \
+ """SELECT count(*) AS `repl_groups`
+ FROM mysql_replication_hostgroups
+ WHERE writer_hostgroup = %s"""
+
+ query_data = \
+ [self.writer_hostgroup]
+
+ cursor.execute(query_string, query_data)
+ check_count = cursor.fetchone()
+ return (int(check_count['repl_groups']) > 0)
+
+ def get_repl_group_config(self, cursor):
+ query_string = \
+ """SELECT *
+ FROM mysql_replication_hostgroups
+ WHERE writer_hostgroup = %s"""
+
+ query_data = \
+ [self.writer_hostgroup]
+
+ cursor.execute(query_string, query_data)
+ repl_group = cursor.fetchone()
+ return repl_group
+
+ def create_repl_group_config(self, cursor):
+ query_string = \
+ """INSERT INTO mysql_replication_hostgroups (
+ writer_hostgroup,
+ reader_hostgroup,
+ comment)
+ VALUES (%s, %s, %s)"""
+
+ query_data = \
+ [self.writer_hostgroup,
+ self.reader_hostgroup,
+ self.comment or '']
+
+ cursor.execute(query_string, query_data)
+
+ if self.check_type_support:
+ self.update_check_type(cursor)
+
+ return True
+
+ def delete_repl_group_config(self, cursor):
+ query_string = \
+ """DELETE FROM mysql_replication_hostgroups
+ WHERE writer_hostgroup = %s"""
+
+ query_data = \
+ [self.writer_hostgroup]
+
+ cursor.execute(query_string, query_data)
+ return True
+
+ def manage_config(self, cursor, state):
+ if state and not self.check_mode:
+ if self.save_to_disk:
+ save_config_to_disk(cursor, "SERVERS")
+ if self.load_to_runtime:
+ load_config_to_runtime(cursor, "SERVERS")
+
+ def create_repl_group(self, result, cursor):
+ if not self.check_mode:
+ result['changed'] = \
+ self.create_repl_group_config(cursor)
+ result['msg'] = "Added server to mysql_hosts"
+ result['repl_group'] = \
+ self.get_repl_group_config(cursor)
+ self.manage_config(cursor,
+ result['changed'])
+ else:
+ result['changed'] = True
+ result['msg'] = ("Repl group would have been added to" +
+ " mysql_replication_hostgroups, however" +
+ " check_mode is enabled.")
+
+ def update_repl_group(self, result, cursor):
+ current = self.get_repl_group_config(cursor)
+
+ if self.check_type_support and current.get('check_type') != self.check_type:
+ result['changed'] = True
+ if not self.check_mode:
+ result['msg'] = "Updated replication hostgroups"
+ self.update_check_type(cursor)
+ else:
+ result['msg'] = "Updated replication hostgroups in check_mode"
+
+ if current.get('comment') != self.comment:
+ result['changed'] = True
+ result['msg'] = "Updated replication hostgroups in check_mode"
+ if not self.check_mode:
+ result['msg'] = "Updated replication hostgroups"
+ self.update_comment(cursor)
+
+ if int(current.get('reader_hostgroup')) != self.reader_hostgroup:
+ result['changed'] = True
+ result['msg'] = "Updated replication hostgroups in check_mode"
+ if not self.check_mode:
+ result['msg'] = "Updated replication hostgroups"
+ self.update_reader_hostgroup(cursor)
+
+ result['repl_group'] = self.get_repl_group_config(cursor)
+
+ self.manage_config(cursor,
+ result['changed'])
+
+ def delete_repl_group(self, result, cursor):
+ if not self.check_mode:
+ result['repl_group'] = \
+ self.get_repl_group_config(cursor)
+ result['changed'] = \
+ self.delete_repl_group_config(cursor)
+ result['msg'] = "Deleted server from mysql_hosts"
+ self.manage_config(cursor,
+ result['changed'])
+ else:
+ result['changed'] = True
+ result['msg'] = ("Repl group would have been deleted from" +
+ " mysql_replication_hostgroups, however" +
+ " check_mode is enabled.")
+
+ def update_check_type(self, cursor):
+ try:
+ query_string = ("UPDATE mysql_replication_hostgroups "
+ "SET check_type = %s "
+ "WHERE writer_hostgroup = %s")
+
+ cursor.execute(query_string, (self.check_type, self.writer_hostgroup))
+ except Exception as e:
+ pass
+
+ def update_reader_hostgroup(self, cursor):
+ query_string = ("UPDATE mysql_replication_hostgroups "
+ "SET reader_hostgroup = %s "
+ "WHERE writer_hostgroup = %s")
+
+ cursor.execute(query_string, (self.reader_hostgroup, self.writer_hostgroup))
+
+ def update_comment(self, cursor):
+ query_string = ("UPDATE mysql_replication_hostgroups "
+ "SET comment = %s "
+ "WHERE writer_hostgroup = %s ")
+
+ cursor.execute(query_string, (self.comment, self.writer_hostgroup))
+
+
+# ===========================================
+# Module execution.
+#
+def main():
+ argument_spec = proxysql_common_argument_spec()
+ argument_spec.update(
+ writer_hostgroup=dict(required=True, type='int'),
+ reader_hostgroup=dict(required=True, type='int'),
+ check_type=dict(type='str', default='read_only', choices=['read_only',
+ 'innodb_read_only',
+ 'super_read_only',
+ 'read_only|innodb_read_only',
+ 'read_only&innodb_read_only']),
+ comment=dict(type='str', default=''),
+ state=dict(default='present', choices=['present',
+ 'absent']),
+ save_to_disk=dict(default=True, type='bool'),
+ load_to_runtime=dict(default=True, type='bool')
+ )
+
+ module = AnsibleModule(
+ supports_check_mode=True,
+ argument_spec=argument_spec
+ )
+
+ 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, version = 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_repl_group = ProxySQLReplicationHostgroup(module, version)
+ result = {}
+
+ result['state'] = proxysql_repl_group.state
+ result['changed'] = False
+
+ if proxysql_repl_group.state == "present":
+ try:
+ if not proxysql_repl_group.check_repl_group_config(cursor,
+ keys=True):
+ proxysql_repl_group.create_repl_group(result,
+ cursor)
+ else:
+ proxysql_repl_group.update_repl_group(result, cursor)
+
+ result['repl_group'] = proxysql_repl_group.get_repl_group_config(cursor)
+
+ except mysql_driver.Error as e:
+ module.fail_json(
+ msg="unable to modify replication hostgroup.. %s" % to_native(e)
+ )
+
+ elif proxysql_repl_group.state == "absent":
+ try:
+ if proxysql_repl_group.check_repl_group_config(cursor,
+ keys=True):
+ proxysql_repl_group.delete_repl_group(result, cursor)
+ else:
+ result['changed'] = False
+ result['msg'] = ("The repl group is already absent from the" +
+ " mysql_replication_hostgroups memory" +
+ " configuration")
+
+ except mysql_driver.Error as e:
+ module.fail_json(
+ msg="unable to delete replication hostgroup.. %s" % to_native(e)
+ )
+
+ module.exit_json(**result)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/community/proxysql/plugins/modules/proxysql_scheduler.py b/ansible_collections/community/proxysql/plugins/modules/proxysql_scheduler.py
new file mode 100644
index 000000000..474ddc0a1
--- /dev/null
+++ b/ansible_collections/community/proxysql/plugins/modules/proxysql_scheduler.py
@@ -0,0 +1,409 @@
+#!/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_scheduler
+author: "Ben Mildren (@bmildren)"
+short_description: Adds or removes schedules from proxysql admin interface
+description:
+ - The M(community.proxysql.proxysql_scheduler) module adds or removes schedules using the
+ proxysql admin interface.
+options:
+ active:
+ description:
+ - A schedule with I(active) set to C(False) will be tracked in the
+ database, but will be never loaded in the in-memory data structures.
+ type: bool
+ default: true
+ interval_ms:
+ description:
+ - How often (in millisecond) the job will be started. The minimum value
+ for I(interval_ms) is 100 milliseconds.
+ type: int
+ default: 10000
+ filename:
+ description:
+ - Full path of the executable to be executed.
+ type: str
+ required: true
+ arg1:
+ description:
+ - Argument that can be passed to the job.
+ type: str
+ arg2:
+ description:
+ - Argument that can be passed to the job.
+ type: str
+ arg3:
+ description:
+ - Argument that can be passed to the job.
+ type: str
+ arg4:
+ description:
+ - Argument that can be passed to the job.
+ type: str
+ arg5:
+ description:
+ - Argument that can be passed to the job.
+ type: str
+ comment:
+ description:
+ - Text field that can be used for any purposed defined by the user.
+ type: str
+ state:
+ description:
+ - When C(present) - adds the schedule, when C(absent) - removes the
+ schedule.
+ type: str
+ choices: [ "present", "absent" ]
+ default: present
+ force_delete:
+ description:
+ - By default we avoid deleting more than one schedule in a single batch,
+ however if you need this behaviour and you are not concerned about the
+ schedules deleted, you can set I(force_delete) to C(True).
+ type: bool
+ default: false
+extends_documentation_fragment:
+- community.proxysql.proxysql.managing_config
+- community.proxysql.proxysql.connectivity
+notes:
+- Supports C(check_mode).
+'''
+
+EXAMPLES = '''
+---
+# This example adds a schedule, it saves the scheduler config to disk, but
+# avoids loading the scheduler 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 schedule
+ community.proxysql.proxysql_scheduler:
+ login_user: 'admin'
+ login_password: 'admin'
+ interval_ms: 1000
+ filename: "/opt/maintenance.py"
+ state: present
+ load_to_runtime: false
+
+# This example removes a schedule, saves the scheduler config to disk, and
+# dynamically loads the scheduler config to runtime. It uses credentials
+# in a supplied config file to connect to the proxysql admin interface.
+
+- name: Remove a schedule
+ community.proxysql.proxysql_scheduler:
+ config_file: '~/proxysql.cnf'
+ filename: "/opt/old_script.py"
+ state: absent
+'''
+
+RETURN = '''
+stdout:
+ description: The schedule modified or removed from proxysql.
+ returned: On create/update will return the newly modified schedule, on
+ delete it will return the deleted record.
+ type: dict
+ "sample": {
+ "changed": true,
+ "filename": "/opt/test.py",
+ "msg": "Added schedule to scheduler",
+ "schedules": [
+ {
+ "active": "1",
+ "arg1": null,
+ "arg2": null,
+ "arg3": null,
+ "arg4": null,
+ "arg5": null,
+ "comment": "",
+ "filename": "/opt/test.py",
+ "id": "1",
+ "interval_ms": "10000"
+ }
+ ],
+ "state": "present"
+ }
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.community.proxysql.plugins.module_utils.mysql import (
+ mysql_connect,
+ mysql_driver,
+ proxysql_common_argument_spec,
+ save_config_to_disk,
+ load_config_to_runtime,
+)
+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["interval_ms"] < 100 \
+ or module.params["interval_ms"] > 100000000:
+ module.fail_json(
+ msg="interval_ms must between 100ms & 100000000ms"
+ )
+
+
+class ProxySQLSchedule(object):
+
+ def __init__(self, module):
+ self.state = module.params["state"]
+ self.force_delete = module.params["force_delete"]
+ self.save_to_disk = module.params["save_to_disk"]
+ self.load_to_runtime = module.params["load_to_runtime"]
+ self.active = module.params["active"]
+ self.interval_ms = module.params["interval_ms"]
+ self.filename = module.params["filename"]
+
+ config_data_keys = ["arg1",
+ "arg2",
+ "arg3",
+ "arg4",
+ "arg5",
+ "comment"]
+
+ self.config_data = dict((k, module.params[k])
+ for k in config_data_keys)
+
+ def check_schedule_config(self, cursor):
+ query_string = \
+ """SELECT count(*) AS `schedule_count`
+ FROM scheduler
+ WHERE active = %s
+ AND interval_ms = %s
+ AND filename = %s"""
+
+ query_data = \
+ [self.active,
+ self.interval_ms,
+ self.filename]
+
+ 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()
+ return int(check_count['schedule_count'])
+
+ def get_schedule_config(self, cursor):
+ query_string = \
+ """SELECT *
+ FROM scheduler
+ WHERE active = %s
+ AND interval_ms = %s
+ AND filename = %s"""
+
+ query_data = \
+ [self.active,
+ self.interval_ms,
+ self.filename]
+
+ 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)
+ schedule = cursor.fetchall()
+ return schedule
+
+ def create_schedule_config(self, cursor):
+ query_string = \
+ """INSERT INTO scheduler (
+ active,
+ interval_ms,
+ filename"""
+
+ cols = 0
+ query_data = \
+ [self.active,
+ self.interval_ms,
+ self.filename]
+
+ 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, %s, %s" +
+ ", %s" * cols +
+ ")")
+
+ cursor.execute(query_string, query_data)
+ return True
+
+ def delete_schedule_config(self, cursor):
+ query_string = \
+ """DELETE FROM scheduler
+ WHERE active = %s
+ AND interval_ms = %s
+ AND filename = %s"""
+
+ query_data = \
+ [self.active,
+ self.interval_ms,
+ self.filename]
+
+ 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.rowcount
+ return True, int(check_count)
+
+ def manage_config(self, cursor, state):
+ if state:
+ if self.save_to_disk:
+ save_config_to_disk(cursor, "SCHEDULER")
+ if self.load_to_runtime:
+ load_config_to_runtime(cursor, "SCHEDULER")
+
+ def create_schedule(self, check_mode, result, cursor):
+ if not check_mode:
+ result['changed'] = \
+ self.create_schedule_config(cursor)
+ result['msg'] = "Added schedule to scheduler"
+ result['schedules'] = \
+ self.get_schedule_config(cursor)
+ self.manage_config(cursor,
+ result['changed'])
+ else:
+ result['changed'] = True
+ result['msg'] = ("Schedule would have been added to" +
+ " scheduler, however check_mode" +
+ " is enabled.")
+
+ def delete_schedule(self, check_mode, result, cursor):
+ if not check_mode:
+ result['schedules'] = \
+ self.get_schedule_config(cursor)
+ result['changed'] = \
+ self.delete_schedule_config(cursor)
+ result['msg'] = "Deleted schedule from scheduler"
+ self.manage_config(cursor,
+ result['changed'])
+ else:
+ result['changed'] = True
+ result['msg'] = ("Schedule would have been deleted from" +
+ " scheduler, however check_mode is" +
+ " enabled.")
+
+# ===========================================
+# Module execution.
+#
+
+
+def main():
+ argument_spec = proxysql_common_argument_spec()
+ argument_spec.update(
+ active=dict(default=True, type='bool'),
+ interval_ms=dict(default=10000, type='int'),
+ filename=dict(required=True, type='str'),
+ arg1=dict(type='str'),
+ arg2=dict(type='str'),
+ arg3=dict(type='str'),
+ arg4=dict(type='str'),
+ arg5=dict(type='str'),
+ comment=dict(type='str'),
+ state=dict(default='present', choices=['present',
+ 'absent']),
+ force_delete=dict(default=False, type='bool'),
+ save_to_disk=dict(default=True, type='bool'),
+ load_to_runtime=dict(default=True, type='bool')
+ )
+
+ module = AnsibleModule(
+ supports_check_mode=True,
+ argument_spec=argument_spec
+ )
+
+ 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, version = 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_schedule = ProxySQLSchedule(module)
+ result = {}
+
+ result['state'] = proxysql_schedule.state
+ result['filename'] = proxysql_schedule.filename
+
+ if proxysql_schedule.state == "present":
+ try:
+ if proxysql_schedule.check_schedule_config(cursor) <= 0:
+ proxysql_schedule.create_schedule(module.check_mode,
+ result,
+ cursor)
+ else:
+ result['changed'] = False
+ result['msg'] = ("The schedule already exists and doesn't" +
+ " need to be updated.")
+ result['schedules'] = \
+ proxysql_schedule.get_schedule_config(cursor)
+ except mysql_driver.Error as e:
+ module.fail_json(
+ msg="unable to modify schedule.. %s" % to_native(e)
+ )
+
+ elif proxysql_schedule.state == "absent":
+ try:
+ existing_schedules = \
+ proxysql_schedule.check_schedule_config(cursor)
+ if existing_schedules > 0:
+ if existing_schedules == 1 or proxysql_schedule.force_delete:
+ proxysql_schedule.delete_schedule(module.check_mode,
+ result,
+ cursor)
+ else:
+ module.fail_json(
+ msg=("Operation would delete multiple records" +
+ " use force_delete to override this")
+ )
+ else:
+ result['changed'] = False
+ result['msg'] = ("The schedule is already absent from the" +
+ " memory configuration")
+ except mysql_driver.Error as e:
+ module.fail_json(
+ msg="unable to remove schedule.. %s" % to_native(e)
+ )
+
+ module.exit_json(**result)
+
+
+if __name__ == '__main__':
+ main()