Skip to content
This repository was archived by the owner on Jan 23, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/source/api-reference/adapters/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Adapter Packages

```{toctree}
network.md
```
95 changes: 95 additions & 0 deletions docs/source/api-reference/adapters/network.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Network adapters

Network adapters are for transforming network connections exposed by drivers

```{eval-rst}
.. autoclass:: jumpstarter_driver_network.adapters.TcpPortforwardAdapter
:members:
```

```{eval-rst}
.. autoclass:: jumpstarter_driver_network.adapters.UnixPortforwardAdapter
:members:
```

```{eval-rst}
.. autoclass:: jumpstarter_driver_network.adapters.NovncAdapter
:members:
```

```{eval-rst}
.. autoclass:: jumpstarter_driver_network.adapters.PexpectAdapter
:members:
```

```{eval-rst}
.. autoclass:: jumpstarter_driver_network.adapters.FabricAdapter
:members:
```

## Examples
```yaml
export:
tcp_port:
type: "jumpstarter_driver_network.driver.TcpNetwork"
config:
host: localhost
port: 80
unix_socket:
type: "jumpstarter_driver_network.driver.UnixNetwork"
config:
path: /tmp/test.sock
```

Forward a remote TCP port to a local TCP port

```{testcode}
# random port on localhost
with TcpPortforwardAdapter(client.tcp_port) as addr:
print(addr[0], addr[1]) # 127.0.0.1 38406

# specific address and port
with TcpPortforwardAdapter(client.tcp_port, local_host="192.0.2.1", local_port=8080) as addr:
print(addr[0], addr[1]) # 192.0.2.1 8080
```

Forward a remote Unix domain socket to a local socket

```{testcode}
with UnixPortforwardAdapter(client.unix_socket) as addr:
print(addr) # /tmp/jumpstarter-w30wxu64/socket

# the type of the remote socket and the local one doesn't have to match
# e.g. forward a remote Unix domain socket to a local TCP port
with TcpPortforwardAdapter(client.unix_socket) as addr:
print(addr[0], addr[1]) # 127.0.0.1 38406
```

Connect to a remote TCP port with a web-based VNC client

```{testcode}
with NovncAdapter(client.tcp_port) as url:
print(url) # https://novnc.com/noVNC/vnc.html?autoconnect=1&reconnect=1&host=127.0.0.1&port=36459
# open the url in browser to access the VNC client
```

Interact with a remote TCP port as if it's a serial console

