Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix mysqldump ignoring errors #403

Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
f720be4
Add schema and tables for the tests
laurent-indermuehle Jun 17, 2022
5ed8661
Add tests for full dump with and without compression
laurent-indermuehle Jun 20, 2022
4f85e37
Add test for distinct dump with and without compression
laurent-indermuehle Jun 20, 2022
9716a4d
Fix sh not seeing errors for command before the pipe
laurent-indermuehle Jun 21, 2022
bb11029
Add cleanup to prevent the following tests from failing
laurent-indermuehle Jun 21, 2022
1489f2f
Fix fqcn in module_defaults
laurent-indermuehle Jun 22, 2022
db02d8e
Add changelog fragment
laurent-indermuehle Jun 22, 2022
36bbc64
Add check to the error message to ensure we captured the right one
laurent-indermuehle Jun 22, 2022
3fe6f9b
Add option to activate the fix on systems with bash
laurent-indermuehle Jun 22, 2022
c1c44f2
Fix errors when data schema is already absent
laurent-indermuehle Jun 22, 2022
220d683
Update changelogs/fragments/fix-256-mysql_dump-errors.yml
laurent-indermuehle Jun 22, 2022
ebf56f0
Add markup for commands in the documentation string
laurent-indermuehle Jun 22, 2022
f170da4
Add markup and next release version in the documentation string
laurent-indermuehle Jun 22, 2022
9699b92
Fix missing dependency for MySQL 8
laurent-indermuehle Jun 22, 2022
b5e8cdc
Add pipefail to tests of uncompressed dumps to enure it still works
laurent-indermuehle Jun 22, 2022
eae5df2
Fix "bash command not found" if pipefail is used for uncompressed dump
laurent-indermuehle Jun 22, 2022
2bc2e59
Fix sanity pep8
laurent-indermuehle Jun 22, 2022
0b3d793
Document example of dump with pipefail
laurent-indermuehle Jun 23, 2022
85696fe
Add dedpulication to command construct
laurent-indermuehle Jun 23, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions changelogs/fragments/fix-256-mysql_dump-errors.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---

bugfixes:
- mysql_dump - Fixes issue 256. Using compression masks errors messages from
mysql_dump. By default the fix is inactiv to ensure retro-compatibility
with system without bash. To activate the fix, use the module option
`pipefail=true`.
laurent-indermuehle marked this conversation as resolved.
Show resolved Hide resolved
25 changes: 21 additions & 4 deletions plugins/modules/mysql_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,14 @@
- Can be useful, for example, when I(state=import) and a dump file contains relative paths.
type: path
version_added: '3.4.0'
pipefail:
description:
- Use bash instead of sh and add -o pipefail to catch errors from the
laurent-indermuehle marked this conversation as resolved.
Show resolved Hide resolved
mysql_dump command when compression is used. The default is false to
prevent issue on system without bash. The default may change in a future release
type: bool
default: no
version_added: '3.3.1'
laurent-indermuehle marked this conversation as resolved.
Show resolved Hide resolved

seealso:
- module: community.mysql.mysql_info
Expand Down Expand Up @@ -355,7 +363,7 @@ def db_dump(module, host, user, password, db_name, target, all_databases, port,
single_transaction=None, quick=None, ignore_tables=None, hex_blob=None,
encoding=None, force=False, master_data=0, skip_lock_tables=False,
dump_extra_args=None, unsafe_password=False, restrict_config_file=False,
check_implicit_admin=False):
check_implicit_admin=False, pipefail=False):
cmd = module.get_bin_path('mysqldump', True)
# If defined, mysqldump demands --defaults-extra-file be the first option
if config_file:
Expand Down Expand Up @@ -422,13 +430,20 @@ def db_dump(module, host, user, password, db_name, target, all_databases, port,
elif os.path.splitext(target)[-1] == '.xz':
path = module.get_bin_path('xz', True)

if path:
if pipefail:
cmd = 'set -o pipefail && %s | %s > %s' % (cmd, path, shlex_quote(target))
elif path:
laurent-indermuehle marked this conversation as resolved.
Show resolved Hide resolved
cmd = '%s | %s > %s' % (cmd, path, shlex_quote(target))
else:
cmd += " > %s" % shlex_quote(target)

executed_commands.append(cmd)
rc, stdout, stderr = module.run_command(cmd, use_unsafe_shell=True)

if pipefail:
rc, stdout, stderr = module.run_command(cmd, use_unsafe_shell=True, executable='bash')
else:
rc, stdout, stderr = module.run_command(cmd, use_unsafe_shell=True)

return rc, stdout, stderr


Expand Down Expand Up @@ -569,6 +584,7 @@ def main():
check_implicit_admin=dict(type='bool', default=False),
config_overrides_defaults=dict(type='bool', default=False),
chdir=dict(type='path'),
pipefail=dict(type='bool', default=False),
)

