github.com/scottwinkler/terraform@v0.11.6-0.20180329211809-05143987aea8/communicator/remote/command.go (about)

     1  package remote
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"sync"
     7  )
     8  
     9  // Cmd represents a remote command being prepared or run.
    10  type Cmd struct {
    11  	// Command is the command to run remotely. This is executed as if
    12  	// it were a shell command, so you are expected to do any shell escaping
    13  	// necessary.
    14  	Command string
    15  
    16  	// Stdin specifies the process's standard input. If Stdin is
    17  	// nil, the process reads from an empty bytes.Buffer.
    18  	Stdin io.Reader
    19  
    20  	// Stdout and Stderr represent the process's standard output and
    21  	// error.
    22  	//
    23  	// If either is nil, it will be set to ioutil.Discard.
    24  	Stdout io.Writer
    25  	Stderr io.Writer
    26  
    27  	// Once Wait returns, his will contain the exit code of the process.
    28  	exitStatus int
    29  
    30  	// Internal fields
    31  	exitCh chan struct{}
    32  
    33  	// err is used to store any error reported by the Communicator during
    34  	// execution.
    35  	err error
    36  
    37  	// This thing is a mutex, lock when making modifications concurrently
    38  	sync.Mutex
    39  }
    40  
    41  // Init must be called by the Communicator before executing the command.
    42  func (c *Cmd) Init() {
    43  	c.Lock()
    44  	defer c.Unlock()
    45  
    46  	c.exitCh = make(chan struct{})
    47  }
    48  
    49  // SetExitStatus stores the exit status of the remote command as well as any
    50  // communicator related error. SetExitStatus then unblocks any pending calls
    51  // to Wait.
    52  // This should only be called by communicators executing the remote.Cmd.
    53  func (c *Cmd) SetExitStatus(status int, err error) {
    54  	c.Lock()
    55  	defer c.Unlock()
    56  
    57  	c.exitStatus = status
    58  	c.err = err
    59  
    60  	close(c.exitCh)
    61  }
    62  
    63  // Wait waits for the remote command to complete.
    64  // Wait may return an error from the communicator, or an ExitError if the
    65  // process exits with a non-zero exit status.
    66  func (c *Cmd) Wait() error {
    67  	<-c.exitCh
    68  
    69  	c.Lock()
    70  	defer c.Unlock()
    71  
    72  	if c.err != nil || c.exitStatus != 0 {
    73  		return &ExitError{
    74  			Command:    c.Command,
    75  			ExitStatus: c.exitStatus,
    76  			Err:        c.err,
    77  		}
    78  	}
    79  
    80  	return nil
    81  }
    82  
    83  // ExitError is returned by Wait to indicate and error executing the remote
    84  // command, or a non-zero exit status.
    85  type ExitError struct {
    86  	Command    string
    87  	ExitStatus int
    88  	Err        error
    89  }
    90  
    91  func (e *ExitError) Error() string {
    92  	if e.Err != nil {
    93  		return fmt.Sprintf("error executing %q: %v", e.Command, e.Err)
    94  	}
    95  	return fmt.Sprintf("%q exit status: %d", e.Command, e.ExitStatus)
    96  }