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