Allow multiple databases(not all) to be dumped from mysql (#56721)
* Allow multiple databases(not all) to be dumped from mysql Fixes: #56059 * Altered fail message to provide atleast one database name * Minor grammatical fix
This commit is contained in:
parent
372b896ed1
commit
44058e9425
5 changed files with 199 additions and 57 deletions
|
@ -0,0 +1,2 @@
|
|||
minor_changes:
|
||||
- mysql_db now supports multiple databases in dump operation (https://github.com/ansible/ansible/issues/56059)
|
|
@ -22,10 +22,12 @@ version_added: "0.6"
|
|||
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).
|
||||
- if name=all Works like --all-databases option for mysqldump (Added in 2.0)
|
||||
- name of the database to add or remove.
|
||||
- I(name=all) May only be provided if I(state) is C(dump) or C(import).
|
||||
- List of databases is provided with I(state=dump) only.
|
||||
- if name=all Works like --all-databases option for mysqldump (Added in 2.0).
|
||||
required: true
|
||||
type: list
|
||||
aliases: [ db ]
|
||||
state:
|
||||
description:
|
||||
|
@ -81,23 +83,38 @@ EXAMPLES = r'''
|
|||
copy:
|
||||
src: dump.sql.bz2
|
||||
dest: /tmp
|
||||
|
||||
- name: Restore database
|
||||
mysql_db:
|
||||
name: my_db
|
||||
state: import
|
||||
target: /tmp/dump.sql.bz2
|
||||
|
||||
- name: Dump multiple databases
|
||||
mysql_db:
|
||||
state: dump
|
||||
name: db_1,db_2
|
||||
target: /tmp/dump.sql
|
||||
|
||||
- name: Dump multiple databases
|
||||
mysql_db:
|
||||
state: dump
|
||||
name:
|
||||
- db_1
|
||||
- db_2
|
||||
target: /tmp/dump.sql
|
||||
|
||||
- name: Dump all databases to hostname.sql
|
||||
mysql_db:
|
||||
state: dump
|
||||
name: all
|
||||
target: /tmp/{{ inventory_hostname }}.sql
|
||||
target: /tmp/dump.sql
|
||||
|
||||
- name: Import file.sql similar to mysql -u <username> -p <password> < hostname.sql
|
||||
mysql_db:
|
||||
state: import
|
||||
name: all
|
||||
target: /tmp/{{ inventory_hostname }}.sql
|
||||
target: /tmp/dump.sql
|
||||
'''
|
||||
|
||||
import os
|
||||
|
@ -117,12 +134,14 @@ from ansible.module_utils._text import to_native
|
|||
|
||||
|
||||
def db_exists(cursor, db):
|
||||
res = cursor.execute("SHOW DATABASES LIKE %s", (db.replace("_", r"\_"),))
|
||||
return bool(res)
|
||||
res = 0
|
||||
for each_db in db:
|
||||
res += cursor.execute("SHOW DATABASES LIKE %s", (each_db.strip().replace("_", r"\_"),))
|
||||
return res == len(db)
|
||||
|
||||
|
||||
def db_delete(cursor, db):
|
||||
query = "DROP DATABASE %s" % mysql_quote_identifier(db, 'database')
|
||||
query = "DROP DATABASE %s" % mysql_quote_identifier(''.join(db), 'database')
|
||||
cursor.execute(query)
|
||||
return True
|
||||
|
||||
|
@ -150,7 +169,7 @@ def db_dump(module, host, user, password, db_name, target, all_databases, port,
|
|||
if all_databases:
|
||||
cmd += " --all-databases"
|
||||
else:
|
||||
cmd += " %s" % shlex_quote(db_name)
|
||||
cmd += " --databases {0} --skip-lock-tables".format(' '.join(db_name))
|
||||
if single_transaction:
|
||||
cmd += " --single-transaction=true"
|
||||
if quick:
|
||||
|
@ -201,7 +220,7 @@ def db_import(module, host, user, password, db_name, target, all_databases, port
|
|||
cmd.append("--port=%i" % port)
|
||||
if not all_databases:
|
||||
cmd.append("-D")
|
||||
cmd.append(shlex_quote(db_name))
|
||||
cmd.append(shlex_quote(''.join(db_name)))
|
||||
|
||||
comp_prog_path = None
|
||||
if os.path.splitext(target)[-1] == '.gz':
|
||||
|
@ -210,7 +229,6 @@ def db_import(module, host, user, password, db_name, target, all_databases, port
|
|||
comp_prog_path = module.get_bin_path('bzip2', required=True)
|
||||
elif os.path.splitext(target)[-1] == '.xz':
|
||||
comp_prog_path = module.get_bin_path('xz', required=True)
|
||||
|
||||
if comp_prog_path:
|
||||
p1 = subprocess.Popen([comp_prog_path, '-dc', target], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
p2 = subprocess.Popen(cmd, stdin=p1.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
@ -231,7 +249,7 @@ def db_import(module, host, user, password, db_name, target, all_databases, port
|
|||
|
||||
def db_create(cursor, db, encoding, collation):
|
||||
query_params = dict(enc=encoding, collate=collation)
|
||||
query = ['CREATE DATABASE %s' % mysql_quote_identifier(db, 'database')]
|
||||
query = ['CREATE DATABASE %s' % mysql_quote_identifier(''.join(db), 'database')]
|
||||
if encoding:
|
||||
query.append("CHARACTER SET %(enc)s")
|
||||
if collation:
|
||||
|
@ -253,7 +271,7 @@ def main():
|
|||
login_host=dict(type='str', default='localhost'),
|
||||
login_port=dict(type='int', default=3306),
|
||||
login_unix_socket=dict(type='str'),
|
||||
name=dict(type='str', required=True, aliases=['db']),
|
||||
name=dict(type='list', required=True, aliases=['db']),
|
||||
encoding=dict(type='str', default=''),
|
||||
collation=dict(type='str', default=''),
|
||||
target=dict(type='path'),
|
||||
|
@ -274,6 +292,9 @@ def main():
|
|||
module.fail_json(msg=mysql_driver_fail_msg)
|
||||
|
||||
db = module.params["name"]
|
||||
if not db:
|
||||
module.fail_json(msg="Please provide at least one database name")
|
||||
|
||||
encoding = module.params["encoding"]
|
||||
collation = module.params["collation"]
|
||||
state = module.params["state"]
|
||||
|
@ -297,16 +318,20 @@ def main():
|
|||
single_transaction = module.params["single_transaction"]
|
||||
quick = module.params["quick"]
|
||||
|
||||
if len(db) > 1 and state != 'dump':
|
||||
module.fail_json(msg="Multiple databases is only supported with state=dump")
|
||||
db_name = ' '.join(db)
|
||||
|
||||
if state in ['dump', 'import']:
|
||||
if target is None:
|
||||
module.fail_json(msg="with state=%s target is required" % state)
|
||||
if db == 'all':
|
||||
db = 'mysql'
|
||||
if db == ['all']:
|
||||
db = ['mysql']
|
||||
all_databases = True
|
||||
else:
|
||||
all_databases = False
|
||||
else:
|
||||
if db == 'all':
|
||||
if db == ['all']:
|
||||
module.fail_json(msg="name is not allowed to equal 'all' unless state equals import, or dump.")
|
||||
try:
|
||||
cursor = mysql_connect(module, login_user, login_password, config_file, ssl_cert, ssl_key, ssl_ca,
|
||||
|
@ -324,18 +349,16 @@ def main():
|
|||
if db_exists(cursor, db):
|
||||
if state == "absent":
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True, db=db)
|
||||
else:
|
||||
module.exit_json(changed=True, db=db_name)
|
||||
try:
|
||||
changed = db_delete(cursor, db)
|
||||
except Exception as e:
|
||||
module.fail_json(msg="error deleting database: %s" % to_native(e))
|
||||
module.exit_json(changed=changed, db=db)
|
||||
module.exit_json(changed=changed, db=db_name)
|
||||
|
||||
elif state == "dump":
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True, db=db)
|
||||
else:
|
||||
module.exit_json(changed=True, db=db_name)
|
||||
rc, stdout, stderr = db_dump(module, login_host, login_user,
|
||||
login_password, db, target, all_databases,
|
||||
login_port, config_file, socket, ssl_cert, ssl_key,
|
||||
|
@ -343,12 +366,11 @@ def main():
|
|||
if rc != 0:
|
||||
module.fail_json(msg="%s" % stderr)
|
||||
else:
|
||||
module.exit_json(changed=True, db=db, msg=stdout)
|
||||
module.exit_json(changed=True, db=db_name, msg=stdout)
|
||||
|
||||
elif state == "import":
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True, db=db)
|
||||
else:
|
||||
module.exit_json(changed=True, db=db_name)
|
||||
rc, stdout, stderr = db_import(module, login_host, login_user,
|
||||
login_password, db, target,
|
||||
all_databases,
|
||||
|
@ -357,12 +379,10 @@ def main():
|
|||
if rc != 0:
|
||||
module.fail_json(msg="%s" % stderr)
|
||||
else:
|
||||
module.exit_json(changed=True, db=db, msg=stdout)
|
||||
module.exit_json(changed=True, db=db_name, msg=stdout)
|
||||
|
||||
elif state == "present":
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=False, db=db)
|
||||
module.exit_json(changed=False, db=db)
|
||||
module.exit_json(changed=False, db=db_name)
|
||||
|
||||
else:
|
||||
if state == "present":
|
||||
|
@ -374,11 +394,11 @@ def main():
|
|||
except Exception as e:
|
||||
module.fail_json(msg="error creating database: %s" % to_native(e),
|
||||
exception=traceback.format_exc())
|
||||
module.exit_json(changed=changed, db=db)
|
||||
module.exit_json(changed=changed, db=db_name)
|
||||
|
||||
elif state == "import":
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True, db=db)
|
||||
module.exit_json(changed=True, db=db_name)
|
||||
else:
|
||||
try:
|
||||
changed = db_create(cursor, db, encoding, collation)
|
||||
|
@ -389,20 +409,18 @@ def main():
|
|||
if rc != 0:
|
||||
module.fail_json(msg="%s" % stderr)
|
||||
else:
|
||||
module.exit_json(changed=True, db=db, msg=stdout)
|
||||
module.exit_json(changed=True, db=db_name, msg=stdout)
|
||||
except Exception as e:
|
||||
module.fail_json(msg="error creating database: %s" % to_native(e),
|
||||
exception=traceback.format_exc())
|
||||
|
||||
elif state == "absent":
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=False, db=db)
|
||||
module.exit_json(changed=False, db=db)
|
||||
module.exit_json(changed=False, db=db_name)
|
||||
|
||||
elif state == "dump":
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=False, db=db)
|
||||
module.fail_json(msg="Cannot dump database %s - not found" % (db))
|
||||
module.exit_json(changed=False, db=db_name)
|
||||
module.fail_json(msg="Cannot dump database %r - not found" % (db_name))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
---
|
||||
# defaults file for test_mysql_db
|
||||
db_name: 'data'
|
||||
db_name2: 'data2'
|
||||
db_user1: 'datauser1'
|
||||
db_user2: 'datauser2'
|
||||
|
||||
|
|
|
@ -238,8 +238,8 @@
|
|||
assert: { that: "'{{ db_user1 }}' not in result.stdout" }
|
||||
|
||||
# ============================================================
|
||||
- include: state_dump_import.yml format_type=sql file=dbdata.sql format_msg_type=ASCII
|
||||
- include: state_dump_import.yml format_type=sql file=dbdata.sql format_msg_type=ASCII file2=dump2.sql file3=dump3.sql
|
||||
|
||||
- include: state_dump_import.yml format_type=gz file=dbdata.gz format_msg_type=gzip
|
||||
- include: state_dump_import.yml format_type=gz file=dbdata.gz format_msg_type=gzip file2=dump2.gz file3=dump3.gz
|
||||
|
||||
- include: state_dump_import.yml format_type=bz2 file=dbdata.bz2 format_msg_type=bzip2
|
||||
- include: state_dump_import.yml format_type=bz2 file=dbdata.bz2 format_msg_type=bzip2 file2=dump2.bz2 file3=dump3.bz2
|
||||
|
|
|
@ -17,7 +17,10 @@
|
|||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# ============================================================
|
||||
- set_fact: db_file_name="{{tmp_dir}}/{{file}}"
|
||||
- set_fact:
|
||||
db_file_name="{{tmp_dir}}/{{file}}"
|
||||
dump_file1="{{tmp_dir}}/{{file2}}"
|
||||
dump_file2="{{tmp_dir}}/{{file3}}"
|
||||
|
||||
- name: state dump/import - create database
|
||||
mysql_db:
|
||||
|
@ -25,6 +28,12 @@
|
|||
state: present
|
||||
login_unix_socket: '{{ mysql_socket }}'
|
||||
|
||||
- name: create database
|
||||
mysql_db:
|
||||
name: '{{ db_name2 }}'
|
||||
state: present
|
||||
login_unix_socket: '{{ mysql_socket }}'
|
||||
|
||||
- name: state dump/import - create table department
|
||||
command: mysql {{ db_name }} '-e create table department(id int, name varchar(100));'
|
||||
|
||||
|
@ -40,6 +49,12 @@
|
|||
- name: state dump/import - file name should not exist
|
||||
file: name={{ db_file_name }} state=absent
|
||||
|
||||
- name: database dump file1 should not exist
|
||||
file: name={{ dump_file1 }} state=absent
|
||||
|
||||
- name: database dump file2 should not exist
|
||||
file: name={{ dump_file2 }} state=absent
|
||||
|
||||
- name: state dump without department table.
|
||||
mysql_db:
|
||||
name: "{{ db_name }}"
|
||||
|
@ -58,12 +73,78 @@
|
|||
- name: state dump/import - file name should exist
|
||||
file: name={{ db_file_name }} state=file
|
||||
|
||||
- name: state dump with multiple databases in comma separated form.
|
||||
mysql_db:
|
||||
name: "{{ db_name }},{{ db_name2 }}"
|
||||
state: dump
|
||||
target: "{{ dump_file1 }}"
|
||||
login_unix_socket: '{{ mysql_socket }}'
|
||||
register: dump_result1
|
||||
|
||||
- name: assert successful completion of dump operation (with multiple databases in comma separated form)
|
||||
assert:
|
||||
that:
|
||||
- "dump_result1.changed == true"
|
||||
|
||||
- name: state dump - dump file1 should exist
|
||||
file: name={{ dump_file1 }} state=file
|
||||
|
||||
- name: state dump with multiple databases in list form via check_mode
|
||||
mysql_db:
|
||||
name:
|
||||
- "{{ db_name }}"
|
||||
- "{{ db_name2 }}"
|
||||
state: dump
|
||||
target: "{{ dump_file2 }}"
|
||||
login_unix_socket: '{{ mysql_socket }}'
|
||||
register: dump_result
|
||||
check_mode: yes
|
||||
|
||||
- name: assert successful completion of dump operation (with multiple databases in list form) via check mode
|
||||
assert:
|
||||
that:
|
||||
- "dump_result.changed == true"
|
||||
|
||||
- name: database dump file2 should not exist
|
||||
stat:
|
||||
path: "{{ dump_file2 }}"
|
||||
register: stat_result
|
||||
|
||||
- name: assert that check_mode does not create dump file for databases
|
||||
assert:
|
||||
that:
|
||||
- stat_result.stat.exists is defined and not stat_result.stat.exists
|
||||
|
||||
- name: state dump with multiple databases in list form.
|
||||
mysql_db:
|
||||
name:
|
||||
- "{{ db_name }}"
|
||||
- "{{ db_name2 }}"
|
||||
state: dump
|
||||
target: "{{ dump_file2 }}"
|
||||
login_unix_socket: '{{ mysql_socket }}'
|
||||
register: dump_result2
|
||||
|
||||
- name: assert successful completion of dump operation (with multiple databases in list form)
|
||||
assert:
|
||||
that:
|
||||
- "dump_result2.changed == true"
|
||||
|
||||
- name: state dump - dump file2 should exist
|
||||
file: name={{ dump_file2 }} state=file
|
||||
|
||||
- name: state dump/import - remove database
|
||||
mysql_db:
|
||||
name: '{{ db_name }}'
|
||||
state: absent
|
||||
login_unix_socket: '{{ mysql_socket }}'
|
||||
|
||||
- name: remove database
|
||||
mysql_db:
|
||||
name: '{{ db_name2 }}'
|
||||
state: absent
|
||||
login_unix_socket: '{{ mysql_socket }}'
|
||||
|
||||
- name: test state=import to restore the database of type {{ format_type }} (expect changed=true)
|
||||
mysql_db:
|
||||
name: '{{ db_name }}'
|
||||
|
@ -81,6 +162,34 @@
|
|||
that:
|
||||
- "'department' not in result.stdout"
|
||||
|
||||
- name: test state=import to restore a database from multiple database dumped file1
|
||||
mysql_db:
|
||||
name: '{{ db_name2 }}'
|
||||
state: import
|
||||
target: '{{ dump_file1 }}'
|
||||
login_unix_socket: '{{ mysql_socket }}'
|
||||
register: import_result
|
||||
|
||||
- name: assert output message restored a database from dump file1
|
||||
assert: { that: "import_result.changed == true" }
|
||||
|
||||
- name: remove database
|
||||
mysql_db:
|
||||
name: '{{ db_name2 }}'
|
||||
state: absent
|
||||
login_unix_socket: '{{ mysql_socket }}'
|
||||
|
||||
- name: test state=import to restore a database from multiple database dumped file2
|
||||
mysql_db:
|
||||
name: '{{ db_name2 }}'
|
||||
state: import
|
||||
target: '{{ dump_file2 }}'
|
||||
login_unix_socket: '{{ mysql_socket }}'
|
||||
register: import_result2
|
||||
|
||||
- name: assert output message restored a database from dump file2
|
||||
assert: { that: "import_result2.changed == true" }
|
||||
|
||||
- name: test state=dump to backup the database of type {{ format_type }} (expect changed=true)
|
||||
mysql_db:
|
||||
name: '{{ db_name }}'
|
||||
|
@ -132,5 +241,17 @@
|
|||
state: absent
|
||||
login_unix_socket: '{{ mysql_socket }}'
|
||||
|
||||
- name: remove database
|
||||
mysql_db:
|
||||
name: '{{ db_name2 }}'
|
||||
state: absent
|
||||
login_unix_socket: '{{ mysql_socket }}'
|
||||
|
||||
- name: remove file name
|
||||
file: name={{ db_file_name }} state=absent
|
||||
|
||||
- name: remove dump file1
|
||||
file: name={{ dump_file1 }} state=absent
|
||||
|
||||
- name: remove dump file2
|
||||
file: name={{ dump_file2 }} state=absent
|
||||
|
|
Loading…
Reference in a new issue