Skip to content

Commit 6215697

Browse files
committed
add ShellSession
1 parent 5471f5a commit 6215697

8 files changed

Lines changed: 761 additions & 5 deletions

File tree

README.md

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -712,8 +712,8 @@ with mistapi.websockets.sites.DeviceStatsEvents(apisession, site_ids=["<site_id>
712712
| Module | Device Type | Functions |
713713
|--------|-------------|-----------|
714714
| `device_utils.ap` | Mist Access Points | `ping`, `traceroute`, `retrieveArpTable` |
715-
| `device_utils.ex` | Juniper EX Switches | `ping`, `monitorTraffic`, `topCommand`, `retrieveArpTable`, `retrieveBgpSummary`, `retrieveDhcpLeases`, `releaseDhcpLeases`, `retrieveMacTable`, `clearMacTable`, `clearLearnedMac`, `clearBpduError`, `clearDot1xSessions`, `clearHitCount`, `bouncePort`, `cableTest` |
716-
| `device_utils.srx` | Juniper SRX Firewalls | `ping`, `monitorTraffic`, `topCommand`, `retrieveArpTable`, `retrieveBgpSummary`, `retrieveDhcpLeases`, `releaseDhcpLeases`, `retrieveOspfDatabase`, `retrieveOspfNeighbors`, `retrieveOspfInterfaces`, `retrieveOspfSummary`, `retrieveSessions`, `clearSessions`, `bouncePort`, `retrieveRoutes` |
715+
| `device_utils.ex` | Juniper EX Switches | `ping`, `monitorTraffic`, `topCommand`, `interactiveShell`, `createShellSession`, `retrieveArpTable`, `retrieveBgpSummary`, `retrieveDhcpLeases`, `releaseDhcpLeases`, `retrieveMacTable`, `clearMacTable`, `clearLearnedMac`, `clearBpduError`, `clearDot1xSessions`, `clearHitCount`, `bouncePort`, `cableTest` |
716+
| `device_utils.srx` | Juniper SRX Firewalls | `ping`, `monitorTraffic`, `topCommand`, `interactiveShell`, `createShellSession`, `retrieveArpTable`, `retrieveBgpSummary`, `retrieveDhcpLeases`, `releaseDhcpLeases`, `retrieveOspfDatabase`, `retrieveOspfNeighbors`, `retrieveOspfInterfaces`, `retrieveOspfSummary`, `retrieveSessions`, `clearSessions`, `bouncePort`, `retrieveRoutes` |
717717
| `device_utils.ssr` | Juniper SSR Routers | `ping`, `retrieveArpTable`, `retrieveBgpSummary`, `retrieveDhcpLeases`, `releaseDhcpLeases`, `retrieveOspfDatabase`, `retrieveOspfNeighbors`, `retrieveOspfInterfaces`, `retrieveOspfSummary`, `retrieveSessions`, `clearSessions`, `bouncePort`, `retrieveRoutes`, `showServicePath` |
718718

719719
### Device Utilities Usage
@@ -831,6 +831,54 @@ All device utility functions return a `UtilResponse` object:
831831
- `ap.TracerouteProtocol``ICMP`, `UDP` (for `ap.traceroute()`)
832832
- `srx.Node` / `ssr.Node``NODE0`, `NODE1` (for dual-node devices)
833833

834+
### Interactive Shell
835+
836+
`interactiveShell()` and `createShellSession()` provide SSH-over-WebSocket access to EX and SRX devices. Unlike the diagnostic utilities above, the shell is **bidirectional** — you send keystrokes and receive terminal output in real time.
837+
838+
#### Interactive mode (human at the keyboard)
839+
840+
Takes over the terminal. Blocks until the connection closes or you press Ctrl+C:
841+
842+
```python
843+
from mistapi.device_utils import ex
844+
845+
ex.interactiveShell(apisession, site_id, device_id)
846+
```
847+
848+
Requires the `sshkeyboard` package (installed automatically as a dependency).
849+
850+
#### Programmatic mode
851+
852+
Use `createShellSession()` to get a `ShellSession` object for scripting:
853+
854+
```python
855+
from mistapi.device_utils import ex
856+
import time
857+
858+
with ex.createShellSession(apisession, site_id, device_id) as session:
859+
session.send_text("show version\r\n")
860+
time.sleep(3)
861+
while True:
862+
data = session.recv(timeout=0.5)
863+
if data is None:
864+
break
865+
print(data.decode("utf-8", errors="replace"), end="")
866+
```
867+
868+
#### ShellSession API
869+
870+
| Method / Property | Returns | Description |
871+
|-------------------|---------|-------------|
872+
| `connect()` | `None` | Open the WebSocket connection. Called automatically by `createShellSession()`. |
873+
| `disconnect()` | `None` | Close the WebSocket connection. |
874+
| `connected` | `bool` | `True` if the WebSocket is currently connected. |
875+
| `send(data)` | `None` | Send raw bytes (keystrokes) to the device. |
876+
| `send_text(text)` | `None` | Send a text string to the device (auto-prefixed with `\x00`). |
877+
| `recv(timeout=0.1)` | `bytes \| None` | Receive output from the device. Returns `None` on timeout or if disconnected. |
878+
| `resize(rows, cols)` | `None` | Send a terminal resize message. |
879+
880+
`ShellSession` also supports the context manager protocol (`with` statement).
881+
834882
---
835883

836884
## Development and Testing

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ dependencies = [
2828
"hvac>=2.3.0",
2929
"keyring>=24.3.0",
3030
"websocket-client>=1.8.0",
31+
"sshkeyboard>=2.3.1",
3132
]
3233

3334
[project.urls]

src/mistapi/device_utils/__tools/miscellaneous.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -353,9 +353,7 @@ def top_command(
353353
def _ws_factory(trigger):
354354
if isinstance(trigger.data, dict) and "url" in trigger.data:
355355
return SessionWithUrl(apisession, url=trigger.data.get("url", ""))
356-
LOGGER.error(
357-
"Monitor traffic command did not return a valid URL: %s", trigger.data
358-
)
356+
LOGGER.error("Top command command did not return a valid URL: %s", trigger.data)
359357
return None
360358

361359
util_response = UtilResponse()

0 commit comments

Comments
 (0)