host.docker_host¶
Docker container host.
A DockerContainerHost satisfies the otto Host protocol by
delegating most operations through a parent host that runs the docker
daemon. oneshot becomes parent.oneshot("docker exec ...");
get / put are two-step docker cp via the parent’s filesystem;
interact opens a PTY-backed docker exec -it over the parent’s
existing SSH connection.
run (and open_session / send / expect) use a persistent
docker exec -it <ctr> sh session multiplexed on the parent’s SSH
connection — shell state (cd, env vars, shell vars) persists across
calls, matching LocalHost and UnixHost. oneshot
stays stateless and concurrent-safe.
Persistent-shell support requires an SSH-based UnixHost parent.
Local-host parents and telnet parents are rejected at session-open time —
the per-call oneshot path still works against any parent.
- class otto.host.docker_host.DockerContainerHost(parent: ~otto.host.host.Host, container_id: str, project: str, service: str, compose_project: str, log: bool = True, log_stdout: bool = True, resources: set[str] = <factory>, products: list[~otto.host.product.Product] = <factory>, power_control: ~otto.host.power.PowerController | None = None)¶
Bases:
PosixPrivilege,PosixFileOps,BaseHostA Docker container exposed as a first-class otto host.
Construction is normally done by
otto.docker.composeafter a successfuldocker compose up; tests instantiate it directly with a mocked parent.- parent : Host¶
The lab host running the docker daemon. Owns auth, hop chain, and the SSH connection used to reach the daemon. Typed as
Host(the protocol) so the type-system surface stays narrow, butrun/open_session/send/expect/interactadditionally require an SSH-basedUnixHostat runtime — they open a persistentdocker execchannel on the parent’s asyncssh connection.oneshotand file transfer work against any parent.
- container_id : str¶
Docker container id or unique name. Resolved by
otto.docker.compose.compose_up()viadocker compose -p <proj> ps -q <service>.
- project : str¶
Owning project name (the repo’s settings
name). Combined with parent and service to form the host id.
- compose_project : str¶
The
-pvalue passed todocker composefor this stack. Stored so other commands (logs,ps,down) can scope correctly.
- id : str¶
Unique host id used as the key in
Lab.hostsand on the CLI. Format:<parent_id>.<project>.<service>.
- resources : set[str]¶
Reservation tags. Containers participate in the same reservation system as UnixHosts; the compose module typically copies the parent’s tags so concurrent test runs serialize through reservations.
- power_control : PowerController | None¶
Always None — LocalHost/DockerContainerHost are not power-controlled.
-
async oneshot(cmd: str, timeout: float | None =
None, log: bool =True) CommandStatus¶ Run a single command in the container via the parent.
Stateless and concurrent-safe — each call spawns a fresh
docker exec.run()is the stateful counterpart that preserves shell state across calls.
- async open_session(name: str) HostSession¶
Open a named persistent shell session inside the container.
-
async expect(pattern: str | re.Pattern[str], timeout: float =
10.0) str¶ Wait for a pattern in the container’s session output stream.
- async put(src_files: ~types.Annotated[list[~pathlib.Path] | ~pathlib.Path, ~otto.utils.Arg(variadic=True, elem_type=~pathlib.Path, name=None, help=Local file(s) to upload.)], dest_dir: ~pathlib.Path) tuple[Status, str]¶
Upload local files into the container.
Two-step:
parent.putto a per-container staging dir, thendocker cpfrom there into the container. The staging dir is cleaned up unconditionally so a failed transfer doesn’t leak.
- async get(src_files: ~types.Annotated[list[~pathlib.Path] | ~pathlib.Path, ~otto.utils.Arg(variadic=True, elem_type=~pathlib.Path, name=None, help=Remote file(s) to download.)], dest_dir: ~pathlib.Path) tuple[Status, str]¶
Download files from the container to the local machine.
Two-step:
docker cpfrom the container into a per-container staging dir on the parent, thenparent.getto the local dir.
- rebuild_connections() None¶
Drop any persistent session so the next call reopens it.
Mirrors
rebuild_connections()for theall_hosts() → host.rebuild_connections()pattern thatotto test --covuses to refresh hosts after pytest installs a new event loop. The container host doesn’t own any raw transport (the parent does), but its_session_mgrmay hold aShellSessionwhoseasyncsshprocess is bound to the old loop. Replacing the manager forces lazy re-opens against the parent’s freshly-rebuilt SSH connection.
- async close() None¶
Stop background tasks and tear down the persistent session.
Repeater stops first so a periodic task can’t reopen the session mid-shutdown. The parent’s underlying connection is owned by the parent and is not closed here — but this host must close before its parent so the session’s docker exec channel can drain cleanly.