Skip to content

Commit 1a8ee4c

Browse files
committed
improve docker output handling
1 parent 4bde1dd commit 1a8ee4c

File tree

1 file changed

+42
-13
lines changed

1 file changed

+42
-13
lines changed

airbyte_cdk/utils/docker.py

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77
import os
88
import subprocess
99
import sys
10+
from contextlib import ExitStack
1011
from dataclasses import dataclass
1112
from enum import Enum
13+
from io import TextIOWrapper
1214
from pathlib import Path
1315

1416
import click
@@ -90,6 +92,7 @@ def _build_image(
9092
run_docker_command(
9193
docker_args,
9294
check=True,
95+
capture_stderr=True,
9396
)
9497
except subprocess.CalledProcessError as e:
9598
raise ConnectorImageBuildError(
@@ -126,6 +129,7 @@ def _tag_image(
126129
run_docker_command(
127130
docker_args,
128131
check=True,
132+
capture_stderr=True,
129133
)
130134
except subprocess.CalledProcessError as e:
131135
raise ConnectorImageBuildError(
@@ -368,31 +372,55 @@ def run_docker_command(
368372
cmd: list[str],
369373
*,
370374
check: bool = True,
371-
capture_output: bool = False,
375+
capture_stdout: bool | Path = False,
376+
capture_stderr: bool | Path = False,
372377
) -> subprocess.CompletedProcess[str]:
373378
"""Run a Docker command as a subprocess.
374379
375380
Args:
376381
cmd: The command to run as a list of strings.
377382
check: If True, raises an exception if the command fails. If False, the caller is
378383
responsible for checking the return code.
379-
capture_output: If True, captures stdout and stderr and returns to the caller.
380-
If False, the output is printed to the console.
384+
capture_stdout: How to process stdout.
385+
capture_stderr: If True, captures stderr in memory and returns to the caller.
386+
If a Path is provided, the output is written to the specified file.
387+
388+
For stdout and stderr process:
389+
- If False (the default), stdout is not captured.
390+
- If True, output is captured in memory and returned within the `CompletedProcess` object.
391+
- If a Path is provided, the output is written to the specified file. (Recommended for large syncs.)
381392
382393
Raises:
383394
subprocess.CalledProcessError: If the command fails and check is True.
384395
"""
385396
print(f"Running command: {' '.join(cmd)}")
386397

387-
process = subprocess.run(
388-
cmd,
389-
text=True,
390-
check=check,
391-
# If capture_output=True, stderr and stdout are captured and returned to caller:
392-
capture_output=capture_output,
393-
env={**os.environ, "DOCKER_BUILDKIT": "1"},
394-
)
395-
return process
398+
with ExitStack() as stack:
399+
# Shared context manager to handle file closing, if needed.
400+
stderr: TextIOWrapper | int | None
401+
stdout: TextIOWrapper | int | None
402+
403+
# If capture_stderr or capture_stdout is a Path, we open the file in write mode.
404+
# If it's a boolean, we set it to either subprocess.PIPE or None.
405+
if isinstance(capture_stderr, Path):
406+
stderr = stack.enter_context(capture_stderr.open("w", encoding="utf-8"))
407+
elif isinstance(capture_stderr, bool):
408+
stderr = subprocess.PIPE if capture_stderr is True else None
409+
410+
if isinstance(capture_stdout, Path):
411+
stdout = stack.enter_context(capture_stdout.open("w", encoding="utf-8"))
412+
elif isinstance(capture_stdout, bool):
413+
stdout = subprocess.PIPE if capture_stdout is True else None
414+
415+
completed_process: subprocess.CompletedProcess[str] = subprocess.run(
416+
cmd,
417+
env={**os.environ, "DOCKER_BUILDKIT": "1"},
418+
text=True,
419+
check=check,
420+
stderr=stderr,
421+
stdout=stdout,
422+
)
423+
return completed_process
396424

397425

398426
def verify_docker_installation() -> bool:
@@ -423,7 +451,8 @@ def verify_connector_image(
423451
result = run_docker_command(
424452
cmd,
425453
check=True,
426-
capture_output=True,
454+
capture_stderr=True,
455+
capture_stdout=True,
427456
)
428457
# check that the output is valid JSON
429458
if result.stdout:

0 commit comments

Comments
 (0)