Documentation
¶
Overview ¶
Package invoke provides a unified interface for command execution and file transfer.
Core Interfaces ¶
- Environment: The connection to a system (Local, SSH, Docker). - Process: A running command handle (allows Wait, Signal, Close).
Streaming ¶
`invoke` is streaming-first. We don't buffer output by default. If you want to capture stdout/stderr, attach an `io.Writer` to your `Command`.
For simple "just give me the output" cases, use the `Executor` wrapper.
Sudo ¶
Privilege escalation is supported via `invoke.WithSudo()`. This uses `sudo -n` for non-interactive execution.
Example (SshConfigReader) ¶
package main
import (
"fmt"
"log"
"strings"
"github.com/ruffel/invoke/providers/ssh"
)
func main() {
// Example of loading SSH config from a string (or file)
configContent := `
Host prod-db
HostName 10.0.0.5
User admin
Port 2222
IdentityFile ~/.ssh/prod_key.pem
StrictHostKeyChecking no
`
// Parse the config
cfg, err := ssh.NewFromSSHConfigReader("prod-db", strings.NewReader(configContent))
if err != nil {
log.Fatal(err)
}
fmt.Printf("Host: %s\n", cfg.Host)
fmt.Printf("User: %s\n", cfg.User)
fmt.Printf("Port: %d\n", cfg.Port)
}
Output: Host: 10.0.0.5 User: admin Port: 2222
Index ¶
- Variables
- type BufferedResult
- type Builder
- func (b *Builder) Arg(arg string) *Builder
- func (b *Builder) Args(args ...string) *Builder
- func (b *Builder) Build() *Command
- func (b *Builder) Dir(dir string) *Builder
- func (b *Builder) Env(key, value string) *Builder
- func (b *Builder) Input(s string) *Builder
- func (b *Builder) Stderr(w io.Writer) *Builder
- func (b *Builder) Stdin(r io.Reader) *Builder
- func (b *Builder) Stdout(w io.Writer) *Builder
- func (b *Builder) Tty() *Builder
- type Command
- type Environment
- type ExecConfig
- type ExecOption
- type Executor
- func (e *Executor) Download(ctx context.Context, remotePath, localPath string, opts ...FileOption) error
- func (e *Executor) LookPath(ctx context.Context, file string) (string, error)
- func (e *Executor) Run(ctx context.Context, cmd *Command, opts ...ExecOption) (*Result, error)
- func (e *Executor) RunBuffered(ctx context.Context, cmd *Command, opts ...ExecOption) (*BufferedResult, error)
- func (e *Executor) RunLineStream(ctx context.Context, cmd *Command, onLine func(string), _ ...ExecOption) error
- func (e *Executor) RunShell(ctx context.Context, cmdStr string, opts ...ExecOption) (*BufferedResult, error)
- func (e *Executor) Start(ctx context.Context, cmd *Command) (Process, error)
- func (e *Executor) TargetOS() TargetOS
- func (e *Executor) Upload(ctx context.Context, localPath, remotePath string, opts ...FileOption) error
- type ExitError
- type FileConfig
- type FileOption
- type Process
- type ProgressFunc
- type Result
- type SudoConfig
- type SudoOption
- type TargetOS
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ErrNotSupported = errors.New("operation not supported")
ErrNotSupported indicates that the requested feature (e.g., TTY) is not supported by the specific provider or OS.
Functions ¶
This section is empty.
Types ¶
type BufferedResult ¶
BufferedResult extends Result to include captured stdout/stderr content. Returned by Executor.RunBuffered.
type Builder ¶ added in v0.0.3
type Builder struct {
// contains filtered or unexported fields
}
Builder provides a fluent API for constructing Commands.
func Cmd ¶ added in v0.0.3
Cmd creates a new Builder for a command with the given name/path.
Example (Builder) ¶
package main
import (
"context"
"fmt"
"os"
"github.com/ruffel/invoke"
"github.com/ruffel/invoke/providers/local"
)
func main() {
// Construct a command using the fluent builder API.
cmd := invoke.Cmd("go").
Arg("env").
Arg("GREETING").
Env("GREETING", "hello builder").
Dir(os.TempDir()).
Build()
// Execute it
env, err := local.New()
if err != nil {
panic(err)
}
defer func() { _ = env.Close() }()
ctx := context.Background()
res, err := env.Run(ctx, cmd)
if err != nil {
panic(err)
}
// Capture output via RunBuffered would be better for verification,
// but here we just check exit code to keep it simple.
fmt.Printf("Exit Code: %d\n", res.ExitCode)
}
Output: Exit Code: 0
func (*Builder) Build ¶ added in v0.0.3
Build returns a deep copy of the constructed Command. The returned command is safe to use while the builder continues to be modified.
type Command ¶
type Command struct {
Cmd string // Binary name or path to executable
Args []string // Arguments to pass to the binary
Env []string // Environment variables in "KEY=VALUE" format
Dir string // Working directory for execution
// Standard streams. If nil, defaults to empty/discard.
Stdin io.Reader
Stdout io.Writer
Stderr io.Writer
// Tty allocates a PTY. Useful for interactive commands (e.g. sudo).
Tty bool
}
Command configures a process execution.
func NewCommand ¶
NewCommand creates a new Command with the given binary and arguments.
func ParseCommand ¶
ParseCommand parses a shell command string into a Command struct using shlex. It handles quoted arguments correctly.
type Environment ¶
type Environment interface {
io.Closer
// Run executes a command synchronously.
// Returns the result (exit code, error). Output is not captured by default; use Command.Stdout/Stderr.
Run(ctx context.Context, cmd *Command) (*Result, error)
// Start initiates a command asynchronously.
// The caller manages the returned Process (Wait/Signal) and must ensure resources are released via Wait().
Start(ctx context.Context, cmd *Command) (Process, error)
// TargetOS returns the operating system of the target environment.
TargetOS() TargetOS
// Upload copies a local file or directory to the remote destination.
Upload(ctx context.Context, localPath, remotePath string, opts ...FileOption) error
// Download copies a remote file or directory to the local destination.
Download(ctx context.Context, remotePath, localPath string, opts ...FileOption) error
// LookPath searches for an executable named file in the directories named by
// the PATH environment variable.
LookPath(ctx context.Context, file string) (string, error)
}
Environment abstracts the underlying system where commands are executed (e.g., Local, SSH, Docker).
Example (Upload) ¶
package main
import (
"context"
"log"
"time"
"github.com/ruffel/invoke"
"github.com/ruffel/invoke/providers/mock"
testifymock "github.com/stretchr/testify/mock"
)
func main() {
// Demonstrating the FileTransfer interface usage (now part of Environment)
var env invoke.Environment = mock.New()
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// Setup mock expectation
env.(*mock.Environment).On("Upload", ctx, "./config.json", "/etc/app/config.json", testifymock.Anything).Return(nil)
// Upload with permissions override
err := env.Upload(ctx, "./config.json", "/etc/app/config.json",
invoke.WithPermissions(0o600),
)
if err != nil {
log.Printf("Upload failed: %v", err)
}
}
Output:
Example (Upload_download) ¶
package main
import (
"context"
"fmt"
"os"
"github.com/ruffel/invoke"
"github.com/ruffel/invoke/providers/local"
)
func main() {
env, err := local.New()
if err != nil {
panic(err)
}
_ = os.WriteFile("localfile.txt", []byte("hello world"), 0o600)
defer func() { _ = os.Remove("localfile.txt") }()
defer func() { _ = os.Remove("localfile.bak") }()
ctx := context.Background()
err = env.Upload(ctx, "localfile.txt", "/tmp/localfile.txt", invoke.WithPermissions(0o644))
if err != nil {
panic(err)
}
err = env.Download(ctx, "/tmp/localfile.txt", "localfile.bak")
if err != nil {
panic(err)
}
content, err := os.ReadFile("localfile.bak")
if err != nil {
panic(err)
}
fmt.Printf("Downloaded content: %s\n", string(content))
}
Output: Downloaded content: hello world
Example (WithProgress) ¶
package main
import (
"context"
"fmt"
"os"
"github.com/ruffel/invoke"
"github.com/ruffel/invoke/providers/local"
)
func main() {
env, err := local.New()
if err != nil {
panic(err)
}
_ = os.WriteFile("largefile.dat", []byte("1234567890"), 0o600)
defer func() { _ = os.Remove("largefile.dat") }()
defer func() { _ = os.Remove("largefile.dat.bak") }()
ctx := context.Background()
err = env.Upload(ctx, "largefile.dat", "largefile.dat.bak",
invoke.WithProgress(func(current, total int64) {
fmt.Printf("Transferred %d/%d bytes\n", current, total)
}),
)
if err != nil {
panic(err)
}
}
Output: Transferred 10/10 bytes
type ExecConfig ¶
type ExecConfig struct {
SudoConfig *SudoConfig
RetryAttempts int
RetryDelay time.Duration
}
ExecConfig holds configuration derived from options.
type ExecOption ¶
type ExecOption func(*ExecConfig)
ExecOption defines a functional option for execution.
type Executor ¶
type Executor struct {
// contains filtered or unexported fields
}
Executor handles command execution with retry logic, sudo support, and output buffering.
func NewExecutor ¶
func NewExecutor(env Environment) *Executor
NewExecutor creates a new Executor with the given environment.
func (*Executor) Download ¶
func (e *Executor) Download(ctx context.Context, remotePath, localPath string, opts ...FileOption) error
Download copies a remote file or directory to the local destination. It delegates directly to the underlying Environment.
func (*Executor) LookPath ¶
LookPath resolves an executable path using the underlying environment's LookPath strategy.
func (*Executor) Run ¶
Run executes a command, respecting context cancellation and configured retry policies.
Example (Sudo) ¶
package main
import (
"context"
"fmt"
"github.com/ruffel/invoke"
"github.com/ruffel/invoke/providers/mock"
testifymock "github.com/stretchr/testify/mock"
)
func main() {
// Example of using high-level options
env := mock.New() // Using mock for safety in example
// Mock the result
// Match any command that has the right string components, ignoring pointers (Stdout/Stderr)
matcher := testifymock.MatchedBy(func(c *invoke.Command) bool {
return c.Cmd == "sudo" && len(c.Args) == 4 && c.Args[3] == "/root"
})
env.On("Run", context.Background(), matcher).Run(func(args testifymock.Arguments) {
cmd := args.Get(1).(*invoke.Command)
if cmd.Stdout != nil {
_, _ = fmt.Fprint(cmd.Stdout, "secret.txt\n")
}
}).Return(&invoke.Result{ExitCode: 0}, nil)
exec := invoke.NewExecutor(env)
ctx := context.Background()
// WithSudo() automatically wraps the command
cmd := invoke.Command{Cmd: "ls", Args: []string{"/root"}}
// We use RunBuffered to capture output
res, err := exec.RunBuffered(ctx, &cmd, invoke.WithSudo())
if err != nil {
panic(err)
}
fmt.Printf("Sudo Output: %s", res.Stdout)
}
Output: Sudo Output: secret.txt
func (*Executor) RunBuffered ¶
func (e *Executor) RunBuffered(ctx context.Context, cmd *Command, opts ...ExecOption) (*BufferedResult, error)
RunBuffered executes a command and captures both Stdout and Stderr.
Example (Local) ¶
package main
import (
"context"
"fmt"
"github.com/ruffel/invoke"
"github.com/ruffel/invoke/providers/local"
)
func main() {
env, err := local.New()
if err != nil {
panic(err)
}
defer func() { _ = env.Close() }()
exec := invoke.NewExecutor(env)
cmd := invoke.Command{
Cmd: "echo",
Args: []string{"hello", "world"},
}
ctx := context.Background()
res, err := exec.RunBuffered(ctx, &cmd)
if err != nil {
panic(err)
}
fmt.Printf("%s\n", res.Stdout)
}
Output: hello world
func (*Executor) RunLineStream ¶
func (e *Executor) RunLineStream(ctx context.Context, cmd *Command, onLine func(string), _ ...ExecOption) error
RunLineStream streams stdout line-by-line to onLine. Useful for live logging. Overrides Command.Stdout.
func (*Executor) RunShell ¶
func (e *Executor) RunShell(ctx context.Context, cmdStr string, opts ...ExecOption) (*BufferedResult, error)
RunShell executes a shell command string using the target OS's default shell.
func (*Executor) Start ¶
Start initiates a command asynchronously. Caller is responsible for Process.Wait().
type ExitError ¶
type ExitError struct {
Command *Command
ExitCode int
Stderr []byte // Captured stderr, if available from RunBuffered
Cause error // Underlying error, if any
}
ExitError represents a successful execution that resulted in a non-zero exit code.
type FileConfig ¶
type FileConfig struct {
Permissions os.FileMode // Destination perms override (0 means preserve/default)
UID, GID int // Destination ownership (0 usually means root/current)
Recursive bool // Default true for generic uploads
Progress ProgressFunc
}
FileConfig holds configuration for file transfers.
type FileOption ¶
type FileOption func(*FileConfig)
FileOption defines a functional option for file transfers.
func WithOwner ¶
func WithOwner(uid, gid int) FileOption
WithOwner forces specific destination ownership.
func WithPermissions ¶
func WithPermissions(mode os.FileMode) FileOption
WithPermissions forces specific destination file mode.
func WithProgress ¶
func WithProgress(fn ProgressFunc) FileOption
WithProgress calls fn with progress updates.
type Process ¶
type Process interface {
io.Closer
// Wait blocks until the process exits.
// Returns an error if the exit code is non-zero.
Wait() error
// Result returns metadata (exit code, termination status) (only valid after Wait).
Result() *Result
// Signal sends an OS signal to the process.
// Note: support for specific signals depends on the underlying provider.
Signal(sig os.Signal) error
}
Process represents a command that has been started but not yet completed.
type ProgressFunc ¶
type ProgressFunc func(current, total int64)
ProgressFunc is a callback for tracking file transfer progress.
type Result ¶
type Result struct {
ExitCode int // Process exit code (0 indicates success)
Duration time.Duration // Time taken for execution
Error error // Launch/Transport error (distinct from non-zero exit code)
}
Result contains metadata about a completed command execution.
type SudoConfig ¶ added in v0.0.3
type SudoConfig struct {
User string // Target user (-u)
Group string // Target group (-g)
PreserveEnv bool // Preserve environment (-E)
CustomFlags []string // Additional flags
}
SudoConfig defines advanced privilege escalation options.
type SudoOption ¶ added in v0.0.3
type SudoOption func(*SudoConfig)
SudoOption defines a functional option for sudo configuration.
func WithSudoPreserveEnv ¶ added in v0.0.3
func WithSudoPreserveEnv() SudoOption
WithSudoPreserveEnv preserves the environment.
func WithSudoUser ¶ added in v0.0.3
func WithSudoUser(user string) SudoOption
WithSudoUser sets the target user.
type TargetOS ¶
type TargetOS int
TargetOS identifies the operating system of the target environment.
func DetectLocalOS ¶
func DetectLocalOS() TargetOS
DetectLocalOS returns the TargetOS of the current running process.
func ParseTargetOS ¶
ParseTargetOS converts a typical OS string (e.g., "linux", "darwin") to a TargetOS.
func (TargetOS) ShellCommand ¶
ShellCommand constructs a command that runs the provided script inside the system shell. Returns "sh -c <script>" for UNIX-likes and "powershell ..." for Windows.