Documentation
¶
Overview ¶
Package connection defines the transport abstraction the typed Hyper-V client (internal/hyperv) uses to ship PowerShell scripts to a Windows host and read their results back. Three backends are implemented: local (exec pwsh.exe directly), ssh (golang.org/x/crypto/ssh), and winrm (github.com/masterzen/winrm).
Contract highlights: script body via -EncodedCommand, stdin for data, stderr has CLIXML progress noise stripped before reaching the Result. Per-call cost is dominated by PowerShell startup, not transport.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrUnreachable means the transport could not reach the host (DNS, // TCP, TLS handshake, auth failure). Resource code typically retries. ErrUnreachable = errors.New("transport unreachable") // ErrTimeout means the call exceeded its context deadline. Distinct // from ErrUnreachable so callers can decide whether to retry vs. // surface as `timeouts.Diagnostics`. ErrTimeout = errors.New("transport timeout") // ErrSessionDropped means the remote command's session ended without // signalling an exit status -- typically because the SSH/WinRM // channel was torn down mid-command. The wrapped underlying error // is `*ssh.ExitMissingError` ("wait: remote command exited without // exit status or exit signal") on the SSH backend. // // Distinct from ErrUnreachable (which fires on connection setup) // and ErrTimeout (which fires on the operator's deadline). This // signals the cmdlet may have completed on the host and the // response got stranded; typed-client methods that know their // cmdlet is idempotent (Remove-VMSwitch on a switch already gone // is a no-op) can verify post-drop with a follow-up Get rather // than surface a false failure. The canonical case is destroying // an External hyperv_virtual_switch over an SSH connection that // traverses the switch's vEthernet -- the cmdlet succeeds, the // host re-binds the NIC, and the SSH session blinks long enough // that session.Wait() returns ExitMissingError before the exit // status reaches the runner. ErrSessionDropped = errors.New("transport session dropped before exit status") // ErrUnsupportedBackend is returned by the backend selector when the // requested backend identifier is not yet implemented. Removed once // SSH/WinRM backends ship. ErrUnsupportedBackend = errors.New("backend not implemented") )
Functions ¶
This section is empty.
Types ¶
type Connection ¶
type Connection interface {
Runner
// Open establishes any persistent state the backend needs (an SSH
// client, a pooled HTTP transport, etc.). Local is stateless and
// returns nil.
Open(ctx context.Context) error
// Close releases the backend's persistent state. Idempotent. Local
// is a no-op.
Close() error
// Healthcheck returns nil if the backend can reach the host and run
// a trivial command. Used at provider Configure time to fail fast on
// misconfiguration.
Healthcheck(ctx context.Context) error
// Backend returns the lowercase identifier of the implementation —
// "local" | "ssh" | "winrm". Used for tflog field decoration; the
// schema's `backend` attribute is the user-facing form.
Backend() string
}
Connection is the abstract transport. Each provider instance holds a single Connection per (backend, host, user) tuple. Resources never reach for a new one; they share the configured Connection via the typed client in internal/hyperv.
func NewLocal ¶
func NewLocal(opts LocalOptions) (Connection, error)
NewLocal returns a Connection backed by a local pwsh / powershell.exe process per call. Opens nothing — the local backend is stateless.
func NewSSH ¶
func NewSSH(opts SSHOptions) (Connection, error)
NewSSH builds a Connection backed by ssh.Client. It resolves authentication methods and the known_hosts callback up-front so misconfiguration surfaces before Open dials. The client itself is established lazily by Open.
func NewWinRM ¶ added in v0.2.0
func NewWinRM(opts WinRMOptions) (Connection, error)
NewWinRM builds a Connection backed by masterzen/winrm. Validates required fields and applies defaults so callers get a fully-resolved backend; the actual HTTP client is constructed lazily by Open so a unit test that only exercises NewWinRM doesn't pay the construction cost.
Auth methods supported: ntlm (default), basic, kerberos.
Kerberos uses jcmturner/gokrb5 under the hood (pure-Go MIT Kerberos, no GSSAPI library on the runner) and supports two credential modes: password (inline AS-REQ) or ccache (re-use an existing TGT). The caller picks the mode by setting Password or KrbCCachePath; setting both, or neither, is rejected.
type LocalOptions ¶
type LocalOptions struct {
// PwshPath, if non-empty, is used as-is. Set via the provider's
// `local.pwsh_path` attribute or the HYPERV_PWSH_PATH env var.
PwshPath string
}
LocalOptions configures the local backend. Empty values mean "discover from PATH": prefer pwsh (faster cold start), fall back to powershell.exe.
type Result ¶
Result is what every script invocation returns. The transport layer captures four pieces of information; the typed Hyper-V client maps them into typed Go errors.
`Stderr` has CLIXML progress noise stripped before reaching this struct. Real PS errors arrive as a JSON envelope on stderr per the Write-HypervError contract.
`error` from RunScript is reserved for transport failures (connection refused, auth failed, ctx canceled). PS-level failures come back via `ExitCode != 0` plus the structured envelope on `Stderr`.
type Runner ¶
type Runner interface {
RunScript(ctx context.Context, script string, stdinJSON []byte) (Result, error)
// StreamFile copies the bytes at localPath on the runner to
// remotePath on the host. Both paths are absolute. Backends create
// (or truncate) the destination file and write streamed bytes —
// nothing is buffered in memory beyond the backend's transport
// chunk size.
//
// No SHA-256 verification is performed at this layer: the typed
// client (internal/hyperv) computes the local hash before calling
// StreamFile and verifies the remote hash via the existing
// image_file/get.ps1 path after the destination rename. Keeping
// the primitive a pure bytes-copy lets each backend pick its own
// optimal transport without dragging hashing into the protocol.
//
// Cancellation through ctx interrupts the stream; partial files at
// the remote path are the caller's problem to clean up. Resources
// that need atomicity stage to a `.part` sibling and rename, the
// same pattern image_file's url-mode uses.
StreamFile(ctx context.Context, localPath, remotePath string) error
}
Runner is the narrowest useful interface — just "run a script, get a result." Connection composes Runner with lifecycle methods (Open/Close/ Healthcheck) that backends with persistent state need.
The split exists so unit tests can implement just Runner via the fake in internal/testutil, without faking lifecycle calls that don't matter for the typed-client tests.
Calling convention:
`script` is the full PowerShell body. The caller has already concatenated common/preamble.ps1 to the top. Backends transmit it as UTF-16LE base64 via -EncodedCommand. **Never** via stdin or as a command-line argument — multi-line scripts get mis-parsed otherwise.
`stdinJSON` is structured input. Empty for scripts that don't need input. Backends pipe these bytes to the PS process's stdin. Scripts read with `$input_json = $Input | ConvertFrom-Json -Compress`.
The returned `Result` carries the four useful streams. The error return is reserved for transport-level failures (connection refused, ctx canceled). Non-zero `ExitCode` is the application-level signal.
type SSHOptions ¶
type SSHOptions struct {
Host string // required
Port int // default 22
Username string // required
// Auth methods, in priority order:
//
// 1. PrivateKey (raw key contents -- wins if both PrivateKey and PrivateKeyPath are set)
// 2. PrivateKeyPath (path read at Open time)
// 3. Password (fallback only -- key auth is preferred)
//
// Passphrase decrypts the key when set.
//
// All three sensitive fields use []byte so Close() can zero the
// long-lived copy held by the backend. Libraries we hand the value
// to (golang.org/x/crypto/ssh) make their own copies; zeroing here
// covers the provider's own state, not theirs.
PrivateKey []byte
PrivateKeyPath string
Passphrase []byte
Password []byte
// KnownHostsPath is the file used for host key verification. Default:
// ~/.ssh/known_hosts. Empty path falls back to the default. A missing
// file is a hard error -- silently disabling host-key checking would be
// a security regression.
KnownHostsPath string
// Timeout is the dial timeout. Default 30s.
Timeout time.Duration
// CommandTimeout bounds an individual RunScript call. Default
// 5m. A wedged remote cmdlet surfaces as ErrTimeout instead of
// blocking the whole apply. Set to 0 to disable.
CommandTimeout time.Duration
// KeepaliveInterval is how often the backend sends an SSH
// keepalive request while the persistent client is open.
// Default 30s. Prevents NAT/firewall mid-apply drops. Set to
// 0 to disable.
KeepaliveInterval time.Duration
// PwshPath is the binary the remote shell invokes per call. Default:
// "powershell.exe" -- universally available on Windows. Set to "pwsh"
// or "pwsh.exe" to prefer PS 7+ if installed.
PwshPath string
// MaxConcurrentSessions caps the number of in-flight RunScript calls
// against the persistent ssh.Client. OpenSSH's per-connection
// MaxSessions limit (default 10, often 4-6 on hardened Windows
// builds) rejects late session opens with "rejected: connect failed
// (open failed)" once exceeded. Default 4 stays well under typical
// Windows OpenSSH caps; raise it on hosts with sshd_config tuned
// upward, lower it if the bench surfaces the error under heavier
// fanout. Set to 0 to use the default; negative values disable the
// cap entirely (not recommended).
MaxConcurrentSessions int
}
SSHOptions configures the SSH backend. The provider's Configure pass resolves env vars + provider attributes into this struct (see internal/provider/backend_select.go).
type WinRMOptions ¶ added in v0.2.0
type WinRMOptions struct {
Host string // required
Port int // default 5986 (HTTPS) or 5985 (HTTP)
Username string // required
// Password is []byte so Close() can zero the long-lived copy held
// by the backend. masterzen/winrm copies the value into its own
// EndpointParams when we construct the client; that copy is outside
// our reach, but our state stays clean across the connection's life.
Password []byte // required for ntlm/basic; for kerberos, mutually exclusive with KrbCCachePath
UseHTTPS bool // default true; flip to false only for diagnosing TLS-only failures
Insecure bool // skip TLS certificate verification (default false)
Auth string // "ntlm" | "basic" | "kerberos"; default "ntlm"
CACert string // path to a CA bundle PEM; empty = system roots
// Kerberos auth fields. Only meaningful when Auth=="kerberos"; ignored
// otherwise. The provider-config layer is responsible for catching the
// "kerberos fields set without auth=kerberos" misconfig at plan time;
// this struct just transports the values.
//
// KrbRealm is required (NewWinRM rejects empty when Auth=="kerberos").
// KrbSpn defaults to "HTTP/<Host>" when empty.
// KrbConfigPath defaults to first-existing of $KRB5_CONFIG,
// ~/.config/krb5.conf, /etc/krb5.conf when empty.
// KrbCCachePath, when set, switches from password-mode (inline AS-REQ)
// to ccache-mode (re-use a pre-existing TGT). When set, Password is
// ignored.
KrbRealm string
KrbSpn string
KrbConfigPath string
KrbCCachePath string
// Timeout sets `http.Client.Timeout` for every WSMan request.
// Default 0 (no wall-clock cap) -- file transfers can run for
// arbitrarily long. The initial TCP dial is bounded by the OS;
// a connection that stalls mid-transfer (host freezes, packets
// silently dropped) holds the apply until ctx cancellation
// (operator Ctrl+C).
Timeout time.Duration
// CommandTimeout bounds a single RunScript call. Default 5m. A wedged
// remote cmdlet surfaces as ErrTimeout instead of blocking the whole
// apply. Set to 0 to disable.
CommandTimeout time.Duration
// PwshPath is the binary the remote shell invokes per call. Default:
// "powershell.exe" -- universally available on Windows. Set to "pwsh"
// or "pwsh.exe" to prefer PS 7+ if installed.
PwshPath string
}
WinRMOptions configures the WinRM backend. The provider's Configure pass resolves env vars + provider attributes into this struct (see internal/provider/backend_select.go).