Documentation
¶
Overview ¶
Package invoke runs commands and transfers files on execution targets — the local machine, remote hosts over SSH, and containers — through one provider-agnostic interface.
The Docker provider lives in a separate module, github.com/ruffel/invoke/docker, so its dependency tree stays out of the graph of anyone who does not use it.
Getting started ¶
An Environment is a target. An Executor wraps one and adds the policy most callers want: capturing output, retrying, running under sudo. Constructing the target is the only line that changes between them.
env, err := local.New() // or ssh.New(ctx, host, ...), docker.New(ctx, container, ...)
if err != nil {
return err
}
defer env.Close()
res, stdout, _, err := invoke.NewExecutor(env).Output(ctx, invoke.New("uname", "-s"))
Command is a plain value describing what to run. It carries no streams and no state, so one can be run repeatedly, or against several targets, without being rebuilt. Where the output goes is a property of the invocation rather than the command, and lives in IO.
Knowing what happened ¶
Every failure answers one question first: did the command run?
- ExitError — it ran and failed. The exit status is trustworthy.
- TransportError — the connection to the target failed. Nothing can be concluded about the command.
- ErrNotFound, ErrInvalidWorkdir — it could not be started.
- ErrClosed — the caller stopped it, or the environment was closed.
- ErrNotSupported — the target cannot do what was asked.
Anything a provider cannot classify stays an ordinary error, and ordinary errors are never retried. Retrying is something a provider has to earn by naming a failure as transient.
Retries ¶
WithRetry re-runs only TransportError, because it is the only family that says the command did not run.
That distinction is sharper than it first looks. A connection dying under a running command is a transport failure by any ordinary reading, but the command may already have taken effect and nothing on this side can tell whether it did. Retrying would run an arbitrary command a second time, so such a failure is terminal and the error says so.
File transfers are the exception, and only because they are built to be: each is delivered whole or not at all, so repeating one is safe.
Optional features ¶
Targets differ in what they can do, and say so through Capabilities. The rule is symmetric, and the contract suite enforces both halves: a declared capability must work, and an undeclared one must fail with ErrNotSupported rather than being quietly ignored.
- TTY — allocated by every target that runs real processes.
- Signals — delivered by every target, though a container must have a shell for one to reach the right process; without a shell the capability is not declared. The docker package documents why, and the ssh package documents the one thing its protocol will not confirm. A signal always reaches the process it names; whether it also reaches that process's children is a property of the target, described on Process.Signal.
- SymlinkPreserve — honored by every target.
Testing ¶
The fake package is a working target that keeps its filesystem in memory. It is not a mock: it passes the same contract suite the real providers do, so a test written against it cannot come to rely on behavior no real target has.
The invoketest package is that suite. Any Environment implementation, in this module or outside it, can be run against it.
Index ¶
- Constants
- Variables
- func CheckFollowTarget(linkPath, resolved, root string, contains func(root, path string) bool) error
- func EffectiveMode(sourceMode fs.FileMode, cfg TransferConfig) fs.FileMode
- type BackoffFunc
- type Capabilities
- type Command
- type EntryAction
- type Environment
- type Executor
- func (e *Executor) Download(ctx context.Context, remotePath, localPath string, opts ...TransferOption) error
- func (e *Executor) Lines(ctx context.Context, cmd Command, onLine func(string), opts ...Option) (Result, error)
- func (e *Executor) Output(ctx context.Context, cmd Command, opts ...Option) (Result, []byte, []byte, error)
- func (e *Executor) Run(ctx context.Context, cmd Command, stdio IO, opts ...Option) (Result, error)
- func (e *Executor) Upload(ctx context.Context, localPath, remotePath string, opts ...TransferOption) error
- type ExitError
- type IO
- type Option
- type Process
- type Result
- type Signal
- type SudoOption
- type SymlinkPolicy
- type TTY
- type TargetOS
- type TransferConfig
- type TransferOption
- type TransferProgress
- type TransportError
Examples ¶
Constants ¶
const UnknownTotal int64 = -1
UnknownTotal is the TransferProgress.Total value reported when the total size of a transfer is not known in advance.
Variables ¶
var ( // ErrClosed reports an operation on an environment or process that // has been closed. ErrClosed = errors.New("invoke: closed") // ErrNotSupported reports a feature the target cannot provide, such // as TTY allocation or signal delivery. Providers return it (wrapped, // with context) rather than silently ignoring the request. ErrNotSupported = errors.New("invoke: not supported") // ErrNotFound reports that the executable could not be resolved on // the target. ErrNotFound = errors.New("invoke: executable not found") // ErrInvalidWorkdir reports that the requested working directory does // not exist or cannot be entered on the target. ErrInvalidWorkdir = errors.New("invoke: invalid working directory") )
The error taxonomy distinguishes three families of failure, and every provider classifies into it:
- The command ran and failed: ExitError. Terminal — never retried, because the command may have had side effects.
- The command could not run because the transport failed (connection lost, daemon unreachable): TransportError. The only retryable family.
- The command could not run for a deterministic reason, or the caller stopped it: errors wrapping the sentinels below, or the context's own error. Terminal.
Anything unclassified is treated as terminal: retrying is the behavior that must be earned by explicit classification, not the default.
Functions ¶
func CheckFollowTarget ¶ added in v0.2.0
func CheckFollowTarget(linkPath, resolved, root string, contains func(root, path string) bool) error
CheckFollowTarget rejects a followed link whose target lies outside the transfer root. Following is the one policy that can read a path the caller never named, so the boundary is enforced rather than trusted.
contains reports whether a path lies within a root, by the path rules of whichever side is being walked.
func EffectiveMode ¶ added in v0.2.0
func EffectiveMode(sourceMode fs.FileMode, cfg TransferConfig) fs.FileMode
EffectiveMode is the mode an entry should be given at the destination: the transfer's override when it set one, and otherwise the source's own permissions.
Types ¶
type BackoffFunc ¶ added in v0.2.0
BackoffFunc returns the delay to wait before a given retry attempt. attempt counts from 2 (the first retry); it is never called for the initial attempt.
func ConstantBackoff ¶ added in v0.2.0
func ConstantBackoff(d time.Duration) BackoffFunc
ConstantBackoff waits a fixed delay before every retry.
func ExponentialBackoff ¶ added in v0.2.0
func ExponentialBackoff(base time.Duration) BackoffFunc
ExponentialBackoff waits base before the first retry and doubles the delay for each subsequent one.
func WithJitter ¶ added in v0.2.0
func WithJitter(backoff BackoffFunc, fraction float64) BackoffFunc
WithJitter wraps a backoff so each delay is randomly reduced by up to fraction of its value (0 < fraction <= 1), spreading retries to avoid a thundering herd.
type Capabilities ¶ added in v0.2.0
type Capabilities struct {
// TTY reports whether the target can allocate a pseudo-terminal for
// a command (IO.TTY).
TTY bool
// Signals reports whether Process.Signal actually delivers signals
// to the running process.
Signals bool
// SymlinkPreserve reports whether file transfers can recreate
// symbolic links as links ([SymlinkPreserve]).
SymlinkPreserve bool
}
Capabilities declares the optional features an Environment supports.
The contract is symmetric: a true field means the feature demonstrably works on this target; a false field means using it fails with an error wrapping ErrNotSupported. Providers never advertise a capability they cannot deliver, and never quietly ignore a request for one they lack.
Example ¶
Ask the target what it can do rather than assuming: an undeclared capability fails rather than being silently ignored.
package main
import (
"context"
"errors"
"fmt"
"github.com/ruffel/invoke"
"github.com/ruffel/invoke/fake"
)
func main() {
env := fake.New()
defer func() { _ = env.Close() }()
if !env.Capabilities().TTY {
_, err := env.Start(context.Background(), invoke.New("true"), invoke.IO{TTY: &invoke.TTY{}})
fmt.Println("tty refused:", errors.Is(err, invoke.ErrNotSupported))
}
}
Output: tty refused: true
type Command ¶
type Command struct {
// Path is the executable to run: a bare name resolved by the target
// (via its PATH or equivalent), or a path on the target.
Path string
// Args are passed to the executable exactly as given, with no shell
// interpretation. Use [Shell] to run a shell script.
Args []string
// Env lists additional environment variables in "KEY=VALUE" form,
// applied over the target's base environment.
Env []string
// Dir is the working directory on the target. Empty means the
// provider's default for the target.
Dir string
}
Command specifies a process to run: the executable, its arguments, its environment, and its working directory. It is a pure value — a Command carries no per-invocation state, so the same value can be started any number of times, sequentially or concurrently.
Everything that belongs to a single invocation — streams and TTY allocation — lives in IO instead.
type EntryAction ¶ added in v0.2.0
type EntryAction int
EntryAction is what a transfer should do with one directory entry, once the entry's type and the transfer's options have been taken together.
Deciding this is the same question for every target, and answering it differently is how two targets come to disagree about what a transfer means. Providers whose transport moves whole trees rather than a file at a time cannot use the shared copy engine, so they decide it here instead — and reach the same answers.
const ( // CopyContent copies the entry's bytes. CopyContent EntryAction = iota // PreserveLink recreates the entry as a symbolic link. PreserveLink // FollowLink copies the content the link resolves to, subject to the // containment check in [CheckFollowTarget]. FollowLink // SkipEntry omits the entry. SkipEntry )
func ClassifyEntry ¶ added in v0.2.0
func ClassifyEntry(path string, mode fs.FileMode, cfg TransferConfig) (EntryAction, error)
ClassifyEntry decides what to do with one entry. The path is used only to name the entry in an error, so it should be the one a caller would recognize.
Special files — devices, sockets, named pipes — are refused by name unless the transfer opted to skip them, because a transfer that silently drops entries cannot be told from one that succeeded.
type Environment ¶
type Environment interface {
io.Closer
// Start launches cmd on the target with stdio wiring its streams,
// and returns a handle to the running process.
//
// The context owns the process: when ctx is canceled the provider
// terminates the process promptly, and Wait returns an error
// matching ctx.Err().
Start(ctx context.Context, cmd Command, stdio IO) (Process, error)
// LookPath resolves name to an executable path on the target,
// searching the target's own lookup path. If the name cannot be
// resolved, the error wraps [ErrNotFound].
LookPath(ctx context.Context, name string) (string, error)
// Upload copies a local file or directory tree to the target.
// Writes are atomic per file (a failed transfer never corrupts an
// existing destination), and missing parent directories at the
// destination are created.
Upload(ctx context.Context, localPath, remotePath string, opts ...TransferOption) error
// Download copies a file or directory tree from the target to the
// local filesystem, with the same atomicity and parent-creation
// semantics as Upload.
Download(ctx context.Context, remotePath, localPath string, opts ...TransferOption) error
// OS reports the target's operating system.
OS() TargetOS
// Capabilities reports which optional features this target supports.
// A declared capability works; an undeclared one fails with an error
// wrapping [ErrNotSupported]. There is no silent middle ground.
Capabilities() Capabilities
}
Environment is a connection to an execution target: the local machine, a remote host, a container. Implementations are provided by subpackages; all of them satisfy the same behavioral contracts (verified by the invoketest suite), so swapping targets does not change semantics.
Lifecycle: Close is idempotent. After Close, every method fails with an error wrapping ErrClosed, and processes still running are terminated.
type Executor ¶
type Executor struct {
// contains filtered or unexported fields
}
Executor adds invocation policy — retry, sudo, and output capture — on top of an Environment. The Environment is the mechanism (start a process, move a file); the Executor is the policy layer over it.
Only the operations that carry policy are exposed here (Run, Output, Lines, Upload, Download). For LookPath, OS, Capabilities, or Close, use the Environment directly; if you need no policy at all, you need no Executor.
func NewExecutor ¶
func NewExecutor(env Environment, opts ...Option) *Executor
NewExecutor returns an Executor over env. The options set defaults that every call inherits and may override.
Example ¶
The quickstart, against a real target: constructing the environment is the only line that differs between local, ssh and docker.
package main
import (
"context"
"fmt"
"github.com/ruffel/invoke"
"github.com/ruffel/invoke/local"
)
func main() {
env, err := local.New()
if err != nil {
panic(err)
}
defer func() { _ = env.Close() }()
_, stdout, _, err := invoke.NewExecutor(env).Output(
context.Background(), invoke.New("echo", "from the host"))
if err != nil {
panic(err)
}
fmt.Printf("%s", stdout)
}
Output: from the host
func (*Executor) Download ¶
func (e *Executor) Download(ctx context.Context, remotePath, localPath string, opts ...TransferOption) error
Download copies a target path to the local filesystem, with the same retry policy as Upload.
func (*Executor) Lines ¶ added in v0.2.0
func (e *Executor) Lines(ctx context.Context, cmd Command, onLine func(string), opts ...Option) (Result, error)
Lines runs cmd and calls onLine for each line of its standard output. Stderr is discarded. It applies the same retry and sudo policy as Run; note that a failed-then-retried attempt may have already delivered some lines before failing.
A single line longer than 1 MiB fails the stream.
func (*Executor) Output ¶ added in v0.2.0
func (e *Executor) Output(ctx context.Context, cmd Command, opts ...Option) (Result, []byte, []byte, error)
Output runs cmd and returns its captured stdout and stderr. It is retry-safe by construction: each attempt writes into fresh buffers, so a failed attempt's partial output never accumulates into the result. On an ExitError, a tail of stderr is attached for diagnostics.
Example ¶
The fake stands in for a real provider wherever an example needs a predictable target; it obeys the same contracts.
package main
import (
"context"
"fmt"
"github.com/ruffel/invoke"
"github.com/ruffel/invoke/fake"
)
func main() {
env := fake.New()
defer func() { _ = env.Close() }()
exec := invoke.NewExecutor(env)
_, stdout, _, err := exec.Output(context.Background(), invoke.New("echo", "hello"))
if err != nil {
panic(err)
}
fmt.Printf("%s", stdout)
}
Output: hello
func (*Executor) Run ¶
Run starts cmd with the given IO, waits for it, and returns the outcome, applying the configured retry and sudo policy.
When retries are configured and stdio carries a non-nil Stdin, a WithFreshIO provider is required: a consumed reader cannot be replayed across attempts. Stdout and Stderr writers are reused across attempts as given unless WithFreshIO replaces them, so a caller capturing into a shared buffer under retry should prefer Executor.Output.
func (*Executor) Upload ¶
func (e *Executor) Upload(ctx context.Context, localPath, remotePath string, opts ...TransferOption) error
Upload copies a local path to the target, retrying on transport failures per the executor's default policy. Transfers are path-based and atomic, so a retry re-reads the source and never corrupts the destination.
Example ¶
Transfers move a file or a whole tree, and are delivered whole or not at all.
package main
import (
"context"
"fmt"
"os"
"path/filepath"
"github.com/ruffel/invoke"
"github.com/ruffel/invoke/fake"
)
func main() {
env := fake.New()
defer func() { _ = env.Close() }()
source := filepath.Join(os.TempDir(), "invoke-example.txt")
if err := os.WriteFile(source, []byte("payload"), 0o600); err != nil {
panic(err)
}
defer func() { _ = os.Remove(source) }()
exec := invoke.NewExecutor(env)
if err := exec.Upload(context.Background(), source, "/tmp/delivered.txt"); err != nil {
panic(err)
}
content, err := env.FS().Open("tmp/delivered.txt")
if err != nil {
panic(err)
}
defer func() { _ = content.Close() }()
fmt.Println("delivered")
}
Output: delivered
type ExitError ¶
type ExitError struct {
// Code is the exit status. It is -1 when the process was terminated
// by a signal.
Code int
// Signal is set when the process was terminated by a signal instead
// of exiting.
Signal Signal
// Stderr optionally holds a tail of the process's standard error,
// attached by capture helpers for diagnostics. Providers leave it
// empty when output went to the caller's own writers.
Stderr []byte
}
ExitError reports that a command ran and terminated unsuccessfully — either by exiting non-zero or by being killed by a signal.
It is always terminal: the command executed, so retrying is never safe to assume. Extract it with errors.As:
var exitErr *invoke.ExitError
if errors.As(err, &exitErr) {
log.Printf("exited %d", exitErr.Code)
}
Example ¶
A command that runs and fails is a different thing from one that never ran, and the error says which. Branch on the kind, not on the message: messages name the transport and differ between targets.
package main
import (
"context"
"errors"
"fmt"
"github.com/ruffel/invoke"
"github.com/ruffel/invoke/fake"
)
func main() {
env := fake.New()
defer func() { _ = env.Close() }()
exec := invoke.NewExecutor(env)
_, err := exec.Run(context.Background(), invoke.Shell("exit 3"), invoke.IO{})
var exitErr *invoke.ExitError
switch {
case errors.As(err, &exitErr):
fmt.Println("ran and failed with", exitErr.Code)
case errors.Is(err, invoke.ErrNotFound):
fmt.Println("could not be started")
case err != nil:
fmt.Println("something else went wrong")
}
}
Output: ran and failed with 3
type IO ¶ added in v0.2.0
type IO struct {
// Stdin is the process's standard input. nil means the process sees
// immediate EOF.
Stdin io.Reader
// Stdout receives the process's standard output. nil discards it.
Stdout io.Writer
// Stderr receives the process's standard error. nil discards it.
// Ignored when TTY is set: a pseudo-terminal merges standard error
// into standard output.
Stderr io.Writer
// TTY, when non-nil, allocates a pseudo-terminal for the process.
// Providers declare PTY support via [Capabilities]; on targets
// without it, starting a command with TTY set fails with an error
// wrapping [ErrNotSupported].
TTY *TTY
}
IO wires the standard streams for a single invocation of a Command.
The zero value is safe and non-interactive: the process reads immediate EOF from stdin and its output is discarded. Because an IO belongs to exactly one invocation, retried or repeated runs construct a fresh IO each time rather than reusing one whose streams have been consumed.
Example ¶
Streams belong to the invocation rather than the command, so the same Command value can be run more than once without being rebuilt.
package main
import (
"context"
"fmt"
"strings"
"github.com/ruffel/invoke"
"github.com/ruffel/invoke/fake"
)
func main() {
env := fake.New()
defer func() { _ = env.Close() }()
cmd := invoke.New("echo", "twice")
for range 2 {
var out strings.Builder
proc, err := env.Start(context.Background(), cmd, invoke.IO{Stdout: &out})
if err != nil {
panic(err)
}
if _, err := proc.Wait(); err != nil {
panic(err)
}
fmt.Print(out.String())
}
}
Output: twice twice
type Option ¶ added in v0.2.0
type Option func(*execConfig)
Option configures command execution policy. The same options apply both as executor defaults (NewExecutor) and as per-call overrides (Executor.Run and friends), with per-call options winning.
func WithFreshIO ¶ added in v0.2.0
WithFreshIO supplies a fresh IO for each attempt, so a retried command gets un-consumed streams. It is required to retry a command whose IO carries a non-nil Stdin, since a consumed reader cannot be replayed.
func WithRetry ¶
func WithRetry(attempts int, backoff BackoffFunc) Option
WithRetry retries a command up to attempts times (a total count, including the first try) when it fails with a TransportError — the only retryable error family. backoff sets the delay between attempts and may be nil for no delay. attempts below 1 is a validation error at call time, never a silent no-op.
Example ¶
Retry only re-runs transport failures, with a bounded backoff.
package main
import (
"context"
"fmt"
"github.com/ruffel/invoke"
"github.com/ruffel/invoke/fake"
)
func main() {
env := fake.New()
defer func() { _ = env.Close() }()
exec := invoke.NewExecutor(env, invoke.WithRetry(3, invoke.ConstantBackoff(0)))
res, _, _, err := exec.Output(context.Background(), invoke.Shell("exit 0"))
fmt.Println(res.ExitCode, err)
}
Output: 0 <nil>
func WithSudo ¶
func WithSudo(opts ...SudoOption) Option
WithSudo runs the command through sudo in non-interactive mode (sudo -n), so a password prompt fails rather than hangs.
The command and its arguments are passed after a -- separator as an argv, never as a shell string, so no argument can be misread as a sudo flag or interpreted by a shell. Note that sudo resets the environment by default: variables set via Command.Env do not reach the target command unless the sudoers policy preserves them or WithSudoPreserveEnv is set and permitted.
type Process ¶
type Process interface {
// Wait blocks until the process exits and returns its Result. It is
// idempotent: repeated calls return the same outcome without
// blocking again.
//
// The (Result, error) pair follows the package error taxonomy: nil
// error means exit zero; an [ExitError] means the process ran and
// terminated unsuccessfully; any other error means the process did
// not run to completion. Cancellation and Close surface as errors
// matching ctx.Err() or wrapping [ErrClosed] — never as ExitError.
Wait() (Result, error)
// Signal delivers sig to the process. It either delivers the signal
// or returns an error (wrapping [ErrNotSupported] when the target
// cannot deliver it); it never silently does nothing.
//
// What reaches the process is guaranteed. What reaches its children
// is not, and targets differ: one that runs the command in a process
// group of its own signals the whole group, which is what a terminal
// does and what the local machine therefore does; one that can only
// address a single process signals that one alone. Code that needs a
// command's children to receive a signal should send it to them
// rather than rely on which kind of target it is talking to.
Signal(sig Signal) error
// Close releases the handle, terminating the process if it is still
// running. It is idempotent, and it unblocks any Wait in progress —
// which then reports an error wrapping [ErrClosed], never an
// ExitError, for a process killed by Close.
io.Closer
}
Process is a handle to a command started by Environment.Start.
type Result ¶
type Result struct {
// ExitCode is the process's exit status. It is -1 when the process
// was terminated by a signal (the accompanying ExitError carries
// which one).
ExitCode int
// Duration is the wall-clock time from process start until its exit
// was observed. Observation can lag the exit itself by output-drain
// time, bounded by the provider's wait timeout.
Duration time.Duration
}
Result describes a completed process.
A Result is only meaningful in combination with the error returned alongside it: on a nil error the process exited zero; with an ExitError the process ran and ExitCode carries its exit status; with any other error the process did not run to completion and the Result's fields carry no information. Result deliberately has no error field — the returned error is the single source of truth.
type Signal ¶ added in v0.2.0
type Signal string
Signal names a POSIX signal to deliver to a process, in a form that maps cleanly onto every target: syscall numbers locally, SSH signal names on the wire, and kill -s in containers.
Providers declare working signal delivery via Capabilities. Delivering a signal a target cannot support returns an error wrapping ErrNotSupported — never a silent no-op.
const ( // SIGINT interrupts the process, as Ctrl-C does. SIGINT Signal = "INT" // SIGTERM requests graceful termination. SIGTERM Signal = "TERM" // SIGKILL forcibly terminates the process; it cannot be caught. SIGKILL Signal = "KILL" // SIGHUP reports that the controlling terminal hung up. SIGHUP Signal = "HUP" // SIGQUIT requests termination with a core dump. SIGQUIT Signal = "QUIT" // SIGUSR1 is the first user-defined signal. SIGUSR1 Signal = "USR1" // SIGUSR2 is the second user-defined signal. SIGUSR2 Signal = "USR2" )
The supported signal set. Window-size changes (SIGWINCH) and terminal resize forwarding are out of scope this cycle.
type SudoOption ¶ added in v0.0.3
type SudoOption func(*sudoConfig)
SudoOption configures how WithSudo wraps a command.
func WithSudoFlags ¶ added in v0.2.0
func WithSudoFlags(flags ...string) SudoOption
WithSudoFlags appends additional flags to the sudo invocation, before the -- separator. Callers own the safety of anything passed here.
func WithSudoGroup ¶ added in v0.1.0
func WithSudoGroup(group string) SudoOption
WithSudoGroup runs the command as the given target group (sudo -g).
func WithSudoPreserveEnv ¶ added in v0.0.3
func WithSudoPreserveEnv() SudoOption
WithSudoPreserveEnv preserves the invoking environment (sudo -E), subject to the sudoers policy allowing it.
func WithSudoUser ¶ added in v0.0.3
func WithSudoUser(user string) SudoOption
WithSudoUser runs the command as the given target user (sudo -u).
type SymlinkPolicy ¶ added in v0.2.0
type SymlinkPolicy int
SymlinkPolicy selects how file transfers treat symbolic links inside a transferred tree.
const ( // SymlinkPreserve recreates links as links at the destination. It is // the default. On targets that cannot represent links (see // [Capabilities].SymlinkPreserve), transfers fail with an error // naming the link rather than silently flattening or dropping it. SymlinkPreserve SymlinkPolicy = iota // SymlinkFollow copies the link target's content in place of the // link. Following never escapes the transfer root; a link pointing // outside it fails the transfer with an error naming the link. SymlinkFollow // SymlinkSkip omits links from the transfer. SymlinkSkip )
type TTY ¶ added in v0.2.0
type TTY struct {
// Cols is the terminal width in character cells. Zero means the
// default of 80.
Cols int
// Rows is the terminal height in character cells. Zero means the
// default of 24.
Rows int
}
TTY describes the pseudo-terminal requested for an invocation.
type TargetOS ¶
type TargetOS string
TargetOS identifies the operating system of an execution target. Values mirror runtime.GOOS strings, so a TargetOS can be compared against GOOS constants and logged directly.
const ( // OSUnknown means the target's operating system has not been // determined. OSUnknown TargetOS = "" // OSLinux is the Linux kernel. OSLinux TargetOS = "linux" // OSDarwin is macOS. OSDarwin TargetOS = "darwin" )
Known target operating systems. Execution semantics are POSIX-only this cycle; providers reject targets they cannot serve correctly.
type TransferConfig ¶ added in v0.2.0
type TransferConfig struct {
// Mode, when non-nil, is applied to transferred files at the
// destination (after creation, so it is umask-proof and applies on
// overwrite too). nil preserves each source file's mode.
Mode *fs.FileMode
// Symlinks selects link handling; the zero value is
// [SymlinkPreserve].
Symlinks SymlinkPolicy
// SkipSpecial omits FIFOs, sockets, and device files from transfers
// instead of failing on them.
SkipSpecial bool
// Progress, when non-nil, receives per-file progress updates.
Progress func(TransferProgress)
}
TransferConfig collects the options applied to an Upload or Download. Providers materialize it with NewTransferConfig; callers use the With... options rather than constructing one directly.
func NewTransferConfig ¶ added in v0.2.0
func NewTransferConfig(opts ...TransferOption) TransferConfig
NewTransferConfig applies opts over the default configuration. It is the entry point providers use to materialize a call's options.
type TransferOption ¶ added in v0.2.0
type TransferOption func(*TransferConfig)
TransferOption configures a single Upload or Download call.
func WithMode ¶ added in v0.2.0
func WithMode(mode fs.FileMode) TransferOption
WithMode forces mode on transferred files at the destination, applied after creation so umask cannot mask it and overwrites receive it too. Without this option, each source file's own mode is preserved.
func WithProgress ¶
func WithProgress(fn func(TransferProgress)) TransferOption
WithProgress registers fn to receive per-file progress updates. See TransferProgress for the callback's concurrency caveat.
func WithSkipSpecial ¶ added in v0.2.0
func WithSkipSpecial() TransferOption
WithSkipSpecial omits FIFOs, sockets, and device files from a transfer. Without it, encountering one fails the transfer with an error naming the path — special files are never silently skipped, and never block a transfer by being opened.
func WithSymlinks ¶ added in v0.2.0
func WithSymlinks(policy SymlinkPolicy) TransferOption
WithSymlinks selects how symbolic links inside a transferred tree are handled. The default is SymlinkPreserve.
type TransferProgress ¶ added in v0.2.0
type TransferProgress struct {
// Path is the file being transferred, relative to the transfer root.
Path string
// Current is the number of bytes transferred so far for this file.
Current int64
// Total is the file's total size in bytes, or [UnknownTotal] when
// the size is not known in advance.
Total int64
}
TransferProgress reports the progress of one file within a transfer.
Callbacks may be invoked from a different goroutine than the caller's, depending on the provider's transport.
type TransportError ¶ added in v0.1.0
type TransportError struct {
// Op names the operation that failed: "start", "wait", "upload",
// "download", "lookpath".
Op string
// Err is the underlying transport failure.
Err error
}
TransportError reports that the transport to the target failed before or while the command ran: a lost connection, an unreachable daemon, a broken session.
It is the only retryable family in the taxonomy: the failure is environmental, not a verdict about the command.
It is not a wrapper for one. Because it unwraps, a TransportError carrying an ExitError or one of the sentinels above belongs to both families at once, and the outcome it carries is the one that decides: such an error is terminal and is never retried. A provider with a verdict about the command should return that verdict rather than dress it as a transport failure.
func (*TransportError) Error ¶ added in v0.1.0
func (e *TransportError) Error() string
Error describes the failed operation and its cause.
func (*TransportError) Unwrap ¶ added in v0.1.0
func (e *TransportError) Unwrap() error
Unwrap returns the underlying transport failure.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
docker
module
|
|
|
Package fake is a scriptable, contract-verified test double for invoke.Environment.
|
Package fake is a scriptable, contract-verified test double for invoke.Environment. |
|
internal
|
|
|
transfer
Package transfer implements the copy engine behind every provider's Upload and Download: atomic temp-and-rename delivery, tree walking with deepest-first directory-mode restoration, symlink and special-file policy, per-file progress, and prompt cancellation.
|
Package transfer implements the copy engine behind every provider's Upload and Download: atomic temp-and-rename delivery, tree walking with deepest-first directory-mode restoration, symlink and special-file policy, per-file progress, and prompt cancellation. |
|
Package invoketest is the executable specification for invoke providers.
|
Package invoketest is the executable specification for invoke providers. |
|
Package local executes commands and transfers files on the machine the program is running on.
|
Package local executes commands and transfers files on the machine the program is running on. |
|
providers
|
|
|
local
module
|
|
|
mock
module
|
|
|
ssh
module
|
|
|
Package ssh executes commands and transfers files on a remote host over SSH.
|
Package ssh executes commands and transfers files on a remote host over SSH. |