Documentation ¶
Overview ¶
Package run is a Go (golang) package that wraps the standard Go os/exec and golang.org/x/crypto/ssh packages to run commands either locally or over ssh while capturing stdout, stderr, and exit codes.
Index ¶
- Constants
- func FormatRun(cmd string, args ...string) string
- func FormatShell(cmd string) string
- func Run(cmd string, args ...string) (string, string, int, error)
- func Shell(cmd string) (string, string, int, error)
- type Credentials
- type Local
- type LocalConfig
- type Remote
- type RemoteConfig
- type Runner
Examples ¶
Constants ¶
const ( // DefaultShellExecutable is the shell that will be run when // using Shell() methods. DefaultShellExecutable = "/bin/sh" )
Variables ¶
This section is empty.
Functions ¶
func FormatRun ¶
FormatRun returns a string representation of the what command would be run using the standard runner's Run() method. Useful for logging commands.
func FormatShell ¶
FormatShell returns a string representation of the what command would be run using the standard runner's Shell() method. Useful for logging commands.
func Run ¶
Run runs a command like glibc's exec() call using the standard runner. It returns the standard out, standard error, and exit code of the command when it completes.
Example ¶
// There is no need for a constructor when running local // commands when using the standard runner. fmt.Println("Run ls command.") stdout, stderr, code, err := run.Run( "/bin/ls", "-1", "/bin/true", "/bin/false") if err != nil { fmt.Printf("Internal error executing ls: %s.\n", err) os.Exit(1) } fmt.Printf("stdout = %q\n", stdout) fmt.Printf("stderr = %q\n", stderr) fmt.Printf("exit code = %d\n", code) fmt.Println() fmt.Println("Run id command.") stdout, stderr, code, err = run.Run("/bin/id", "root") if err != nil { fmt.Printf("Internal error executing ls: %s.\n", err) os.Exit(1) } fmt.Printf("stdout = %q\n", stdout) fmt.Printf("stderr = %q\n", stderr) fmt.Printf("exit code = %d\n", code) fmt.Println()
Output: Run ls command. stdout = "/bin/false\n/bin/true\n" stderr = "" exit code = 0 Run id command. stdout = "uid=0(root) gid=0(root) groups=0(root)\n" stderr = "" exit code = 0
func Shell ¶
Shell runs a command in a shell using the standard runner. The command is passed to the shell as the -c option, so just about any shell code that can be used on the command-line will be passed to it. It returns the standard out, standard error, and exit code of the command when it completes
Example ¶
// There is no need for a constructor when running local // commands when using the standard runner. fmt.Println("Run ls command using shell.") stdout, stderr, code, err := run.Shell("/bin/ls -1 /bin/true /bin/false") if err != nil { fmt.Printf("Internal error executing ls: %s.\n", err) os.Exit(1) } fmt.Printf("stdout = %q\n", stdout) fmt.Printf("stderr = %q\n", stderr) fmt.Printf("exit code = %d\n", code) fmt.Println() // Print out the command to run, then run it. fmt.Println("Run id command.") stdout, stderr, code, err = run.Shell("/bin/id root | cut -f1 -d ' '") if err != nil { fmt.Printf("Internal error executing ls: %s.\n", err) os.Exit(1) } fmt.Printf("stdout = %q\n", stdout) fmt.Printf("stderr = %q\n", stderr) fmt.Printf("exit code = %d\n", code) fmt.Println()
Output: Run ls command using shell. stdout = "/bin/false\n/bin/true\n" stderr = "" exit code = 0 Run id command. stdout = "uid=0(root)\n" stderr = "" exit code = 0
Types ¶
type Credentials ¶
type Credentials struct { // Hostname is either the hostname or IP of the remote host. Hostname string // Port is the port used to connect to the ssh server on the // remote host. Port int // Username is the account name used to authenticate on the // remote host. Username string // Password is password used to authenticate on the remote // host. Not needed if using PrivateKeyFilename. Password string // PrivateKeyFilename is the full path the SSH private key // used to authenticate with the remote host. Not used if // Password is specified. You must use ssh-agent or something // similar to provide the passphrase if the key is passphrase // protected. PrivateKeyFilename string }
Credentials contains needed credentials to SSH to a host. It can use either a password or SSH private key.
type Local ¶
type Local struct { // ShellExecutable is the full path to the shell to be run // when executing shell commands. ShellExecutable string // Env specifies the environment of the process. // Each entry is of the form "key=value". // If Env is nil, the new process uses the current process's // environment. // If Env contains duplicate environment keys, only the last // value in the slice for each duplicate key is used. Env []string // Dir specifies the working directory of the command. // If Dir is the empty string, Run runs the command in the // calling process's current directory. Dir string // Stdin specifies the process's standard input. // // If Stdin is nil, the process reads from the null device (os.DevNull). // // If Stdin is an *os.File, the process's standard input is connected // directly to that file. // // Otherwise, during the execution of the command a separate // goroutine reads from Stdin and delivers that data to the command // over a pipe. In this case, Wait does not complete until the goroutine // stops copying, either because it has reached the end of Stdin // (EOF or a read error) or because writing to the pipe returned an error. Stdin io.Reader // Stdout and Stderr specify the process's standard output and error. // // If either is nil, the descriptor's output is captured and // is returned in the Run and Shell functions. // // If either is an *os.File, the corresponding output from the process // is connected directly to that file. // // Otherwise, during the execution of the command a separate goroutine // reads from the process over a pipe and delivers that data to the // corresponding Writer. In this case, Wait does not complete until the // goroutine reaches EOF or encounters an error. // // If Stdout and Stderr are the same writer, and have a type that can // be compared with ==, at most one goroutine at a time will call Write. Stdout io.Writer Stderr io.Writer }
Local wraps os/exec Cmd to make running external commands on the local host relatively as easy as when running them in shell script.
func NewLocal ¶
func NewLocal(config LocalConfig) *Local
NewLocal is the constuctor for Local. It takes a LocalConfig object to configure it. The following configuration options are set if the default LocalConfig constructor, LocalConfig{}, is used:
ShellExecutable = DefaultShellExecutable Env = []string{} // Use existing environment. Dir = nil // Current working directory. Stdin = nil // Discard stdin. Stdout = nil // Capture stdout. Stderr = nil // Capture stderr,
func (*Local) FormatRun ¶
FormatRun returns a string representation of the what command would be run using Run(). Useful for logging commands.
func (*Local) FormatShell ¶
FormatShell returns a string representation of the what command would be run using Shell(). Useful for logging commands.
func (*Local) Run ¶
Run runs a command like glibc's exec() call. It returns the standard out, standard error, and exit code of the command when it completes.
Example ¶
// Initialize Local object using defaults. runner := run.NewLocal(run.LocalConfig{}) fmt.Println("Run ls command.") stdout, stderr, code, err := runner.Run( "/bin/ls", "-1", "/bin/true", "/bin/false") if err != nil { fmt.Printf("Internal error executing ls: %s.\n", err) os.Exit(1) } fmt.Printf("stdout = %q\n", stdout) fmt.Printf("stderr = %q\n", stderr) fmt.Printf("exit code = %d\n", code) fmt.Println() fmt.Println("Run ls command with an expected error.") stdout, stderr, code, err = runner.Run( "/bin/ls", "-1", "/bin/true", "/bin/false", "/xyzzy") if err != nil { fmt.Printf("Internal error executing ls: %s.\n", err) os.Exit(1) } fmt.Printf("stdout = %q\n", stdout) fmt.Printf("stderr = %q\n", stderr) fmt.Printf("exit code = %d\n", code) fmt.Println() fmt.Println("Run the ls command with an internal error (bad path).") _, _, _, err = runner.Run( "/not_a_good_path/ls", "-1", "/bin/true", "/bin/false") fmt.Printf("Internal error executing ls: %s.\n", err) fmt.Println() fmt.Println("Run ls command after changing directory.") runner = run.NewLocal(run.LocalConfig{Dir: "/bin"}) stdout, stderr, code, err = runner.Run( "/bin/ls", "-1", "true", "false") if err != nil { fmt.Printf("Internal error executing ls: %s.\n", err) os.Exit(1) } fmt.Printf("stdout = %q\n", stdout) fmt.Printf("stderr = %q\n", stderr) fmt.Printf("exit code = %d\n", code) fmt.Println()
Output: Run ls command. stdout = "/bin/false\n/bin/true\n" stderr = "" exit code = 0 Run ls command with an expected error. stdout = "/bin/false\n/bin/true\n" stderr = "/bin/ls: cannot access /xyzzy: No such file or directory\n" exit code = 2 Run the ls command with an internal error (bad path). Internal error executing ls: fork/exec /not_a_good_path/ls: no such file or directory. Run ls command after changing directory. stdout = "false\ntrue\n" stderr = "" exit code = 0
func (*Local) Shell ¶
Shell runs a command in a shell. The command is passed to the shell as the -c option, so just about any shell code that can be used on the command-line will be passed to it. It returns the standard out, standard error, and exit code of the command when it completes.
Example ¶
// Initialize Local object using defaults. runner := run.NewLocal(run.LocalConfig{}) fmt.Println("Run ls command using shell.") stdout, stderr, code, err := runner.Shell("/bin/ls -1 /bin/true /bin/false") if err != nil { fmt.Printf("Internal error executing ls: %s.\n", err) os.Exit(1) } fmt.Printf("stdout = %q\n", stdout) fmt.Printf("stderr = %q\n", stderr) fmt.Printf("exit code = %d\n", code) fmt.Println() fmt.Println("Run ls command using shell with an expected error.") stdout, stderr, code, err = runner.Shell("/bin/ls -1 /bin/true /bin/false /xyzzy") if err != nil { fmt.Printf("Internal error executing ls: %s.\n", err) os.Exit(1) } fmt.Printf("stdout = %q\n", stdout) fmt.Printf("stderr = %q\n", stderr) fmt.Printf("exit code = %d\n", code) fmt.Println() fmt.Println("Run the ls command using shell with an internal error (bad path to shell).") runner = run.NewLocal(run.LocalConfig{ShellExecutable: "/bin/badsh"}) _, _, _, err = runner.Shell("/bin/ls -1 /bin/true /bin/false") fmt.Printf("Internal error executing ls: %s.\n", err) fmt.Println() fmt.Println("Run ls command using shell after changing directory.") runner = run.NewLocal(run.LocalConfig{Dir: "/bin"}) stdout, stderr, code, err = runner.Shell("/bin/ls -1 true false") if err != nil { fmt.Printf("Internal error executing ls: %s.\n", err) os.Exit(1) } fmt.Printf("stdout = %q\n", stdout) fmt.Printf("stderr = %q\n", stderr) fmt.Printf("exit code = %d\n", code) fmt.Println() fmt.Println("Run complex shell command.") runner = run.NewLocal(run.LocalConfig{}) stdout, stderr, code, err = runner.Shell("cd /bin && /bin/ls -1 true false | head -n 1") if err != nil { fmt.Printf("Internal error executing ls: %s.\n", err) os.Exit(1) } fmt.Printf("stdout = %q\n", stdout) fmt.Printf("stderr = %q\n", stderr) fmt.Printf("exit code = %d\n", code) fmt.Println()
Output: Run ls command using shell. stdout = "/bin/false\n/bin/true\n" stderr = "" exit code = 0 Run ls command using shell with an expected error. stdout = "/bin/false\n/bin/true\n" stderr = "/bin/ls: cannot access /xyzzy: No such file or directory\n" exit code = 2 Run the ls command using shell with an internal error (bad path to shell). Internal error executing ls: fork/exec : no such file or directory. Run ls command using shell after changing directory. stdout = "false\ntrue\n" stderr = "" exit code = 0 Run complex shell command. stdout = "false\n" stderr = "" exit code = 0
type LocalConfig ¶
type LocalConfig struct { // ShellExecutable is the path to the shell used to run // commands in Shell() methods. See Local for details. ShellExecutable string // Env specifies the environment of the command to be run. // See Local for details. Env []string // Dir specifies the working directory of the command. See // Local for details. The default is the empty string. Dir string // Stdin specifies the process's standard input. See Local for // details. Stdin io.Reader // Stdout specifies the process's standard output. See Local // for details. Stdout io.Writer // Stderr specifies the process's standard error. See Local // for details. Stderr io.Writer }
LocalConfig is used to configure the Local constructor.
type Remote ¶
type Remote struct { // ShellExecutable is the full path to the shell on the remote // host to be run when executing shell commands. ShellExecutable string // Stdin specifies the process's standard input. // // If Stdin is nil, the process reads from the null device (os.DevNull). // // If Stdin is an *os.File, the process's standard input is connected // directly to that file. // // Otherwise, during the execution of the command a separate // goroutine reads from Stdin and delivers that data to the command // over a pipe. In this case, Wait does not complete until the goroutine // stops copying, either because it has reached the end of Stdin // (EOF or a read error) or because writing to the pipe returned an error. Stdin io.Reader // Stdout and Stderr specify the process's standard output and error. // // If either is nil, the descriptor's output is captured and // is returned in the Run and Shell functions. // // If either is an *os.File, the corresponding output from the process // is connected directly to that file. // // Otherwise, during the execution of the command a separate goroutine // reads from the process over a pipe and delivers that data to the // corresponding Writer. In this case, Wait does not complete until the // goroutine reaches EOF or encounters an error. // // If Stdout and Stderr are the same writer, and have a type that can // be compared with ==, at most one goroutine at a time will call Write. Stdout io.Writer Stderr io.Writer // Credentials are used to authenticate with the remote host. Credentials Credentials // contains filtered or unexported fields }
Remote wraps ssh.Client to make running commands over SSH on a remote host relatively as easy as when running them in shell script.
func NewRemote ¶
func NewRemote(config RemoteConfig) (*Remote, error)
NewRemote is the constructor for Remote. It takes a RemoteConfig object to configure it. The following configuration options are set if the default RemoteConfig constructor, RemoteConfig{}, is used:
ShellExecutable = DefaultShellExecutable Stdin = nil // Discard stdin. Stdout = nil // Capture stdout. Stderr = nil // Capture stderr, Credentials.Hostname = "localhost" Credentials.Port = 22 Credentials.Username = Current user Credentials.Password = "" Credentials.PrivateKeyFilename = Current users default private RSA keyfile ($HOME/.ssh/id_rsa) if present.
func (*Remote) FormatRun ¶
FormatRun returns a string representation of the what command would be run using Run(). Useful for logging commands.
func (*Remote) FormatShell ¶
FormatShell returns a string representation of the what command would be run using Shell(). Useful for logging commands.
func (*Remote) Run ¶
Run runs a command like glibc's exec() call. It returns the standard out, standard error, and exit code of the command when it completes.
Example ¶
// Initialize Remote object using defaults. runner, _ := run.NewRemote(run.RemoteConfig{ Credentials: run.Credentials{ Hostname: "localhost"}, }) fmt.Println("Run ls command.") stdout, stderr, code, err := runner.Run( "/bin/ls", "-1", "/bin/true", "/bin/false") if err != nil { fmt.Printf("Internal error executing ls: %s.\n", err) os.Exit(1) } fmt.Printf("stdout = %q\n", stdout) fmt.Printf("stderr = %q\n", stderr) fmt.Printf("exit code = %d\n", code) fmt.Println() fmt.Println("Run ls command with an expected error.") stdout, stderr, code, err = runner.Run( "/bin/ls", "-1", "/bin/true", "/bin/false", "/xyzzy") if err != nil { fmt.Printf("Internal error executing ls: %s.\n", err) os.Exit(1) } fmt.Printf("stdout = %q\n", stdout) fmt.Printf("stderr = %q\n", stderr) fmt.Printf("exit code = %d\n", code) fmt.Println() fmt.Println("Run the ls command with an internal error (bad user/passwd).") runner, _ = run.NewRemote(run.RemoteConfig{ Credentials: run.Credentials{ Hostname: "localhost", Username: "bad_user", Password: "xyzzy"}, }) _, _, _, err = runner.Run( "/bin/ls", "-1", "/bin/true", "/bin/false") fmt.Printf("Internal error executing ls: %s.\n", err) fmt.Println()
Output: Run ls command. stdout = "/bin/false\n/bin/true\n" stderr = "" exit code = 0 Run ls command with an expected error. stdout = "/bin/false\n/bin/true\n" stderr = "/bin/ls: cannot access '/xyzzy': No such file or directory\n" exit code = 2 Run the ls command with an internal error (bad user/passwd). Internal error executing ls: run: connection to bad_user@localhost failed: ssh: handshake failed: ssh: unable to authenticate, attempted methods [none password], no supported methods remain.
func (*Remote) Shell ¶
Shell runs a command in a shell. The command is passed to the shell as the -c option, so just about any shell code that can be used on the command-line will be passed to it. It returns the standard out, standard error, and exit code of the command when it completes.
Example ¶
// Initialize Remote object using defaults. runner, _ := run.NewRemote(run.RemoteConfig{}) fmt.Println("Run ls command using shell.") stdout, stderr, code, err := runner.Shell("/bin/ls -1 /bin/true /bin/false") if err != nil { fmt.Printf("Internal error executing ls: %s.\n", err) os.Exit(1) } fmt.Printf("stdout = %q\n", stdout) fmt.Printf("stderr = %q\n", stderr) fmt.Printf("exit code = %d\n", code) fmt.Println() fmt.Println("Run ls command using shell with an expected error.") stdout, stderr, code, err = runner.Shell("/bin/ls -1 /bin/true /bin/false /xyzzy") if err != nil { fmt.Printf("Internal error executing ls: %s.\n", err) os.Exit(1) } fmt.Printf("stdout = %q\n", stdout) fmt.Printf("stderr = %q\n", stderr) fmt.Printf("exit code = %d\n", code) fmt.Println() fmt.Println("Run the ls command using a shell with an internal error (bad user/passwd).") runner, _ = run.NewRemote(run.RemoteConfig{ Credentials: run.Credentials{ Hostname: "localhost", Username: "bad_user", Password: "xyzzy"}, }) _, _, _, err = runner.Shell("/bin/ls -1 /bin/true /bin/false") fmt.Printf("Internal error executing ls: %s.\n", err) fmt.Println() fmt.Println("Run complex shell command.") runner, _ = run.NewRemote(run.RemoteConfig{}) stdout, stderr, code, err = runner.Shell("cd /bin && /bin/ls -1 true false | head -n 1") if err != nil { fmt.Printf("Internal error executing ls: %s.\n", err) os.Exit(1) } fmt.Printf("stdout = %q\n", stdout) fmt.Printf("stderr = %q\n", stderr) fmt.Printf("exit code = %d\n", code) fmt.Println()
Output: Run ls command using shell. stdout = "/bin/false\n/bin/true\n" stderr = "" exit code = 0 Run ls command using shell with an expected error. stdout = "/bin/false\n/bin/true\n" stderr = "/bin/ls: cannot access '/xyzzy': No such file or directory\n" exit code = 2 Run the ls command using a shell with an internal error (bad user/passwd). Internal error executing ls: run: connection to bad_user@localhost failed: ssh: handshake failed: ssh: unable to authenticate, attempted methods [none password], no supported methods remain. Run complex shell command. stdout = "false\n" stderr = "" exit code = 0
type RemoteConfig ¶
type RemoteConfig struct { // ShellExecutable is the path to the shell on the remote host // used to run commands in Shell() methods. See Remote for // details. ShellExecutable string // Stdin specifies the process's standard input. See Remote for // details. Stdin io.Reader // Stdout specifies the process's standard output. See Remote // for details. Stdout io.Writer // Stderr specifies the process's standard error. See Remote // for details. Stderr io.Writer // Credentials used to authenticate on the remote system. Credentials Credentials }
RemoteConfig contains configuration data used in the Remote constructor.
type Runner ¶
type Runner interface { // Run runs a command like glibc's exec() call. It returns the // standard out, standard error, and exit code of the command // when it completes. Run(cmd string, args ...string) (string, string, int, error) // FormatRun returns a string representation of the what // command would be run using Run(). Useful for logging // commands. FormatRun(cmd string, args ...string) string // Shell runs a command in a shell. The command is passed to // the shell as the -c option, so just about any shell code // that can be used on the command-line will be passed to // it. It returns the standard out, standard error, and exit // code of the command when it completes Shell(cmd string) (string, string, int, error) // FormatShell returns a string representation of the what // command would be run using Shell(). Useful for logging // commands. FormatShell(cmd string) string }
Runner is the interface for both Local and Remote.
Example ¶
// This example shows how to use the run.Runner interface to create // functions to take either Local or Remote objects. In this example, // we "log" the command run by the Run() and Shell() methods. package main import ( "fmt" "github.com/apatters/go-run" ) // logRun outputs the command as if run on the command line and then // "runs" the command using the Run() method. It can take either a // run.Local or run.Remote object as they both fulfill the run.Runner // interface. func logRun(r run.Runner, cmd string, cmdArgs ...string) (stdout string, stderr string, exitCode int, err error) { fmt.Printf("%s\n", r.FormatRun(cmd, cmdArgs...)) return r.Run(cmd, cmdArgs...) } // logShell outputs the shell command as if run on the command line // and then "runs" the command using the Shell() method. It can take // either a run.Local or run.Remote object as they both fulfill the // run.Runner interface. func logShell(r run.Runner, cmd string) (stdout string, stderr string, exitCode int, err error) { fmt.Printf("%s\n", r.FormatShell(cmd)) return r.Shell(cmd) } func main() { l := run.NewLocal(run.LocalConfig{}) r, _ := run.NewRemote(run.RemoteConfig{ Credentials: run.Credentials{ Hostname: "localhost", Username: "buildman"}, }) stdout, stderr, code, _ := logRun(l, "/bin/ls", "-1", "/bin/true", "/bin/false") fmt.Printf("stdout = %q\n", stdout) fmt.Printf("stderr = %q\n", stderr) fmt.Printf("exit code = %d\n", code) fmt.Println() stdout, stderr, code, _ = logRun(r, "/bin/ls", "-1", "/bin/true", "/bin/false") fmt.Printf("stdout = %q\n", stdout) fmt.Printf("stderr = %q\n", stderr) fmt.Printf("exit code = %d\n", code) fmt.Println() stdout, stderr, code, _ = logShell(l, "/bin/ls -1 /bin/true /bin/false") fmt.Printf("stdout = %q\n", stdout) fmt.Printf("stderr = %q\n", stderr) fmt.Printf("exit code = %d\n", code) fmt.Println() stdout, stderr, code, _ = logShell(r, "/bin/ls -1 /bin/true /bin/false") fmt.Printf("stdout = %q\n", stdout) fmt.Printf("stderr = %q\n", stderr) fmt.Printf("exit code = %d\n", code) fmt.Println() }
Output: /bin/ls -1 /bin/true /bin/false stdout = "/bin/false\n/bin/true\n" stderr = "" exit code = 0 ssh buildman@localhost /bin/ls -1 /bin/true /bin/false stdout = "/bin/false\n/bin/true\n" stderr = "" exit code = 0 /bin/sh -c "/bin/ls -1 /bin/true /bin/false" stdout = "/bin/false\n/bin/true\n" stderr = "" exit code = 0 ssh buildman@localhost /bin/sh -c "/bin/ls -1 /bin/true /bin/false" stdout = "/bin/false\n/bin/true\n" stderr = "" exit code = 0