2015-04-09 21:58:00 +02:00
|
|
|
package remote
|
|
|
|
|
|
|
|
import (
|
2018-03-16 15:54:53 +01:00
|
|
|
"fmt"
|
2015-04-09 21:58:00 +02:00
|
|
|
"io"
|
|
|
|
"sync"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Cmd represents a remote command being prepared or run.
|
|
|
|
type Cmd struct {
|
|
|
|
// Command is the command to run remotely. This is executed as if
|
|
|
|
// it were a shell command, so you are expected to do any shell escaping
|
|
|
|
// necessary.
|
|
|
|
Command string
|
|
|
|
|
|
|
|
// Stdin specifies the process's standard input. If Stdin is
|
|
|
|
// nil, the process reads from an empty bytes.Buffer.
|
|
|
|
Stdin io.Reader
|
|
|
|
|
|
|
|
// Stdout and Stderr represent the process's standard output and
|
|
|
|
// error.
|
|
|
|
//
|
|
|
|
// If either is nil, it will be set to ioutil.Discard.
|
|
|
|
Stdout io.Writer
|
|
|
|
Stderr io.Writer
|
|
|
|
|
2018-03-15 15:50:17 +01:00
|
|
|
// Once Wait returns, his will contain the exit code of the process.
|
|
|
|
exitStatus int
|
2015-04-09 21:58:00 +02:00
|
|
|
|
|
|
|
// Internal fields
|
|
|
|
exitCh chan struct{}
|
|
|
|
|
2018-03-15 15:50:17 +01:00
|
|
|
// err is used to store any error reported by the Communicator during
|
|
|
|
// execution.
|
|
|
|
err error
|
|
|
|
|
2015-04-09 21:58:00 +02:00
|
|
|
// This thing is a mutex, lock when making modifications concurrently
|
|
|
|
sync.Mutex
|
|
|
|
}
|
|
|
|
|
2018-03-15 15:50:17 +01:00
|
|
|
// Init must be called by the Communicator before executing the command.
|
|
|
|
func (c *Cmd) Init() {
|
|
|
|
c.Lock()
|
|
|
|
defer c.Unlock()
|
|
|
|
|
|
|
|
c.exitCh = make(chan struct{})
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetExitStatus stores the exit status of the remote command as well as any
|
|
|
|
// communicator related error. SetExitStatus then unblocks any pending calls
|
|
|
|
// to Wait.
|
|
|
|
// This should only be called by communicators executing the remote.Cmd.
|
|
|
|
func (c *Cmd) SetExitStatus(status int, err error) {
|
|
|
|
c.Lock()
|
|
|
|
defer c.Unlock()
|
|
|
|
|
|
|
|
c.exitStatus = status
|
|
|
|
c.err = err
|
|
|
|
|
|
|
|
close(c.exitCh)
|
|
|
|
}
|
|
|
|
|
2018-03-16 15:54:53 +01:00
|
|
|
// Wait waits for the remote command to complete.
|
|
|
|
// Wait may return an error from the communicator, or an ExitError if the
|
|
|
|
// process exits with a non-zero exit status.
|
|
|
|
func (c *Cmd) Wait() error {
|
|
|
|
<-c.exitCh
|
|
|
|
|
2018-03-15 15:50:17 +01:00
|
|
|
c.Lock()
|
|
|
|
defer c.Unlock()
|
|
|
|
|
2018-03-23 16:36:57 +01:00
|
|
|
if c.err != nil || c.exitStatus != 0 {
|
|
|
|
return &ExitError{
|
|
|
|
Command: c.Command,
|
|
|
|
ExitStatus: c.exitStatus,
|
|
|
|
Err: c.err,
|
|
|
|
}
|
2018-03-16 15:54:53 +01:00
|
|
|
}
|
2015-04-09 21:58:00 +02:00
|
|
|
|
2018-03-16 15:54:53 +01:00
|
|
|
return nil
|
2015-04-09 21:58:00 +02:00
|
|
|
}
|
|
|
|
|
2018-03-23 16:36:57 +01:00
|
|
|
// ExitError is returned by Wait to indicate and error executing the remote
|
|
|
|
// command, or a non-zero exit status.
|
|
|
|
type ExitError struct {
|
|
|
|
Command string
|
|
|
|
ExitStatus int
|
|
|
|
Err error
|
|
|
|
}
|
2018-03-16 15:54:53 +01:00
|
|
|
|
2018-03-23 16:36:57 +01:00
|
|
|
func (e *ExitError) Error() string {
|
|
|
|
if e.Err != nil {
|
|
|
|
return fmt.Sprintf("error executing %q: %v", e.Command, e.Err)
|
|
|
|
}
|
|
|
|
return fmt.Sprintf("%q exit status: %d", e.Command, e.ExitStatus)
|
2015-04-09 21:58:00 +02:00
|
|
|
}
|