diff --git a/pgclone/dump_cmd.py b/pgclone/dump_cmd.py index 9b64033..188a12a 100644 --- a/pgclone/dump_cmd.py +++ b/pgclone/dump_cmd.py @@ -42,7 +42,7 @@ def _dump(*, exclude, config, pre_dump_hooks, instance, database, storage_locati [f"--exclude-table-data={table_name}" for table_name in exclude_tables] ) # Note - do note format {db_dump_url} with an `f` string. - # It will be formatted later when running the command + # It will be formatted later when running the command. pg_dump_cmd_fmt = "pg_dump -Fc --no-acl --no-owner {db_dump_url} " + exclude_args pg_dump_cmd_fmt += " " + storage_client.pg_dump(file_path) @@ -50,7 +50,7 @@ def _dump(*, exclude, config, pre_dump_hooks, instance, database, storage_locati logging.success_msg(f"Creating DB copy with cmd: {anon_pg_dump_cmd}") pg_dump_cmd = pg_dump_cmd_fmt.format(db_dump_url=db.url(dump_db)) - run.shell(pg_dump_cmd, env=storage_client.env) + run.shell(pg_dump_cmd, env=storage_client.env, pipefail=True) logging.success_msg(f'Database "{database}" successfully dumped to "{dump_key}"') diff --git a/pgclone/run.py b/pgclone/run.py index 399fd28..d3421d1 100644 --- a/pgclone/run.py +++ b/pgclone/run.py @@ -2,17 +2,34 @@ import io import os import subprocess +import sys from django.core.management import call_command from pgclone import exceptions, logging -def shell(cmd, ignore_errors=False, env=None): +def _is_pipefail_supported() -> bool: + """Check if the current shell supports pipefail.""" + if sys.platform == "win32": + return False + + try: + current_shell = os.environ.get("SHELL", "/bin/sh") + subprocess.check_call([current_shell, "-c", "set -o pipefail"], stderr=subprocess.DEVNULL) + return True + except (subprocess.CalledProcessError, FileNotFoundError): + return False + + +def shell(cmd, ignore_errors=False, env=None, pipefail=False): """ Utility for running a command. Ensures that an error is raised if it fails. """ + if pipefail and _is_pipefail_supported(): # pragma: no cover + cmd = [f"set -o pipefail; {cmd}"] + env = env or {} logger = logging.get_logger() process = subprocess.Popen( diff --git a/pgclone/tests/test_run.py b/pgclone/tests/test_run.py new file mode 100644 index 0000000..6f61948 --- /dev/null +++ b/pgclone/tests/test_run.py @@ -0,0 +1,16 @@ +import subprocess +from unittest.mock import patch + +from pgclone import run + + +def test_is_pipefail_supported(): + with patch("subprocess.check_call", autospec=True, return_value=0): + assert run._is_pipefail_supported() is True + + with patch( + "subprocess.check_call", + side_effect=subprocess.CalledProcessError(1, "cmd"), + autospec=True, + ): + assert run._is_pipefail_supported() is False