runner

package module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 13, 2023 License: MIT Imports: 7 Imported by: 2

README

go-runner

Go package exposing a simple interface for executing commands, enabling easy mocking and wrapping of executed commands.

Go Reference GitHub tag (latest SemVer) Actions Status Coverage GitHub last commit GitHub issues GitHub pull requests License Status

The Runner interface is basic and minimal, but it is sufficient for most use cases. This makes it easy to mock Runner for testing purposes.

It's also easy to create wrapper runners which modify commands before executing them. The Sudo struct is a simple example of this.

Import

import "github.com/krystal/go-runner"

Interface

type Runner interface {
	Run(
		stdin io.Reader,
		stdout, stderr io.Writer,
		command string,
		args ...string,
	) error
	RunContext(
		ctx context.Context,
		stdin io.Reader,
		stdout, stderr io.Writer,
		command string,
		args ...string,
	) error
	Env(env ...string)
}

Usage

Basic:

var stdout bytes.Buffer

r := runner.New()
_ = r.Run(nil, &stdout, nil, "echo", "Hello world!")

fmt.Print(stdout.String())
Hello world!

Environment:

var stdout bytes.Buffer

r := runner.New()
r.Env("USER=johndoe", "HOME=/home/johnny")
_ = r.Run(nil, &stdout, nil, "sh", "-c", `echo "Hi, ${USER} (${HOME})"`)

fmt.Print(stdout.String())
Hi, johndoe (/home/johnny)

Stdin, Stdout, and Stderr:

stdin := bytes.NewBufferString("Hello world!")
var stdout, stderr bytes.Buffer

r := runner.New()
err := r.Run(
	stdin, &stdout, &stderr,
	"sh", "-c", "cat; echo 'Oh noes! :(' >&2",
)
if err != nil {
	fmt.Println(err)
}

