2012-07-12 01:00:55 +02:00
#!/usr/bin/python
2012-08-03 03:29:10 +02:00
# -*- coding: utf-8 -*-
2012-07-12 01:00:55 +02:00
# (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:52 +02:00
DOCUMENTATION = '''
- - -
module : mysql_db
short_description : Add or remove MySQL databases from a remote host .
description :
- Add or remove MySQL databases from a remote host .
2013-11-28 03:23:03 +01:00
version_added : " 0.6 "
2012-09-29 16:15:52 +02:00
options :
name :
description :
- name of the database to add or remove
2014-09-30 01:18:58 +02:00
- 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 )
2012-09-29 16:15:52 +02:00
required : true
default : null
2013-07-14 13:11:03 +02:00
aliases : [ db ]
2012-09-29 16:15:52 +02:00
login_user :
description :
- The username used to authenticate with
required : false
default : null
login_password :
description :
2012-09-29 16:43:21 +02:00
- The password used to authenticate with
2012-09-29 16:15:52 +02:00
required : false
default : null
login_host :
description :
- Host running the database
required : false
default : localhost
2013-09-05 17:25:34 +02:00
login_port :
description :
2014-07-09 20:38:27 +02:00
- Port of the MySQL server . Requires login_host be defined as other then localhost if login_port is used
2013-09-05 17:25:34 +02:00
required : false
default : 3306
2013-03-15 16:40:54 +01:00
login_unix_socket :
description :
- The path to a Unix domain socket for local connections
required : false
default : null
2012-09-29 16:15:52 +02:00
state :
description :
- The database state
required : false
default : present
2012-10-11 05:28:42 +02:00
choices : [ " present " , " absent " , " dump " , " import " ]
2012-09-29 16:15:52 +02:00
collation :
description :
- Collation mode
required : false
default : null
encoding :
description :
- Encoding mode
required : false
default : null
2012-10-11 05:28:42 +02:00
target :
description :
2014-02-26 12:56:24 +01:00
- Location , on the remote host , of the dump file to read from or write to . Uncompressed SQL
2015-05-12 23:28:18 +02:00
files ( C ( . sql ) ) as well as bzip2 ( C ( . bz2 ) ) , gzip ( C ( . gz ) ) and xz compressed files are supported .
2013-03-05 23:11:12 +01:00
required : false
2012-09-29 16:15:52 +02:00
notes :
2012-10-01 09:18:54 +02:00
- Requires the MySQLdb Python package on the remote host . For Ubuntu , this
2012-11-21 18:49:30 +01:00
is as easy as apt - get install python - mysqldb . ( See M ( apt ) . )
2013-01-09 23:57:36 +01:00
- Both I ( login_password ) and I ( login_user ) are required when you are
2012-10-01 09:18:54 +02:00
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
2012-11-21 18:49:30 +01:00
default login of C ( root ) with no password .
2012-09-29 16:15:52 +02:00
requirements : [ ConfigParser ]
author : Mark Theunissen
'''
2013-06-14 11:53:43 +02:00
EXAMPLES = '''
# Create a new database with name 'bobdata'
2013-07-14 13:11:03 +02:00
- mysql_db : name = bobdata state = present
2014-02-26 12:56:24 +01:00
# Copy database dump file to remote host and restore it to database 'my_db'
- copy : src = dump . sql . bz2 dest = / tmp
- mysql_db : name = my_db state = import target = / tmp / dump . sql . bz2
2014-09-30 01:18:58 +02:00
# Dumps all databases to hostname.sql
- mysql_db : state = dump name = all target = / tmp / { { inventory_hostname } } . sql
# Imports file.sql similiar to mysql -u <username> -p <password> < hostname.sql
- mysql_db : state = import name = all target = / tmp / { { inventory_hostname } } . sql
2013-06-14 11:53:43 +02:00
'''
2012-07-25 23:31:12 +02:00
import ConfigParser
2012-10-11 05:28:42 +02:00
import os
2014-03-13 04:15:56 +01:00
import pipes
2014-09-30 01:11:45 +02:00
import stat
2012-07-12 01:00:55 +02:00
try :
2012-07-22 01:02:34 +02:00
import MySQLdb
2012-07-12 01:00:55 +02:00
except ImportError :
2012-07-22 01:02:34 +02:00
mysqldb_found = False
else :
mysqldb_found = True
2012-07-12 01:00:55 +02:00
# ===========================================
# MySQL module specific support methods.
#
2012-07-22 01:02:34 +02:00
def db_exists ( cursor , db ) :
2014-08-04 13:15:58 +02:00
res = cursor . execute ( " SHOW DATABASES LIKE %s " , ( db . replace ( " _ " , " \ _ " ) , ) )
2012-07-12 01:00:55 +02:00
return bool ( res )
2012-07-22 01:02:34 +02:00
def db_delete ( cursor , db ) :
2014-11-25 10:46:09 +01:00
query = " DROP DATABASE %s " % mysql_quote_identifier ( db , ' database ' )
2012-07-12 01:00:55 +02:00
cursor . execute ( query )
return True
2014-09-30 01:18:58 +02:00
def db_dump ( module , host , user , password , db_name , target , all_databases , port , socket = None ) :
2013-10-16 15:51:08 +02:00
cmd = module . get_bin_path ( ' mysqldump ' , True )
2014-04-01 14:44:38 +02:00
cmd + = " --quick --user= %s --password= %s " % ( pipes . quote ( user ) , pipes . quote ( password ) )
2013-10-16 15:51:08 +02:00
if socket is not None :
2014-03-13 04:15:56 +01:00
cmd + = " --socket= %s " % pipes . quote ( socket )
2013-10-16 15:59:31 +02:00
else :
2015-03-03 23:23:07 +01:00
cmd + = " --host= %s --port= %i " % ( pipes . quote ( host ) , port )
2014-09-30 01:18:58 +02:00
if all_databases :
cmd + = " --all-databases "
else :
cmd + = " %s " % pipes . quote ( db_name )
2013-09-30 20:09:32 +02:00
if os . path . splitext ( target ) [ - 1 ] == ' .gz ' :
2014-03-13 04:15:56 +01:00
cmd = cmd + ' | gzip > ' + pipes . quote ( target )
2013-09-30 20:09:32 +02:00
elif os . path . splitext ( target ) [ - 1 ] == ' .bz2 ' :
2014-03-13 04:15:56 +01:00
cmd = cmd + ' | bzip2 > ' + pipes . quote ( target )
2015-05-12 23:28:18 +02:00
elif os . path . splitext ( target ) [ - 1 ] == ' .xz ' :
cmd = cmd + ' | xz > ' + pipes . quote ( target )
2013-09-30 20:09:32 +02:00
else :
2014-03-13 04:15:56 +01:00
cmd + = " > %s " % pipes . quote ( target )
rc , stdout , stderr = module . run_command ( cmd , use_unsafe_shell = True )
2013-10-16 15:51:08 +02:00
return rc , stdout , stderr
2012-10-11 05:28:42 +02:00
2014-09-30 01:18:58 +02:00
def db_import ( module , host , user , password , db_name , target , all_databases , port , socket = None ) :
2014-06-17 23:37:14 +02:00
if not os . path . exists ( target ) :
return module . fail_json ( msg = " target %s does not exist on the host " % target )
2013-10-29 15:33:46 +01:00
cmd = module . get_bin_path ( ' mysql ' , True )
2014-04-01 14:44:38 +02:00
cmd + = " --user= %s --password= %s " % ( pipes . quote ( user ) , pipes . quote ( password ) )
2013-10-16 15:51:08 +02:00
if socket is not None :
2014-03-13 04:15:56 +01:00
cmd + = " --socket= %s " % pipes . quote ( socket )
2013-10-16 15:59:31 +02:00
else :
2015-03-03 23:23:07 +01:00
cmd + = " --host= %s --port= %i " % ( pipes . quote ( host ) , port )
2014-09-30 01:18:58 +02:00
if not all_databases :
cmd + = " -D %s " % pipes . quote ( db_name )
2013-09-30 20:09:32 +02:00
if os . path . splitext ( target ) [ - 1 ] == ' .gz ' :
2015-02-19 22:48:14 +01:00
gzip_path = module . get_bin_path ( ' gzip ' )
if not gzip_path :
module . fail_json ( msg = " gzip command not found " )
#gzip -d file (uncompress)
rc , stdout , stderr = module . run_command ( ' %s -d %s ' % ( gzip_path , target ) )
if rc != 0 :
return rc , stdout , stderr
#Import sql
cmd + = " < %s " % pipes . quote ( os . path . splitext ( target ) [ 0 ] )
try :
2014-03-30 19:39:00 +02:00
rc , stdout , stderr = module . run_command ( cmd , use_unsafe_shell = True )
if rc != 0 :
return rc , stdout , stderr
2015-02-19 22:48:14 +01:00
finally :
#gzip file back up
module . run_command ( ' %s %s ' % ( gzip_path , os . path . splitext ( target ) [ 0 ] ) )
2013-09-30 20:09:32 +02:00
elif os . path . splitext ( target ) [ - 1 ] == ' .bz2 ' :
2015-02-19 22:48:14 +01:00
bzip2_path = module . get_bin_path ( ' bzip2 ' )
if not bzip2_path :
module . fail_json ( msg = " bzip2 command not found " )
#bzip2 -d file (uncompress)
rc , stdout , stderr = module . run_command ( ' %s -d %s ' % ( bzip2_path , target ) )
if rc != 0 :
return rc , stdout , stderr
#Import sql
cmd + = " < %s " % pipes . quote ( os . path . splitext ( target ) [ 0 ] )
try :
2014-03-30 19:39:00 +02:00
rc , stdout , stderr = module . run_command ( cmd , use_unsafe_shell = True )
if rc != 0 :
return rc , stdout , stderr
2015-02-19 22:48:14 +01:00
finally :
#bzip2 file back up
rc , stdout , stderr = module . run_command ( ' %s %s ' % ( bzip2_path , os . path . splitext ( target ) [ 0 ] ) )
2015-05-12 23:28:18 +02:00
elif os . path . splitext ( target ) [ - 1 ] == ' .xz ' :
xz_path = module . get_bin_path ( ' xz ' )
if not xz_path :
module . fail_json ( msg = " xz command not found " )
#xz -d file (uncompress)
rc , stdout , stderr = module . run_command ( ' %s -d %s ' % ( xz_path , target ) )
if rc != 0 :
return rc , stdout , stderr
#Import sql
cmd + = " < %s " % pipes . quote ( os . path . splitext ( target ) [ 0 ] )
try :
rc , stdout , stderr = module . run_command ( cmd , use_unsafe_shell = True )
if rc != 0 :
return rc , stdout , stderr
finally :
#xz file back up
rc , stdout , stderr = module . run_command ( ' %s %s ' % ( xz_path , os . path . splitext ( target ) [ 0 ] ) )
2013-09-30 20:09:32 +02:00
else :
2014-03-13 04:15:56 +01:00
cmd + = " < %s " % pipes . quote ( target )
2014-03-30 19:39:00 +02:00
rc , stdout , stderr = module . run_command ( cmd , use_unsafe_shell = True )
2013-10-16 15:51:08 +02:00
return rc , stdout , stderr
2012-10-11 05:28:42 +02:00
2012-07-31 11:56:29 +02:00
def db_create ( cursor , db , encoding , collation ) :
2014-11-25 10:46:09 +01:00
query_params = dict ( enc = encoding , collate = collation )
query = [ ' CREATE DATABASE %s ' % mysql_quote_identifier ( db , ' database ' ) ]
2012-07-31 11:56:29 +02:00
if encoding :
2014-11-25 10:46:09 +01:00
query . append ( " CHARACTER SET %(enc)s " )
2012-07-31 11:56:29 +02:00
if collation :
2014-11-25 10:46:09 +01:00
query . append ( " COLLATE %(collate)s " )
query = ' ' . join ( query )
res = cursor . execute ( query , query_params )
2012-07-12 01:00:55 +02:00
return True
2013-03-25 14:53:04 +01:00
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 ) )
2012-07-25 23:31:12 +02:00
def load_mycnf ( ) :
config = ConfigParser . RawConfigParser ( )
mycnf = os . path . expanduser ( ' ~/.my.cnf ' )
2012-07-26 18:30:22 +02:00
if not os . path . exists ( mycnf ) :
return False
2012-07-25 23:31:12 +02:00
try :
2012-07-26 15:58:21 +02:00
config . readfp ( open ( mycnf ) )
2013-02-26 23:27:23 +01:00
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 :
2013-03-25 14:53:04 +01:00
passwd = config_get ( config , ' client ' , ' password ' )
2013-02-26 23:27:23 +01:00
except ( ConfigParser . NoOptionError ) :
try :
2013-03-25 14:53:04 +01:00
passwd = config_get ( config , ' client ' , ' pass ' )
2013-02-26 23:27:23 +01:00
except ( ConfigParser . NoOptionError ) :
return False
try :
2013-03-25 14:53:04 +01:00
creds = dict ( user = config_get ( config , ' client ' , ' user ' ) , passwd = passwd )
2013-02-26 23:27:23 +01:00
except ( ConfigParser . NoOptionError ) :
2012-07-25 23:31:12 +02:00
return False
return creds
2012-07-12 01:00:55 +02:00
# ===========================================
# Module execution.
#
2012-07-22 01:02:34 +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:34:55 +02:00
login_unix_socket = dict ( default = None ) ,
2014-07-11 04:37:46 +02:00
name = dict ( required = True , aliases = [ ' db ' ] ) ,
2012-07-31 11:56:29 +02:00
encoding = dict ( default = " " ) ,
collation = dict ( default = " " ) ,
2012-10-11 05:28:42 +02:00
target = dict ( default = None ) ,
state = dict ( default = " present " , choices = [ " absent " , " present " , " dump " , " import " ] ) ,
2012-07-22 01:02:34 +02:00
)
)
if not mysqldb_found :
module . fail_json ( msg = " the python mysqldb module is required " )
2014-07-11 04:37:46 +02:00
db = module . params [ " name " ]
2012-07-31 11:56:29 +02:00
encoding = module . params [ " encoding " ]
collation = module . params [ " collation " ]
2012-07-22 01:02:34 +02:00
state = module . params [ " state " ]
2012-10-11 05:28:42 +02:00
target = module . params [ " target " ]
2014-09-30 01:11:45 +02:00
socket = module . params [ " login_unix_socket " ]
2015-03-03 23:23:07 +01:00
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) " )
2012-07-25 23:31:12 +02:00
2014-07-16 20:39:47 +02:00
# make sure the target path is expanded for ~ and $HOME
2014-07-18 15:52:37 +02:00
if target is not None :
target = os . path . expandvars ( os . path . expanduser ( target ) )
2014-07-16 20:39:47 +02:00
2012-07-25 23:31:12 +02:00
# 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.
2012-07-31 00:15:24 +02:00
login_password = module . params [ " login_password " ]
login_user = module . params [ " login_user " ]
if login_user is None and login_password is None :
2012-07-25 23:31:12 +02:00
mycnf_creds = load_mycnf ( )
if mycnf_creds is False :
2012-07-31 00:15:24 +02:00
login_user = " root "
login_password = " "
2012-07-25 23:31:12 +02:00
else :
2012-07-31 00:15:24 +02:00
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 " )
2013-03-12 17:07:16 +01:00
login_host = module . params [ " login_host " ]
2012-07-25 23:31:12 +02:00
2012-10-12 22:26:59 +02:00
if state in [ ' dump ' , ' import ' ] :
if target is None :
2013-01-10 19:24:23 +01:00
module . fail_json ( msg = " with state= %s target is required " % ( state ) )
2014-09-30 01:18:58 +02:00
if db == ' all ' :
connect_to_db = ' mysql '
db = ' mysql '
all_databases = True
else :
connect_to_db = db
all_databases = False
2012-10-12 22:26:59 +02:00
else :
2014-09-30 01:18:58 +02:00
if db == ' all ' :
module . fail_json ( msg = " name is not allowed to equal ' all ' unless state equals import, or dump. " )
2015-02-14 23:16:35 +01:00
connect_to_db = ' '
2012-07-12 01:00:55 +02:00
try :
2014-09-30 01:11:45 +02:00
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 )
2015-03-03 23:23:07 +01:00
elif login_port != 3306 and module . params [ " login_host " ] == " localhost " :
2014-07-09 20:38:27 +02:00
module . fail_json ( msg = " login_host is required when login_port is defined, login_host cannot be localhost when login_port is defined " )
2012-08-03 12:34:55 +02:00
else :
2015-03-03 23:23:07 +01:00
db_connection = MySQLdb . connect ( host = module . params [ " login_host " ] , port = login_port , user = login_user , passwd = login_password , db = connect_to_db )
2012-07-12 01:00:55 +02:00
cursor = db_connection . cursor ( )
2012-11-09 14:27:03 +01:00
except Exception , e :
2014-07-11 20:57:45 +02:00
if " Unknown database " in str ( e ) :
errno , errstr = e . args
module . fail_json ( msg = " ERROR: %s %s " % ( errno , errstr ) )
else :
2014-09-30 01:11:45 +02:00
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 " )
2012-07-12 01:00:55 +02:00
2012-07-25 23:31:12 +02:00
changed = False
2012-07-22 01:02:34 +02:00
if db_exists ( cursor , db ) :
2012-07-12 01:00:55 +02:00
if state == " absent " :
2014-07-11 04:28:56 +02:00
try :
changed = db_delete ( cursor , db )
except Exception , e :
module . fail_json ( msg = " error deleting database: " + str ( e ) )
2012-10-11 05:28:42 +02:00
elif state == " dump " :
2013-10-16 15:51:08 +02:00
rc , stdout , stderr = db_dump ( module , login_host , login_user ,
2014-09-30 01:18:58 +02:00
login_password , db , target , all_databases ,
2015-03-03 23:23:07 +01:00
port = login_port ,
2013-10-16 15:51:08 +02:00
socket = module . params [ ' login_unix_socket ' ] )
if rc != 0 :
module . fail_json ( msg = " %s " % stderr )
else :
module . exit_json ( changed = True , db = db , msg = stdout )
2012-10-11 05:28:42 +02:00
elif state == " import " :
2013-10-16 15:51:08 +02:00
rc , stdout , stderr = db_import ( module , login_host , login_user ,
2014-09-30 01:18:58 +02:00
login_password , db , target , all_databases ,
2015-03-03 23:23:07 +01:00
port = login_port ,
2013-10-16 15:51:08 +02:00
socket = module . params [ ' login_unix_socket ' ] )
if rc != 0 :
module . fail_json ( msg = " %s " % stderr )
else :
module . exit_json ( changed = True , db = db , msg = stdout )
2012-07-12 01:00:55 +02:00
else :
if state == " present " :
2014-07-11 04:28:56 +02:00
try :
changed = db_create ( cursor , db , encoding , collation )
except Exception , e :
module . fail_json ( msg = " error creating database: " + str ( e ) )
2012-07-22 01:02:34 +02:00
module . exit_json ( changed = changed , db = db )
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 *
if __name__ == ' __main__ ' :
main ( )