Kubernetes and SSH Tunneling
The dapcli Kubernetes module provides a secure proxy mechanism for executing kubectl commands and managing cluster resources from an operator’s laptop, which typically lacks direct network access to the cluster’s pod CIDR. This is achieved by tunneling SSH traffic through a management host to an Incus-managed VM (the “runner”) that resides on the cluster network. The module abstracts the complexity of this multi-hop execution chain, handling SSH configuration, argument quoting to prevent shell mangling, and resource lifecycle management for port forwards.
Architecture and Control Flow
Section titled “Architecture and Control Flow”The execution path involves three distinct layers: the local CLI, the remote management host, and the isolated runner VM. The CLI initiates an SSH connection to the management host, which then executes an incus exec command to run a shell inside the designated VM. All kubectl operations are performed within this VM environment.
Configuration and Initialization
Section titled “Configuration and Initialization”Access parameters for the runner environment are defined in the RunnerConfig dataclass, which is frozen to ensure immutability. Configuration is primarily driven by environment variables, which can be initialized via dap config init. The default values assume a standard Ed25519 key and a VM named dap-runner.
DAP_RUNNER_SSH_HOST: The SSH target for the management host (default:user@mgmt.example.com).DAP_RUNNER_SSH_KEY: Path to the SSH private key (default:~/.ssh/id_ed25519).DAP_RUNNER_VM: The name of the Incus VM to target (default:dap-runner).
The RunnerConfig.from_env() class method retrieves these values, expanding the SSH key path to an absolute path via Path.expanduser() 1.
Command Execution and Quoting
Section titled “Command Execution and Quoting”Directly passing argument lists to SSH is problematic because SSH concatenates all arguments following the host specification with spaces. This behavior can mangle kubectl flags (e.g., -n might be interpreted by the remote shell rather than kubectl), leading to silent failures or help output.
To mitigate this, the module uses shlex.quote() to properly escape each argument before joining them into a single string payload. The run_in_runner function constructs the remote command as incus exec <vm_name> -- bash -lc <quoted_command>. The kubectl_in_runner helper wraps this by formatting the kubectl command with pre-quoted arguments.
Port Forwarding and HTTP Probes
Section titled “Port Forwarding and HTTP Probes”For services requiring persistent connections, the port_forward context manager opens a kubectl port-forward process inside the runner VM. It yields the local port on the runner (not the operator’s laptop) and ensures the process is killed upon exiting the context 2.
For one-off HTTP requests, runner_curl executes curl inside the runner. It parses the HTTP status code from the output and returns the body separately, allowing callers to handle responses without parsing raw shell output.
"""SSH-via-runner kubectl proxy.
The operator's laptop typically has no direct network path to the
cluster's pod CIDR, so every cluster operation tunnels through an SSH
jump to the management host, then ``incus exec`` into an installation
runner VM that lives on the cluster network, then runs ``kubectl``
from there.
This module hides that chain behind a handful of helpers:
- :func:`run_in_runner` -- execute a shell command inside the runner.
- :func:`kubectl_in_runner` -- execute a kubectl command, with proper
argv quoting (SSH concatenates everything past the host with
spaces, so naive list-based invocation mangles kubectl flags).
- :func:`port_forward` -- context manager that opens
``kubectl port-forward`` inside the runner.
- :func:`runner_curl` -- one-shot HTTP probe from inside the
runner; convenience wrapper around :func:`run_in_runner`.
Configuration via env (overridden by ``dap config init``):
- ``DAP_RUNNER_SSH_HOST`` -- default ``<EMAIL>``
- ``DAP_RUNNER_SSH_KEY`` -- default ``~/.ssh/id_ed25519``
- ``DAP_RUNNER_VM`` -- Incus VM name, default ``dap-runner``
"""
from __future__ import annotations
import os
import shlex
import subprocess
from collections.abc import Iterator
from contextlib import contextmanager
from dataclasses import dataclass
from pathlib import Path
from ..errors import DapError
@dataclass(frozen=True)
"""Open ``kubectl port-forward`` inside the runner.
Yields the local port on the *runner* (not on the operator's
laptop). To make HTTP calls against it, route them through the
same SSH+incus chain via :func:`runner_curl`, or call from another
command in the same runner shell.
"""
cfg = config or RunnerConfig.from_env()
script = (
f"kubectl -n {shlex.quote(namespace)} port-forward {shlex.quote(pod)} "
f"{local_port}:{remote_port} >/tmp/dap-pf-{local_port}.log 2>&1 & PF=$!; "
f"sleep 2; echo READY-$PF"
)
rc, out, err = run_in_runner(script, timeout=15, config=cfg)
if rc != 0 or "READY-" not in out:
raise DapError(f"port-forward failed: rc={rc} out={out!r} err={err.strip()[:200]}")
pid = out.strip().rsplit("-", 1)[-1]
try:
yield local_port
finally:
run_in_runner(f"kill {pid} 2>/dev/null || true", timeout=5, config=cfg)
def runner_curl(
url: str,
*,
method: str = "GET",
headers: dict[str, str] | None = None,
data: str | None = None,
timeout: int = 10,
runner_config: RunnerConfig | None = None,
) -> tuple[int, str]:
"""One-shot curl from inside the runner. Returns (http_status, body)."""
parts = [
"curl",
"-ks",
"-X",
method,
shlex.quote(url),
"-w",