mysql.py (salt-3002.1) | : | mysql.py (salt-3002.2) | ||
---|---|---|---|---|
# -*- coding: utf-8 -*- | ||||
""" | """ | |||
Module to provide MySQL compatibility to salt. | Module to provide MySQL compatibility to salt. | |||
:depends: - MySQLdb Python module | :depends: - MySQLdb Python module | |||
.. note:: | .. note:: | |||
On CentOS 5 (and possibly RHEL 5) both MySQL-python and python26-mysqldb | On CentOS 5 (and possibly RHEL 5) both MySQL-python and python26-mysqldb | |||
need to be installed. | need to be installed. | |||
skipping to change at line 36 | skipping to change at line 35 | |||
mysql.default_file: '/etc/mysql/debian.cnf' | mysql.default_file: '/etc/mysql/debian.cnf' | |||
.. versionchanged:: 2014.1.0 | .. versionchanged:: 2014.1.0 | |||
\'charset\' connection argument added. This is a MySQL charset, not a python one. | \'charset\' connection argument added. This is a MySQL charset, not a python one. | |||
.. versionchanged:: 0.16.2 | .. versionchanged:: 0.16.2 | |||
Connection arguments from the minion config file can be overridden on the | Connection arguments from the minion config file can be overridden on the | |||
CLI by using the arguments defined :mod:`here <salt.states.mysql_user>`. | CLI by using the arguments defined :mod:`here <salt.states.mysql_user>`. | |||
Additionally, it is now possible to setup a user with no password. | Additionally, it is now possible to setup a user with no password. | |||
""" | """ | |||
# Import python libs | ||||
from __future__ import absolute_import, print_function, unicode_literals | ||||
import copy | import copy | |||
import hashlib | import hashlib | |||
import logging | import logging | |||
import os | import os | |||
import re | import re | |||
import shlex | import shlex | |||
import sys | import sys | |||
import time | import time | |||
# Import salt libs | ||||
import salt.utils.data | import salt.utils.data | |||
import salt.utils.files | import salt.utils.files | |||
import salt.utils.stringutils | import salt.utils.stringutils | |||
# Import third party libs | ||||
from salt.ext import six | ||||
# pylint: disable=import-error | # pylint: disable=import-error | |||
from salt.ext.six.moves import range, zip | from salt.ext.six.moves import range, zip | |||
try: | try: | |||
# Trying to import MySQLdb | # Trying to import MySQLdb | |||
import MySQLdb | import MySQLdb | |||
import MySQLdb.cursors | import MySQLdb.cursors | |||
import MySQLdb.converters | import MySQLdb.converters | |||
from MySQLdb.constants import FIELD_TYPE, FLAG | from MySQLdb.constants import FIELD_TYPE, FLAG | |||
from MySQLdb import OperationalError | from MySQLdb import OperationalError | |||
skipping to change at line 262 | skipping to change at line 254 | |||
''' | ''' | |||
def __virtual__(): | def __virtual__(): | |||
""" | """ | |||
Confirm that a python mysql client is installed. | Confirm that a python mysql client is installed. | |||
""" | """ | |||
return bool(MySQLdb), "No python mysql client installed." if MySQLdb is None else "" | return bool(MySQLdb), "No python mysql client installed." if MySQLdb is None else "" | |||
def __mysql_hash_password(password): | def __mysql_hash_password(password): | |||
_password = hashlib.sha1(password.encode()).digest() | _password = hashlib.sha1(password.encode()).digest() | |||
_password = "*{0}".format(hashlib.sha1(_password).hexdigest().upper()) | _password = "*{}".format(hashlib.sha1(_password).hexdigest().upper()) | |||
return _password | return _password | |||
def __check_table(name, table, **connection_args): | def __check_table(name, table, **connection_args): | |||
dbc = _connect(**connection_args) | dbc = _connect(**connection_args) | |||
if dbc is None: | if dbc is None: | |||
return {} | return {} | |||
cur = dbc.cursor(MySQLdb.cursors.DictCursor) | cur = dbc.cursor(MySQLdb.cursors.DictCursor) | |||
s_name = quote_identifier(name) | s_name = quote_identifier(name) | |||
s_table = quote_identifier(table) | s_table = quote_identifier(table) | |||
# identifiers cannot be used as values | # identifiers cannot be used as values | |||
qry = "CHECK TABLE {0}.{1}".format(s_name, s_table) | qry = "CHECK TABLE {}.{}".format(s_name, s_table) | |||
_execute(cur, qry) | _execute(cur, qry) | |||
results = cur.fetchall() | results = cur.fetchall() | |||
log.debug(results) | log.debug(results) | |||
return results | return results | |||
def __repair_table(name, table, **connection_args): | def __repair_table(name, table, **connection_args): | |||
dbc = _connect(**connection_args) | dbc = _connect(**connection_args) | |||
if dbc is None: | if dbc is None: | |||
return {} | return {} | |||
cur = dbc.cursor(MySQLdb.cursors.DictCursor) | cur = dbc.cursor(MySQLdb.cursors.DictCursor) | |||
s_name = quote_identifier(name) | s_name = quote_identifier(name) | |||
s_table = quote_identifier(table) | s_table = quote_identifier(table) | |||
# identifiers cannot be used as values | # identifiers cannot be used as values | |||
qry = "REPAIR TABLE {0}.{1}".format(s_name, s_table) | qry = "REPAIR TABLE {}.{}".format(s_name, s_table) | |||
_execute(cur, qry) | _execute(cur, qry) | |||
results = cur.fetchall() | results = cur.fetchall() | |||
log.debug(results) | log.debug(results) | |||
return results | return results | |||
def __optimize_table(name, table, **connection_args): | def __optimize_table(name, table, **connection_args): | |||
dbc = _connect(**connection_args) | dbc = _connect(**connection_args) | |||
if dbc is None: | if dbc is None: | |||
return {} | return {} | |||
cur = dbc.cursor(MySQLdb.cursors.DictCursor) | cur = dbc.cursor(MySQLdb.cursors.DictCursor) | |||
s_name = quote_identifier(name) | s_name = quote_identifier(name) | |||
s_table = quote_identifier(table) | s_table = quote_identifier(table) | |||
# identifiers cannot be used as values | # identifiers cannot be used as values | |||
qry = "OPTIMIZE TABLE {0}.{1}".format(s_name, s_table) | qry = "OPTIMIZE TABLE {}.{}".format(s_name, s_table) | |||
_execute(cur, qry) | _execute(cur, qry) | |||
results = cur.fetchall() | results = cur.fetchall() | |||
log.debug(results) | log.debug(results) | |||
return results | return results | |||
def __password_column(**connection_args): | def __password_column(**connection_args): | |||
if "mysql.password_column" in __context__: | if "mysql.password_column" in __context__: | |||
return __context__["mysql.password_column"] | return __context__["mysql.password_column"] | |||
dbc = _connect(**connection_args) | dbc = _connect(**connection_args) | |||
skipping to change at line 339 | skipping to change at line 331 | |||
def __get_auth_plugin(user, host, **connection_args): | def __get_auth_plugin(user, host, **connection_args): | |||
dbc = _connect(**connection_args) | dbc = _connect(**connection_args) | |||
if dbc is None: | if dbc is None: | |||
return [] | return [] | |||
cur = dbc.cursor(MySQLdb.cursors.DictCursor) | cur = dbc.cursor(MySQLdb.cursors.DictCursor) | |||
try: | try: | |||
qry = "SELECT plugin FROM mysql.user WHERE User=%(user)s and Host=%(host )s" | qry = "SELECT plugin FROM mysql.user WHERE User=%(user)s and Host=%(host )s" | |||
args = {"user": user, "host": host} | args = {"user": user, "host": host} | |||
_execute(cur, qry, args) | _execute(cur, qry, args) | |||
except MySQLdb.OperationalError as exc: | except MySQLdb.OperationalError as exc: | |||
err = "MySQL Error {0}: {1}".format(*exc.args) | err = "MySQL Error {}: {}".format(*exc.args) | |||
__context__["mysql.error"] = err | __context__["mysql.error"] = err | |||
log.error(err) | log.error(err) | |||
return "mysql_native_password" | return "mysql_native_password" | |||
results = cur.fetchall() | results = cur.fetchall() | |||
log.debug(results) | log.debug(results) | |||
if results: | if results: | |||
return results[0].get("plugin", "mysql_native_password") | return results[0].get("plugin", "mysql_native_password") | |||
else: | else: | |||
return "mysql_native_password" | return "mysql_native_password" | |||
skipping to change at line 379 | skipping to change at line 371 | |||
if name in kwargs: | if name in kwargs: | |||
connargs[key] = kwargs[name] | connargs[key] = kwargs[name] | |||
elif get_opts: | elif get_opts: | |||
prefix = "connection_" | prefix = "connection_" | |||
if name.startswith(prefix): | if name.startswith(prefix): | |||
try: | try: | |||
name = name[len(prefix) :] | name = name[len(prefix) :] | |||
except IndexError: | except IndexError: | |||
return | return | |||
val = __salt__["config.option"]("mysql.{0}".format(name), None) | val = __salt__["config.option"]("mysql.{}".format(name), None) | |||
if val is not None: | if val is not None: | |||
connargs[key] = val | connargs[key] = val | |||
# If a default file is explicitly passed to kwargs, don't grab the | # If a default file is explicitly passed to kwargs, don't grab the | |||
# opts/pillar settings, as it can override info in the defaults file | # opts/pillar settings, as it can override info in the defaults file | |||
if "connection_default_file" in kwargs: | if "connection_default_file" in kwargs: | |||
get_opts = False | get_opts = False | |||
else: | else: | |||
get_opts = True | get_opts = True | |||
skipping to change at line 422 | skipping to change at line 414 | |||
del connargs[key] | del connargs[key] | |||
if ( | if ( | |||
connargs.get("passwd", True) is None | connargs.get("passwd", True) is None | |||
): # If present but set to None. (Extreme edge case.) | ): # If present but set to None. (Extreme edge case.) | |||
log.warning("MySQL password of None found. Attempting passwordless login .") | log.warning("MySQL password of None found. Attempting passwordless login .") | |||
connargs.pop("passwd") | connargs.pop("passwd") | |||
try: | try: | |||
dbc = MySQLdb.connect(**connargs) | dbc = MySQLdb.connect(**connargs) | |||
except OperationalError as exc: | except OperationalError as exc: | |||
err = "MySQL Error {0}: {1}".format(*exc.args) | err = "MySQL Error {}: {}".format(*exc.args) | |||
__context__["mysql.error"] = err | __context__["mysql.error"] = err | |||
log.error(err) | log.error(err) | |||
return None | return None | |||
except MySQLdb.err.InternalError as exc: | except MySQLdb.err.InternalError as exc: | |||
err = "MySQL Error {0}: {1}".format(*exc.args) | err = "MySQL Error {}: {}".format(*exc.args) | |||
__context__["mysql.error"] = err | __context__["mysql.error"] = err | |||
log.error(err) | log.error(err) | |||
return None | return None | |||
dbc.autocommit(True) | dbc.autocommit(True) | |||
return dbc | return dbc | |||
def _grant_to_tokens(grant): | def _grant_to_tokens(grant): | |||
""" | """ | |||
skipping to change at line 560 | skipping to change at line 552 | |||
): | ): | |||
# End of token detected | # End of token detected | |||
if multiword_statement: | if multiword_statement: | |||
multiword_statement.append(token) | multiword_statement.append(token) | |||
grant_tokens.append(" ".join(multiword_statement)) | grant_tokens.append(" ".join(multiword_statement)) | |||
multiword_statement = [] | multiword_statement = [] | |||
else: | else: | |||
if not column: | if not column: | |||
current_grant = token | current_grant = token | |||
else: | else: | |||
token = "{0}.{1}".format(current_grant, token) | token = "{}.{}".format(current_grant, token) | |||
grant_tokens.append(token) | grant_tokens.append(token) | |||
else: # This is a multi-word, ala LOCK TABLES | else: # This is a multi-word, ala LOCK TABLES | |||
multiword_statement.append(token) | multiword_statement.append(token) | |||
elif phrase == "db": | elif phrase == "db": | |||
# the shlex splitter may have split on special database characters ` | # the shlex splitter may have split on special database characters ` | |||
database += token | database += token | |||
# Read-ahead | # Read-ahead | |||
try: | try: | |||
if exploded_grant[position_tracker + 1] == ".": | if exploded_grant[position_tracker + 1] == ".": | |||
skipping to change at line 776 | skipping to change at line 768 | |||
connection_args.update({"connection_db": database, "connection_conv": conv}) | connection_args.update({"connection_db": database, "connection_conv": conv}) | |||
dbc = _connect(**connection_args) | dbc = _connect(**connection_args) | |||
if dbc is None: | if dbc is None: | |||
return {} | return {} | |||
cur = dbc.cursor() | cur = dbc.cursor() | |||
start = time.time() | start = time.time() | |||
log.debug("Using db: %s to run query %s", database, query) | log.debug("Using db: %s to run query %s", database, query) | |||
try: | try: | |||
affected = _execute(cur, query) | affected = _execute(cur, query) | |||
except OperationalError as exc: | except OperationalError as exc: | |||
err = "MySQL Error {0}: {1}".format(*exc.args) | err = "MySQL Error {}: {}".format(*exc.args) | |||
__context__["mysql.error"] = err | __context__["mysql.error"] = err | |||
log.error(err) | log.error(err) | |||
return False | return False | |||
results = cur.fetchall() | results = cur.fetchall() | |||
elapsed = time.time() - start | elapsed = time.time() - start | |||
if elapsed < 0.200: | if elapsed < 0.200: | |||
elapsed_h = str(round(elapsed * 1000, 1)) + "ms" | elapsed_h = str(round(elapsed * 1000, 1)) + "ms" | |||
else: | else: | |||
elapsed_h = str(round(elapsed, 2)) + "s" | elapsed_h = str(round(elapsed, 2)) + "s" | |||
skipping to change at line 885 | skipping to change at line 877 | |||
ret["query time"]["raw"] += float(query_result["query time"]["ra w"]) | ret["query time"]["raw"] += float(query_result["query time"]["ra w"]) | |||
if "rows returned" in query_result: | if "rows returned" in query_result: | |||
ret["rows returned"] += query_result["rows returned"] | ret["rows returned"] += query_result["rows returned"] | |||
if "columns" in query_result: | if "columns" in query_result: | |||
ret["columns"].append(query_result["columns"]) | ret["columns"].append(query_result["columns"]) | |||
if "results" in query_result: | if "results" in query_result: | |||
ret["results"].append(query_result["results"]) | ret["results"].append(query_result["results"]) | |||
if "rows affected" in query_result: | if "rows affected" in query_result: | |||
ret["rows affected"] += query_result["rows affected"] | ret["rows affected"] += query_result["rows affected"] | |||
ret["query time"]["human"] = ( | ret["query time"]["human"] = str(round(float(ret["query time"]["raw"]), 2)) | |||
six.text_type(round(float(ret["query time"]["raw"]), 2)) + "s" | + "s" | |||
) | ||||
ret["query time"]["raw"] = round(float(ret["query time"]["raw"]), 5) | ret["query time"]["raw"] = round(float(ret["query time"]["raw"]), 5) | |||
"s" | ||||
# Remove empty keys in ret | # Remove empty keys in ret | |||
ret = {k: v for k, v in six.iteritems(ret) if v} | ret = {k: v for k, v in ret.items() if v} | |||
return ret | return ret | |||
def status(**connection_args): | def status(**connection_args): | |||
""" | """ | |||
Return the status of a MySQL server using the output from the ``SHOW | Return the status of a MySQL server using the output from the ``SHOW | |||
STATUS`` query. | STATUS`` query. | |||
CLI Example: | CLI Example: | |||
skipping to change at line 914 | skipping to change at line 904 | |||
salt '*' mysql.status | salt '*' mysql.status | |||
""" | """ | |||
dbc = _connect(**connection_args) | dbc = _connect(**connection_args) | |||
if dbc is None: | if dbc is None: | |||
return {} | return {} | |||
cur = dbc.cursor() | cur = dbc.cursor() | |||
qry = "SHOW STATUS" | qry = "SHOW STATUS" | |||
try: | try: | |||
_execute(cur, qry) | _execute(cur, qry) | |||
except OperationalError as exc: | except OperationalError as exc: | |||
err = "MySQL Error {0}: {1}".format(*exc.args) | err = "MySQL Error {}: {}".format(*exc.args) | |||
__context__["mysql.error"] = err | __context__["mysql.error"] = err | |||
log.error(err) | log.error(err) | |||
return {} | return {} | |||
ret = {} | ret = {} | |||
for _ in range(cur.rowcount): | for _ in range(cur.rowcount): | |||
row = cur.fetchone() | row = cur.fetchone() | |||
ret[row[0]] = row[1] | ret[row[0]] = row[1] | |||
return ret | return ret | |||
skipping to change at line 947 | skipping to change at line 937 | |||
return __context__["mysql.version"] | return __context__["mysql.version"] | |||
dbc = _connect(**connection_args) | dbc = _connect(**connection_args) | |||
if dbc is None: | if dbc is None: | |||
return "" | return "" | |||
cur = dbc.cursor() | cur = dbc.cursor() | |||
qry = "SELECT VERSION()" | qry = "SELECT VERSION()" | |||
try: | try: | |||
_execute(cur, qry) | _execute(cur, qry) | |||
except MySQLdb.OperationalError as exc: | except MySQLdb.OperationalError as exc: | |||
err = "MySQL Error {0}: {1}".format(*exc.args) | err = "MySQL Error {}: {}".format(*exc.args) | |||
__context__["mysql.error"] = err | __context__["mysql.error"] = err | |||
log.error(err) | log.error(err) | |||
return "" | return "" | |||
try: | try: | |||
__context__["mysql.version"] = salt.utils.data.decode(cur.fetchone()[0]) | __context__["mysql.version"] = salt.utils.data.decode(cur.fetchone()[0]) | |||
return __context__["mysql.version"] | return __context__["mysql.version"] | |||
except IndexError: | except IndexError: | |||
return "" | return "" | |||
skipping to change at line 980 | skipping to change at line 970 | |||
salt '*' mysql.slave_lag | salt '*' mysql.slave_lag | |||
""" | """ | |||
dbc = _connect(**connection_args) | dbc = _connect(**connection_args) | |||
if dbc is None: | if dbc is None: | |||
return -3 | return -3 | |||
cur = dbc.cursor(MySQLdb.cursors.DictCursor) | cur = dbc.cursor(MySQLdb.cursors.DictCursor) | |||
qry = "show slave status" | qry = "show slave status" | |||
try: | try: | |||
_execute(cur, qry) | _execute(cur, qry) | |||
except MySQLdb.OperationalError as exc: | except MySQLdb.OperationalError as exc: | |||
err = "MySQL Error {0}: {1}".format(*exc.args) | err = "MySQL Error {}: {}".format(*exc.args) | |||
__context__["mysql.error"] = err | __context__["mysql.error"] = err | |||
log.error(err) | log.error(err) | |||
return -3 | return -3 | |||
results = cur.fetchone() | results = cur.fetchone() | |||
if cur.rowcount == 0: | if cur.rowcount == 0: | |||
# Server is not a slave if master is not defined. Return empty tuple | # Server is not a slave if master is not defined. Return empty tuple | |||
# in this case. Could probably check to see if Slave_IO_Running and | # in this case. Could probably check to see if Slave_IO_Running and | |||
# Slave_SQL_Running are both set to 'Yes' as well to be really really | # Slave_SQL_Running are both set to 'Yes' as well to be really really | |||
# sure that it is a slave. | # sure that it is a slave. | |||
skipping to change at line 1063 | skipping to change at line 1053 | |||
salt '*' mysql.db_list | salt '*' mysql.db_list | |||
""" | """ | |||
dbc = _connect(**connection_args) | dbc = _connect(**connection_args) | |||
if dbc is None: | if dbc is None: | |||
return [] | return [] | |||
cur = dbc.cursor() | cur = dbc.cursor() | |||
qry = "SHOW DATABASES" | qry = "SHOW DATABASES" | |||
try: | try: | |||
_execute(cur, qry) | _execute(cur, qry) | |||
except MySQLdb.OperationalError as exc: | except MySQLdb.OperationalError as exc: | |||
err = "MySQL Error {0}: {1}".format(*exc.args) | err = "MySQL Error {}: {}".format(*exc.args) | |||
__context__["mysql.error"] = err | __context__["mysql.error"] = err | |||
log.error(err) | log.error(err) | |||
return [] | return [] | |||
ret = [] | ret = [] | |||
results = cur.fetchall() | results = cur.fetchall() | |||
for dbs in results: | for dbs in results: | |||
ret.append(dbs[0]) | ret.append(dbs[0]) | |||
log.debug(ret) | log.debug(ret) | |||
skipping to change at line 1092 | skipping to change at line 1082 | |||
.. code-block:: bash | .. code-block:: bash | |||
salt '*' mysql.alter_db testdb charset='latin1' | salt '*' mysql.alter_db testdb charset='latin1' | |||
""" | """ | |||
dbc = _connect(**connection_args) | dbc = _connect(**connection_args) | |||
if dbc is None: | if dbc is None: | |||
return [] | return [] | |||
cur = dbc.cursor() | cur = dbc.cursor() | |||
existing = db_get(name, **connection_args) | existing = db_get(name, **connection_args) | |||
qry = "ALTER DATABASE `{0}` CHARACTER SET {1} COLLATE {2};".format( | qry = "ALTER DATABASE `{}` CHARACTER SET {} COLLATE {};".format( | |||
name.replace("%", r"\%").replace("_", r"\_"), | name.replace("%", r"\%").replace("_", r"\_"), | |||
character_set or existing.get("character_set"), | character_set or existing.get("character_set"), | |||
collate or existing.get("collate"), | collate or existing.get("collate"), | |||
) | ) | |||
args = {} | args = {} | |||
_execute(cur, qry, args) | _execute(cur, qry, args) | |||
def db_get(name, **connection_args): | def db_get(name, **connection_args): | |||
""" | """ | |||
Return a list of databases of a MySQL server using the output | Return a list of databases of a MySQL server using the output | |||
skipping to change at line 1147 | skipping to change at line 1137 | |||
if not db_exists(name, **connection_args): | if not db_exists(name, **connection_args): | |||
log.info("Database '%s' does not exist", name) | log.info("Database '%s' does not exist", name) | |||
return False | return False | |||
dbc = _connect(**connection_args) | dbc = _connect(**connection_args) | |||
if dbc is None: | if dbc is None: | |||
return [] | return [] | |||
cur = dbc.cursor() | cur = dbc.cursor() | |||
s_name = quote_identifier(name) | s_name = quote_identifier(name) | |||
# identifiers cannot be used as values | # identifiers cannot be used as values | |||
qry = "SHOW TABLES IN {0}".format(s_name) | qry = "SHOW TABLES IN {}".format(s_name) | |||
try: | try: | |||
_execute(cur, qry) | _execute(cur, qry) | |||
except MySQLdb.OperationalError as exc: | except MySQLdb.OperationalError as exc: | |||
err = "MySQL Error {0}: {1}".format(*exc.args) | err = "MySQL Error {}: {}".format(*exc.args) | |||
__context__["mysql.error"] = err | __context__["mysql.error"] = err | |||
log.error(err) | log.error(err) | |||
return [] | return [] | |||
ret = [] | ret = [] | |||
results = cur.fetchall() | results = cur.fetchall() | |||
for table in results: | for table in results: | |||
ret.append(table[0]) | ret.append(table[0]) | |||
log.debug(ret) | log.debug(ret) | |||
return ret | return ret | |||
skipping to change at line 1185 | skipping to change at line 1175 | |||
return False | return False | |||
cur = dbc.cursor() | cur = dbc.cursor() | |||
# Warn: here db identifier is not backtyped but should be | # Warn: here db identifier is not backtyped but should be | |||
# escaped as a string value. Note also that LIKE special characters | # escaped as a string value. Note also that LIKE special characters | |||
# '_' and '%' should also be escaped. | # '_' and '%' should also be escaped. | |||
args = {"dbname": name} | args = {"dbname": name} | |||
qry = "SHOW DATABASES LIKE %(dbname)s;" | qry = "SHOW DATABASES LIKE %(dbname)s;" | |||
try: | try: | |||
_execute(cur, qry, args) | _execute(cur, qry, args) | |||
except MySQLdb.OperationalError as exc: | except MySQLdb.OperationalError as exc: | |||
err = "MySQL Error {0}: {1}".format(*exc.args) | err = "MySQL Error {}: {}".format(*exc.args) | |||
__context__["mysql.error"] = err | __context__["mysql.error"] = err | |||
log.error(err) | log.error(err) | |||
return False | return False | |||
cur.fetchall() | cur.fetchall() | |||
return cur.rowcount == 1 | return cur.rowcount == 1 | |||
def db_create(name, character_set=None, collate=None, **connection_args): | def db_create(name, character_set=None, collate=None, **connection_args): | |||
""" | """ | |||
Adds a databases to the MySQL server. | Adds a databases to the MySQL server. | |||
skipping to change at line 1224 | skipping to change at line 1214 | |||
log.info("DB '%s' already exists", name) | log.info("DB '%s' already exists", name) | |||
return False | return False | |||
# db doesn't exist, proceed | # db doesn't exist, proceed | |||
dbc = _connect(**connection_args) | dbc = _connect(**connection_args) | |||
if dbc is None: | if dbc is None: | |||
return False | return False | |||
cur = dbc.cursor() | cur = dbc.cursor() | |||
s_name = quote_identifier(name) | s_name = quote_identifier(name) | |||
# identifiers cannot be used as values | # identifiers cannot be used as values | |||
qry = "CREATE DATABASE IF NOT EXISTS {0}".format(s_name) | qry = "CREATE DATABASE IF NOT EXISTS {}".format(s_name) | |||
args = {} | args = {} | |||
if character_set is not None: | if character_set is not None: | |||
qry += " CHARACTER SET %(character_set)s" | qry += " CHARACTER SET %(character_set)s" | |||
args["character_set"] = character_set | args["character_set"] = character_set | |||
if collate is not None: | if collate is not None: | |||
qry += " COLLATE %(collate)s" | qry += " COLLATE %(collate)s" | |||
args["collate"] = collate | args["collate"] = collate | |||
qry += ";" | qry += ";" | |||
try: | try: | |||
if _execute(cur, qry, args): | if _execute(cur, qry, args): | |||
log.info("DB '%s' created", name) | log.info("DB '%s' created", name) | |||
return True | return True | |||
except MySQLdb.OperationalError as exc: | except MySQLdb.OperationalError as exc: | |||
err = "MySQL Error {0}: {1}".format(*exc.args) | err = "MySQL Error {}: {}".format(*exc.args) | |||
__context__["mysql.error"] = err | __context__["mysql.error"] = err | |||
log.error(err) | log.error(err) | |||
return False | return False | |||
def db_remove(name, **connection_args): | def db_remove(name, **connection_args): | |||
""" | """ | |||
Removes a databases from the MySQL server. | Removes a databases from the MySQL server. | |||
CLI Example: | CLI Example: | |||
skipping to change at line 1270 | skipping to change at line 1260 | |||
log.info("DB '%s' may not be removed", name) | log.info("DB '%s' may not be removed", name) | |||
return False | return False | |||
# db does exists, proceed | # db does exists, proceed | |||
dbc = _connect(**connection_args) | dbc = _connect(**connection_args) | |||
if dbc is None: | if dbc is None: | |||
return False | return False | |||
cur = dbc.cursor() | cur = dbc.cursor() | |||
s_name = quote_identifier(name) | s_name = quote_identifier(name) | |||
# identifiers cannot be used as values | # identifiers cannot be used as values | |||
qry = "DROP DATABASE {0};".format(s_name) | qry = "DROP DATABASE {};".format(s_name) | |||
try: | try: | |||
_execute(cur, qry) | _execute(cur, qry) | |||
except MySQLdb.OperationalError as exc: | except MySQLdb.OperationalError as exc: | |||
err = "MySQL Error {0}: {1}".format(*exc.args) | err = "MySQL Error {}: {}".format(*exc.args) | |||
__context__["mysql.error"] = err | __context__["mysql.error"] = err | |||
log.error(err) | log.error(err) | |||
return False | return False | |||
if not db_exists(name, **connection_args): | if not db_exists(name, **connection_args): | |||
log.info("Database '%s' has been removed", name) | log.info("Database '%s' has been removed", name) | |||
return True | return True | |||
log.info("Database '%s' has not been removed", name) | log.info("Database '%s' has not been removed", name) | |||
return False | return False | |||
skipping to change at line 1305 | skipping to change at line 1295 | |||
salt '*' mysql.user_list | salt '*' mysql.user_list | |||
""" | """ | |||
dbc = _connect(**connection_args) | dbc = _connect(**connection_args) | |||
if dbc is None: | if dbc is None: | |||
return [] | return [] | |||
cur = dbc.cursor(MySQLdb.cursors.DictCursor) | cur = dbc.cursor(MySQLdb.cursors.DictCursor) | |||
try: | try: | |||
qry = "SELECT User,Host FROM mysql.user" | qry = "SELECT User,Host FROM mysql.user" | |||
_execute(cur, qry) | _execute(cur, qry) | |||
except MySQLdb.OperationalError as exc: | except MySQLdb.OperationalError as exc: | |||
err = "MySQL Error {0}: {1}".format(*exc.args) | err = "MySQL Error {}: {}".format(*exc.args) | |||
__context__["mysql.error"] = err | __context__["mysql.error"] = err | |||
log.error(err) | log.error(err) | |||
return [] | return [] | |||
results = cur.fetchall() | results = cur.fetchall() | |||
log.debug(results) | log.debug(results) | |||
return results | return results | |||
def _mysql_user_exists( | def _mysql_user_exists( | |||
user, | user, | |||
host="localhost", | host="localhost", | |||
skipping to change at line 1343 | skipping to change at line 1333 | |||
if salt.utils.data.is_true(passwordless): | if salt.utils.data.is_true(passwordless): | |||
if salt.utils.data.is_true(unix_socket): | if salt.utils.data.is_true(unix_socket): | |||
qry += " AND plugin=%(unix_socket)s" | qry += " AND plugin=%(unix_socket)s" | |||
args["unix_socket"] = "auth_socket" | args["unix_socket"] = "auth_socket" | |||
else: | else: | |||
qry += " AND " + password_column + " = ''" | qry += " AND " + password_column + " = ''" | |||
elif password: | elif password: | |||
if salt.utils.versions.version_cmp(server_version, compare_version) >= 0 : | if salt.utils.versions.version_cmp(server_version, compare_version) >= 0 : | |||
if auth_plugin == "mysql_native_password": | if auth_plugin == "mysql_native_password": | |||
_password = __mysql_hash_password(six.text_type(password)) | _password = __mysql_hash_password(str(password)) | |||
qry += " AND " + password_column + " = %(password)s" | qry += " AND " + password_column + " = %(password)s" | |||
args["password"] = six.text_type(_password) | args["password"] = str(_password) | |||
else: | else: | |||
err = "Unable to verify password." | err = "Unable to verify password." | |||
log.error(err) | log.error(err) | |||
__context__["mysql.error"] = err | __context__["mysql.error"] = err | |||
else: | else: | |||
qry += " AND " + password_column + " = PASSWORD(%(password)s)" | qry += " AND " + password_column + " = PASSWORD(%(password)s)" | |||
args["password"] = six.text_type(password) | args["password"] = str(password) | |||
elif password_hash: | elif password_hash: | |||
qry += " AND " + password_column + " = %(password)s" | qry += " AND " + password_column + " = %(password)s" | |||
args["password"] = password_hash | args["password"] = password_hash | |||
return qry, args | return qry, args | |||
def _mariadb_user_exists( | def _mariadb_user_exists( | |||
user, | user, | |||
host="localhost", | host="localhost", | |||
password=None, | password=None, | |||
skipping to change at line 1386 | skipping to change at line 1376 | |||
args["host"] = host | args["host"] = host | |||
if salt.utils.data.is_true(passwordless): | if salt.utils.data.is_true(passwordless): | |||
if salt.utils.data.is_true(unix_socket): | if salt.utils.data.is_true(unix_socket): | |||
qry += " AND plugin=%(unix_socket)s" | qry += " AND plugin=%(unix_socket)s" | |||
args["unix_socket"] = "unix_socket" | args["unix_socket"] = "unix_socket" | |||
else: | else: | |||
qry += " AND " + password_column + " = ''" | qry += " AND " + password_column + " = ''" | |||
elif password: | elif password: | |||
qry += " AND " + password_column + " = PASSWORD(%(password)s)" | qry += " AND " + password_column + " = PASSWORD(%(password)s)" | |||
args["password"] = six.text_type(password) | args["password"] = str(password) | |||
elif password_hash: | elif password_hash: | |||
qry += " AND " + password_column + " = %(password)s" | qry += " AND " + password_column + " = %(password)s" | |||
args["password"] = password_hash | args["password"] = password_hash | |||
return qry, args | return qry, args | |||
def user_exists( | def user_exists( | |||
user, | user, | |||
host="localhost", | host="localhost", | |||
password=None, | password=None, | |||
skipping to change at line 1422 | skipping to change at line 1412 | |||
.. code-block:: bash | .. code-block:: bash | |||
salt '*' mysql.user_exists 'username' 'hostname' 'password' | salt '*' mysql.user_exists 'username' 'hostname' 'password' | |||
salt '*' mysql.user_exists 'username' 'hostname' password_hash='hash' | salt '*' mysql.user_exists 'username' 'hostname' password_hash='hash' | |||
salt '*' mysql.user_exists 'username' passwordless=True | salt '*' mysql.user_exists 'username' passwordless=True | |||
salt '*' mysql.user_exists 'username' password_column='authentication_st ring' | salt '*' mysql.user_exists 'username' password_column='authentication_st ring' | |||
""" | """ | |||
run_verify = False | run_verify = False | |||
server_version = salt.utils.data.decode(version(**connection_args)) | server_version = salt.utils.data.decode(version(**connection_args)) | |||
if not server_version: | if not server_version and password: | |||
last_err = __context__["mysql.error"] | # Did we fail to connect with the user we are checking | |||
err = 'MySQL Error: Unable to fetch current server version. Last error w | # Its password might have previously change with the same command/state | |||
as: "{}"'.format( | ||||
last_err | # Clear the previous error | |||
) | __context__["mysql.error"] = None | |||
log.error(err) | connection_args["connection_pass"] = password | |||
return False | ||||
server_version = salt.utils.data.decode(version(**connection_args)) | ||||
if not server_version: | ||||
last_err = __context__["mysql.error"] | ||||
err = 'MySQL Error: Unable to fetch current server version. Last err | ||||
or was: "{}"'.format( | ||||
last_err | ||||
) | ||||
log.error(err) | ||||
return False | ||||
dbc = _connect(**connection_args) | dbc = _connect(**connection_args) | |||
# Did we fail to connect with the user we are checking | # Did we fail to connect with the user we are checking | |||
# Its password might have previously change with the same command/state | # Its password might have previously change with the same command/state | |||
if ( | if ( | |||
dbc is None | dbc is None | |||
and __context__["mysql.error"].startswith( | and __context__["mysql.error"].startswith( | |||
"MySQL Error 1045: Access denied for user '{0}'@".format(user) | "MySQL Error 1045: Access denied for user '{}'@".format(user) | |||
) | ) | |||
and password | and password | |||
): | ): | |||
# Clear the previous error | # Clear the previous error | |||
__context__["mysql.error"] = None | __context__["mysql.error"] = None | |||
connection_args["connection_pass"] = password | connection_args["connection_pass"] = password | |||
dbc = _connect(**connection_args) | dbc = _connect(**connection_args) | |||
if dbc is None: | if dbc is None: | |||
return False | return False | |||
skipping to change at line 1480 | skipping to change at line 1480 | |||
passwordless, | passwordless, | |||
unix_socket, | unix_socket, | |||
password_column=password_column, | password_column=password_column, | |||
auth_plugin=auth_plugin, | auth_plugin=auth_plugin, | |||
**connection_args | **connection_args | |||
) | ) | |||
try: | try: | |||
_execute(cur, qry, args) | _execute(cur, qry, args) | |||
except MySQLdb.OperationalError as exc: | except MySQLdb.OperationalError as exc: | |||
err = "MySQL Error {0}: {1}".format(*exc.args) | err = "MySQL Error {}: {}".format(*exc.args) | |||
__context__["mysql.error"] = err | __context__["mysql.error"] = err | |||
log.error(err) | log.error(err) | |||
return False | return False | |||
return cur.rowcount == 1 | return cur.rowcount == 1 | |||
def user_info(user, host="localhost", **connection_args): | def user_info(user, host="localhost", **connection_args): | |||
""" | """ | |||
Get full info on a MySQL user | Get full info on a MySQL user | |||
skipping to change at line 1510 | skipping to change at line 1510 | |||
cur = dbc.cursor(MySQLdb.cursors.DictCursor) | cur = dbc.cursor(MySQLdb.cursors.DictCursor) | |||
qry = "SELECT * FROM mysql.user WHERE User = %(user)s AND " "Host = %(host)s " | qry = "SELECT * FROM mysql.user WHERE User = %(user)s AND " "Host = %(host)s " | |||
args = {} | args = {} | |||
args["user"] = user | args["user"] = user | |||
args["host"] = host | args["host"] = host | |||
try: | try: | |||
_execute(cur, qry, args) | _execute(cur, qry, args) | |||
except MySQLdb.OperationalError as exc: | except MySQLdb.OperationalError as exc: | |||
err = "MySQL Error {0}: {1}".format(*exc.args) | err = "MySQL Error {}: {}".format(*exc.args) | |||
__context__["mysql.error"] = err | __context__["mysql.error"] = err | |||
log.error(err) | log.error(err) | |||
return False | return False | |||
result = cur.fetchone() | result = cur.fetchone() | |||
log.debug(result) | log.debug(result) | |||
return result | return result | |||
def _mysql_user_create( | def _mysql_user_create( | |||
user, | user, | |||
host="localhost", | host="localhost", | |||
skipping to change at line 1543 | skipping to change at line 1543 | |||
qry = "CREATE USER %(user)s@%(host)s" | qry = "CREATE USER %(user)s@%(host)s" | |||
args = {} | args = {} | |||
args["user"] = user | args["user"] = user | |||
args["host"] = host | args["host"] = host | |||
if password is not None: | if password is not None: | |||
if salt.utils.versions.version_cmp(server_version, compare_version) >= 0 : | if salt.utils.versions.version_cmp(server_version, compare_version) >= 0 : | |||
args["auth_plugin"] = auth_plugin | args["auth_plugin"] = auth_plugin | |||
qry += " IDENTIFIED WITH %(auth_plugin)s BY %(password)s" | qry += " IDENTIFIED WITH %(auth_plugin)s BY %(password)s" | |||
else: | else: | |||
qry += " IDENTIFIED BY %(password)s" | qry += " IDENTIFIED BY %(password)s" | |||
args["password"] = six.text_type(password) | args["password"] = str(password) | |||
elif password_hash is not None: | elif password_hash is not None: | |||
if salt.utils.versions.version_cmp(server_version, compare_version) >= 0 : | if salt.utils.versions.version_cmp(server_version, compare_version) >= 0 : | |||
qry += " IDENTIFIED BY %(password)s" | qry += " IDENTIFIED BY %(password)s" | |||
else: | else: | |||
qry += " IDENTIFIED BY PASSWORD %(password)s" | qry += " IDENTIFIED BY PASSWORD %(password)s" | |||
args["password"] = password_hash | args["password"] = password_hash | |||
elif salt.utils.data.is_true(allow_passwordless): | elif salt.utils.data.is_true(allow_passwordless): | |||
if not plugin_status("auth_socket", **connection_args): | if not plugin_status("auth_socket", **connection_args): | |||
err = "The auth_socket plugin is not enabled." | err = "The auth_socket plugin is not enabled." | |||
log.error(err) | log.error(err) | |||
skipping to change at line 1589 | skipping to change at line 1589 | |||
auth_plugin="mysql_native_password", | auth_plugin="mysql_native_password", | |||
**connection_args | **connection_args | |||
): | ): | |||
qry = "CREATE USER %(user)s@%(host)s" | qry = "CREATE USER %(user)s@%(host)s" | |||
args = {} | args = {} | |||
args["user"] = user | args["user"] = user | |||
args["host"] = host | args["host"] = host | |||
if password is not None: | if password is not None: | |||
qry += " IDENTIFIED BY %(password)s" | qry += " IDENTIFIED BY %(password)s" | |||
args["password"] = six.text_type(password) | args["password"] = str(password) | |||
elif password_hash is not None: | elif password_hash is not None: | |||
qry += " IDENTIFIED BY PASSWORD %(password)s" | qry += " IDENTIFIED BY PASSWORD %(password)s" | |||
args["password"] = password_hash | args["password"] = password_hash | |||
elif salt.utils.data.is_true(allow_passwordless): | elif salt.utils.data.is_true(allow_passwordless): | |||
if not plugin_status("unix_socket", **connection_args): | if not plugin_status("unix_socket", **connection_args): | |||
err = "The unix_socket plugin is not enabled." | err = "The unix_socket plugin is not enabled." | |||
log.error(err) | log.error(err) | |||
__context__["mysql.error"] = err | __context__["mysql.error"] = err | |||
qry = False | qry = False | |||
else: | else: | |||
skipping to change at line 1673 | skipping to change at line 1673 | |||
CLI Examples: | CLI Examples: | |||
.. code-block:: bash | .. code-block:: bash | |||
salt '*' mysql.user_create 'username' 'hostname' 'password' | salt '*' mysql.user_create 'username' 'hostname' 'password' | |||
salt '*' mysql.user_create 'username' 'hostname' password_hash='hash' | salt '*' mysql.user_create 'username' 'hostname' password_hash='hash' | |||
salt '*' mysql.user_create 'username' 'hostname' allow_passwordless=True | salt '*' mysql.user_create 'username' 'hostname' allow_passwordless=True | |||
""" | """ | |||
server_version = salt.utils.data.decode(version(**connection_args)) | server_version = salt.utils.data.decode(version(**connection_args)) | |||
if not server_version: | if not server_version and password: | |||
last_err = __context__["mysql.error"] | # Did we fail to connect with the user we are checking | |||
err = 'MySQL Error: Unable to fetch current server version. Last error w | # Its password might have previously change with the same command/state | |||
as: "{}"'.format( | ||||
last_err | # Clear the previous error | |||
) | __context__["mysql.error"] = None | |||
log.error(err) | connection_args["connection_pass"] = password | |||
return False | ||||
server_version = salt.utils.data.decode(version(**connection_args)) | ||||
if not server_version: | ||||
last_err = __context__["mysql.error"] | ||||
err = 'MySQL Error: Unable to fetch current server version. Last err | ||||
or was: "{}"'.format( | ||||
last_err | ||||
) | ||||
log.error(err) | ||||
return False | ||||
if user_exists(user, host, **connection_args): | if user_exists(user, host, **connection_args): | |||
log.info("User '%s'@'%s' already exists", user, host) | log.info("User '%s'@'%s' already exists", user, host) | |||
return False | return False | |||
dbc = _connect(**connection_args) | dbc = _connect(**connection_args) | |||
if dbc is None: | if dbc is None: | |||
return False | return False | |||
if not password_column: | if not password_column: | |||
skipping to change at line 1724 | skipping to change at line 1733 | |||
auth_plugin=auth_plugin, | auth_plugin=auth_plugin, | |||
**connection_args | **connection_args | |||
) | ) | |||
if isinstance(qry, bool): | if isinstance(qry, bool): | |||
return qry | return qry | |||
try: | try: | |||
_execute(cur, qry, args) | _execute(cur, qry, args) | |||
except MySQLdb.OperationalError as exc: | except MySQLdb.OperationalError as exc: | |||
err = "MySQL Error {0}: {1}".format(*exc.args) | err = "MySQL Error {}: {}".format(*exc.args) | |||
__context__["mysql.error"] = err | __context__["mysql.error"] = err | |||
log.error(err) | log.error(err) | |||
return False | return False | |||
if user_exists( | if user_exists( | |||
user, | user, | |||
host, | host, | |||
password, | password, | |||
password_hash, | password_hash, | |||
password_column=password_column, | password_column=password_column, | |||
**connection_args | **connection_args | |||
): | ): | |||
msg = "User '{0}'@'{1}' has been created".format(user, host) | msg = "User '{}'@'{}' has been created".format(user, host) | |||
if not any((password, password_hash)): | if not any((password, password_hash)): | |||
msg += " with passwordless login" | msg += " with passwordless login" | |||
log.info(msg) | log.info(msg) | |||
return True | return True | |||
log.info("User '%s'@'%s' was not created", user, host) | log.info("User '%s'@'%s' was not created", user, host) | |||
return False | return False | |||
def _mysql_user_chpass( | def _mysql_user_chpass( | |||
user, | user, | |||
skipping to change at line 1941 | skipping to change at line 1950 | |||
CLI Examples: | CLI Examples: | |||
.. code-block:: bash | .. code-block:: bash | |||
salt '*' mysql.user_chpass frank localhost newpassword | salt '*' mysql.user_chpass frank localhost newpassword | |||
salt '*' mysql.user_chpass frank localhost password_hash='hash' | salt '*' mysql.user_chpass frank localhost password_hash='hash' | |||
salt '*' mysql.user_chpass frank localhost allow_passwordless=True | salt '*' mysql.user_chpass frank localhost allow_passwordless=True | |||
""" | """ | |||
server_version = salt.utils.data.decode(version(**connection_args)) | server_version = salt.utils.data.decode(version(**connection_args)) | |||
if not server_version: | if not server_version and password: | |||
last_err = __context__["mysql.error"] | # Did we fail to connect with the user we are checking | |||
err = 'MySQL Error: Unable to fetch current server version. Last error w | # Its password might have previously change with the same command/state | |||
as: "{}"'.format( | ||||
last_err | # Clear the previous error | |||
) | __context__["mysql.error"] = None | |||
log.error(err) | connection_args["connection_pass"] = password | |||
return False | ||||
server_version = salt.utils.data.decode(version(**connection_args)) | ||||
if not server_version: | ||||
last_err = __context__["mysql.error"] | ||||
err = 'MySQL Error: Unable to fetch current server version. Last err | ||||
or was: "{}"'.format( | ||||
last_err | ||||
) | ||||
log.error(err) | ||||
return False | ||||
if not user_exists(user, host, **connection_args): | if not user_exists(user, host, **connection_args): | |||
log.info("User '%s'@'%s' does not exists", user, host) | log.info("User '%s'@'%s' does not exists", user, host) | |||
return False | return False | |||
dbc = _connect(**connection_args) | dbc = _connect(**connection_args) | |||
if dbc is None: | if dbc is None: | |||
return False | return False | |||
skipping to change at line 1993 | skipping to change at line 2011 | |||
allow_passwordless, | allow_passwordless, | |||
unix_socket, | unix_socket, | |||
password_column=password_column, | password_column=password_column, | |||
auth_plugin=auth_plugin, | auth_plugin=auth_plugin, | |||
**connection_args | **connection_args | |||
) | ) | |||
try: | try: | |||
result = _execute(cur, qry, args) | result = _execute(cur, qry, args) | |||
except MySQLdb.OperationalError as exc: | except MySQLdb.OperationalError as exc: | |||
err = "MySQL Error {0}: {1}".format(*exc.args) | err = "MySQL Error {}: {}".format(*exc.args) | |||
__context__["mysql.error"] = err | __context__["mysql.error"] = err | |||
log.error(err) | log.error(err) | |||
return False | return False | |||
compare_version = "10.4.0" if "MariaDB" in server_version else "8.0.11" | compare_version = "10.4.0" if "MariaDB" in server_version else "8.0.11" | |||
res = False | res = False | |||
if salt.utils.versions.version_cmp(server_version, compare_version) >= 0: | if salt.utils.versions.version_cmp(server_version, compare_version) >= 0: | |||
_execute(cur, "FLUSH PRIVILEGES;") | _execute(cur, "FLUSH PRIVILEGES;") | |||
res = True | res = True | |||
else: | else: | |||
skipping to change at line 2053 | skipping to change at line 2071 | |||
return False | return False | |||
cur = dbc.cursor() | cur = dbc.cursor() | |||
qry = "DROP USER %(user)s@%(host)s" | qry = "DROP USER %(user)s@%(host)s" | |||
args = {} | args = {} | |||
args["user"] = user | args["user"] = user | |||
args["host"] = host | args["host"] = host | |||
try: | try: | |||
_execute(cur, qry, args) | _execute(cur, qry, args) | |||
except MySQLdb.OperationalError as exc: | except MySQLdb.OperationalError as exc: | |||
err = "MySQL Error {0}: {1}".format(*exc.args) | err = "MySQL Error {}: {}".format(*exc.args) | |||
__context__["mysql.error"] = err | __context__["mysql.error"] = err | |||
log.error(err) | log.error(err) | |||
return False | return False | |||
if not user_exists(user, host, **connection_args): | if not user_exists(user, host, **connection_args): | |||
log.info("User '%s'@'%s' has been removed", user, host) | log.info("User '%s'@'%s' has been removed", user, host) | |||
return True | return True | |||
log.info("User '%s'@'%s' has NOT been removed", user, host) | log.info("User '%s'@'%s' has NOT been removed", user, host) | |||
return False | return False | |||
skipping to change at line 2159 | skipping to change at line 2177 | |||
def __grant_normalize(grant): | def __grant_normalize(grant): | |||
# MySQL normalizes ALL to ALL PRIVILEGES, we do the same so that | # MySQL normalizes ALL to ALL PRIVILEGES, we do the same so that | |||
# grant_exists and grant_add ALL work correctly | # grant_exists and grant_add ALL work correctly | |||
if grant.strip().upper() == "ALL": | if grant.strip().upper() == "ALL": | |||
grant = "ALL PRIVILEGES" | grant = "ALL PRIVILEGES" | |||
# Grants are paste directly in SQL, must filter it | # Grants are paste directly in SQL, must filter it | |||
exploded_grants = __grant_split(grant) | exploded_grants = __grant_split(grant) | |||
for chkgrant, _ in exploded_grants: | for chkgrant, _ in exploded_grants: | |||
if chkgrant.strip().upper() not in __grants__: | if chkgrant.strip().upper() not in __grants__: | |||
raise Exception("Invalid grant : '{0}'".format(chkgrant)) | raise Exception("Invalid grant : '{}'".format(chkgrant)) | |||
return grant | return grant | |||
def __grant_split(grant): | def __grant_split(grant): | |||
pattern = re.compile(r"([\w\s]+)(\([^)(]*\))?\s*,?") | pattern = re.compile(r"([\w\s]+)(\([^)(]*\))?\s*,?") | |||
return pattern.findall(grant) | return pattern.findall(grant) | |||
def __ssl_option_sanitize(ssl_option): | def __ssl_option_sanitize(ssl_option): | |||
new_ssl_option = [] | new_ssl_option = [] | |||
# Like most other "salt dsl" YAML structures, ssl_option is a list of single -element dicts | # Like most other "salt dsl" YAML structures, ssl_option is a list of single -element dicts | |||
for opt in ssl_option: | for opt in ssl_option: | |||
key = next(six.iterkeys(opt)) | key = next(iter(opt.keys())) | |||
normal_key = key.strip().upper() | normal_key = key.strip().upper() | |||
if normal_key not in __ssl_options__: | if normal_key not in __ssl_options__: | |||
raise Exception("Invalid SSL option : '{0}'".format(key)) | raise Exception("Invalid SSL option : '{}'".format(key)) | |||
if normal_key in __ssl_options_parameterized__: | if normal_key in __ssl_options_parameterized__: | |||
# SSL option parameters (cipher, issuer, subject) are pasted directl y to SQL so | # SSL option parameters (cipher, issuer, subject) are pasted directl y to SQL so | |||
# we need to sanitize for single quotes... | # we need to sanitize for single quotes... | |||
new_ssl_option.append( | new_ssl_option.append( | |||
"{0} '{1}'".format(normal_key, opt[key].replace("'", "")) | "{} '{}'".format(normal_key, opt[key].replace("'", "")) | |||
) | ) | |||
# omit if falsey | # omit if falsey | |||
elif opt[key]: | elif opt[key]: | |||
new_ssl_option.append(normal_key) | new_ssl_option.append(normal_key) | |||
return " REQUIRE " + " AND ".join(new_ssl_option) | return " REQUIRE " + " AND ".join(new_ssl_option) | |||
def __grant_generate( | def __grant_generate( | |||
grant, | grant, | |||
database, | database, | |||
skipping to change at line 2224 | skipping to change at line 2242 | |||
table = db_part[2] | table = db_part[2] | |||
if escape: | if escape: | |||
if dbc != "*": | if dbc != "*": | |||
# _ and % are authorized on GRANT queries and should get escaped | # _ and % are authorized on GRANT queries and should get escaped | |||
# on the db name, but only if not requesting a table level grant | # on the db name, but only if not requesting a table level grant | |||
dbc = quote_identifier(dbc, for_grants=(table == "*")) | dbc = quote_identifier(dbc, for_grants=(table == "*")) | |||
if table != "*": | if table != "*": | |||
table = quote_identifier(table) | table = quote_identifier(table) | |||
# identifiers cannot be used as values, and same thing for grants | # identifiers cannot be used as values, and same thing for grants | |||
qry = "GRANT {0} ON {1}.{2} TO %(user)s@%(host)s".format(grant, dbc, table) | qry = "GRANT {} ON {}.{} TO %(user)s@%(host)s".format(grant, dbc, table) | |||
args = {} | args = {} | |||
args["user"] = user | args["user"] = user | |||
args["host"] = host | args["host"] = host | |||
if ssl_option and isinstance(ssl_option, list): | if ssl_option and isinstance(ssl_option, list): | |||
qry += __ssl_option_sanitize(ssl_option) | qry += __ssl_option_sanitize(ssl_option) | |||
if salt.utils.data.is_true(grant_option): | if salt.utils.data.is_true(grant_option): | |||
qry += " WITH GRANT OPTION" | qry += " WITH GRANT OPTION" | |||
log.debug("Grant Query generated: %s args %s", qry, repr(args)) | log.debug("Grant Query generated: %s args %s", qry, repr(args)) | |||
return {"qry": qry, "args": args} | return {"qry": qry, "args": args} | |||
skipping to change at line 2260 | skipping to change at line 2278 | |||
if dbc is None: | if dbc is None: | |||
return False | return False | |||
cur = dbc.cursor() | cur = dbc.cursor() | |||
qry = "SHOW GRANTS FOR %(user)s@%(host)s" | qry = "SHOW GRANTS FOR %(user)s@%(host)s" | |||
args = {} | args = {} | |||
args["user"] = user | args["user"] = user | |||
args["host"] = host | args["host"] = host | |||
try: | try: | |||
_execute(cur, qry, args) | _execute(cur, qry, args) | |||
except MySQLdb.OperationalError as exc: | except MySQLdb.OperationalError as exc: | |||
err = "MySQL Error {0}: {1}".format(*exc.args) | err = "MySQL Error {}: {}".format(*exc.args) | |||
__context__["mysql.error"] = err | __context__["mysql.error"] = err | |||
log.error(err) | log.error(err) | |||
return False | return False | |||
ret = [] | ret = [] | |||
results = salt.utils.data.decode(cur.fetchall()) | results = salt.utils.data.decode(cur.fetchall()) | |||
for grant in results: | for grant in results: | |||
tmp = grant[0].split(" IDENTIFIED BY")[0] | tmp = grant[0].split(" IDENTIFIED BY")[0] | |||
if "WITH GRANT OPTION" in grant[0] and "WITH GRANT OPTION" not in tmp: | if "WITH GRANT OPTION" in grant[0] and "WITH GRANT OPTION" not in tmp: | |||
tmp = "{0} WITH GRANT OPTION".format(tmp) | tmp = "{} WITH GRANT OPTION".format(tmp) | |||
ret.append(tmp) | ret.append(tmp) | |||
log.debug(ret) | log.debug(ret) | |||
return ret | return ret | |||
def grant_exists( | def grant_exists( | |||
grant, | grant, | |||
database, | database, | |||
user, | user, | |||
host="localhost", | host="localhost", | |||
grant_option=False, | grant_option=False, | |||
skipping to change at line 2425 | skipping to change at line 2443 | |||
try: | try: | |||
qry = __grant_generate( | qry = __grant_generate( | |||
grant, database, user, host, grant_option, escape, ssl_option | grant, database, user, host, grant_option, escape, ssl_option | |||
) | ) | |||
except Exception: # pylint: disable=broad-except | except Exception: # pylint: disable=broad-except | |||
log.error("Error during grant generation") | log.error("Error during grant generation") | |||
return False | return False | |||
try: | try: | |||
_execute(cur, qry["qry"], qry["args"]) | _execute(cur, qry["qry"], qry["args"]) | |||
except (MySQLdb.OperationalError, MySQLdb.ProgrammingError) as exc: | except (MySQLdb.OperationalError, MySQLdb.ProgrammingError) as exc: | |||
err = "MySQL Error {0}: {1}".format(*exc.args) | err = "MySQL Error {}: {}".format(*exc.args) | |||
__context__["mysql.error"] = err | __context__["mysql.error"] = err | |||
log.error(err) | log.error(err) | |||
return False | return False | |||
if grant_exists( | if grant_exists( | |||
grant, database, user, host, grant_option, escape, **connection_args | grant, database, user, host, grant_option, escape, **connection_args | |||
): | ): | |||
log.info( | log.info( | |||
"Grant '%s' on '%s' for user '%s' has been added", grant, database, user | "Grant '%s' on '%s' for user '%s' has been added", grant, database, user | |||
) | ) | |||
return True | return True | |||
skipping to change at line 2486 | skipping to change at line 2504 | |||
# on the db name, but only if not requesting a table level grant | # on the db name, but only if not requesting a table level grant | |||
s_database = quote_identifier(dbc, for_grants=(table == "*")) | s_database = quote_identifier(dbc, for_grants=(table == "*")) | |||
if dbc == "*": | if dbc == "*": | |||
# add revoke for *.* | # add revoke for *.* | |||
# before the modification query send to mysql will looks like | # before the modification query send to mysql will looks like | |||
# REVOKE SELECT ON `*`.* FROM %(user)s@%(host)s | # REVOKE SELECT ON `*`.* FROM %(user)s@%(host)s | |||
s_database = dbc | s_database = dbc | |||
if table != "*": | if table != "*": | |||
table = quote_identifier(table) | table = quote_identifier(table) | |||
# identifiers cannot be used as values, same thing for grants | # identifiers cannot be used as values, same thing for grants | |||
qry = "REVOKE {0} ON {1}.{2} FROM %(user)s@%(host)s;".format( | qry = "REVOKE {} ON {}.{} FROM %(user)s@%(host)s;".format(grant, s_database, | |||
grant, s_database, table | table) | |||
) | ||||
args = {} | args = {} | |||
args["user"] = user | args["user"] = user | |||
args["host"] = host | args["host"] = host | |||
try: | try: | |||
_execute(cur, qry, args) | _execute(cur, qry, args) | |||
except MySQLdb.OperationalError as exc: | except MySQLdb.OperationalError as exc: | |||
err = "MySQL Error {0}: {1}".format(*exc.args) | err = "MySQL Error {}: {}".format(*exc.args) | |||
__context__["mysql.error"] = err | __context__["mysql.error"] = err | |||
log.error(err) | log.error(err) | |||
return False | return False | |||
if not grant_exists( | if not grant_exists( | |||
grant, database, user, host, grant_option, escape, **connection_args | grant, database, user, host, grant_option, escape, **connection_args | |||
): | ): | |||
log.info( | log.info( | |||
"Grant '%s' on '%s' for user '%s' has been " "revoked", | "Grant '%s' on '%s' for user '%s' has been " "revoked", | |||
grant, | grant, | |||
skipping to change at line 2809 | skipping to change at line 2825 | |||
salt '*' mysql.plugins_list | salt '*' mysql.plugins_list | |||
""" | """ | |||
dbc = _connect(**connection_args) | dbc = _connect(**connection_args) | |||
if dbc is None: | if dbc is None: | |||
return [] | return [] | |||
cur = dbc.cursor() | cur = dbc.cursor() | |||
qry = "SHOW PLUGINS" | qry = "SHOW PLUGINS" | |||
try: | try: | |||
_execute(cur, qry) | _execute(cur, qry) | |||
except MySQLdb.OperationalError as exc: | except MySQLdb.OperationalError as exc: | |||
err = "MySQL Error {0}: {1}".format(*exc.args) | err = "MySQL Error {}: {}".format(*exc.args) | |||
__context__["mysql.error"] = err | __context__["mysql.error"] = err | |||
log.error(err) | log.error(err) | |||
return [] | return [] | |||
ret = [] | ret = [] | |||
results = cur.fetchall() | results = cur.fetchall() | |||
for dbs in results: | for dbs in results: | |||
ret.append({"name": dbs[0], "status": dbs[1]}) | ret.append({"name": dbs[0], "status": dbs[1]}) | |||
log.debug(ret) | log.debug(ret) | |||
skipping to change at line 2844 | skipping to change at line 2860 | |||
return False | return False | |||
if plugin_status(name, **connection_args): | if plugin_status(name, **connection_args): | |||
log.error("Plugin %s is already installed.", name) | log.error("Plugin %s is already installed.", name) | |||
return True | return True | |||
dbc = _connect(**connection_args) | dbc = _connect(**connection_args) | |||
if dbc is None: | if dbc is None: | |||
return False | return False | |||
cur = dbc.cursor() | cur = dbc.cursor() | |||
qry = "INSTALL PLUGIN {0}".format(name) | qry = "INSTALL PLUGIN {}".format(name) | |||
if soname: | if soname: | |||
qry += ' SONAME "{0}"'.format(soname) | qry += ' SONAME "{}"'.format(soname) | |||
else: | else: | |||
qry += ' SONAME "{0}.so"'.format(name) | qry += ' SONAME "{}.so"'.format(name) | |||
try: | try: | |||
_execute(cur, qry) | _execute(cur, qry) | |||
except MySQLdb.OperationalError as exc: | except MySQLdb.OperationalError as exc: | |||
err = "MySQL Error {0}: {1}".format(*exc.args) | err = "MySQL Error {}: {}".format(*exc.args) | |||
__context__["mysql.error"] = err | __context__["mysql.error"] = err | |||
log.error(err) | log.error(err) | |||
return False | return False | |||
return True | return True | |||
def plugin_remove(name, **connection_args): | def plugin_remove(name, **connection_args): | |||
""" | """ | |||
Remove a plugin. | Remove a plugin. | |||
skipping to change at line 2883 | skipping to change at line 2899 | |||
return False | return False | |||
if not plugin_status(name, **connection_args): | if not plugin_status(name, **connection_args): | |||
log.error("Plugin %s is not installed.", name) | log.error("Plugin %s is not installed.", name) | |||
return True | return True | |||
dbc = _connect(**connection_args) | dbc = _connect(**connection_args) | |||
if dbc is None: | if dbc is None: | |||
return False | return False | |||
cur = dbc.cursor() | cur = dbc.cursor() | |||
qry = "UNINSTALL PLUGIN {0}".format(name) | qry = "UNINSTALL PLUGIN {}".format(name) | |||
args = {} | args = {} | |||
args["name"] = name | args["name"] = name | |||
try: | try: | |||
_execute(cur, qry) | _execute(cur, qry) | |||
except MySQLdb.OperationalError as exc: | except MySQLdb.OperationalError as exc: | |||
err = "MySQL Error {0}: {1}".format(*exc.args) | err = "MySQL Error {}: {}".format(*exc.args) | |||
__context__["mysql.error"] = err | __context__["mysql.error"] = err | |||
log.error(err) | log.error(err) | |||
return False | return False | |||
return True | return True | |||
def plugin_status(name, **connection_args): | def plugin_status(name, **connection_args): | |||
""" | """ | |||
Return the status of a plugin. | Return the status of a plugin. | |||
skipping to change at line 2922 | skipping to change at line 2938 | |||
if dbc is None: | if dbc is None: | |||
return "" | return "" | |||
cur = dbc.cursor() | cur = dbc.cursor() | |||
qry = "SELECT PLUGIN_STATUS FROM INFORMATION_SCHEMA.PLUGINS WHERE PLUGIN_NAM E = %(name)s" | qry = "SELECT PLUGIN_STATUS FROM INFORMATION_SCHEMA.PLUGINS WHERE PLUGIN_NAM E = %(name)s" | |||
args = {} | args = {} | |||
args["name"] = name | args["name"] = name | |||
try: | try: | |||
_execute(cur, qry, args) | _execute(cur, qry, args) | |||
except MySQLdb.OperationalError as exc: | except MySQLdb.OperationalError as exc: | |||
err = "MySQL Error {0}: {1}".format(*exc.args) | err = "MySQL Error {}: {}".format(*exc.args) | |||
__context__["mysql.error"] = err | __context__["mysql.error"] = err | |||
log.error(err) | log.error(err) | |||
return "" | return "" | |||
try: | try: | |||
status = cur.fetchone() | status = cur.fetchone() | |||
if status is None: | if status is None: | |||
return "" | return "" | |||
else: | else: | |||
return status[0] | return status[0] | |||
End of changes. 64 change blocks. | ||||
92 lines changed or deleted | 111 lines changed or added |