Files
opentf/communicator/remote/command.go
James Bardin 2d7dc605a0 get communicator errors from a remote.Cmd
The remote.Cmd struct could not convey any transport related error to
the caller, meaning that interrupted commands would show that they
succeeded.

Change Cmd.SetExited to accept an exit status, as well as an error to
store for the caller.  Make the status and error fields internal,
require serialized access through the getter methods.

Users of remote.Cmd should not check both Cmd.Err() and Cmd.ExitStatus()
until after Wait returns.

Require communicators to call Cmd.Init before executing the command.
This will indicate incorrect usage of the remote.Cmd by causing a panic
in SetExitStatus.
2018-03-15 16:03:20 -04:00

82 lines
1.8 KiB
Go

package remote
import (
"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
// Once Wait returns, his will contain the exit code of the process.
exitStatus int
// Internal fields
exitCh chan struct{}
// err is used to store any error reported by the Communicator during
// execution.
err error
// This thing is a mutex, lock when making modifications concurrently
sync.Mutex
}
// 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)
}
// Err returns any communicator related error.
func (c *Cmd) Err() error {
c.Lock()
defer c.Unlock()
return c.err
}
// ExitStatus returns the exit status of the remote command
func (c *Cmd) ExitStatus() int {
c.Lock()
defer c.Unlock()
return c.exitStatus
}
// Wait waits for the remote command to complete.
func (c *Cmd) Wait() {
<-c.exitCh
}