See [pexpect](https://pexpect.readthedocs.io/en/stable/api/fdpexpect.html) for API documentation

```{testcode}
with PexpectAdapter(client.tcp_port) as expect:
expect.expect("localhost login:")
expect.send("root\n")
expect.expect("Password:")
expect.send("secret\n")
```

Connect to a remote TCP port with the fabric SSH client

See [fabric](https://docs.fabfile.org/en/latest/api/connection.html#fabric.connection.Connection) for API documentation

```{testcode}
with FabricAdapter(client=client.tcp_port, connect_kwargs={"password": "secret"}) as conn:
conn.run("uname")
```
1 change: 1 addition & 0 deletions docs/source/api-reference/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ This section provides details on the Jumpstarter core API and contrib drivers.
drivers.md
adapters.md
drivers/index.md
adapters/index.md
```
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from .fabric import FabricAdapter
from .novnc import NovncAdapter
from .pexpect import PexpectAdapter
from .portforward import PortforwardAdapter
from .portforward import TcpPortforwardAdapter, UnixPortforwardAdapter

__all__ = ["FabricAdapter", "NovncAdapter", "PexpectAdapter", "PortforwardAdapter"]
__all__ = ["FabricAdapter", "NovncAdapter", "PexpectAdapter", "TcpPortforwardAdapter", "UnixPortforwardAdapter"]
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
from fabric.config import Config
from fabric.connection import Connection

from .portforward import PortforwardAdapter
from .portforward import TcpPortforwardAdapter


@dataclass(kw_only=True)
class FabricAdapter(PortforwardAdapter):
class FabricAdapter(TcpPortforwardAdapter):
user: str | None = None
config: Config | None = None
forward_agent: bool | None = None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
from urllib.parse import urlencode, urlunparse

from ..streams import WebsocketServerStream
from .portforward import PortforwardAdapter
from .portforward import TcpPortforwardAdapter
from jumpstarter.streams import forward_stream


@dataclass(kw_only=True)
class NovncAdapter(PortforwardAdapter):
class NovncAdapter(TcpPortforwardAdapter):
async def __aenter__(self):
addr = await super().__aenter__()
return urlunparse(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@

from pexpect.fdpexpect import fdspawn

from .portforward import PortforwardAdapter
from .portforward import TcpPortforwardAdapter


@dataclass(kw_only=True)
class PexpectAdapter(PortforwardAdapter):
class PexpectAdapter(TcpPortforwardAdapter):
async def __aenter__(self):
addr = await super().__aenter__()

Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,28 @@
from dataclasses import dataclass

from jumpstarter.client.adapters import ClientAdapter
from jumpstarter.common import TemporaryTcpListener
from jumpstarter.common import TemporaryTcpListener, TemporaryUnixListener
from jumpstarter.streams import forward_stream


@dataclass(kw_only=True)
class PortforwardAdapter(ClientAdapter):
method: str = "connect"

async def __aexit__(self, exc_type, exc_value, traceback):
return await self.listener.__aexit__(exc_type, exc_value, traceback)

async def handler(self, conn):
async with conn:
async with self.client.stream_async(self.method) as stream:
async with forward_stream(conn, stream):
pass


@dataclass(kw_only=True)
class TcpPortforwardAdapter(PortforwardAdapter):
local_host: str = "127.0.0.1"
local_port: int = 0
method: str = "connect"

async def __aenter__(self):
self.listener = TemporaryTcpListener(
Expand All @@ -18,11 +31,10 @@ async def __aenter__(self):

return await self.listener.__aenter__()

async def __aexit__(self, exc_type, exc_value, traceback):
return await self.listener.__aexit__(exc_type, exc_value, traceback)

async def handler(self, conn):
async with conn:
async with self.client.stream_async(self.method) as stream:
async with forward_stream(conn, stream):
pass
@dataclass(kw_only=True)
class UnixPortforwardAdapter(PortforwardAdapter):
async def __aenter__(self):
self.listener = TemporaryUnixListener(self.handler)

return await self.listener.__aenter__()
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import pytest
from anyio.from_thread import start_blocking_portal

from .adapters import PortforwardAdapter
from .adapters import TcpPortforwardAdapter, UnixPortforwardAdapter
from .driver import EchoNetwork, TcpNetwork, UdpNetwork, UnixNetwork
from jumpstarter.common import TemporaryTcpListener, TemporaryUnixListener
from jumpstarter.common.utils import serve
Expand Down Expand Up @@ -41,13 +41,24 @@ def test_tcp_network_portforward():
with start_blocking_portal() as portal:
with portal.wrap_async_context_manager(TemporaryTcpListener(echo_handler, local_host="127.0.0.1")) as inner:
with serve(TcpNetwork(host=inner[0], port=inner[1])) as client:
with PortforwardAdapter(client=client) as addr:
with TcpPortforwardAdapter(client=client) as addr:
stream = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
stream.connect(addr)
stream.send(b"hello")
assert stream.recv(5) == b"hello"


def test_unix_network_portforward():
with start_blocking_portal() as portal:
with portal.wrap_async_context_manager(TemporaryUnixListener(echo_handler)) as inner:
with serve(UnixNetwork(path=inner)) as client:
with UnixPortforwardAdapter(client=client) as addr:
stream = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
stream.connect(str(addr))
stream.send(b"hello")
assert stream.recv(5) == b"hello"


def test_udp_network():
with serve(
UdpNetwork(
Expand Down Expand Up @@ -90,7 +101,7 @@ def test_tcp_network_performance():
stderr=subprocess.DEVNULL,
)

with PortforwardAdapter(client=client) as addr:
with TcpPortforwardAdapter(client=client) as addr:
subprocess.run(
[
"iperf3",
Expand Down