module = AnsibleModule(
Expand Down Expand Up @@ -618,6 +634,7 @@ def main():
check_implicit_admin = module.params['check_implicit_admin']
config_overrides_defaults = module.params['config_overrides_defaults']
chdir = module.params['chdir']
pipefail = module.params['pipefail']

if chdir:
try:
Expand Down Expand Up @@ -704,7 +721,7 @@ def main():
ssl_ca, single_transaction, quick, ignore_tables,
hex_blob, encoding, force, master_data, skip_lock_tables,
dump_extra_args, unsafe_login_password, restrict_config_file,
check_implicit_admin)
check_implicit_admin, pipefail)
if rc != 0:
module.fail_json(msg="%s" % stderr)
module.exit_json(changed=True, db=db_name, db_list=db, msg=stdout,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
---

# When mysqldump encountered an issue, mysql_db was still happy. But the
# dump produced was empty or worse, only contained `DROP TABLE IF EXISTS...`

- module_defaults:
community.mysql.mysql_db: &mysql_defaults
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
community.mysql.mysql_query: *mysql_defaults

block:

- name: Dumps errors | Setup test | Create 2 schemas
community.mysql.mysql_db:
name:
- "db1"
- "db2"
state: present

- name: Dumps errors | Setup test | Create 2 tables
community.mysql.mysql_query:
query:
- "CREATE TABLE db1.t1 (id int)"
- "CREATE TABLE db1.t2 (id int)"
- "CREATE VIEW db2.v1 AS SELECT id from db1.t1"

- name: Dumps errors | Full dump without compression
community.mysql.mysql_db:
state: dump
name: all
target: /tmp/full-dump.sql
register: full_dump

- name: Dumps errors | Full dump with gunzip
community.mysql.mysql_db:
state: dump
name: all
target: /tmp/full-dump.sql.gz
register: full_dump_gz

- name: Dumps errors | Distinct dump without compression
community.mysql.mysql_db:
state: dump
name: db2
target: /tmp/dump-db2.sql
register: dump_db2

- name: Dumps errors | Distinct dump with gunzip
community.mysql.mysql_db:
state: dump
name: db2
target: /tmp/dump-db2.sql.gz
register: dump_db2_gz

- name: Dumps errors | Check distinct dumps are changed
ansible.builtin.assert:
that:
- dump_db2 is changed
- dump_db2_gz is changed

# Now db2.v1 targets an inexistant table so mysqldump will fail
- name: Dumps errors | Drop t1
community.mysql.mysql_query:
query:
- "DROP TABLE db1.t1"

- name: Dumps errors | Full dump after drop t1 without compression
community.mysql.mysql_db:
state: dump
name: all
target: /tmp/full-dump-without-t1.sql
register: full_dump_without_t1
ignore_errors: true

- name: Dumps errors | Full dump after drop t1 with gzip without the fix
community.mysql.mysql_db:
state: dump
name: all
target: /tmp/full-dump-without-t1.sql.gz
register: full_dump_without_t1_gz_without_fix
ignore_errors: true

- name: Dumps errors | Full dump after drop t1 with gzip with the fix
community.mysql.mysql_db:
state: dump
name: all
target: /tmp/full-dump-without-t1.sql.gz
pipefail: true
register: full_dump_without_t1_gz_with_fix
ignore_errors: true

- name: Dumps errors | Check full dump
ansible.builtin.assert:
that:
- full_dump_without_t1 is failed
- full_dump_without_t1.msg is search(
'references invalid table')
- full_dump_without_t1_gz_without_fix is changed
- full_dump_without_t1_gz_with_fix is failed
- full_dump_without_t1_gz_with_fix.msg is search(
'references invalid table')

- name: Dumps errors | Distinct dump after drop t1 without compression
community.mysql.mysql_db:
state: dump
name: db2
target: /tmp/dump-db2-without_t1.sql
register: dump_db2_without_t1
ignore_errors: true

- name: Dumps errors | Distinct dump after drop t1 with gzip without the fix
community.mysql.mysql_db:
state: dump
name: db2
target: /tmp/dump-db2-without_t1.sql.gz
register: dump_db2_without_t1_gz_without_fix
ignore_errors: true

- name: Dumps errors | Distinct dump after drop t1 with gzip with the fix
community.mysql.mysql_db:
state: dump
name: db2
target: /tmp/dump-db2-without_t1.sql.gz
pipefail: true
register: dump_db2_without_t1_gz_with_fix
ignore_errors: true

- name: Dumps errors | Check distinct dump
ansible.builtin.assert:
that:
- dump_db2_without_t1 is failed
- dump_db2_without_t1.msg is search(
'references invalid table')
- dump_db2_without_t1_gz_without_fix is changed
- dump_db2_without_t1_gz_with_fix is failed
- dump_db2_without_t1_gz_with_fix.msg is search(
'references invalid table')
- name: Dumps errors | Cleanup
community.mysql.mysql_db:
name:
- "db1"
- "db2"
state: absent
3 changes: 3 additions & 0 deletions tests/integration/targets/test_mysql_db/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,6 @@
vars:
db_name: "{{ item }}"
loop: "{{ db_names }}"

- name: Check errors from mysqldump are seen issue 256
ansible.builtin.include_tasks: issue_256_mysqldump_errors.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
# ============================================================
- name: remove database if it exists
command: >
"{{ mysql_command }} -sse 'drop database {{ db_name }}'"
ignore_errors: True
"{{ mysql_command }} -sse 'DROP DATABASE IF EXISTS {{ db_name }}'"
ignore_errors: true

Comment on lines -21 to 23
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this related to the issue ? 🤨

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No sorry. I did that when the tests were failing on python 3.6.

Should I revert that?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@laurent-indermuehle

No sorry. I did that when the tests were failing on python 3.6.

I'm curious why they fail if they are not related to the changes. If it's safe, we can leave it as is.

- name: make sure the test database is not there
command: "{{ mysql_command }} {{ db_name }}"
Expand Down