dexec

package
v1.4.3 Latest Latest
Warning

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

Go to latest
Published: May 14, 2020 License: Apache-2.0 Imports: 11 Imported by: 0

Documentation

Overview

nolint

Package dexec is a logging variant of os/exec.

dexec is *almost* a drop-in replacement for os/exec. Differences are:

- The "Command" function is missing, because a context is always required; use CommandContext.

- It is not valid to create a "Cmd" entirely by hand; you must create it using CommandContext. After it has been created, you may adjust the fields as you would with an os/exec.Cmd.

The logger used is configured in the context.Context passed to CommandContext by calling github.com/datawire/ambassador/pkg/dlog.WithLogger.

A Cmd logs when it starts, its exit status, and if they aren't an *os.File, logs everything read from or written to .Stdin, .Stdout, and .Stderr. If one of those is an *os.File (as it is following a call to .StdinPipe, .StdoutPipe, or .StderrPipe), then that stream won't be logged (but it will print a message at process-start noting that it isn't being logged).

For example:

ctx := dlog.WithLogger(context.Background(), myLogger)
cmd := dexec.CommandContext(ctx, "printf", "%s\n", "foo bar", "baz")
cmd.Stdin = os.Stdin
err := cmd.Run()

will log the lines

[pid:24272] started command []string{"printf", "%s\n", "foo bar", "baz"}
[pid:24272] stdin  < not logging input read from file /dev/stdin
[pid:24272] stdout+stderr > "foo bar\n"
[pid:24272] stdout+stderr > "baz\n"
[pid:24272] finished successfully: exit status 0

If you would like a "pipe" to be logged, use an io.Pipe instead of calling .StdinPipe, .StdoutPipe, or .StderrPipe.

See the os/exec documentation for more information.

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrNotFound = exec.ErrNotFound

ErrNotFound is the os/exec.ErrNotFound value.

View Source
var LookPath = exec.LookPath

LookPath is the os/exe.LookPath function.

Functions

This section is empty.

Types

type Cmd

type Cmd struct {
	*exec.Cmd
	// contains filtered or unexported fields
}

Cmd represents an external command being prepared or run.

A Cmd cannot be reused after calling its Run, Output or CombinedOutput methods.

See the os/exec.Cmd documentation for information on the fields within it.

Unlike an os/exec.Cmd, you MUST NOT construct a Cmd by hand, it must be created with CommandContext.

func CommandContext

func CommandContext(ctx context.Context, name string, arg ...string) *Cmd

CommandContext returns the Cmd struct to execute the named program with the given arguments.

The provided context is used for two purposes:

  1. To kill the process (by calling os.Process.Kill) if the context becomes done before the command completes on its own.
  2. To get the logger (by calling github.com/datawire/ambassador/pkg/dlog.GetLogger on it).

See the os/exec.Command and os/exec.CommandContext documentation for more information.

Example
package main

import (
	"bytes"
	"context"
	"fmt"
	"log"
	"strings"

	exec "github.com/datawire/ambassador/pkg/dexec"
)

func main() {
	cmd := exec.CommandContext(context.Background(), "tr", "a-z", "A-Z")
	cmd.Stdin = strings.NewReader("some input")
	var out bytes.Buffer
	cmd.Stdout = &out
	err := cmd.Run()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("in all caps: %q\n", out.String())
}
Output:

Example (Environment)
package main

import (
	"context"
	"log"
	"os"

	exec "github.com/datawire/ambassador/pkg/dexec"
)

func main() {
	cmd := exec.CommandContext(context.Background(), "prog")
	cmd.Env = append(os.Environ(),
		"FOO=duplicate_value", // ignored
		"FOO=actual_value",    // this value is used
	)
	if err := cmd.Run(); err != nil {
		log.Fatal(err)
	}
}
Output:

Example (Timeout)
package main

import (
	"context"
	"time"

	exec "github.com/datawire/ambassador/pkg/dexec"
)

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

	if err := exec.CommandContext(ctx, "sleep", "5").Run(); err != nil {
		// This will fail after 100 milliseconds. The 5 second sleep
		// will be interrupted.
	}
}
Output:

func (*Cmd) CombinedOutput

func (c *Cmd) CombinedOutput() ([]byte, error)

CombinedOutput runs the command and returns its combined standard output and standard error.

Example
package main

import (
	"context"
	"fmt"
	"log"

	exec "github.com/datawire/ambassador/pkg/dexec"
)

func main() {
	cmd := exec.CommandContext(context.Background(), "sh", "-c", "echo stdout; echo 1>&2 stderr")
	stdoutStderr, err := cmd.CombinedOutput()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s\n", stdoutStderr)
}
Output:

func (*Cmd) Output

func (c *Cmd) Output() ([]byte, error)

Output runs the command and returns its standard output. Any returned error will usually be of type *ExitError. If c.Stderr was nil, Output populates ExitError.Stderr.

Example
package main

import (
	"context"
	"fmt"
	"log"

	exec "github.com/datawire/ambassador/pkg/dexec"
)

