2012-07-12 01:00:55 +02:00
#!/usr/bin/python
# (c) 2012, Mark Theunissen <mark.theunissen@gmail.com>
# Sponsored by Four Kitchens http://fourkitchens.com.
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
2012-09-29 16:15:41 +02:00
DOCUMENTATION = '''
- - -
module : mysql_user
short_description : Adds or removes a user from a MySQL database .
description :
- Adds or removes a user from a MySQL database .
2013-11-28 03:23:03 +01:00
version_added : " 0.6 "
2012-09-29 16:15:41 +02:00
options :
name :
description :
- name of the user ( role ) to add or remove
required : true
password :
description :
2015-10-27 18:17:24 +01:00
- set the user ' s password. (Required when adding a user)
2012-09-29 16:15:41 +02:00
required : false
default : null
2015-12-12 02:29:45 +01:00
encrypted :
2014-10-02 19:05:35 +02:00
description :
2015-06-23 22:57:18 +02:00
- Indicate that the ' password ' field is a ` mysql_native_password ` hash
2014-10-02 19:05:35 +02:00
required : false
2015-06-23 22:57:18 +02:00
choices : [ " yes " , " no " ]
default : " no "
version_added : " 2.0 "
2012-09-29 16:15:41 +02:00
host :
description :
- the ' host ' part of the MySQL username
required : false
default : localhost
2015-10-22 14:45:50 +02:00
host_all :
description :
- override the host option , making ansible apply changes to
all hostnames for a given user . This option cannot be used
when creating users
required : false
choices : [ " yes " , " no " ]
default : " no "
2015-12-16 08:06:02 +01:00
version_added : " 2.1 "
2012-09-29 16:15:41 +02:00
priv :
description :
2012-09-30 12:21:35 +02:00
- " MySQL privileges string in the format: C(db.table:priv1,priv2) "
2012-09-29 16:15:41 +02:00
required : false
default : null
2013-10-03 13:00:17 +02:00
append_privs :
description :
- Append the privileges defined by priv to the existing ones for this
user instead of overwriting existing ones .
required : false
choices : [ " yes " , " no " ]
default : " no "
version_added : " 1.4 "
2012-09-29 16:15:41 +02:00
state :
description :
2013-07-29 23:48:49 +02:00
- Whether the user should exist . When C ( absent ) , removes
the user .
2012-09-29 16:15:41 +02:00
required : false
default : present
choices : [ " present " , " absent " ]
2013-07-01 12:56:04 +02:00
check_implicit_admin :
description :
- Check if mysql allows login as root / nopassword before trying supplied credentials .
required : false
2015-05-18 22:33:46 +02:00
choices : [ " yes " , " no " ]
default : " no "
2013-07-20 18:33:42 +02:00
version_added : " 1.3 "
2015-02-20 18:30:27 +01:00
update_password :
required : false
default : always
choices : [ ' always ' , ' on_create ' ]
2015-03-31 21:31:54 +02:00
version_added : " 2.0 "
2015-02-20 18:30:27 +01:00
description :
- C ( always ) will update passwords if they differ . C ( on_create ) will only set the password for newly created users .
2012-09-29 16:15:41 +02:00
notes :
2013-03-01 23:17:00 +01:00
- " MySQL server installs with default login_user of ' root ' and no password. To secure this user
2013-02-28 17:25:09 +01:00
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
2013-03-01 23:17:00 +01:00
the file . "
2015-06-23 22:57:18 +02:00
- Currently , there is only support for the ` mysql_native_password ` encryted password hash module .
2013-02-28 17:25:09 +01:00
2015-12-12 02:29:45 +01:00
author : " Jonathan Mainguy (@Jmainguy) "
2015-11-11 05:13:48 +01:00
extends_documentation_fragment : mysql
2012-09-29 16:15:41 +02:00
'''
2013-03-15 15:58:27 +01:00
EXAMPLES = """
2015-12-16 08:03:30 +01:00
# Removes anonymous user account for localhost
- mysql_user : name = ' ' host = localhost state = absent
2015-12-14 17:46:32 +01:00
# Removes all anonymous user accounts
2015-12-16 08:03:30 +01:00
- mysql_user : name = ' ' host_all = yes state = absent
2015-12-14 17:46:32 +01:00
2013-06-14 11:53:43 +02:00
# Create database user with name 'bob' and password '12345' with all database privileges
- mysql_user : name = bob password = 12345 priv = * . * : ALL state = present
2015-06-23 22:57:18 +02:00
# 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
2014-01-14 13:00:25 +01:00
# 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
2015-05-04 13:54:03 +02:00
# Modify user Bob to require SSL connections. Note that REQUIRESSL is a special privilege that should only apply to *.* by itself.
2015-02-25 15:49:05 +01:00
- mysql_user : name = bob append_privs = true priv = * . * : REQUIRESSL state = present
2015-01-08 17:26:22 +01:00
2015-10-22 14:45:50 +02:00
# Ensure no user named 'sally'@'localhost' exists, also passing in the auth credentials.
2013-06-14 11:53:43 +02:00
- mysql_user : login_user = root login_password = 123456 name = sally state = absent
2015-10-22 14:45:50 +02:00
# Ensure no user named 'sally' exists at all
- mysql_user : name = sally host_all = yes state = absent
2014-05-20 20:41:18 +02:00
# Specify grants composed of more than one word
2015-11-23 05:09:15 +01:00
- mysql_user : name = replication password = 12345 priv = " *.*:REPLICATION CLIENT " state = present
2014-05-20 20:41:18 +02:00
2015-12-08 18:09:50 +01:00
# Revoke all privileges for user 'bob' and password '12345'
2014-07-22 17:24:09 +02:00
- mysql_user : name = bob password = 12345 priv = * . * : USAGE state = present
2013-06-14 11:53:43 +02:00
# Example privileges string format
mydb . * : INSERT , UPDATE / anotherdb . * : SELECT / yetanotherdb . * : ALL
# Example using login_unix_socket to connect to server
- mysql_user : name = root password = abc123 login_unix_socket = / var / run / mysqld / mysqld . sock
2013-03-15 15:58:27 +01:00
# Example .my.cnf file for setting the root password
2013-06-14 11:53:43 +02:00
2013-03-15 15:58:27 +01:00
[ client ]
user = root
password = n < _665 { vS43y
"""
import getpass
2013-10-18 23:42:40 +02:00
import tempfile
2015-05-06 23:44:40 +02:00
import re
2014-10-02 19:05:35 +02:00
import string
2012-07-12 01:00:55 +02:00
try :
2012-07-22 19:11:39 +02:00
import MySQLdb
2012-07-12 01:00:55 +02:00
except ImportError :
2012-07-22 19:11:39 +02:00
mysqldb_found = False
else :
mysqldb_found = True
2012-07-12 01:00:55 +02:00
2014-11-26 17:26:53 +01:00
VALID_PRIVS = frozenset ( ( ' CREATE ' , ' DROP ' , ' GRANT ' , ' GRANT OPTION ' ,
' LOCK TABLES ' , ' REFERENCES ' , ' EVENT ' , ' ALTER ' ,
' DELETE ' , ' INDEX ' , ' INSERT ' , ' SELECT ' , ' UPDATE ' ,
2014-11-25 10:46:09 +01:00
' CREATE TEMPORARY TABLES ' , ' TRIGGER ' , ' CREATE VIEW ' ,
' SHOW VIEW ' , ' ALTER ROUTINE ' , ' CREATE ROUTINE ' ,
2014-12-10 17:53:55 +01:00
' EXECUTE ' , ' FILE ' , ' CREATE TABLESPACE ' , ' CREATE USER ' ,
' PROCESS ' , ' PROXY ' , ' RELOAD ' , ' REPLICATION CLIENT ' ,
' REPLICATION SLAVE ' , ' SHOW DATABASES ' , ' SHUTDOWN ' ,
2015-01-08 17:26:22 +01:00
' SUPER ' , ' ALL ' , ' ALL PRIVILEGES ' , ' USAGE ' , ' REQUIRESSL ' ) )
2014-11-25 10:46:09 +01:00
class InvalidPrivsError ( Exception ) :
pass
2012-07-12 01:00:55 +02:00
# ===========================================
# MySQL module specific support methods.
#
2015-06-23 22:57:18 +02:00
# 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 ( ' . ' )
2015-12-16 23:07:17 +01:00
# Currently we have no facility to handle new-style password update on
# mariadb and the old-style update continues to work
2015-12-17 22:45:04 +01:00
if ' mariadb ' in version_str . lower ( ) :
2015-12-16 23:07:17 +01:00
return True
2015-06-23 22:57:18 +02:00
if ( int ( version [ 0 ] ) < = 5 and int ( version [ 1 ] ) < 7 ) :
2015-12-16 23:07:17 +01:00
return True
2015-06-23 22:57:18 +02:00
else :
2015-12-16 23:07:17 +01:00
return False
2015-06-23 22:57:18 +02:00
2016-01-06 01:57:01 +01:00
def get_mode ( cursor ) :
cursor . execute ( ' SELECT @@GLOBAL.sql_mode ' )
result = cursor . fetchone ( )
mode_str = result [ 0 ]
if ' ANSI ' in mode_str :
mode = ' ANSI '
else :
mode = ' NOTANSI '
return mode
2015-10-22 14:45:50 +02:00
def user_exists ( cursor , user , host , host_all ) :
if host_all :
2015-11-04 17:37:18 +01:00
cursor . execute ( " SELECT count(*) FROM user WHERE user = %s " , user )
2015-10-22 14:45:50 +02:00
else :
cursor . execute ( " SELECT count(*) FROM user WHERE user = %s AND host = %s " , ( user , host ) )
2012-07-12 01:00:55 +02:00
count = cursor . fetchone ( )
return count [ 0 ] > 0
2016-01-07 07:23:13 +01:00
def user_add ( cursor , user , host , host_all , password , encrypted , new_priv , check_mode ) :
2015-10-22 14:45:50 +02:00
# we cannot create users without a proper hostname
if host_all :
return False
2016-01-07 07:23:13 +01:00
if check_mode :
return True
2015-06-23 22:57:18 +02:00
if password and encrypted :
cursor . execute ( " CREATE USER %s @ %s IDENTIFIED BY PASSWORD %s " , ( user , host , password ) )
elif password and not encrypted :
2014-10-02 19:05:35 +02:00
cursor . execute ( " CREATE USER %s @ %s IDENTIFIED BY %s " , ( user , host , password ) )
2015-12-14 17:40:20 +01:00
2012-07-12 01:00:55 +02:00
if new_priv is not None :
for db_table , priv in new_priv . iteritems ( ) :
2012-07-22 19:11:39 +02:00
privileges_grant ( cursor , user , host , db_table , priv )
2012-07-12 01:00:55 +02:00
return True
2014-10-02 19:05:35 +02:00
def is_hash ( password ) :
ishash = False
2015-06-23 22:57:18 +02:00
if len ( password ) == 41 and password [ 0 ] == ' * ' :
if frozenset ( password [ 1 : ] ) . issubset ( string . hexdigits ) :
ishash = True
2014-10-02 19:05:35 +02:00
return ishash
2016-01-07 07:23:13 +01:00
def user_mod ( cursor , user , host , host_all , password , encrypted , new_priv , append_privs , check_mode ) :
2012-07-12 01:00:55 +02:00
changed = False
Only revoke GRANT OPTION when user actually has it
When revoking privileges from a user, the GRANT OPTION is always
revoked, even if the user doesn't have it. If the user exists, this
doesn't give an error, but if the user doesn't exist, it does:
mysql> GRANT ALL ON test.* TO 'test'@'localhost';
Query OK, 0 rows affected (0.00 sec)
mysql> REVOKE GRANT OPTION ON test.* FROM 'test'@'localhost';
Query OK, 0 rows affected (0.00 sec)
mysql> REVOKE GRANT OPTION ON test.* FROM 'test'@'localhost';
Query OK, 0 rows affected (0.00 sec)
mysql> REVOKE ALL ON test.* FROM 'test'@'localhost';
Query OK, 0 rows affected (0.00 sec)
mysql> REVOKE GRANT OPTION ON test.* FROM 'test'@'localhost';
ERROR 1141 (42000): There is no such grant defined for user 'test' on
host 'localhost'
Additionally, in MySQL 5.6 this breaks replication because of
http://bugs.mysql.com/bug.php?id=68892.
Rather than revoking the GRANT OPTION and catching the error, check if
the user actually has it and only revoke it when he does.
2013-07-23 13:00:29 +02:00
grant_option = False
2012-07-12 01:00:55 +02:00
2015-10-22 14:45:50 +02:00
if host_all :
hostnames = user_get_hostnames ( cursor , user )
else :
hostnames = [ host ]
for host in hostnames :
2015-12-14 17:40:20 +01:00
# Handle clear text and hashed passwords.
if bool ( password ) :
# Determine what user management method server uses
old_user_mgmt = server_version_check ( cursor )
2015-06-23 22:57:18 +02:00
if old_user_mgmt :
2015-12-14 17:40:20 +01:00
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 ) )
2015-10-22 14:45:50 +02:00
current_pass_hash = cursor . fetchone ( )
2015-12-14 17:40:20 +01:00
if encrypted :
encrypted_string = ( password )
if is_hash ( password ) :
if current_pass_hash [ 0 ] != encrypted_string :
2016-01-07 07:23:13 +01:00
if check_mode :
return True
2015-12-14 17:40:20 +01:00
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)) " )
2015-06-23 22:57:18 +02:00
else :
if old_user_mgmt :
2015-12-14 17:40:20 +01:00
cursor . execute ( " SELECT PASSWORD( %s ) " , ( password , ) )
2015-06-23 22:57:18 +02:00
else :
2015-12-14 17:40:20 +01:00
cursor . execute ( " SELECT CONCAT( ' * ' , UCASE(SHA1(UNHEX(SHA1( %s ))))) " , ( password , ) )
new_pass_hash = cursor . fetchone ( )
if current_pass_hash [ 0 ] != new_pass_hash [ 0 ] :
2016-01-07 07:23:13 +01:00
if check_mode :
return True
2015-12-14 17:40:20 +01:00
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
2015-10-22 14:45:50 +02:00
# 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 :
2016-01-07 07:23:13 +01:00
if check_mode :
return True
2015-10-22 14:45:50 +02:00
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 :
2016-01-07 07:23:13 +01:00
if check_mode :
return True
2015-10-22 14:45:50 +02:00
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 ) :
2016-01-07 07:23:13 +01:00
if check_mode :
return True
2015-10-22 14:45:50 +02:00
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
2012-07-12 01:00:55 +02:00
return changed
2016-01-07 07:23:13 +01:00
def user_delete ( cursor , user , host , host_all , check_mode ) :
2016-01-14 14:53:22 +01:00
if check_mode :
return True
2016-01-07 07:23:13 +01:00
2015-10-22 14:45:50 +02:00
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 ) )
2012-07-12 01:00:55 +02:00
return True
2015-10-22 14:45:50 +02:00
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
2012-07-22 19:11:39 +02:00
def privileges_get ( cursor , user , host ) :
2012-07-12 01:00:55 +02:00
""" MySQL doesn ' t have a better method of getting privileges aside from the
SHOW GRANTS query syntax , which requires us to then parse the returned string .
Here ' s an example of the string that is returned from MySQL:
GRANT USAGE ON * . * TO ' user ' @ ' localhost ' IDENTIFIED BY ' pass ' ;
This function makes the query and returns a dictionary containing the results .
The dictionary format is the same as that returned by privileges_unpack ( ) below .
"""
output = { }
2014-11-25 10:46:09 +01:00
cursor . execute ( " SHOW GRANTS FOR %s @ %s " , ( user , host ) )
2012-07-12 01:00:55 +02:00
grants = cursor . fetchall ( )
2012-11-15 02:02:39 +01:00
def pick ( x ) :
if x == ' ALL PRIVILEGES ' :
return ' ALL '
else :
return x
2012-07-12 01:00:55 +02:00
for grant in grants :
2014-12-25 23:36:51 +01:00
res = re . match ( " GRANT (.+) ON (.+) TO ' .* ' @ ' .+ ' ( IDENTIFIED BY PASSWORD ' .+ ' )? ?(.*) " , grant [ 0 ] )
2012-07-12 01:00:55 +02:00
if res is None :
2014-12-01 16:15:27 +01:00
raise InvalidPrivsError ( ' unable to parse the MySQL grant string: %s ' % grant [ 0 ] )
2012-07-12 01:00:55 +02:00
privileges = res . group ( 1 ) . split ( " , " )
2012-11-15 02:02:39 +01:00
privileges = [ pick ( x ) for x in privileges ]
2012-10-24 14:32:49 +02:00
if " WITH GRANT OPTION " in res . group ( 4 ) :
2012-10-31 01:42:07 +01:00
privileges . append ( ' GRANT ' )
2015-01-08 17:26:22 +01:00
if " REQUIRE SSL " in res . group ( 4 ) :
privileges . append ( ' REQUIRESSL ' )
2013-08-26 18:16:34 +02:00
db = res . group ( 2 )
2012-07-12 01:00:55 +02:00
output [ db ] = privileges
return output
2016-01-06 01:57:01 +01:00
def privileges_unpack ( priv , mode ) :
2012-07-12 01:00:55 +02:00
""" Take a privileges string, typically passed as a parameter, and unserialize
it into a dictionary , the same format as privileges_get ( ) above . We have this
custom format to avoid using YAML / JSON strings inside YAML playbooks . Example
of a privileges string :
mydb . * : INSERT , UPDATE / anotherdb . * : SELECT / yetanother . * : ALL
The privilege USAGE stands for no privileges , so we add that in on * . * if it ' s
not specified in the string , as MySQL will always provide this by default .
"""
2016-01-06 01:57:01 +01:00
if mode == ' ANSI ' :
quote = ' " '
else :
quote = ' ` '
2012-07-12 01:00:55 +02:00
output = { }
2015-05-06 23:44:40 +02:00
privs = [ ]
2014-12-09 23:14:16 +01:00
for item in priv . strip ( ) . split ( ' / ' ) :
pieces = item . strip ( ) . split ( ' : ' )
2015-08-19 06:22:31 +02:00
dbpriv = pieces [ 0 ] . rsplit ( " . " , 1 )
# Do not escape if privilege is for database '*' (all databases)
if dbpriv [ 0 ] . strip ( ' ` ' ) != ' * ' :
2016-01-06 01:57:01 +01:00
pieces [ 0 ] = ' %s %s %s . %s ' % ( quote , dbpriv [ 0 ] . strip ( ' ` ' ) , quote , dbpriv [ 1 ] )
2015-08-19 06:22:31 +02:00
2015-05-06 23:44:40 +02:00
if ' ( ' in pieces [ 1 ] :
output [ pieces [ 0 ] ] = re . split ( r ' , \ s*(?=[^)]*(?: \ (|$)) ' , pieces [ 1 ] . upper ( ) )
for i in output [ pieces [ 0 ] ] :
privs . append ( re . sub ( r ' \ (.* \ ) ' , ' ' , i ) )
else :
output [ pieces [ 0 ] ] = pieces [ 1 ] . upper ( ) . split ( ' , ' )
privs = output [ pieces [ 0 ] ]
new_privs = frozenset ( privs )
2014-11-25 10:46:09 +01:00
if not new_privs . issubset ( VALID_PRIVS ) :
raise InvalidPrivsError ( ' Invalid privileges specified: %s ' % new_privs . difference ( VALID_PRIVS ) )
2012-07-12 01:00:55 +02:00
if ' *.* ' not in output :
output [ ' *.* ' ] = [ ' USAGE ' ]
2015-09-29 12:37:44 +02:00
# if we are only specifying something like REQUIRESSL and/or GRANT (=WITH GRANT OPTION) in *.*
# we still need to add USAGE as a privilege to avoid syntax errors
2016-01-06 11:53:06 +01:00
if ' REQUIRESSL ' in priv and not set ( output [ ' *.* ' ] ) . difference ( set ( [ ' GRANT ' , ' REQUIRESSL ' ] ) ) :
2015-01-08 17:26:22 +01:00
output [ ' *.* ' ] . append ( ' USAGE ' )
2012-07-12 01:00:55 +02:00
return output
2015-05-06 00:54:02 +02:00
def privileges_revoke ( cursor , user , host , db_table , priv , grant_option ) :
2014-12-01 19:38:47 +01:00
# Escape '%' since mysql db.execute() uses a format string
db_table = db_table . replace ( ' % ' , ' %% ' )
Only revoke GRANT OPTION when user actually has it
When revoking privileges from a user, the GRANT OPTION is always
revoked, even if the user doesn't have it. If the user exists, this
doesn't give an error, but if the user doesn't exist, it does:
mysql> GRANT ALL ON test.* TO 'test'@'localhost';
Query OK, 0 rows affected (0.00 sec)
mysql> REVOKE GRANT OPTION ON test.* FROM 'test'@'localhost';
Query OK, 0 rows affected (0.00 sec)
mysql> REVOKE GRANT OPTION ON test.* FROM 'test'@'localhost';
Query OK, 0 rows affected (0.00 sec)
mysql> REVOKE ALL ON test.* FROM 'test'@'localhost';
Query OK, 0 rows affected (0.00 sec)
mysql> REVOKE GRANT OPTION ON test.* FROM 'test'@'localhost';
ERROR 1141 (42000): There is no such grant defined for user 'test' on
host 'localhost'
Additionally, in MySQL 5.6 this breaks replication because of
http://bugs.mysql.com/bug.php?id=68892.
Rather than revoking the GRANT OPTION and catching the error, check if
the user actually has it and only revoke it when he does.
2013-07-23 13:00:29 +02:00
if grant_option :
2016-01-06 01:57:01 +01:00
query = [ " REVOKE GRANT OPTION ON %s " % db_table ]
2014-11-25 10:46:09 +01:00
query . append ( " FROM %s @ %s " )
query = ' ' . join ( query )
cursor . execute ( query , ( user , host ) )
2015-05-26 19:36:46 +02:00
priv_string = " , " . join ( [ p for p in priv if p not in ( ' GRANT ' , ' REQUIRESSL ' ) ] )
2016-01-06 01:57:01 +01:00
query = [ " REVOKE %s ON %s " % ( priv_string , db_table ) ]
2014-11-25 10:46:09 +01:00
query . append ( " FROM %s @ %s " )
query = ' ' . join ( query )
2015-01-08 22:41:15 +01:00
cursor . execute ( query , ( user , host ) )
2012-07-12 01:00:55 +02:00
2012-07-22 19:11:39 +02:00
def privileges_grant ( cursor , user , host , db_table , priv ) :
2014-12-01 19:38:47 +01:00
# Escape '%' since mysql db.execute uses a format string and the
# specification of db and table often use a % (SQL wildcard)
db_table = db_table . replace ( ' % ' , ' %% ' )
2015-05-26 19:36:46 +02:00
priv_string = " , " . join ( [ p for p in priv if p not in ( ' GRANT ' , ' REQUIRESSL ' ) ] )
2016-01-06 01:57:01 +01:00
query = [ " GRANT %s ON %s " % ( priv_string , db_table ) ]
2014-11-25 10:46:09 +01:00
query . append ( " TO %s @ %s " )
2015-01-08 17:26:22 +01:00
if ' REQUIRESSL ' in priv :
2015-01-08 22:41:15 +01:00
query . append ( " REQUIRE SSL " )
2015-09-29 12:37:44 +02:00
if ' GRANT ' in priv :
query . append ( " WITH GRANT OPTION " )
2014-11-25 10:46:09 +01:00
query = ' ' . join ( query )
2015-01-08 22:41:15 +01:00
cursor . execute ( query , ( user , host ) )
2013-03-25 14:53:04 +01:00
2012-07-12 01:00:55 +02:00
# ===========================================
# Module execution.
#
2012-07-22 19:11:39 +02:00
def main ( ) :
module = AnsibleModule (
argument_spec = dict (
2012-07-31 00:15:24 +02:00
login_user = dict ( default = None ) ,
login_password = dict ( default = None ) ,
login_host = dict ( default = " localhost " ) ,
2015-03-03 23:23:07 +01:00
login_port = dict ( default = 3306 , type = ' int ' ) ,
2012-08-03 12:35:18 +02:00
login_unix_socket = dict ( default = None ) ,
2012-08-01 06:21:36 +02:00
user = dict ( required = True , aliases = [ ' name ' ] ) ,
2016-02-11 22:04:00 +01:00
password = dict ( default = None , no_log = True , type = ' str ' ) ,
2015-06-23 22:57:18 +02:00
encrypted = dict ( default = False , type = ' bool ' ) ,
2012-07-22 19:11:39 +02:00
host = dict ( default = " localhost " ) ,
2015-10-22 14:45:50 +02:00
host_all = dict ( type = " bool " , default = " no " ) ,
2012-07-22 19:11:39 +02:00
state = dict ( default = " present " , choices = [ " absent " , " present " ] ) ,
priv = dict ( default = None ) ,
2015-05-18 22:33:46 +02:00
append_privs = dict ( default = False , type = ' bool ' ) ,
check_implicit_admin = dict ( default = False , type = ' bool ' ) ,
2015-02-20 18:30:27 +01:00
update_password = dict ( default = " always " , choices = [ " always " , " on_create " ] ) ,
2015-04-30 17:40:04 +02:00
config_file = dict ( default = " ~/.my.cnf " ) ,
2015-11-11 05:13:48 +01:00
ssl_cert = dict ( default = None ) ,
ssl_key = dict ( default = None ) ,
ssl_ca = dict ( default = None ) ,
2016-01-07 07:23:13 +01:00
) ,
supports_check_mode = True
2012-07-22 19:11:39 +02:00
)
2014-09-30 09:08:32 +02:00
login_user = module . params [ " login_user " ]
login_password = module . params [ " login_password " ]
2012-07-22 19:11:39 +02:00
user = module . params [ " user " ]
2012-07-31 00:15:24 +02:00
password = module . params [ " password " ]
2015-06-23 22:57:18 +02:00
encrypted = module . boolean ( module . params [ " encrypted " ] )
2015-05-21 15:32:12 +02:00
host = module . params [ " host " ] . lower ( )
2015-10-22 14:45:50 +02:00
host_all = module . params [ " host_all " ]
2012-07-22 19:11:39 +02:00
state = module . params [ " state " ]
priv = module . params [ " priv " ]
2013-07-01 12:56:04 +02:00
check_implicit_admin = module . params [ ' check_implicit_admin ' ]
2014-09-30 09:08:32 +02:00
config_file = module . params [ ' config_file ' ]
2013-10-03 13:00:17 +02:00
append_privs = module . boolean ( module . params [ " append_privs " ] )
2015-02-20 18:30:27 +01:00
update_password = module . params [ ' update_password ' ]
2015-11-11 05:13:48 +01:00
ssl_cert = module . params [ " ssl_cert " ]
ssl_key = module . params [ " ssl_key " ]
ssl_ca = module . params [ " ssl_ca " ]
db = ' mysql '
2012-07-22 19:11:39 +02:00
2015-05-08 19:40:26 +02:00
config_file = os . path . expanduser ( os . path . expandvars ( config_file ) )
2012-07-22 19:11:39 +02:00
if not mysqldb_found :
module . fail_json ( msg = " the python mysqldb module is required " )
2013-07-01 12:56:04 +02:00
cursor = None
2012-07-12 01:00:55 +02:00
try :
2013-07-01 12:56:04 +02:00
if check_implicit_admin :
try :
2015-11-11 05:13:48 +01:00
cursor = mysql_connect ( module , ' root ' , ' ' , config_file , ssl_cert , ssl_key , ssl_ca , db )
2013-07-01 12:56:04 +02:00
except :
pass
if not cursor :
2015-11-11 05:13:48 +01:00
cursor = mysql_connect ( module , login_user , login_password , config_file , ssl_cert , ssl_key , ssl_ca , db )
2012-11-09 14:28:21 +01:00
except Exception , e :
2015-11-11 05:13:48 +01:00
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 ) )
2012-07-12 01:00:55 +02:00
2016-01-06 01:57:01 +01:00
if priv is not None :
try :
mode = get_mode ( cursor )
except Exception , e :
module . fail_json ( msg = str ( e ) )
try :
priv = privileges_unpack ( priv , mode )
except Exception , e :
module . fail_json ( msg = " invalid privileges string: %s " % str ( e ) )
2012-07-12 01:00:55 +02:00
if state == " present " :
2015-10-22 14:45:50 +02:00
if user_exists ( cursor , user , host , host_all ) :
2014-11-25 10:46:09 +01:00
try :
2015-02-20 18:30:27 +01:00
if update_password == ' always ' :
2016-01-07 07:23:13 +01:00
changed = user_mod ( cursor , user , host , host_all , password , encrypted , priv , append_privs , module . check_mode )
2015-02-20 18:30:27 +01:00
else :
2016-01-07 07:23:13 +01:00
changed = user_mod ( cursor , user , host , host_all , None , encrypted , priv , append_privs , module . check_mode )
2015-02-20 18:30:27 +01:00
2015-01-12 23:36:57 +01:00
except ( SQLParseError , InvalidPrivsError , MySQLdb . Error ) , e :
2014-12-04 22:35:07 +01:00
module . fail_json ( msg = str ( e ) )
2012-07-12 01:00:55 +02:00
else :
2012-07-31 00:15:24 +02:00
if password is None :
module . fail_json ( msg = " password parameter required when adding a user " )
2015-10-22 14:45:50 +02:00
if host_all :
module . fail_json ( msg = " host_all parameter cannot be used when adding a user " )
2014-11-25 10:46:09 +01:00
try :
2016-01-07 07:23:13 +01:00
changed = user_add ( cursor , user , host , host_all , password , encrypted , priv , module . check_mode )
2015-01-12 23:36:57 +01:00
except ( SQLParseError , InvalidPrivsError , MySQLdb . Error ) , e :
2014-11-25 10:46:09 +01:00
module . fail_json ( msg = str ( e ) )
2012-07-12 01:00:55 +02:00
elif state == " absent " :
2015-10-22 14:45:50 +02:00
if user_exists ( cursor , user , host , host_all ) :
2016-01-07 07:23:13 +01:00
changed = user_delete ( cursor , user , host , host_all , module . check_mode )
2012-07-12 01:00:55 +02:00
else :
changed = False
2012-07-22 19:11:39 +02:00
module . exit_json ( changed = changed , user = user )
2012-07-12 01:00:55 +02:00
2013-12-02 21:13:49 +01:00
# import module snippets
2013-12-02 21:11:23 +01:00
from ansible . module_utils . basic import *
2014-11-25 10:46:09 +01:00
from ansible . module_utils . database import *
2015-11-11 05:13:48 +01:00
from ansible . module_utils . mysql import *
2014-11-25 10:46:09 +01:00
if __name__ == ' __main__ ' :
main ( )