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  }