func main() {
	out, err := exec.CommandContext(context.Background(), "date").Output()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("The date is %s\n", out)
}
Output:

func (*Cmd) Run

func (c *Cmd) Run() error

Run starts the specified command and waits for it to complete.

The returned error is nil if the command runs, has no problems copying stdin, stdout, and stderr, and exits with a zero exit status.

If the command starts but does not complete successfully, the error is of type *ExitError. Other error types may be returned for other situations.

If the calling goroutine has locked the operating system thread with runtime.LockOSThread and modified any inheritable OS-level thread state (for example, Linux or Plan 9 name spaces), the new process will inherit the caller's thread state.

Example
package main

import (
	"context"
	"log"

	exec "github.com/datawire/ambassador/pkg/dexec"
)

func main() {
	cmd := exec.CommandContext(context.Background(), "sleep", "1")
	log.Printf("Running command and waiting for it to finish...")
	err := cmd.Run()
	log.Printf("Command finished with error: %v", err)
}
Output:

func (*Cmd) Start

func (c *Cmd) Start() error

Start starts the specified command but does not wait for it to complete.

See the os/exec.Cmd.Start documenaton for more information.

Example
package main

import (
	"context"
	"log"

	exec "github.com/datawire/ambassador/pkg/dexec"
)

func main() {
	cmd := exec.CommandContext(context.Background(), "sleep", "5")
	err := cmd.Start()
	if err != nil {
		log.Fatal(err)
	}
	log.Printf("Waiting for command to finish...")
	err = cmd.Wait()
	log.Printf("Command finished with error: %v", err)
}
Output:

func (*Cmd) StderrPipe

func (c *Cmd) StderrPipe() (io.ReadCloser, error)

StderrPipe returns a pipe that will be connected to the command's standard error when the command starts.

This sets .Stderr to an *os.File, causing what you read from the pipe to not be logged.

See the os/exec.Cmd.StderrPipe documenaton for more information.

Example
package main

import (
	"context"
	"fmt"
	"io/ioutil"
	"log"

	exec "github.com/datawire/ambassador/pkg/dexec"
)

func main() {
	cmd := exec.CommandContext(context.Background(), "sh", "-c", "echo stdout; echo 1>&2 stderr")
	stderr, err := cmd.StderrPipe()
	if err != nil {
		log.Fatal(err)
	}

	if err := cmd.Start(); err != nil {
		log.Fatal(err)
	}

	slurp, _ := ioutil.ReadAll(stderr)
	fmt.Printf("%s\n", slurp)

	if err := cmd.Wait(); err != nil {
		log.Fatal(err)
	}
}
Output:

func (*Cmd) StdinPipe

func (c *Cmd) StdinPipe() (io.WriteCloser, error)

StdinPipe returns a pipe that will be connected to the command's standard input when the command starts.

This sets .Stdin to an *os.File, causing what you write to the pipe to not be logged.

See the os/exec.Cmd.StdinPipe documenaton for more information.

Example
package main

import (
	"context"
	"fmt"
	"io"
	"log"

	exec "github.com/datawire/ambassador/pkg/dexec"
)

func main() {
	cmd := exec.CommandContext(context.Background(), "cat")
	stdin, err := cmd.StdinPipe()
	if err != nil {
		log.Fatal(err)
	}

	go func() {
		defer stdin.Close()
		io.WriteString(stdin, "values written to stdin are passed to cmd's standard input")
	}()

	out, err := cmd.CombinedOutput()
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("%s\n", out)
}
Output:

func (*Cmd) StdoutPipe

func (c *Cmd) StdoutPipe() (io.ReadCloser, error)

StdoutPipe returns a pipe that will be connected to the command's standard output when the command starts.

This sets .Stdout to an *os.File, causing what you read from the pipe to not be logged.

See the os/exec.Cmd.StdoutPipe documenaton for more information.

Example
package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log"

	exec "github.com/datawire/ambassador/pkg/dexec"
)

func main() {
	cmd := exec.CommandContext(context.Background(), "echo", "-n", `{"Name": "Bob", "Age": 32}`)
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		log.Fatal(err)
	}
	if err := cmd.Start(); err != nil {
		log.Fatal(err)
	}
	var person struct {
		Name string
		Age  int
	}
	if err := json.NewDecoder(stdout).Decode(&person); err != nil {
		log.Fatal(err)
	}
	if err := cmd.Wait(); err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s is %d years old\n", person.Name, person.Age)
}
Output:

func (*Cmd) Wait

func (c *Cmd) Wait() error

Wait waits for the command to exit and waits for any copying to stdin or copying from stdout or stderr to complete.

See the os/exec.Cmd.Wait documenaton for more information.

type Error

type Error = exec.Error

Error is returned by LookPath when it fails to classify a file as an executable.

type ExitError

type ExitError = exec.ExitError

An ExitError reports an unsuccessful exit by a command.

Jump to

Keyboard shortcuts

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