@ -31,6 +31,13 @@ options:
- name of the user ( role ) to add or remove
- name of the user ( role ) to add or remove
required : true
required : true
default : null
default : null
user_anonymous :
description :
- username is to be ignored and anonymous users with no username
handled
required : false
choices : [ " yes " , " no " ]
default : no
password :
password :
description :
description :
- set the user ' s password. (Required when adding a user)
- set the user ' s password. (Required when adding a user)
@ -48,6 +55,14 @@ options:
- the ' host ' part of the MySQL username
- the ' host ' part of the MySQL username
required : false
required : false
default : localhost
default : localhost
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 "
login_user :
login_user :
description :
description :
- The username used to authenticate with
- The username used to authenticate with
@ -145,9 +160,12 @@ EXAMPLES = """
# Modifiy user Bob to require SSL connections. Note that REQUIRESSL is a special privilege that should only apply to *.* by itself.
# Modifiy 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
- 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
- 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
# Specify grants composed of more than one word
- mysql_user : name = replication password = 12345 priv = * . * : " REPLICATION CLIENT " state = present
- mysql_user : name = replication password = 12345 priv = * . * : " REPLICATION CLIENT " state = present
@ -215,77 +233,46 @@ def connect(module, login_user=None, login_password=None, config_file=''):
db_connection = MySQLdb . connect ( * * config )
db_connection = MySQLdb . connect ( * * config )
return db_connection . cursor ( )
return db_connection . cursor ( )
# User Authentication Management was change in MySQL 5.7
def user_exists ( cursor , user , host , host_all ) :
# This is a generic check for if the server version is less than version 5.7
if host_all :
def server_version_check ( cursor ) :
cursor . execute ( " SELECT count(*) FROM user WHERE user = %s AND host = %s " , ( user , host ) )
cursor . execute ( " SELECT VERSION() " ) ;
result = cursor . fetchone ( )
version_str = result [ 0 ]
version = version_str . split ( ' . ' )
if ( int ( version [ 0 ] ) < = 5 and int ( version [ 1 ] ) < 7 ) :
return True
else :
else :
return False
def user_exists ( cursor , user , host ) :
cursor . execute ( " SELECT count(*) FROM user WHERE user = %s AND host = %s " , ( user , host ) )
cursor . execute ( " SELECT count(*) FROM user WHERE user = %s AND host = %s " , ( user , host ) )
count = cursor . fetchone ( )
count = cursor . fetchone ( )
return count [ 0 ] > 0
return count [ 0 ] > 0
def user_add ( cursor , user , host , password , encrypted , new_priv ) :
def user_add ( cursor , user , host , host_all , password , new_priv ) :
if password and encrypted :
# we cannot create users without a proper hostname
cursor . execute ( " CREATE USER %s @ %s IDENTIFIED BY PASSWORD %s " , ( user , host , password ) )
if host_all :
elif password and not encrypted :
return False
cursor . execute ( " CREATE USER %s @ %s IDENTIFIED BY %s " , ( user , host , password ) )
cursor . execute ( " CREATE USER %s @ %s IDENTIFIED BY %s " , ( user , host , password ) )
if new_priv is not None :
if new_priv is not None :
for db_table , priv in new_priv . iteritems ( ) :
for db_table , priv in new_priv . iteritems ( ) :
privileges_grant ( cursor , user , host , db_table , priv )
privileges_grant ( cursor , user , host , db_table , priv )
return True
return True
def is_hash ( password ) :
def user_mod ( cursor , user , host , host_all , password , new_priv , append_privs ) :
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 , password , password_hash , new_priv , append_privs ) :
changed = False
changed = False
grant_option = False
grant_option = False
# Handle clear text and hashed passwords.
# to simplify code, if we have a specific host and no host_all, we create
if bool ( password ) :
# a list with just host and loop over that
# Determine what user management method server uses
if host_all :
old_user_mgmt = server_version_check ( cursor )
hostnames = user_get_hostnames ( cursor , user )
else :
hostnames = [ host ]
if old_user_mgmt :
for host in hostnames :
# Handle passwords
if password is not None :
cursor . execute ( " SELECT password FROM user WHERE user = %s AND host = %s " , ( user , host ) )
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 ( )
current_pass_hash = cursor . fetchone ( )
if encrypted :
if is_hash ( password ) :
if current_pass_hash [ 0 ] != encrypted :
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 , ) )
cursor . execute ( " SELECT PASSWORD( %s ) " , ( password , ) )
else :
cursor . execute ( " SELECT CONCAT( ' * ' , UCASE(SHA1(UNHEX(SHA1( %s ))))) " , ( password , ) )
new_pass_hash = cursor . fetchone ( )
new_pass_hash = cursor . fetchone ( )
if current_pass_hash [ 0 ] != new_pass_hash [ 0 ] :
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 ) )
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
changed = True
# Handle privileges
# Handle privileges
@ -323,10 +310,27 @@ def user_mod(cursor, user, host, password, password_hash, new_priv, append_privs
return changed
return changed
def user_delete ( cursor , 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 ) )
cursor . execute ( " DROP USER %s @ %s " , ( user , host ) )
return True
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 ) :
def privileges_get ( cursor , user , host ) :
""" MySQL doesn ' t have a better method of getting privileges aside from the
""" 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 .
SHOW GRANTS query syntax , which requires us to then parse the returned string .
@ -443,9 +447,11 @@ def main():
login_port = dict ( default = 3306 , type = ' int ' ) ,
login_port = dict ( default = 3306 , type = ' int ' ) ,
login_unix_socket = dict ( default = None ) ,
login_unix_socket = dict ( default = None ) ,
user = dict ( required = True , aliases = [ ' name ' ] ) ,
user = dict ( required = True , aliases = [ ' name ' ] ) ,
user_anonymous = dict ( type = " bool " , default = " no " ) ,
password = dict ( default = None , no_log = True ) ,
password = dict ( default = None , no_log = True ) ,
encrypted = dict ( default = False , type = ' bool ' ) ,
encrypted = dict ( default = False , type = ' bool ' ) ,
host = dict ( default = " localhost " ) ,
host = dict ( default = " localhost " ) ,
host_all = dict ( type = " bool " , default = " no " ) ,
state = dict ( default = " present " , choices = [ " absent " , " present " ] ) ,
state = dict ( default = " present " , choices = [ " absent " , " present " ] ) ,
priv = dict ( default = None ) ,
priv = dict ( default = None ) ,
append_privs = dict ( default = False , type = ' bool ' ) ,
append_privs = dict ( default = False , type = ' bool ' ) ,
@ -457,9 +463,11 @@ def main():
login_user = module . params [ " login_user " ]
login_user = module . params [ " login_user " ]
login_password = module . params [ " login_password " ]
login_password = module . params [ " login_password " ]
user = module . params [ " user " ]
user = module . params [ " user " ]
user_anonymous = module . params [ " user_anonymous " ]
password = module . params [ " password " ]
password = module . params [ " password " ]
encrypted = module . boolean ( module . params [ " encrypted " ] )
encrypted = module . boolean ( module . params [ " encrypted " ] )
host = module . params [ " host " ] . lower ( )
host = module . params [ " host " ] . lower ( )
host_all = module . params [ " host_all " ]
state = module . params [ " state " ]
state = module . params [ " state " ]
priv = module . params [ " priv " ]
priv = module . params [ " priv " ]
check_implicit_admin = module . params [ ' check_implicit_admin ' ]
check_implicit_admin = module . params [ ' check_implicit_admin ' ]
@ -467,6 +475,10 @@ def main():
append_privs = module . boolean ( module . params [ " append_privs " ] )
append_privs = module . boolean ( module . params [ " append_privs " ] )
update_password = module . params [ ' update_password ' ]
update_password = module . params [ ' update_password ' ]
if user_anonymous :
user = ' '
config_file = os . path . expanduser ( os . path . expandvars ( config_file ) )
if not mysqldb_found :
if not mysqldb_found :
module . fail_json ( msg = " the python mysqldb module is required " )
module . fail_json ( msg = " the python mysqldb module is required " )
@ -490,18 +502,27 @@ def main():
module . fail_json ( msg = " unable to connect to database, check login_user and login_password are correct or ~/.my.cnf has the credentials " )
module . fail_json ( msg = " unable to connect to database, check login_user and login_password are correct or ~/.my.cnf has the credentials " )
if state == " present " :
if state == " present " :
if user_exists ( cursor , user , host ) :
if user_exists ( cursor , user , host , host_all ) :
changed = user_mod ( cursor , user , host , password , password_hash , priv , append_privs )
try :
if update_password == ' always ' :
changed = user_mod ( cursor , user , host , host_all , password , priv , append_privs )
else :
changed = user_mod ( cursor , user , host , host_all , None , priv , append_privs )
except ( SQLParseError , InvalidPrivsError , MySQLdb . Error ) , e :
module . fail_json ( msg = str ( e ) )
else :
else :
if password is None :
if password is None :
module . fail_json ( msg = " password parameter required when adding a user " )
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 :
try :
changed = user_add ( cursor , user , host , password , encrypted , priv )
changed = user_add ( cursor , user , host , host_all, passwor d, priv )
except ( SQLParseError , InvalidPrivsError , MySQLdb . Error ) , e :
except ( SQLParseError , InvalidPrivsError , MySQLdb . Error ) , e :
module . fail_json ( msg = str ( e ) )
module . fail_json ( msg = str ( e ) )
elif state == " absent " :
elif state == " absent " :
if user_exists ( cursor , user , host ):
if user_exists ( cursor , user , host , host_all ):
changed = user_delete ( cursor , user , host )
changed = user_delete ( cursor , user , host , host_all )
else :
else :
changed = False
changed = False
module . exit_json ( changed = changed , user = user )
module . exit_json ( changed = changed , user = user )