(
self,
sandbox_id: str,
command: Sequence[str],
*,
workdir: str | None = None,
env: Mapping[str, str] | None = None,
stdin: bytes | None = None,
timeout_seconds: int | None = None,
)
| 471 | raise SandboxError(f"sandbox {sandbox_name} was not ready within timeout") |
| 472 | |
| 473 | def exec_stream( |
| 474 | self, |
| 475 | sandbox_id: str, |
| 476 | command: Sequence[str], |
| 477 | *, |
| 478 | workdir: str | None = None, |
| 479 | env: Mapping[str, str] | None = None, |
| 480 | stdin: bytes | None = None, |
| 481 | timeout_seconds: int | None = None, |
| 482 | ) -> Iterator[ExecChunk | ExecResult]: |
| 483 | if not command: |
| 484 | raise SandboxError("command must not be empty") |
| 485 | |
| 486 | request = openshell_pb2.ExecSandboxRequest( |
| 487 | sandbox_id=sandbox_id, |
| 488 | command=list(command), |
| 489 | workdir=workdir or "", |
| 490 | environment=dict(env or {}), |
| 491 | timeout_seconds=timeout_seconds or 0, |
| 492 | stdin=stdin or b"", |
| 493 | ) |
| 494 | # Use whichever is larger: the default client timeout or the command |
| 495 | # timeout plus headroom for SSH setup / teardown overhead. |
| 496 | grpc_deadline = self._timeout |
| 497 | if timeout_seconds and timeout_seconds + 10 > grpc_deadline: |
| 498 | grpc_deadline = timeout_seconds + 10 |
| 499 | stream = self._stub.ExecSandbox(request, timeout=grpc_deadline) |
| 500 | |
| 501 | stdout_parts: list[bytes] = [] |
| 502 | stderr_parts: list[bytes] = [] |
| 503 | exit_code: int | None = None |
| 504 | |
| 505 | for event in stream: |
| 506 | payload = event.WhichOneof("payload") |
| 507 | if payload == "stdout": |
| 508 | data = bytes(event.stdout.data) |
| 509 | stdout_parts.append(data) |
| 510 | yield ExecChunk(stream="stdout", data=data) |
| 511 | elif payload == "stderr": |
| 512 | data = bytes(event.stderr.data) |
| 513 | stderr_parts.append(data) |
| 514 | yield ExecChunk(stream="stderr", data=data) |
| 515 | elif payload == "exit": |
| 516 | exit_code = int(event.exit.exit_code) |
| 517 | |
| 518 | if exit_code is None: |
| 519 | raise SandboxError("ExecSandbox stream ended without an exit event") |
| 520 | |
| 521 | yield ExecResult( |
| 522 | exit_code=exit_code, |
| 523 | stdout=b"".join(stdout_parts).decode("utf-8", errors="replace"), |
| 524 | stderr=b"".join(stderr_parts).decode("utf-8", errors="replace"), |
| 525 | ) |
| 526 | |
| 527 | def exec( |
| 528 | self, |
no test coverage detected