|
7 | 7 | import os |
8 | 8 | import subprocess |
9 | 9 | import sys |
| 10 | +from contextlib import ExitStack |
10 | 11 | from dataclasses import dataclass |
11 | 12 | from enum import Enum |
| 13 | +from io import TextIOWrapper |
12 | 14 | from pathlib import Path |
13 | 15 |
|
14 | 16 | import click |
@@ -90,6 +92,7 @@ def _build_image( |
90 | 92 | run_docker_command( |
91 | 93 | docker_args, |
92 | 94 | check=True, |
| 95 | + capture_stderr=True, |
93 | 96 | ) |
94 | 97 | except subprocess.CalledProcessError as e: |
95 | 98 | raise ConnectorImageBuildError( |
@@ -126,6 +129,7 @@ def _tag_image( |
126 | 129 | run_docker_command( |
127 | 130 | docker_args, |
128 | 131 | check=True, |
| 132 | + capture_stderr=True, |
129 | 133 | ) |
130 | 134 | except subprocess.CalledProcessError as e: |
131 | 135 | raise ConnectorImageBuildError( |
@@ -368,31 +372,55 @@ def run_docker_command( |
368 | 372 | cmd: list[str], |
369 | 373 | *, |
370 | 374 | check: bool = True, |
371 | | - capture_output: bool = False, |
| 375 | + capture_stdout: bool | Path = False, |
| 376 | + capture_stderr: bool | Path = False, |
372 | 377 | ) -> subprocess.CompletedProcess[str]: |
373 | 378 | """Run a Docker command as a subprocess. |
374 | 379 |
|
375 | 380 | Args: |
376 | 381 | cmd: The command to run as a list of strings. |
377 | 382 | check: If True, raises an exception if the command fails. If False, the caller is |
378 | 383 | 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.) |
381 | 392 |
|
382 | 393 | Raises: |
383 | 394 | subprocess.CalledProcessError: If the command fails and check is True. |
384 | 395 | """ |
385 | 396 | print(f"Running command: {' '.join(cmd)}") |
386 | 397 |
|
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 |
396 | 424 |
|
397 | 425 |
|
398 | 426 | def verify_docker_installation() -> bool: |
@@ -423,7 +451,8 @@ def verify_connector_image( |
423 | 451 | result = run_docker_command( |
424 | 452 | cmd, |
425 | 453 | check=True, |
426 | | - capture_output=True, |
| 454 | + capture_stderr=True, |
| 455 | + capture_stdout=True, |
427 | 456 | ) |
428 | 457 | # check that the output is valid JSON |
429 | 458 | if result.stdout: |
|
0 commit comments