fmt.Print(stderr.String())
fmt.Print(stdout.String())
Oh noes! :(
Hello world!

Failure:

var stdout, stderr bytes.Buffer

r := runner.New()
err := r.Run(
	nil, &stdout, &stderr,
	"sh", "-c", "echo 'Hello world!'; echo 'Oh noes! :(' >&2; exit 3",
)
if err != nil {
	fmt.Printf("%s: %s", err.Error(), stderr.String())
}
exit status 3: Oh noes! :(

Context:

var stdout bytes.Buffer

ctx, cancel := context.WithTimeout(
	context.Background(), 1*time.Second,
)
defer cancel()

r := runner.New()
err := r.RunContext(
	ctx, nil, &stdout, nil,
	"sh", "-c", "sleep 0.5 && echo 'Hello world!'",
)
if err != nil {
	fmt.Println(err)
}

fmt.Print(stdout.String())
Hello world!

Sudo (requires NOPASS in sudoers file):

var stdout bytes.Buffer
r := runner.New()

sudo := &runner.Sudo{Runner: r}
_ = sudo.Run(nil, &stdout, nil, "whoami")

sudo.User = "web"
_ = sudo.Run(nil, &stdout, nil, "whoami")

fmt.Print(stdout.String())
root
web

Documentation

Please see the Go Reference for documentation and examples.

License

MIT

Documentation

Overview

Package runner exposes a simple interface for executing commands, enabling easy mocking and wrapping of executed commands.

The Runner interface is basic and minimal, but it is sufficient for most use cases. This makes it easy to mock Runner for testing purposes.

It's also easy to create wrapper runners which modify commands before executing them. The Sudo struct is a simple example of this.

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	ErrSSHCLI              = fmt.Errorf("%w: sshcli: ", Err)
	ErrSSHCLINoDestination = fmt.Errorf(
		"%w: destination must be set", ErrSSHCLI,
	)
)
View Source
var Err = errors.New("runner")

Functions

This section is empty.

Types

type Local

type Local struct {
	// contains filtered or unexported fields
}

Local is a Runner implementation that executes commands locally on the host machine.

func (*Local) Env

func (r *Local) Env(env ...string)

Env sets the environment which will apply to all commands invoked by the runner. Each entry is of the form "key=value".

func (*Local) Run

func (r *Local) Run(
	stdin io.Reader,
	stdout io.Writer,
	stderr io.Writer,
	command string,
	args ...string,
) error

Run executes the given command locally on the host machine.

func (*Local) RunContext

func (r *Local) RunContext(
	ctx context.Context,
	stdin io.Reader,
	stdout io.Writer,
	stderr io.Writer,
	command string,
	args ...string,
) error

RunContext executes the given command locally on the host machine, using the provided context to kill the process if the context becomes done before the command completes on its own.

type Runner

type Runner interface {
	// Run executes the given command with any provided arguments. Stdin,
	// Stdout, and Stderr can be provided/captured if the io.Reader/Writer is
	// not nil.
	Run(
		stdin io.Reader,
		stdout, stderr io.Writer,
		command string,
		args ...string,
	) error

	// RunContext is like Run but includes a context.
	//
	// The provided context is used to kill the command process if the context
	// becomes done before the command completes on its own.
	RunContext(
		ctx context.Context,
		stdin io.Reader,
		stdout, stderr io.Writer,
		command string,
		args ...string,
	) error

	// Env specifies the environment variables which will be available to all
	// commands invoked by the runner. Each entry is of the form "key=value".
	// Entries with duplicate keys will cause all but the last to be ignored.
	//
	// Multiple calls to Env will overwrite any previous calls to Env.
	//
	// If no env is set, no environment variables will be set for executed
	// commands.
	//
	// To set the environment to match that of the Go runtime, call Env with
	// os.Environ().
	Env(env ...string)
}

Runner is the interface that all runner structs implements. It makes it easy to replace the underlying command runner with a mock for testing, or a different runner that executes givens commands in a different manner.

Example (Basic)
var stdout bytes.Buffer

r := runner.New()
_ = r.Run(nil, &stdout, nil, "echo", "Hello world!")

fmt.Print(stdout.String())
Output:

Hello world!
Example (Combined)
var out bytes.Buffer

r := runner.New()
err := r.Run(
	nil, &out, &out,
	"sh", "-c", "echo 'Hello world!'; echo 'Oh noes! :(' >&2",
)
if err != nil {
	fmt.Println(err)
}

fmt.Print(out.String())
Output:

Hello world!
Oh noes! :(
Example (Context)
var stdout bytes.Buffer

ctx, cancel := context.WithTimeout(
	context.Background(), 1*time.Second,
)
defer cancel()

r := runner.New()
err := r.RunContext(
	ctx, nil, &stdout, nil,
	"sh", "-c", "sleep 0.5 && echo 'Hello world!'",
)
if err != nil {
	fmt.Println(err)
}

fmt.Print(stdout.String())
Output:

Hello world!
Example (ContextTimeout)
var stdout, stderr bytes.Buffer

ctx, cancel := context.WithTimeout(
	context.Background(), 100*time.Millisecond,
)
defer cancel()

r := runner.New()
err := r.RunContext(
	ctx, nil, &stdout, &stderr,
	"sh", "-c", "sleep 0.5 && echo 'Hello world!'",
)
if err != nil {
	fmt.Println(err)
}
Output:

signal: killed
Example (Environment)
var stdout bytes.Buffer

r := runner.New()
r.Env("USER=johndoe", "HOME=/home/johnny")
_ = r.Run(nil, &stdout, nil, "sh", "-c", `echo "Hi, ${USER} (${HOME})"`)

fmt.Print(stdout.String())
Output:

Hi, johndoe (/home/johnny)
Example (Failure)
var stdout, stderr bytes.Buffer

r := runner.New()
err := r.Run(
	nil, &stdout, &stderr,
	"sh", "-c", "echo 'Hello world!'; echo 'Oh noes! :(' >&2; exit 3",
)
if err != nil {
	fmt.Printf("%s: %s", err.Error(), stderr.String())
}
Output:

exit status 3: Oh noes! :(
Example (Stderr)
var stderr bytes.Buffer

r := runner.New()
err := r.Run(nil, nil, &stderr, "sh", "-c", "echo 'Oh noes! :(' >&2")
if err != nil {
	fmt.Println(err)
}

fmt.Print(stderr.String())
Output:

Oh noes! :(
Example (Stdin)
var stdout bytes.Buffer

r := runner.New()
_ = r.Run(bytes.NewBufferString("Hello world!"), &stdout, nil, "cat")

fmt.Print(stdout.String())
Output:

Hello world!
Example (StdinStdoutAndStderr)
stdin := bytes.NewBufferString("Hello world!")
var stdout, stderr bytes.Buffer

r := runner.New()
err := r.Run(
	stdin, &stdout, &stderr,
	"sh", "-c", "cat; echo 'Oh noes! :(' >&2",
)
if err != nil {
	fmt.Println(err)
}

fmt.Print(stderr.String())
fmt.Print(stdout.String())
Output:

Oh noes! :(
Hello world!
Example (StdoutAndStderr)
var stdout, stderr bytes.Buffer

r := runner.New()
err := r.Run(
	nil, &stdout, &stderr,
	"sh", "-c", "echo 'Hello world!'; echo 'Oh noes! :(' >&2",
)
if err != nil {
	fmt.Println(err)
}

fmt.Print(stderr.String())
fmt.Print(stdout.String())
Output:

Oh noes! :(
Hello world!

func New

func New() Runner

New returns a Local instance which meets the Runner interface, and executes commands locally on the host machine.

type SSHCLI added in v0.1.4

type SSHCLI struct {
	// Runner is the underlying Runner to run commands with, after wrapping them
	// with ssh. If not set, running commands will cause a panic.
	Runner Runner

	// Destination is the remote SSH destination to connect to, which may be
	// specified as either "[user@]hostname" or a URI of the form
	// "ssh://[user@]hostname[:port]".
	Destination string

	// Port is the remote SSH port (-p) flag to use. When 0, no -p flag will be
	// used.
	Port int

	// IdentityFile is the remote SSH identity file (-i) flag to use. When
	// empty, no -i flag will be used.
	IdentityFile string

	// Login is the remote SSH login (-l) flag to use. When empty, no -l flag
	// will be used.
	Login string

	// Args is a string slice of extra arguments to pass to ssh.
	Args []string
	// contains filtered or unexported fields
}

SSHCLI is a Runner that wraps another Runner, essentially prefixing given commands and arguments with "ssh", relevant SSH CLI arguments, and the given destination. It then passes this new "ssh" command to the underlying Runner.

This is useful for running commands on remote hosts via SSH, without having to use the Go ssh package.

Interactive commands are not supported, meaning SSH password prompts will not work, and the remote machine's hostkey should already be known and trusted by the ssh CLI client.

func (*SSHCLI) Env added in v0.1.4

func (rsc *SSHCLI) Env(env ...string)

Env sets the environment by calling Env on the underlying Runner. Will panic if Runner field is nil on SSH instance.

func (*SSHCLI) Run added in v0.1.4

func (rsc *SSHCLI) Run(
	stdin io.Reader,
	stdout io.Writer,
	stderr io.Writer,
	command string,
	args ...string,
) error

Run executes the command remotely via ssh by calling Run on the underlying Runner.

Will panic if Runner field is nil. Will return a error if Destination field is empty.

func (*SSHCLI) RunContext added in v0.1.4

func (rsc *SSHCLI) RunContext(
	ctx context.Context,
	stdin io.Reader,
	stdout io.Writer,
	stderr io.Writer,
	command string,
	args ...string,
) error

RunContext executes the command remotely via ssh by calling RunContext on the underlying Runner.

Will panic if Runner field is nil. Will return a error if Destination field is empty.

type Sudo

type Sudo struct {

	// Runner is the underlying Runner to run commands with, after wrapping them
	// with sudo. If not set, running commands will cause a panic.
	Runner Runner

	// User value passed to sudo via -u flag.
	User string

	// Args is a string slice of extra arguments to pass to sudo.
	Args []string
	// contains filtered or unexported fields
}

Sudo is a Runner that wraps another Runner and runs commands via sudo.

Password prompts are not supported, hence commands must be set to NOPASS via the sudoers file before they can be run.

func (*Sudo) Env

func (r *Sudo) Env(env ...string)

Env sets the environment by calling Env on the underlying Runner. Will panic if Runner field is nil on Sudo instance.

func (*Sudo) Run

func (r *Sudo) Run(
	stdin io.Reader,
	stdout io.Writer,
	stderr io.Writer,
	command string,
	args ...string,
) error

Run executes the command via sudo by calling Run on the underlying Runner. Will panic if Runner field is nil on Sudo instance.

func (*Sudo) RunContext

func (r *Sudo) RunContext(
	ctx context.Context,
	stdin io.Reader,
	stdout io.Writer,
	stderr io.Writer,
	command string,
	args ...string,
) error

RunContext executes the command via sudo by calling RunContext on the underlying Runner. Will panic if Runner field is nil on Sudo instance.

type Testing added in v0.1.2

type Testing struct {
	// Runner is the underlying Runner to run commands with. If not set, running
	// commands will cause a panic.
	Runner Runner

	// TestingT is the *testing.T instance used to log output. If not set,
	// running commands will cause a panic.
	TestingT TestingT

	// LogEnv indicates if calls to Env() should be logged.
	LogEnv bool
}

Testing is a Runner that wraps another Runner, and logs all executed commands and their arguments to a *testing.T instance.

Both Runner and T must be non-nil, or running commands will cause a panic.

func (*Testing) Env added in v0.1.2

func (r *Testing) Env(vars ...string)

Env sets the environment variables for the underlying Runner, and if LogEnv is true it logs the given environment variables to TestingT.

func (*Testing) Run added in v0.1.2

func (r *Testing) Run(
	stdin io.Reader,
	stdout io.Writer,
	stderr io.Writer,
	command string,
	args ...string,
) error

Run executes the command with the underlying Runner, and logs command and arguments to TestingT.

func (*Testing) RunContext added in v0.1.2

func (r *Testing) RunContext(
	ctx context.Context,
	stdin io.Reader,
	stdout io.Writer,
	stderr io.Writer,
	command string,
	args ...string,
) error

RunContext executes the command with the underlying Runner, and logs command and arguments to TestingT.

type TestingT added in v0.1.2

type TestingT interface {
	Logf(format string, args ...interface{})
}

TestingT is a interface that describes the *testing.T methods needed by the Testing runner implementation.

Directories

Path Synopsis
Code generated by MockGen.
Code generated by MockGen.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL