Documentation
¶
Overview ¶
Package com is a lightweight wrapper around golang command execution. It provides a simplified API to create commands with baked-in: - timeout - pty - environment filtering - stdin manipulation - proper termination of the process group - wrapping commands and prepended args
Index ¶
- Constants
- Variables
- type Command
- func (gc *Command) Clone() *Command
- func (gc *Command) Feed(reader io.Reader)
- func (gc *Command) Run(parentCtx context.Context) error
- func (gc *Command) Signal(sig os.Signal) error
- func (gc *Command) Wait() (*Result, error)
- func (gc *Command) WithFeeder(writers ...func() io.Reader)
- func (gc *Command) WithPTY(stdin, stdout, stderr bool)
- type Result
Examples ¶
Constants ¶
const LoggerKey = contextKey("logger")
LoggerKey defines the key to attach a logger to on the context.
Variables ¶
var ( // ErrTimeout is returned by Wait() in case a command fail to complete within allocated time. ErrTimeout = errors.New("command timed out") // ErrFailedStarting is returned by Run() and Wait() in case a command fails to start (eg: // binary missing). ErrFailedStarting = errors.New("command failed starting") // ErrSignaled is returned by Wait() if a signal was sent to the command while running. ErrSignaled = errors.New("command execution signaled") // ErrExecutionFailed is returned by Wait() when a command executes but returns a non-zero error code. ErrExecutionFailed = errors.New("command returned a non-zero exit code") // ErrFailedSendingSignal may happen if sending a signal to an already terminated process. ErrFailedSendingSignal = errors.New("failed sending signal") // ErrExecAlreadyStarted is a system error normally indicating a bogus double call to Run(). ErrExecAlreadyStarted = errors.New("command has already been started (double `Run`)") // ErrExecNotStarted is a system error normally indicating that Wait() has been called without first calling Run(). ErrExecNotStarted = errors.New("command has not been started (call `Run` first)") // ErrExecAlreadyFinished is a system error indicating a double call to Wait(). ErrExecAlreadyFinished = errors.New("command is already finished") )
var ( // ErrFailedCreating could be returned by newStdPipes() on pty creation failure. ErrFailedCreating = errors.New("failed acquiring pipe") // ErrFailedReading could be returned by the ioGroup in case the go routines fails to read out of a pipe. ErrFailedReading = errors.New("failed reading") // ErrFailedWriting could be returned by the ioGroup in case the go routines fails to write on a pipe. ErrFailedWriting = errors.New("failed writing") )
Functions ¶
This section is empty.
Types ¶
type Command ¶
type Command struct {
Binary string
PrependArgs []string
Args []string
WrapBinary string
WrapArgs []string
Timeout time.Duration
WorkingDir string
Env map[string]string
EnvBlackList []string
EnvWhiteList []string
// contains filtered or unexported fields
}
Command is a thin wrapper on-top of golang exec.Command.
Example ¶
package main
import (
"context"
"fmt"
"github.com/containerd/nerdctl/mod/tigron/internal/com"
)
func main() {
cmd := com.Command{
Binary: "printf",
Args: []string{"hello world"},
}
err := cmd.Run(context.Background())
if err != nil {
fmt.Println("Run err:", err)
return
}
exec, err := cmd.Wait()
if err != nil {
fmt.Println("Wait err:", err)
return
}
fmt.Println("Exit code:", exec.ExitCode)
fmt.Println("Stdout:")
fmt.Println(exec.Stdout)
fmt.Println("Stderr:")
fmt.Println(exec.Stderr)
}
Output: Exit code: 0 Stdout: hello world Stderr:
func (*Command) Feed ¶
Feed ensures that the provider reader will be copied on the command stdin. Feed, like WithFeeder, can be used multiple times, and writes will be performed in sequentially, in order. This command has no effect if Run has already been called.
Example ¶
package main
import (
"context"
"fmt"
"io"
"strings"
"time"
"github.com/containerd/nerdctl/mod/tigron/internal/com"
)
func main() {
cmd := &com.Command{
Binary: "bash",
Args: []string{
"-c", "--",
"read line1; read line2; printf 'from stdin%s%s%s' \"$line1\" \"$line2\";",
},
}
// Use WithFeeder if you do want to perform additional tasks before feeding to stdin
cmd.WithFeeder(func() io.Reader {
time.Sleep(100 * time.Millisecond)
return strings.NewReader("hello world\n")
})
// Or use the simpler Feed if you just want to pass along content to stdin
// Note that successive calls to WithFeeder / Feed will be written to stdin in order.
cmd.Feed(strings.NewReader("hello again\n"))
err := cmd.Run(context.Background())
if err != nil {
fmt.Println("Run err:", err)
return
}
exec, err := cmd.Wait()
if err != nil {
fmt.Println("Wait err:", err)
return
}
fmt.Println("Exit code:", exec.ExitCode)
fmt.Println("Stdout:")
fmt.Println(exec.Stdout)
fmt.Println("Stderr:")
fmt.Println(exec.Stderr)
}
Output: Exit code: 0 Stdout: from stdinhello worldhello again Stderr:
func (*Command) Run ¶
Run starts the command in the background. It may error out immediately if the command fails to start (ErrFailedStarting).
func (*Command) Signal ¶
Signal sends a signal to the command. It should be called after Run() but before Wait().
Example ¶
package main
import (
"context"
"fmt"
"os"
"time"
"github.com/containerd/nerdctl/mod/tigron/internal/com"
)
func main() {
cmd := com.Command{
Binary: "sleep",
Args: []string{"3600"},
Timeout: time.Second,
}
err := cmd.Run(context.Background())
if err != nil {
fmt.Println("Run err:", err)
return
}
err = cmd.Signal(os.Interrupt)
if err != nil {
fmt.Println("Signal err:", err)
return
}
exec, err := cmd.Wait()
fmt.Println("Wait err:", err)
fmt.Println("Exit code:", exec.ExitCode)
fmt.Println("Stdout:")
fmt.Println(exec.Stdout)
fmt.Println("Stderr:")
fmt.Println(exec.Stderr)
fmt.Println("Signal:", exec.Signal)
}
Output: Wait err: command execution signaled Exit code: -1 Stdout: Stderr: Signal: interrupt
func (*Command) Wait ¶
Wait should be called after Run(), and will return the outcome of the command execution.
func (*Command) WithFeeder ¶
WithFeeder ensures that the provider function will be executed and its output fed to the command stdin. WithFeeder, like Feed, can be used multiple times, and writes will be performed sequentially, in order. This command has no effect if Run has already been called. Note that if the `writer` function runs a forever loop, we will deadlock and just Wait() forever on the errgroup.
func (*Command) WithPTY ¶
WithPTY requests that the command be executed with a pty for std streams. Parameters allow showing which streams are to be tied to the pty. This command has no effect if Run has already been called.
Example ¶
package main
import (
"context"
"fmt"
"time"
"github.com/containerd/nerdctl/mod/tigron/internal/com"
)
func main() {
cmd := &com.Command{
Binary: "bash",
Args: []string{
"-c",
"--",
"[ -t 1 ] || { echo not a pty; exit 41; }; printf onstdout; >&2 printf onstderr;",
},
Timeout: 1 * time.Second,
}
// The PTY can be set to any of stdin, stdout, stderr
// Note that PTY are supported only on Linux, Darwin and FreeBSD
cmd.WithPTY(false, true, false)
err := cmd.Run(context.Background())
if err != nil {
fmt.Println("Run err:", err)
return
}
exec, err := cmd.Wait()
if err != nil {
fmt.Println("Wait err:", err)
return
}
fmt.Println("Exit code:", exec.ExitCode)
fmt.Println("Stdout:")
fmt.Println(exec.Stdout)
fmt.Println("Stderr:")
fmt.Println(exec.Stderr)
}
Output: Exit code: 0 Stdout: onstdout Stderr: onstderr