diff --git a/docs/runbooks/profile-compose-e2e.md b/docs/runbooks/profile-compose-e2e.md index 2ffeb72..0c97f54 100644 --- a/docs/runbooks/profile-compose-e2e.md +++ b/docs/runbooks/profile-compose-e2e.md @@ -16,9 +16,10 @@ Provision a compose-based e2e sandbox via `ext.compose-ssh` (e2e-framework linea - Sufficient disk for images ```bash -export SANDBOXER_HOST= # e.g. coulombcore IP -export SANDBOXER_SSH_USER=root # optional -export SANDBOXER_SSH_KEY=~/.ssh/id_rsa # optional +export SANDBOXER_HOST=coulombcore # or 92.205.130.254 +# Omit SANDBOXER_SSH_USER to use ~/.ssh/config (CoulombCore: tegwick + id_ops) +# export SANDBOXER_SSH_USER=tegwick # only if not in ssh config +# export SANDBOXER_SSH_KEY=~/.ssh/id_ops # only to override config ``` ## Create diff --git a/extensions/ext.compose-ssh.yaml b/extensions/ext.compose-ssh.yaml index 7a66370..f581821 100644 --- a/extensions/ext.compose-ssh.yaml +++ b/extensions/ext.compose-ssh.yaml @@ -12,5 +12,5 @@ capabilities: pricing_model: self-hosted config: base_dir: /tmp/sandboxer - ssh_user: root + # ssh_user omitted — uses SANDBOXER_SSH_USER or ~/.ssh/config (e.g. tegwick@coulombcore) compose_timeout_s: 180 \ No newline at end of file diff --git a/src/sandboxer/extensions/compose_ssh.py b/src/sandboxer/extensions/compose_ssh.py index fe70de3..1113df3 100644 --- a/src/sandboxer/extensions/compose_ssh.py +++ b/src/sandboxer/extensions/compose_ssh.py @@ -18,7 +18,7 @@ class ComposeSSHExtension: def __init__(self, config: dict[str, Any] | None = None) -> None: cfg = config or {} self.base_dir: str = cfg.get("base_dir", "/tmp/sandboxer") - self.ssh_user: str = cfg.get("ssh_user", "root") + self.ssh_user: str | None = cfg.get("ssh_user") self.compose_timeout_s: int = int(cfg.get("compose_timeout_s", 180)) def provision( @@ -33,7 +33,7 @@ class ComposeSSHExtension: sandbox_id = inputs.get("sandbox_id") or str(uuid.uuid4())[:8] remote_dir = f"{self.base_dir}/{sandbox_id}" - ssh = SSHConfig.from_env(host, user=self.ssh_user) + ssh = SSHConfig.from_env(host, user=self.ssh_user or None) rc, out = ssh.run(f"mkdir -p {remote_dir}") if rc != 0: @@ -64,7 +64,8 @@ class ComposeSSHExtension: def wait_ready(self, handle: dict[str, str]) -> dict[str, str]: """Confirm compose services are running (no HTTP health polling).""" - ssh = SSHConfig.from_env(handle["host"], user=handle.get("ssh_user", self.ssh_user)) + ssh_user = handle.get("ssh_user") or self.ssh_user or None + ssh = SSHConfig.from_env(handle["host"], user=ssh_user) project = handle["compose_project"] remote_dir = handle["remote_dir"] compose_file = handle["compose_file"] @@ -83,7 +84,8 @@ class ComposeSSHExtension: } def teardown(self, handle: dict[str, str]) -> dict[str, str]: - ssh = SSHConfig.from_env(handle["host"], user=handle.get("ssh_user", self.ssh_user)) + ssh_user = handle.get("ssh_user") or self.ssh_user or None + ssh = SSHConfig.from_env(handle["host"], user=ssh_user) project = handle.get("compose_project") remote_dir = handle.get("remote_dir") compose_file = handle.get("compose_file") diff --git a/src/sandboxer/extensions/ssh.py b/src/sandboxer/extensions/ssh.py index 8f16939..dd53692 100644 --- a/src/sandboxer/extensions/ssh.py +++ b/src/sandboxer/extensions/ssh.py @@ -11,19 +11,27 @@ from pathlib import Path @dataclass class SSHConfig: host: str - user: str = "root" + user: str | None = None key: str | None = None connect_timeout: int = 15 + @property + def destination(self) -> str: + """SSH destination; omit user when unset so ~/.ssh/config applies.""" + if self.user: + return f"{self.user}@{self.host}" + return self.host + @property def target(self) -> str: - return f"{self.user}@{self.host}" + return self.destination @classmethod def from_env(cls, host: str, *, user: str | None = None, key: str | None = None) -> SSHConfig: + env_user = os.environ.get("SANDBOXER_SSH_USER") return cls( host=host, - user=user or os.environ.get("SANDBOXER_SSH_USER", "root"), + user=user if user is not None else (env_user or None), key=key or os.environ.get("SANDBOXER_SSH_KEY"), ) @@ -39,7 +47,7 @@ class SSHConfig: ] if self.key: args += ["-i", self.key] - args.append(self.target) + args.append(self.destination) return args def run(self, cmd: str, *, timeout: int = 60) -> tuple[int, str]: diff --git a/tests/test_ssh.py b/tests/test_ssh.py new file mode 100644 index 0000000..96e5b73 --- /dev/null +++ b/tests/test_ssh.py @@ -0,0 +1,13 @@ +"""SSH config behavior.""" + +from sandboxer.extensions.ssh import SSHConfig + + +def test_destination_uses_ssh_config_when_user_unset() -> None: + ssh = SSHConfig(host="92.205.130.254") + assert ssh.destination == "92.205.130.254" + + +def test_destination_includes_explicit_user() -> None: + ssh = SSHConfig(host="92.205.130.254", user="tegwick") + assert ssh.destination == "tegwick@92.205.130.254" \ No newline at end of file