Skip to content

feat: sandbox commands API and per-command cwd/env - #685

Open
zackmckennarunpod wants to merge 1 commit into
zeke/con-1525-create-sandbox-domainfrom
zackmckenna/sandbox-commands-cwd-env
Open

zackmckennarunpod wants to merge 1 commit into
zeke/con-1525-create-sandbox-domainfrom
zackmckenna/sandbox-commands-cwd-env

Conversation

@zackmckennarunpod

Copy link
Copy Markdown

Brings the sandbox SDK in #595 up to the sandbox REST API in rphttp2 v2.15.0. That release added two things the SDK didn't cover:

  • Per-command cwd and env on exec (runpod/rphttp2#485).
  • The commands API (runpod/rphttp2#457): POST /v2/sandboxes/{id}/commands, GET/DELETE .../commands/{commandId}, GET .../commands/{commandId}/logs (JSON snapshot or SSE). Commands keep their output and can run for up to 24 hours, whereas exec(background=True) returns no output.

API

command = sandbox.start_command(["sh", "-c", "pytest -x"], cwd="/workspace", env={"CI": "1"})
with command.logs() as stream:          # SSE; CommandLogLine(line, stream, timestamp, partial)
    for line in stream:
        print(line.line)
stream.end                              # CommandLogEnd(status, exit_code, output_bytes, truncated)
command.wait(timeout=600)               # polls GET until status != "running"
command.output(offset=..., tail=..., streams="stderr")   # CommandOutput page
command.cancel(); sandbox.get_command(command.id)
sandbox.exec(["pwd"], cwd="/tmp", env={"FOO": "bar"})

AsyncioSandbox has the same methods, awaited (AsyncioSandboxCommand, AsyncioCommandLogStream).

Behavior notes

  • start_command goes through _ready_operation, so only a 409 sandbox_starting is retried. A 409 command_limit_reached (8 concurrent commands) raises SandboxConflictError with that code.
  • A 422 that comes back as application/json with the command (status failed) is returned as a handle, not raised. A 422 application/problem+json (invalid request) still raises.
  • The log stream stops at event: end and keeps it on stream.end. On event: timeout (after 1 hour) or event: error, it stops with end unset and keeps next_offset and error so the caller can resume with logs(offset=...). It never reconnects on its own, the same as sandbox logs. Unknown named events are skipped.
  • exec refuses cwd/env together with background=True before sending anything. The API returns 422 for that combination.
  • SandboxInfo gains runtime and egress_policy (raw mappings, defaulted to None).
  • SSE parsing is factored into _sse_events(), which both log streams use. Sandbox log behavior is unchanged, and the existing SSE tests still pass.
  • README: the exec section documents cwd/env and commands. The "about four seconds" exec cap now reads "can be shorter, as low as four seconds on some sandboxes", which is what the spec says.

Testing

  • pytest tests/test_sandbox.py tests/test_sandbox_contract.py tests/test_sandbox_commands.py tests/test_init.py tests/test_api/test_sandbox_rate_limit.py: 137 passed (25 new, in tests/test_sandbox_commands.py). The shared REST peer in tests/test_sandbox.py gained the command routes.
  • Full suite: one failure, tests/test_serverless/test_refresh_worker.py::test_process_per_job_refresh_exits_without_taking_next_job. It fails the same way on this PR's base without these changes.
  • Live against dev REST (v2-rest.runpod.dev, python:3.12-slim):
    • exec(cwd="/tmp", env={"FOO": "bar"}) printed /tmp and FOO=bar.
    • A command with cwd="/usr" and env streamed 6 lines tagged stdout/stderr, then end (exited, exit code 0, 279 bytes).
    • wait returned exited and output() returned the full text with eof=True. streams="stderr" returned only the stderr line.
    • cancel() on sleep 300 returned cancelled.

Adds start_command/get_command on Sandbox and AsyncioSandbox, returning a
command handle with refresh, wait, cancel, output (paged snapshot) and logs
(SSE stream that keeps the end event and the resume offset). exec accepts
cwd and env, refused locally with background as the API refuses them.
SandboxInfo reads runtime and egressPolicy. Matches rphttp2 v2.15.0.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant