summaryrefslogtreecommitdiff
path: root/database
diff options
context:
space:
mode:
authorBermudez, Jaime <jaime_bermudez@harvard.edu>2016-01-11 13:13:33 -0500
committerBermudez, Jaime <jaime_bermudez@harvard.edu>2016-01-11 13:13:33 -0500
commit2ad0be942569636fc2a8c4f00151f02312125736 (patch)
tree0d3c9dea1083825f392a9e7638cc5a97e3aa153a /database
parent6992d0d4652ef9eb0ee32658d1ce8e29f7b64c0a (diff)
parentf312f48937eb19932c20d93fac93cfee4f929874 (diff)
downloadansible-modules-core-2ad0be942569636fc2a8c4f00151f02312125736.tar.gz
Merge branch 'devel' into feature/iam_policy_present_state_includes_policy_changes
Conflicts: cloud/amazon/iam_policy.py
Diffstat (limited to 'database')
-rw-r--r--database/mysql/mysql_db.py194
-rw-r--r--database/mysql/mysql_user.py302
-rw-r--r--database/mysql/mysql_variables.py124
-rw-r--r--database/postgresql/postgresql_db.py2
-rw-r--r--database/postgresql/postgresql_user.py2
5 files changed, 260 insertions, 364 deletions
diff --git a/database/mysql/mysql_db.py b/database/mysql/mysql_db.py
index 33720f5d..24bcf40e 100644
--- a/database/mysql/mysql_db.py
+++ b/database/mysql/mysql_db.py
@@ -30,36 +30,11 @@ options:
name:
description:
- name of the database to add or remove
- - name=all May only be provided if I(state) is C(dump) or C(import).
+ - name=all May only be provided if I(state) is C(dump) or C(import).
- if name=all Works like --all-databases option for mysqldump (Added in 2.0)
required: true
default: null
aliases: [ db ]
- login_user:
- description:
- - The username used to authenticate with
- required: false
- default: null
- login_password:
- description:
- - The password used to authenticate with
- required: false
- default: null
- login_host:
- description:
- - Host running the database
- required: false
- default: localhost
- login_port:
- description:
- - Port of the MySQL server. Requires login_host be defined as other then localhost if login_port is used
- required: false
- default: 3306
- login_unix_socket:
- description:
- - The path to a Unix domain socket for local connections
- required: false
- default: null
state:
description:
- The database state
@@ -68,7 +43,7 @@ options:
choices: [ "present", "absent", "dump", "import" ]
collation:
description:
- - Collation mode
+ - Collation mode (sorting). This only applies to new table/databases and does not update existing ones, this is a limitation of MySQL.
required: false
default: null
encoding:
@@ -79,18 +54,10 @@ options:
target:
description:
- Location, on the remote host, of the dump file to read from or write to. Uncompressed SQL
- files (C(.sql)) as well as bzip2 (C(.bz2)), gzip (C(.gz)) and xz compressed files are supported.
+ files (C(.sql)) as well as bzip2 (C(.bz2)), gzip (C(.gz)) and xz (Added in 2.0) compressed files are supported.
required: false
-notes:
- - Requires the MySQLdb Python package on the remote host. For Ubuntu, this
- is as easy as apt-get install python-mysqldb. (See M(apt).) For CentOS/Fedora, this
- is as easy as yum install MySQL-python. (See M(yum).)
- - Both I(login_password) and I(login_user) are required when you are
- passing credentials. If none are present, the module will attempt to read
- the credentials from C(~/.my.cnf), and finally fall back to using the MySQL
- default login of C(root) with no password.
-requirements: [ ConfigParser ]
-author: "Mark Theunissen (@marktheunissen)"
+author: "Ansible Core Team"
+extends_documentation_fragment: mysql
'''
EXAMPLES = '''
@@ -108,11 +75,11 @@ EXAMPLES = '''
- mysql_db: state=import name=all target=/tmp/{{ inventory_hostname }}.sql
'''
-import ConfigParser
import os
import pipes
import stat
import subprocess
+
try:
import MySQLdb
except ImportError:
@@ -133,9 +100,22 @@ def db_delete(cursor, db):
cursor.execute(query)
return True
-def db_dump(module, host, user, password, db_name, target, all_databases, port, socket=None):
+def db_dump(module, host, user, password, db_name, target, all_databases, port, config_file, socket=None, ssl_cert=None, ssl_key=None, ssl_ca=None):
cmd = module.get_bin_path('mysqldump', True)
- cmd += " --quick --user=%s --password=%s" % (pipes.quote(user), pipes.quote(password))
+ # If defined, mysqldump demands --defaults-extra-file be the first option
+ if config_file:
+ cmd += " --defaults-extra-file=%s" % pipes.quote(config_file)
+ cmd += " --quick"
+ if user is not None:
+ cmd += " --user=%s" % pipes.quote(user)
+ if password is not None:
+ cmd += " --password=%s" % pipes.quote(password)
+ if ssl_cert is not None:
+ cmd += " --ssl-cert=%s" % pipes.quote(ssl_cert)
+ if ssl_key is not None:
+ cmd += " --ssl-key=%s" % pipes.quote(ssl_key)
+ if ssl_cert is not None:
+ cmd += " --ssl-ca=%s" % pipes.quote(ssl_ca)
if socket is not None:
cmd += " --socket=%s" % pipes.quote(socket)
else:
@@ -161,17 +141,26 @@ def db_dump(module, host, user, password, db_name, target, all_databases, port,
rc, stdout, stderr = module.run_command(cmd, use_unsafe_shell=True)
return rc, stdout, stderr
-def db_import(module, host, user, password, db_name, target, all_databases, port, socket=None):
+def db_import(module, host, user, password, db_name, target, all_databases, port, config_file, socket=None, ssl_cert=None, ssl_key=None, ssl_ca=None):
if not os.path.exists(target):
return module.fail_json(msg="target %s does not exist on the host" % target)
cmd = [module.get_bin_path('mysql', True)]
+ # --defaults-file must go first, or errors out
+ if config_file:
+ cmd.append("--defaults-extra-file=%s" % pipes.quote(config_file))
if user:
cmd.append("--user=%s" % pipes.quote(user))
if password:
cmd.append("--password=%s" % pipes.quote(password))
if socket is not None:
cmd.append("--socket=%s" % pipes.quote(socket))
+ if ssl_cert is not None:
+ cmd.append("--ssl-cert=%s" % pipes.quote(ssl_cert))
+ if ssl_key is not None:
+ cmd.append("--ssl-key=%s" % pipes.quote(ssl_key))
+ if ssl_cert is not None:
+ cmd.append("--ssl-ca=%s" % pipes.quote(ssl_ca))
else:
cmd.append("--host=%s" % pipes.quote(host))
cmd.append("--port=%i" % port)
@@ -215,61 +204,6 @@ def db_create(cursor, db, encoding, collation):
res = cursor.execute(query, query_params)
return True
-def strip_quotes(s):
- """ Remove surrounding single or double quotes
-
- >>> print strip_quotes('hello')
- hello
- >>> print strip_quotes('"hello"')
- hello
- >>> print strip_quotes("'hello'")
- hello
- >>> print strip_quotes("'hello")
- 'hello
-
- """
- single_quote = "'"
- double_quote = '"'
-
- if s.startswith(single_quote) and s.endswith(single_quote):
- s = s.strip(single_quote)
- elif s.startswith(double_quote) and s.endswith(double_quote):
- s = s.strip(double_quote)
- return s
-
-
-def config_get(config, section, option):
- """ Calls ConfigParser.get and strips quotes
-
- See: http://dev.mysql.com/doc/refman/5.0/en/option-files.html
- """
- return strip_quotes(config.get(section, option))
-
-
-def load_mycnf():
- config = ConfigParser.RawConfigParser()
- mycnf = os.path.expanduser('~/.my.cnf')
- if not os.path.exists(mycnf):
- return False
- try:
- config.readfp(open(mycnf))
- except (IOError):
- return False
- # We support two forms of passwords in .my.cnf, both pass= and password=,
- # as these are both supported by MySQL.
- try:
- passwd = config_get(config, 'client', 'password')
- except (ConfigParser.NoOptionError):
- try:
- passwd = config_get(config, 'client', 'pass')
- except (ConfigParser.NoOptionError):
- return False
- try:
- creds = dict(user=config_get(config, 'client', 'user'),passwd=passwd)
- except (ConfigParser.NoOptionError):
- return False
- return creds
-
# ===========================================
# Module execution.
#
@@ -287,6 +221,10 @@ def main():
collation=dict(default=""),
target=dict(default=None),
state=dict(default="present", choices=["absent", "present","dump", "import"]),
+ ssl_cert=dict(default=None),
+ ssl_key=dict(default=None),
+ ssl_ca=dict(default=None),
+ config_file=dict(default="~/.my.cnf"),
)
)
@@ -302,64 +240,41 @@ def main():
login_port = module.params["login_port"]
if login_port < 0 or login_port > 65535:
module.fail_json(msg="login_port must be a valid unix port number (0-65535)")
+ ssl_cert = module.params["ssl_cert"]
+ ssl_key = module.params["ssl_key"]
+ ssl_ca = module.params["ssl_ca"]
+ config_file = module.params['config_file']
+ config_file = os.path.expanduser(os.path.expandvars(config_file))
+ login_password = module.params["login_password"]
+ login_user = module.params["login_user"]
+ login_host = module.params["login_host"]
# make sure the target path is expanded for ~ and $HOME
if target is not None:
target = os.path.expandvars(os.path.expanduser(target))
- # Either the caller passes both a username and password with which to connect to
- # mysql, or they pass neither and allow this module to read the credentials from
- # ~/.my.cnf.
- login_password = module.params["login_password"]
- login_user = module.params["login_user"]
- if login_user is None and login_password is None:
- mycnf_creds = load_mycnf()
- if mycnf_creds is False:
- login_user = "root"
- login_password = ""
- else:
- login_user = mycnf_creds["user"]
- login_password = mycnf_creds["passwd"]
- elif login_password is None or login_user is None:
- module.fail_json(msg="when supplying login arguments, both login_user and login_password must be provided")
- login_host = module.params["login_host"]
-
if state in ['dump','import']:
if target is None:
module.fail_json(msg="with state=%s target is required" % (state))
if db == 'all':
- connect_to_db = 'mysql'
db = 'mysql'
all_databases = True
else:
- connect_to_db = db
all_databases = False
else:
if db == 'all':
module.fail_json(msg="name is not allowed to equal 'all' unless state equals import, or dump.")
- connect_to_db = ''
try:
- if socket:
- try:
- socketmode = os.stat(socket).st_mode
- if not stat.S_ISSOCK(socketmode):
- module.fail_json(msg="%s, is not a socket, unable to connect" % socket)
- except OSError:
- module.fail_json(msg="%s, does not exist, unable to connect" % socket)
- db_connection = MySQLdb.connect(host=module.params["login_host"], unix_socket=socket, user=login_user, passwd=login_password, db=connect_to_db)
- elif login_port != 3306 and module.params["login_host"] == "localhost":
- module.fail_json(msg="login_host is required when login_port is defined, login_host cannot be localhost when login_port is defined")
- else:
- db_connection = MySQLdb.connect(host=module.params["login_host"], port=login_port, user=login_user, passwd=login_password, db=connect_to_db)
- cursor = db_connection.cursor()
+ cursor = mysql_connect(module, login_user, login_password, config_file, ssl_cert, ssl_key, ssl_ca)
except Exception, e:
- errno, errstr = e.args
- if "Unknown database" in str(e):
- module.fail_json(msg="ERROR: %s %s" % (errno, errstr))
+ if os.path.exists(config_file):
+ module.fail_json(msg="unable to connect to database, check login_user and login_password are correct or %s has the credentials. Exception message: %s" % (config_file, e))
else:
- module.fail_json(msg="unable to connect, check login credentials (login_user, and login_password, which can be defined in ~/.my.cnf), check that mysql socket exists and mysql server is running (ERROR: %s %s)" % (errno, errstr))
+ module.fail_json(msg="unable to find %s. Exception message: %s" % (config_file, e))
changed = False
+ if not os.path.exists(config_file):
+ config_file = None
if db_exists(cursor, db):
if state == "absent":
try:
@@ -367,19 +282,17 @@ def main():
except Exception, e:
module.fail_json(msg="error deleting database: " + str(e))
elif state == "dump":
- rc, stdout, stderr = db_dump(module, login_host, login_user,
+ rc, stdout, stderr = db_dump(module, login_host, login_user,
login_password, db, target, all_databases,
- port=login_port,
- socket=module.params['login_unix_socket'])
+ login_port, config_file, socket, ssl_cert, ssl_key, ssl_ca)
if rc != 0:
module.fail_json(msg="%s" % stderr)
else:
module.exit_json(changed=True, db=db, msg=stdout)
elif state == "import":
- rc, stdout, stderr = db_import(module, login_host, login_user,
+ rc, stdout, stderr = db_import(module, login_host, login_user,
login_password, db, target, all_databases,
- port=login_port,
- socket=module.params['login_unix_socket'])
+ login_port, config_file, socket, ssl_cert, ssl_key, ssl_ca)
if rc != 0:
module.fail_json(msg="%s" % stderr)
else:
@@ -396,5 +309,6 @@ def main():
# import module snippets
from ansible.module_utils.basic import *
from ansible.module_utils.database import *
+from ansible.module_utils.mysql import *
if __name__ == '__main__':
main()
diff --git a/database/mysql/mysql_user.py b/database/mysql/mysql_user.py
index 1ea54b41..3ca952ca 100644
--- a/database/mysql/mysql_user.py
+++ b/database/mysql/mysql_user.py
@@ -32,40 +32,30 @@ options:
required: true
password:
description:
- - set the user's password
+ - set the user's password. (Required when adding a user)
required: false
default: null
- host:
- description:
- - the 'host' part of the MySQL username
- required: false
- default: localhost
- login_user:
- description:
- - The username used to authenticate with
- required: false
- default: null
- login_password:
+ encrypted:
description:
- - The password used to authenticate with
+ - Indicate that the 'password' field is a `mysql_native_password` hash
required: false
- default: null
- login_host:
+ choices: [ "yes", "no" ]
+ default: "no"
+ version_added: "2.0"
+ host:
description:
- - Host running the database
+ - the 'host' part of the MySQL username
required: false
default: localhost
- login_port:
- description:
- - Port of the MySQL server
- required: false
- default: 3306
- version_added: '1.4'
- login_unix_socket:
+ host_all:
description:
- - The path to a Unix domain socket for local connections
+ - override the host option, making ansible apply changes to
+ all hostnames for a given user. This option cannot be used
+ when creating users
required: false
- default: null
+ choices: [ "yes", "no" ]
+ default: "no"
+ version_added: "2.1"
priv:
description:
- "MySQL privileges string in the format: C(db.table:priv1,priv2)"
@@ -100,46 +90,47 @@ options:
version_added: "2.0"
description:
- C(always) will update passwords if they differ. C(on_create) will only set the password for newly created users.
- config_file:
- description:
- - Specify a config file from which user and password are to be read
- required: false
- default: '~/.my.cnf'
- version_added: "2.0"
notes:
- - Requires the MySQLdb Python package on the remote host. For Ubuntu, this
- is as easy as apt-get install python-mysqldb.
- - Both C(login_password) and C(login_user) are required when you are
- passing credentials. If none are present, the module will attempt to read
- the credentials from C(~/.my.cnf), and finally fall back to using the MySQL
- default login of 'root' with no password.
- "MySQL server installs with default login_user of 'root' and no password. To secure this user
as part of an idempotent playbook, you must create at least two tasks: the first must change the root user's password,
without providing any login_user/login_password details. The second must drop a ~/.my.cnf file containing
the new root credentials. Subsequent runs of the playbook will then succeed by reading the new credentials from
the file."
+ - Currently, there is only support for the `mysql_native_password` encryted password hash module.
-requirements: [ "MySQLdb" ]
-author: "Mark Theunissen (@marktheunissen)"
+author: "Jonathan Mainguy (@Jmainguy)"
+extends_documentation_fragment: mysql
'''
EXAMPLES = """
+# Removes anonymous user account for localhost
+- mysql_user: name='' host=localhost state=absent
+
+# Removes all anonymous user accounts
+- mysql_user: name='' host_all=yes state=absent
+
# Create database user with name 'bob' and password '12345' with all database privileges
- mysql_user: name=bob password=12345 priv=*.*:ALL state=present
+# Create database user with name 'bob' and previously hashed mysql native password '*EE0D72C1085C46C5278932678FBE2C6A782821B4' with all database privileges
+- mysql_user: name=bob password='*EE0D72C1085C46C5278932678FBE2C6A782821B4' encrypted=yes priv=*.*:ALL state=present
+
# Creates database user 'bob' and password '12345' with all database privileges and 'WITH GRANT OPTION'
- mysql_user: name=bob password=12345 priv=*.*:ALL,GRANT state=present
# Modify user Bob to require SSL connections. Note that REQUIRESSL is a special privilege that should only apply to *.* by itself.
- mysql_user: name=bob append_privs=true priv=*.*:REQUIRESSL state=present
-# Ensure no user named 'sally' exists, also passing in the auth credentials.
+# Ensure no user named 'sally'@'localhost' exists, also passing in the auth credentials.
- mysql_user: login_user=root login_password=123456 name=sally state=absent
+# Ensure no user named 'sally' exists at all
+- mysql_user: name=sally host_all=yes state=absent
+
# Specify grants composed of more than one word
- mysql_user: name=replication password=12345 priv=*.*:"REPLICATION CLIENT" state=present
-# Revoke all privileges for user 'bob' and password '12345'
+# Revoke all privileges for user 'bob' and password '12345'
- mysql_user: name=bob password=12345 priv=*.*:USAGE state=present
# Example privileges string format
@@ -158,6 +149,7 @@ password=n<_665{vS43y
import getpass
import tempfile
import re
+import string
try:
import MySQLdb
except ImportError:
@@ -182,95 +174,155 @@ class InvalidPrivsError(Exception):
# MySQL module specific support methods.
#
-def connect(module, login_user=None, login_password=None, config_file=''):
- config = {
- 'host': module.params['login_host'],
- 'db': 'mysql'
- }
-
- if module.params['login_unix_socket']:
- config['unix_socket'] = module.params['login_unix_socket']
+# User Authentication Management was change in MySQL 5.7
+# This is a generic check for if the server version is less than version 5.7
+def server_version_check(cursor):
+ cursor.execute("SELECT VERSION()");
+ result = cursor.fetchone()
+ version_str = result[0]
+ version = version_str.split('.')
+
+ # Currently we have no facility to handle new-style password update on
+ # mariadb and the old-style update continues to work
+ if 'mariadb' in version_str.lower():
+ return True
+ if (int(version[0]) <= 5 and int(version[1]) < 7):
+ return True
else:
- config['port'] = module.params['login_port']
-
- if os.path.exists(config_file):
- config['read_default_file'] = config_file
-
- # 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
+ return False
- db_connection = MySQLdb.connect(**config)
- return db_connection.cursor()
+def user_exists(cursor, user, host, host_all):
+ if host_all:
+ cursor.execute("SELECT count(*) FROM user WHERE user = %s", user)
+ else:
+ cursor.execute("SELECT count(*) FROM user WHERE user = %s AND host = %s", (user,host))
-def user_exists(cursor, user, host):
- cursor.execute("SELECT count(*) FROM user WHERE user = %s AND host = %s", (user,host))
count = cursor.fetchone()
return count[0] > 0
-def user_add(cursor, user, host, password, new_priv):
- cursor.execute("CREATE USER %s@%s IDENTIFIED BY %s", (user,host,password))
+def user_add(cursor, user, host, host_all, password, encrypted, new_priv):
+ # we cannot create users without a proper hostname
+ if host_all:
+ return False
+
+ if password and encrypted:
+ cursor.execute("CREATE USER %s@%s IDENTIFIED BY PASSWORD %s", (user,host,password))
+ elif password and not encrypted:
+ cursor.execute("CREATE USER %s@%s IDENTIFIED BY %s", (user,host,password))
+
if new_priv is not None:
for db_table, priv in new_priv.iteritems():
privileges_grant(cursor, user,host,db_table,priv)
return True
-def user_mod(cursor, user, host, password, new_priv, append_privs):
+def is_hash(password):
+ ishash = False
+ if len(password) == 41 and password[0] == '*':
+ if frozenset(password[1:]).issubset(string.hexdigits):
+ ishash = True
+ return ishash
+
+def user_mod(cursor, user, host, host_all, password, encrypted, new_priv, append_privs):
changed = False
grant_option = False
- # Handle passwords
- if password is not None:
- cursor.execute("SELECT password FROM user WHERE user = %s AND host = %s", (user,host))
- current_pass_hash = cursor.fetchone()
- cursor.execute("SELECT PASSWORD(%s)", (password,))
- new_pass_hash = cursor.fetchone()
- if current_pass_hash[0] != new_pass_hash[0]:
- cursor.execute("SET PASSWORD FOR %s@%s = PASSWORD(%s)", (user,host,password))
- changed = True
-
- # Handle privileges
- if new_priv is not None:
- curr_priv = privileges_get(cursor, user,host)
-
- # If the user has privileges on a db.table that doesn't appear at all in
- # the new specification, then revoke all privileges on it.
- for db_table, priv in curr_priv.iteritems():
- # If the user has the GRANT OPTION on a db.table, revoke it first.
- if "GRANT" in priv:
- grant_option = True
- if db_table not in new_priv:
- if user != "root" and "PROXY" not in priv and not append_privs:
- privileges_revoke(cursor, user,host,db_table,priv,grant_option)
+ if host_all:
+ hostnames = user_get_hostnames(cursor, user)
+ else:
+ hostnames = [host]
+
+ for host in hostnames:
+ # Handle clear text and hashed passwords.
+ if bool(password):
+ # Determine what user management method server uses
+ old_user_mgmt = server_version_check(cursor)
+
+ if old_user_mgmt:
+ cursor.execute("SELECT password FROM user WHERE user = %s AND host = %s", (user,host))
+ else:
+ cursor.execute("SELECT authentication_string FROM user WHERE user = %s AND host = %s", (user,host))
+ current_pass_hash = cursor.fetchone()
+
+ if encrypted:
+ encrypted_string = (password)
+ if is_hash(password):
+ if current_pass_hash[0] != encrypted_string:
+ if old_user_mgmt:
+ cursor.execute("SET PASSWORD FOR %s@%s = %s", (user, host, password))
+ else:
+ cursor.execute("ALTER USER %s@%s IDENTIFIED WITH mysql_native_password AS %s", (user, host, password))
+ changed = True
+ else:
+ module.fail_json(msg="encrypted was specified however it does not appear to be a valid hash expecting: *SHA1(SHA1(your_password))")
+ else:
+ if old_user_mgmt:
+ cursor.execute("SELECT PASSWORD(%s)", (password,))
+ else:
+ cursor.execute("SELECT CONCAT('*', UCASE(SHA1(UNHEX(SHA1(%s)))))", (password,))
+ new_pass_hash = cursor.fetchone()
+ if current_pass_hash[0] != new_pass_hash[0]:
+ if old_user_mgmt:
+ cursor.execute("SET PASSWORD FOR %s@%s = PASSWORD(%s)", (user, host, password))
+ else:
+ cursor.execute("ALTER USER %s@%s IDENTIFIED BY %s", (user, host, password))
+ changed = True
+
+ # Handle privileges
+ if new_priv is not None:
+ curr_priv = privileges_get(cursor, user,host)
+
+ # If the user has privileges on a db.table that doesn't appear at all in
+ # the new specification, then revoke all privileges on it.
+ for db_table, priv in curr_priv.iteritems():
+ # If the user has the GRANT OPTION on a db.table, revoke it first.
+ if "GRANT" in priv:
+ grant_option = True
+ if db_table not in new_priv:
+ if user != "root" and "PROXY" not in priv and not append_privs:
+ privileges_revoke(cursor, user,host,db_table,priv,grant_option)
+ changed = True
+
+ # If the user doesn't currently have any privileges on a db.table, then
+ # we can perform a straight grant operation.
+ for db_table, priv in new_priv.iteritems():
+ if db_table not in curr_priv:
+ privileges_grant(cursor, user,host,db_table,priv)
changed = True
- # If the user doesn't currently have any privileges on a db.table, then
- # we can perform a straight grant operation.
- for db_table, priv in new_priv.iteritems():
- if db_table not in curr_priv:
- privileges_grant(cursor, user,host,db_table,priv)
- changed = True
-
- # If the db.table specification exists in both the user's current privileges
- # and in the new privileges, then we need to see if there's a difference.
- db_table_intersect = set(new_priv.keys()) & set(curr_priv.keys())
- for db_table in db_table_intersect:
- priv_diff = set(new_priv[db_table]) ^ set(curr_priv[db_table])
- if (len(priv_diff) > 0):
- if not append_privs:
- privileges_revoke(cursor, user,host,db_table,curr_priv[db_table],grant_option)
- privileges_grant(cursor, user,host,db_table,new_priv[db_table])
- changed = True
+ # If the db.table specification exists in both the user's current privileges
+ # and in the new privileges, then we need to see if there's a difference.
+ db_table_intersect = set(new_priv.keys()) & set(curr_priv.keys())
+ for db_table in db_table_intersect:
+ priv_diff = set(new_priv[db_table]) ^ set(curr_priv[db_table])
+ if (len(priv_diff) > 0):
+ if not append_privs:
+ privileges_revoke(cursor, user,host,db_table,curr_priv[db_table],grant_option)
+ privileges_grant(cursor, user,host,db_table,new_priv[db_table])
+ changed = True
return changed
-def user_delete(cursor, user, host):
- cursor.execute("DROP USER %s@%s", (user, host))
+def user_delete(cursor, user, host, host_all):
+ if host_all:
+ hostnames = user_get_hostnames(cursor, user)
+
+ for hostname in hostnames:
+ cursor.execute("DROP USER %s@%s", (user, hostname))
+ else:
+ cursor.execute("DROP USER %s@%s", (user, host))
+
return True
+def user_get_hostnames(cursor, user):
+ cursor.execute("SELECT Host FROM mysql.user WHERE user = %s", user)
+ hostnames_raw = cursor.fetchall()
+ hostnames = []
+
+ for hostname_raw in hostnames_raw:
+ hostnames.append(hostname_raw[0])
+
+ return hostnames
+
def privileges_get(cursor, user,host):
""" MySQL doesn't have a better method of getting privileges aside from the
SHOW GRANTS query syntax, which requires us to then parse the returned string.
@@ -388,26 +440,37 @@ def main():
login_unix_socket=dict(default=None),
user=dict(required=True, aliases=['name']),
password=dict(default=None, no_log=True),
+ encrypted=dict(default=False, type='bool'),
host=dict(default="localhost"),
+ host_all=dict(type="bool", default="no"),
state=dict(default="present", choices=["absent", "present"]),
priv=dict(default=None),
append_privs=dict(default=False, type='bool'),
check_implicit_admin=dict(default=False, type='bool'),
update_password=dict(default="always", choices=["always", "on_create"]),
config_file=dict(default="~/.my.cnf"),
+ ssl_cert=dict(default=None),
+ ssl_key=dict(default=None),
+ ssl_ca=dict(default=None),
)
)
login_user = module.params["login_user"]
login_password = module.params["login_password"]
user = module.params["user"]
password = module.params["password"]
+ encrypted = module.boolean(module.params["encrypted"])
host = module.params["host"].lower()
+ host_all = module.params["host_all"]
state = module.params["state"]
priv = module.params["priv"]
check_implicit_admin = module.params['check_implicit_admin']
config_file = module.params['config_file']
append_privs = module.boolean(module.params["append_privs"])
update_password = module.params['update_password']
+ ssl_cert = module.params["ssl_cert"]
+ ssl_key = module.params["ssl_key"]
+ ssl_ca = module.params["ssl_ca"]
+ db = 'mysql'
config_file = os.path.expanduser(os.path.expandvars(config_file))
if not mysqldb_found:
@@ -423,35 +486,37 @@ def main():
try:
if check_implicit_admin:
try:
- cursor = connect(module, 'root', '', config_file)
+ cursor = mysql_connect(module, 'root', '', config_file, ssl_cert, ssl_key, ssl_ca, db)
except:
pass
if not cursor:
- cursor = connect(module, login_user, login_password, config_file)
+ cursor = mysql_connect(module, login_user, login_password, config_file, ssl_cert, ssl_key, ssl_ca, db)
except Exception, e:
- module.fail_json(msg="unable to connect to database, check login_user and login_password are correct or ~/.my.cnf has the credentials. Exception message: %s" % e)
+ module.fail_json(msg="unable to connect to database, check login_user and login_password are correct or %s has the credentials. Exception message: %s" % (config_file, e))
if state == "present":
- if user_exists(cursor, user, host):
+ if user_exists(cursor, user, host, host_all):
try:
if update_password == 'always':
- changed = user_mod(cursor, user, host, password, priv, append_privs)
+ changed = user_mod(cursor, user, host, host_all, password, encrypted, priv, append_privs)
else:
- changed = user_mod(cursor, user, host, None, priv, append_privs)
+ changed = user_mod(cursor, user, host, host_all, None, encrypted, priv, append_privs)
except (SQLParseError, InvalidPrivsError, MySQLdb.Error), e:
module.fail_json(msg=str(e))
else:
if password is None:
module.fail_json(msg="password parameter required when adding a user")
+ if host_all:
+ module.fail_json(msg="host_all parameter cannot be used when adding a user")
try:
- changed = user_add(cursor, user, host, password, priv)
+ changed = user_add(cursor, user, host, host_all, password, encrypted, priv)
except (SQLParseError, InvalidPrivsError, MySQLdb.Error), e:
module.fail_json(msg=str(e))
elif state == "absent":
- if user_exists(cursor, user, host):
- changed = user_delete(cursor, user, host)
+ if user_exists(cursor, user, host, host_all):
+ changed = user_delete(cursor, user, host, host_all)
else:
changed = False
module.exit_json(changed=changed, user=user)
@@ -459,5 +524,6 @@ def main():
# import module snippets
from ansible.module_utils.basic import *
from ansible.module_utils.database import *
+from ansible.module_utils.mysql import *
if __name__ == '__main__':
main()
diff --git a/database/mysql/mysql_variables.py b/database/mysql/mysql_variables.py
index d7187e85..5e551cd0 100644
--- a/database/mysql/mysql_variables.py
+++ b/database/mysql/mysql_variables.py
@@ -40,26 +40,7 @@ options:
description:
- If set, then sets variable value to this
required: False
- login_user:
- description:
- - username to connect mysql host, if defined login_password also needed.
- required: False
- login_password:
- description:
- - password to connect mysql host, if defined login_user also needed.
- required: False
- login_host:
- description:
- - mysql host to connect
- required: False
- login_port:
- version_added: "2.0"
- description:
- - mysql port to connect
- required: False
- login_unix_socket:
- description:
- - unix socket to connect mysql server
+extends_documentation_fragment: mysql
'''
EXAMPLES = '''
# Check for sync_binlog setting
@@ -70,7 +51,6 @@ EXAMPLES = '''
'''
-import ConfigParser
import os
import warnings
from re import match
@@ -134,66 +114,6 @@ def setvariable(cursor, mysqlvar, value):
result = str(e)
return result
-
-def strip_quotes(s):
- """ Remove surrounding single or double quotes
-
- >>> print strip_quotes('hello')
- hello
- >>> print strip_quotes('"hello"')
- hello
- >>> print strip_quotes("'hello'")
- hello
- >>> print strip_quotes("'hello")
- 'hello
-
- """
- single_quote = "'"
- double_quote = '"'
-
- if s.startswith(single_quote) and s.endswith(single_quote):
- s = s.strip(single_quote)
- elif s.startswith(double_quote) and s.endswith(double_quote):
- s = s.strip(double_quote)
- return s
-
-
-def config_get(config, section, option):
- """ Calls ConfigParser.get and strips quotes
-
- See: http://dev.mysql.com/doc/refman/5.0/en/option-files.html
- """
- return strip_quotes(config.get(section, option))
-
-
-def load_mycnf():
- config = ConfigParser.RawConfigParser()
- mycnf = os.path.expanduser('~/.my.cnf')
- if not os.path.exists(mycnf):
- return False
- try:
- config.readfp(open(mycnf))
- except (IOError):
- return False
- # We support two forms of passwords in .my.cnf, both pass= and password=,
- # as these are both supported by MySQL.
- try:
- passwd = config_get(config, 'client', 'password')
- except (ConfigParser.NoOptionError):
- try:
- passwd = config_get(config, 'client', 'pass')
- except (ConfigParser.NoOptionError):
- return False
-
- # If .my.cnf doesn't specify a user, default to user login name
- try:
- user = config_get(config, 'client', 'user')
- except (ConfigParser.NoOptionError):
- user = getpass.getuser()
- creds = dict(user=user, passwd=passwd)
- return creds
-
-
def main():
module = AnsibleModule(
argument_spec = dict(
@@ -203,14 +123,24 @@ def main():
login_port=dict(default="3306", type='int'),
login_unix_socket=dict(default=None),
variable=dict(default=None),
- value=dict(default=None)
-
+ value=dict(default=None),
+ ssl_cert=dict(default=None),
+ ssl_key=dict(default=None),
+ ssl_ca=dict(default=None),
+ config_file=dict(default="~/.my.cnf")
)
)
user = module.params["login_user"]
password = module.params["login_password"]
host = module.params["login_host"]
port = module.params["login_port"]
+ ssl_cert = module.params["ssl_cert"]
+ ssl_key = module.params["ssl_key"]
+ ssl_ca = module.params["ssl_ca"]
+ config_file = module.params['config_file']
+ config_file = os.path.expanduser(os.path.expandvars(config_file))
+ db = 'mysql'
+
mysqlvar = module.params["variable"]
value = module.params["value"]
if mysqlvar is None:
@@ -222,29 +152,14 @@ def main():
else:
warnings.filterwarnings('error', category=MySQLdb.Warning)
- # Either the caller passes both a username and password with which to connect to
- # mysql, or they pass neither and allow this module to read the credentials from
- # ~/.my.cnf.
- login_password = module.params["login_password"]
- login_user = module.params["login_user"]
- if login_user is None and login_password is None:
- mycnf_creds = load_mycnf()
- if mycnf_creds is False:
- login_user = "root"
- login_password = ""
- else:
- login_user = mycnf_creds["user"]
- login_password = mycnf_creds["passwd"]
- elif login_password is None or login_user is None:
- module.fail_json(msg="when supplying login arguments, both login_user and login_password must be provided")
try:
- if module.params["login_unix_socket"]:
- db_connection = MySQLdb.connect(host=module.params["login_host"], port=module.params["login_port"], unix_socket=module.params["login_unix_socket"], user=login_user, passwd=login_password, db="mysql")
- else:
- db_connection = MySQLdb.connect(host=module.params["login_host"], port=module.params["login_port"], user=login_user, passwd=login_password, db="mysql")
- cursor = db_connection.cursor()
+ cursor = mysql_connect(module, user, password, config_file, ssl_cert, ssl_key, ssl_ca, db)
except Exception, e:
- module.fail_json(msg="unable to connect to database, check login_user and login_password are correct or ~/.my.cnf has the credentials")
+ if os.path.exists(config_file):
+ module.fail_json(msg="unable to connect to database, check login_user and login_password are correct or %s has the credentials. Exception message: %s" % (config_file, e))
+ else:
+ module.fail_json(msg="unable to find %s. Exception message: %s" % (config_file, e))
+
mysqlvar_val = getvariable(cursor, mysqlvar)
if mysqlvar_val is None:
module.fail_json(msg="Variable not available \"%s\"" % mysqlvar, changed=False)
@@ -268,4 +183,5 @@ def main():
# import module snippets
from ansible.module_utils.basic import *
from ansible.module_utils.database import *
+from ansible.module_utils.mysql import *
main()
diff --git a/database/postgresql/postgresql_db.py b/database/postgresql/postgresql_db.py
index 469d68fa..762cb65e 100644
--- a/database/postgresql/postgresql_db.py
+++ b/database/postgresql/postgresql_db.py
@@ -95,7 +95,7 @@ notes:
- This module uses I(psycopg2), a Python PostgreSQL database adapter. You must ensure that psycopg2 is installed on
the host before using this module. If the remote host is the PostgreSQL server (which is the default case), then PostgreSQL must also be installed on the remote host. For Ubuntu-based systems, install the C(postgresql), C(libpq-dev), and C(python-psycopg2) packages on the remote host before using this module.
requirements: [ psycopg2 ]
-author: "Lorin Hochstein (@lorin)"
+author: "Ansible Core Team"
'''
EXAMPLES = '''
diff --git a/database/postgresql/postgresql_user.py b/database/postgresql/postgresql_user.py
index cee5a9ae..4f217433 100644
--- a/database/postgresql/postgresql_user.py
+++ b/database/postgresql/postgresql_user.py
@@ -137,7 +137,7 @@ notes:
to all users. You may not specify password or role_attr_flags when the
PUBLIC user is specified.
requirements: [ psycopg2 ]
-author: "Lorin Hochstein (@lorin)"
+author: "Ansible Core Team"
'''
EXAMPLES = '''