Skip to content

v0.2.0

Choose a tag to compare

@github-actions github-actions released this 27 Jul 15:09

Rebases pytruenas' generic host machinery onto [hostctl], keeping only the
TrueNAS-specific parts here: the middleware websocket, the api namespace,
login/2FA, subscriptions, and the upload/download side channels. Everything
else — shell quoting, transport selection, the asyncssh lifecycle, path
backends — is now inherited.

Requires hostctl>=0.1.0,<0.2.

Changed

  • BREAKING: TrueNASClient.shell is gone. .shell now means what it means
    throughout hostctl — the bound shell object (client.shell.run(...)). The
    SSH connection target lives on the configuration as client.config.ssh, an
    SshConfig. The constructor argument is still spelled shell= and still
    takes a connection string (shell="ssh://root@nas").
  • .run() and .path() now select a transport rather than branching.
    Which one serves a call is chosen from the available providers, and
    .last_selection records what was tried and why — with credentials redacted.
    Previously .run() hard-coded a local-vs-SSH branch and TruenasPath
    hand-rolled its own SFTP→websocket fallback.
  • A remote target with no SSH can now run commands over the web shell. The
    TrueNAS JSON-RPC API exposes no remote command execution (verified against
    26.0.0-BETA.1: of 781 methods only core.resize_shell and
    user.shell_choices are shell-adjacent, and the former only resizes an
    already-open session) — so such a host previously had no run() at all.
    /websocket/shell, the PTY the web UI's Shell page drives, is a real command
    channel on the same port. Pass executor=["ssh"] to require SSH instead.
  • The scheme/API-path probe moved from construction to first connect.
    TrueNASClient("bad-host") now constructs successfully and raises on first
    use. Configs are therefore buildable offline, which is what HostConfig
    requires.

Added

  • pytruenas.hostTrueNASConfig (a hostctl.host.HostConfig) and
    TrueNASHost (a PosixHost). HostConfig("truenas+wss://nas") resolves
    through hostctl's registry; every connection string TrueNASClient accepts
    still works, normalized to a truenas+* scheme.
  • TrueNASClient and TrueNASHost are now one class. They were briefly
    two objects that forwarded halves of their surface to each other —
    client.run() called client.host.run() while host.api called
    host.client.api, each holding a reference to the other. TrueNASClient is
    an alias for TrueNASHost, so every existing import and call keeps working,
    and client.host / host.client both return the object itself.
    TrueNASHost("wss://nas") also takes a connection string directly, with the
    same options as TrueNASConfig.from_target.
  • pytruenas.providersTnasWsPathProvider (the filesystem.*
    websocket leg) and local_providers(), which returns hostctl's stock local
    executor and path providers unchanged. A local target runs plain
    subprocess and uses plain local paths; there is nothing TrueNAS-specific to
    add, so pytruenas defines no provider class for it.
  • pytruenas.webshellWebShellExecutorProvider, command execution over
    /websocket/shell. Ordered after SSH; declares its limits rather than hiding
    them (stdout and stderr are one stream, no piped input, single-line commands
    only — pipes and here-strings work, being ordinary shell syntax).
  • executor= / path= on TrueNASConfig — name the providers to use, in
    preference order, as a single name or a sequence: executor=["ssh"],
    path=["local", "tnasws"], executor=[] for no command channel at all.
    Unknown names, and ssh/sftp without an SshConfig, raise rather than
    composing a host that would fail later. Matches hostctl's own
    SystemConfig(executor=..., path=...) spelling.
  • Credentials.from_host_credentials() — maps hostctl's already-parsed
    credential mapping (including a URI-supplied OTP) onto a Credentials
    subclass, with no second round of string parsing.
  • Inherited from hostctl: .capabilities (so a host that genuinely cannot
    run commands says so up front rather than failing mid-call),
    .last_selection, .info(), .spawn(), .connect()/.close(), and
    context-manager support.
  • black in the dev extra, pinned to the 3.9 floor.

Removed

  • BREAKING: pytruenas.jsonrpc is now pytruenas.connection, and its
    Client class is TrueNASWSConnection. Client was doubly wrong: the class
    is not generic JSON-RPC (it knows core.subscribe, TrueNAS error codes, and
    the middleware unix socket), and the name collided with TrueNASClient.
    client.conn is the connection, with .websocket kept as an alias.
  • BREAKING: pytruenas.client and pytruenas._conn are gone.
    from pytruenas import TrueNASClient is unaffected. _conn was a re-export
    shim for swapping the client implementation, which never happened; client
    had been reduced to an alias by the host/client merge.
  • ~190 lines of generic host machinery: shell quoting, the local-vs-SSH branch
    in run(), the asyncssh connection, and _shellquote.

Known limitations

  • A local unix-socket client cannot use download(). The HTTP side channel
    resolves to https://localhost and trips the appliance's self-signed
    certificate. This is pre-existing and unrelated to the migration — the URL
    construction is byte-identical to 0.1.1.
  • The web shell merges stdout and stderr (a PTY is one stream), takes no
    piped input=, and requires single-line commands. Pipes and here-strings
    work, being ordinary shell syntax. It is ordered after SSH for these reasons.

Full Changelog: v0.1.1...v0.2.0