mysql_db 8.41 KB
Newer Older
Mark Theunissen committed
1
#!/usr/bin/python
2
# -*- coding: utf-8 -*-
Mark Theunissen committed
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

# (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/>.

22 23 24 25 26 27 28 29 30 31 32 33 34
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.
version_added: "0.6"
options:
  name:
    description:
      - name of the database to add or remove
    required: true
    default: null
35
    aliases: [ db ]
36 37 38 39 40 41 42
  login_user:
    description:
      - The username used to authenticate with
    required: false
    default: null
  login_password:
    description:
43
      - The password used to authenticate with
44 45 46 47 48 49 50
    required: false
    default: null
  login_host:
    description:
      - Host running the database
    required: false
    default: localhost
51 52 53 54 55
  login_unix_socket:
    description:
      - The path to a Unix domain socket for local connections
    required: false
    default: null
56 57 58 59 60
  state:
    description:
      - The database state
    required: false
    default: present
61
    choices: [ "present", "absent", "dump", "import" ]
62 63 64 65 66 67 68 69 70 71
  collation:
    description:
      - Collation mode
    required: false
    default: null
  encoding:
    description:
      - Encoding mode
    required: false
    default: null
72 73
  target:
    description:
Jan-Piet Mens committed
74
      - Where to dump/get the C(.sql) file
asad-at-srt committed
75
    required: false
76
notes:
77
   - Requires the MySQLdb Python package on the remote host. For Ubuntu, this
Jan-Piet Mens committed
78
     is as easy as apt-get install python-mysqldb. (See M(apt).)
79
   - Both I(login_password) and I(login_user) are required when you are
80 81
     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
Jan-Piet Mens committed
82
     default login of C(root) with no password.
83 84 85 86
requirements: [ ConfigParser ]
author: Mark Theunissen
'''

87 88
EXAMPLES = '''
# Create a new database with name 'bobdata'
89
- mysql_db: name=bobdata state=present
90 91
'''

92
import ConfigParser
93
import os
Mark Theunissen committed
94
try:
95
    import MySQLdb
Mark Theunissen committed
96
except ImportError:
97 98 99
    mysqldb_found = False
else:
    mysqldb_found = True
Mark Theunissen committed
100 101 102 103 104

# ===========================================
# MySQL module specific support methods.
#

105
def db_exists(cursor, db):
Mark Theunissen committed
106 107 108
    res = cursor.execute("SHOW DATABASES LIKE %s", (db,))
    return bool(res)

109
def db_delete(cursor, db):
Alex Quach committed
110
    query = "DROP DATABASE `%s`" % db
Mark Theunissen committed
111 112 113
    cursor.execute(query)
    return True

114
def db_dump(host, user, password, db_name, target):
115
    res = os.system("/usr/bin/mysqldump -q -h "+host+" -u "+user+ " --password="+password+" "
116 117 118 119
            +db_name+" > "
            +target)
    return (res == 0)

120
def db_import(host, user, password, db_name, target):
121
    res = os.system("/usr/bin/mysql -h "+host+" -u "+user+" --password="+password+" "
122 123 124 125
            +db_name+" < "
            +target)
    return (res == 0)

126 127 128 129 130
def db_create(cursor, db, encoding, collation):
    if encoding:
        encoding = " CHARACTER SET %s" % encoding
    if collation:
        collation = " COLLATE %s" % collation
Alex Quach committed
131
    query = "CREATE DATABASE `%s`%s%s" % (db, encoding, collation)
Mark Theunissen committed
132 133 134
    res = cursor.execute(query)
    return True

135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
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))


166 167 168
def load_mycnf():
    config = ConfigParser.RawConfigParser()
    mycnf = os.path.expanduser('~/.my.cnf')
169 170
    if not os.path.exists(mycnf):
        return False
171
    try:
172
        config.readfp(open(mycnf))
173 174 175 176 177
    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:
178
        passwd = config_get(config, 'client', 'password')
179 180
    except (ConfigParser.NoOptionError):
        try:
181
            passwd = config_get(config, 'client', 'pass')
182 183 184
        except (ConfigParser.NoOptionError):
            return False
    try:
185
        creds = dict(user=config_get(config, 'client', 'user'),passwd=passwd)
186
    except (ConfigParser.NoOptionError):
187 188 189
        return False
    return creds

Mark Theunissen committed
190 191 192 193
# ===========================================
# Module execution.
#

194 195 196
def main():
    module = AnsibleModule(
        argument_spec = dict(
197 198 199
            login_user=dict(default=None),
            login_password=dict(default=None),
            login_host=dict(default="localhost"),
200
            login_unix_socket=dict(default=None),
201
            db=dict(required=True, aliases=['name']),
202 203
            encoding=dict(default=""),
            collation=dict(default=""),
204 205
            target=dict(default=None),
            state=dict(default="present", choices=["absent", "present","dump", "import"]),
206 207 208 209 210 211 212
        )
    )

    if not mysqldb_found:
        module.fail_json(msg="the python mysqldb module is required")

    db = module.params["db"]
213 214
    encoding = module.params["encoding"]
    collation = module.params["collation"]
215
    state = module.params["state"]
216
    target = module.params["target"]
217 218 219 220

    # 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.
221 222 223
    login_password = module.params["login_password"]
    login_user = module.params["login_user"]
    if login_user is None and login_password is None:
224 225
        mycnf_creds = load_mycnf()
        if mycnf_creds is False:
226 227
            login_user = "root"
            login_password = ""
228
        else:
229 230 231 232
            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")
233
    login_host = module.params["login_host"]
234

235 236
    if state in ['dump','import']:
        if target is None:
237
            module.fail_json(msg="with state=%s target is required" % (state))
238 239 240
        connect_to_db = db
    else:
        connect_to_db = 'mysql'
Mark Theunissen committed
241
    try:
242
        if module.params["login_unix_socket"]:
243
            db_connection = MySQLdb.connect(host=module.params["login_host"], unix_socket=module.params["login_unix_socket"], user=login_user, passwd=login_password, db=connect_to_db)
244
        else:
245
            db_connection = MySQLdb.connect(host=module.params["login_host"], user=login_user, passwd=login_password, db=connect_to_db)
Mark Theunissen committed
246
        cursor = db_connection.cursor()
247
    except Exception, e:
248
        module.fail_json(msg="unable to connect, check login_user and login_password are correct, or alternatively check ~/.my.cnf contains credentials")
Mark Theunissen committed
249

250
    changed = False
251
    if db_exists(cursor, db):
Mark Theunissen committed
252
        if state == "absent":
253
            changed = db_delete(cursor, db)
254
        elif state == "dump":
255
            changed = db_dump(login_host, login_user, login_password, db, target)
256 257 258
            if not changed:
                module.fail_json(msg="dump failed!")
        elif state == "import":
259
            changed = db_import(login_host, login_user, login_password, db, target)
260 261
            if not changed:
                module.fail_json(msg="import failed!")
Mark Theunissen committed
262 263
    else:
        if state == "present":
264
            changed = db_create(cursor, db, encoding, collation)
265 266

    module.exit_json(changed=changed, db=db)
Mark Theunissen committed
267

268 269 270
# this is magic, see lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
main()