github.com/cloudbase/juju-core@v0.0.0-20140504232958-a7271ac7912f/utils/ssh/run.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package ssh 5 6 import ( 7 "bytes" 8 "fmt" 9 "os/exec" 10 "strings" 11 "syscall" 12 "time" 13 14 utilexec "launchpad.net/juju-core/utils/exec" 15 ) 16 17 // ExecParams are used for the parameters for ExecuteCommandOnMachine. 18 type ExecParams struct { 19 IdentityFile string 20 Host string 21 Command string 22 Timeout time.Duration 23 } 24 25 // ExecuteCommandOnMachine will execute the command passed through on 26 // the host specified. This is done using ssh, and passing the commands 27 // through /bin/bash. If the command is not finished within the timeout 28 // specified, an error is returned. Any output captured during that time 29 // is also returned in the remote response. 30 func ExecuteCommandOnMachine(params ExecParams) (result utilexec.ExecResponse, err error) { 31 // execute bash accepting commands on stdin 32 if params.Host == "" { 33 return result, fmt.Errorf("missing host address") 34 } 35 logger.Debugf("execute on %s", params.Host) 36 var options Options 37 if params.IdentityFile != "" { 38 options.SetIdentities(params.IdentityFile) 39 } 40 command := Command(params.Host, []string{"/bin/bash", "-s"}, &options) 41 // start a go routine to do the actual execution 42 var stdout, stderr bytes.Buffer 43 command.Stdout = &stdout 44 command.Stderr = &stderr 45 command.Stdin = strings.NewReader(params.Command + "\n") 46 47 if err = command.Start(); err != nil { 48 return result, err 49 } 50 commandDone := make(chan error) 51 go func() { 52 defer close(commandDone) 53 err := command.Wait() 54 logger.Debugf("command.Wait finished: %v", err) 55 commandDone <- err 56 }() 57 58 select { 59 case err = <-commandDone: 60 logger.Debugf("select from commandDone channel: %v", err) 61 // command finished and returned us the results 62 if ee, ok := err.(*exec.ExitError); ok && err != nil { 63 status := ee.ProcessState.Sys().(syscall.WaitStatus) 64 if status.Exited() { 65 // A non-zero return code isn't considered an error here. 66 result.Code = status.ExitStatus() 67 err = nil 68 } 69 } 70 71 case <-time.After(params.Timeout): 72 logger.Infof("killing the command due to timeout") 73 err = fmt.Errorf("command timed out") 74 command.Kill() 75 } 76 // In either case, gather as much as we have from stdout and stderr 77 result.Stderr = stderr.Bytes() 78 result.Stdout = stdout.Bytes() 79 return result, err 80 }