| // Copyright (c) HashiCorp, Inc. |
| // SPDX-License-Identifier: MPL-2.0 |
| |
| package remote |
| |
| import ( |
| "fmt" |
| "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) |
| } |
| |
| // 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 |
| |
| c.Lock() |
| defer c.Unlock() |
| |
| if c.err != nil || c.exitStatus != 0 { |
| return &ExitError{ |
| Command: c.Command, |
| ExitStatus: c.exitStatus, |
| Err: c.err, |
| } |
| } |
| |
| return nil |
| } |
| |
| // 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 |
| } |
| |
| 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) |
| } |