~ubuntu-branches/ubuntu/trusty/juju-core/trusty-proposed

« back to all changes in this revision

Viewing changes to src/launchpad.net/juju-core/utils/ssh/run.go

  • Committer: Package Import Robot
  • Author(s): James Page
  • Date: 2014-01-29 11:40:20 UTC
  • mfrom: (23.1.1 trusty-proposed)
  • Revision ID: package-import@ubuntu.com-20140129114020-ejieitm8smtt5vln
Tags: 1.17.1-0ubuntu2
d/tests/local-provider: Don't fail tests if ~/.juju is present as its
created by the juju version command. 

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
}