~juju-qa/ubuntu/trusty/juju/juju-1.25.8

« back to all changes in this revision

Viewing changes to src/github.com/juju/juju/cmd/jujud/run.go

  • Committer: Nicholas Skaggs
  • Date: 2016-12-02 18:01:10 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161202180110-dl1helep8qfebmhx
ImportĀ upstreamĀ 1.25.6

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 main
 
5
 
 
6
import (
 
7
        "fmt"
 
8
        "os"
 
9
        "strconv"
 
10
        "strings"
 
11
        "time"
 
12
 
 
13
        "github.com/juju/cmd"
 
14
        "github.com/juju/errors"
 
15
        "github.com/juju/mutex"
 
16
        "github.com/juju/names"
 
17
        "github.com/juju/utils/clock"
 
18
        "github.com/juju/utils/exec"
 
19
        "launchpad.net/gnuflag"
 
20
 
 
21
        "github.com/juju/juju/agent"
 
22
        cmdutil "github.com/juju/juju/cmd/jujud/util"
 
23
        "github.com/juju/juju/juju/sockets"
 
24
        "github.com/juju/juju/version"
 
25
        "github.com/juju/juju/worker/uniter"
 
26
)
 
27
 
 
28
type RunCommand struct {
 
29
        cmd.CommandBase
 
30
        MachineLockName string
 
31
        unit            names.UnitTag
 
32
        commands        string
 
33
        showHelp        bool
 
34
        noContext       bool
 
35
        forceRemoteUnit bool
 
36
        relationId      string
 
37
        remoteUnitName  string
 
38
}
 
39
 
 
40
const runCommandDoc = `
 
41
Run the specified commands in the hook context for the unit.
 
42
 
 
43
unit-name can be either the unit tag:
 
44
 i.e.  unit-ubuntu-0
 
45
or the unit id:
 
46
 i.e.  ubuntu/0
 
47
 
 
48
If --no-context is specified, the <unit-name> positional
 
49
argument is not needed.
 
50
 
 
51
The commands are executed with '/bin/bash -s', and the output returned.
 
52
`
 
53
 
 
54
// Info returns usage information for the command.
 
55
func (c *RunCommand) Info() *cmd.Info {
 
56
        return &cmd.Info{
 
57
                Name:    "juju-run",
 
58
                Args:    "<unit-name> <commands>",
 
59
                Purpose: "run commands in a unit's hook context",
 
60
                Doc:     runCommandDoc,
 
61
        }
 
62
}
 
63
 
 
64
func (c *RunCommand) SetFlags(f *gnuflag.FlagSet) {
 
65
        f.BoolVar(&c.noContext, "no-context", false, "do not run the command in a unit context")
 
66
        f.StringVar(&c.relationId, "r", "", "run the commands for a specific relation context on a unit")
 
67
        f.StringVar(&c.relationId, "relation", "", "")
 
68
        f.StringVar(&c.remoteUnitName, "remote-unit", "", "run the commands for a specific remote unit in a relation context on a unit")
 
69
        f.BoolVar(&c.forceRemoteUnit, "force-remote-unit", false, "run the commands for a specific relation context, bypassing the remote unit check")
 
70
}
 
71
 
 
72
func (c *RunCommand) Init(args []string) error {
 
73
        // make sure we aren't in an existing hook context
 
74
        if contextId, err := getenv("JUJU_CONTEXT_ID"); err == nil && contextId != "" {
 
75
                return fmt.Errorf("juju-run cannot be called from within a hook, have context %q", contextId)
 
76
        }
 
77
        if !c.noContext {
 
78
                if len(args) < 1 {
 
79
                        return fmt.Errorf("missing unit-name")
 
80
                }
 
81
                var unitName string
 
82
                unitName, args = args[0], args[1:]
 
83
                // If the command line param is a unit id (like service/2) we need to
 
84
                // change it to the unit tag as that is the format of the agent directory
 
85
                // on disk (unit-service-2).
 
86
                if names.IsValidUnit(unitName) {
 
87
                        c.unit = names.NewUnitTag(unitName)
 
88
                } else {
 
89
                        var err error
 
90
                        c.unit, err = names.ParseUnitTag(unitName)
 
91
                        if err != nil {
 
92
                                return errors.Trace(err)
 
93
                        }
 
94
                }
 
95
        }
 
96
        if len(args) < 1 {
 
97
                return fmt.Errorf("missing commands")
 
98
        }
 
99
        c.commands, args = args[0], args[1:]
 
100
        return cmd.CheckEmpty(args)
 
101
}
 
102
 
 
103
func (c *RunCommand) Run(ctx *cmd.Context) error {
 
104
        var result *exec.ExecResponse
 
105
        var err error
 
106
        if c.noContext {
 
107
                result, err = c.executeNoContext()
 
108
        } else {
 
109
                result, err = c.executeInUnitContext()
 
110
        }
 
111
        if err != nil {
 
112
                return errors.Trace(err)
 
113
        }
 
114
 
 
115
        ctx.Stdout.Write(result.Stdout)
 
116
        ctx.Stderr.Write(result.Stderr)
 
117
        return cmd.NewRcPassthroughError(result.Code)
 
118
}
 
119
 
 
120
func (c *RunCommand) socketPath() string {
 
121
        paths := uniter.NewPaths(cmdutil.DataDir, c.unit)
 
122
        return paths.Runtime.JujuRunSocket
 
123
}
 
124
 
 
125
func (c *RunCommand) executeInUnitContext() (*exec.ExecResponse, error) {
 
126
        unitDir := agent.Dir(cmdutil.DataDir, c.unit)
 
127
        logger.Debugf("looking for unit dir %s", unitDir)
 
128
        // make sure the unit exists
 
129
        _, err := os.Stat(unitDir)
 
130
        if os.IsNotExist(err) {
 
131
                return nil, errors.Errorf("unit %q not found on this machine", c.unit.Id())
 
132
        } else if err != nil {
 
133
                return nil, errors.Trace(err)
 
134
        }
 
135
 
 
136
        relationId, err := checkRelationId(c.relationId)
 
137
        if err != nil {
 
138
                return nil, errors.Trace(err)
 
139
        }
 
140
 
 
141
        if len(c.remoteUnitName) > 0 && relationId == -1 {
 
142
                return nil, errors.Errorf("remote unit: %s, provided without a relation", c.remoteUnitName)
 
143
        }
 
144
        client, err := sockets.Dial(c.socketPath())
 
145
        if err != nil {
 
146
                return nil, errors.Trace(err)
 
147
        }
 
148
        defer client.Close()
 
149
 
 
150
        var result exec.ExecResponse
 
151
        args := uniter.RunCommandsArgs{
 
152
                Commands:        c.commands,
 
153
                RelationId:      relationId,
 
154
                RemoteUnitName:  c.remoteUnitName,
 
155
                ForceRemoteUnit: c.forceRemoteUnit,
 
156
        }
 
157
        err = client.Call(uniter.JujuRunEndpoint, args, &result)
 
158
        return &result, errors.Trace(err)
 
159
}
 
160
 
 
161
// appendProxyToCommands activates proxy settings on platforms
 
162
// that support this feature via the command line. Currently this
 
163
// will work on most GNU/Linux systems, but has no use in Windows
 
164
// where the proxy settings are taken from the registry or from
 
165
// application specific settings (proxy settings in firefox ignore
 
166
// registry values on Windows).
 
167
func (c *RunCommand) appendProxyToCommands() string {
 
168
        switch version.Current.OS {
 
169
        case version.Ubuntu:
 
170
                return `[ -f "/home/ubuntu/.juju-proxy" ] && . "/home/ubuntu/.juju-proxy"` + "\n" + c.commands
 
171
        default:
 
172
                return c.commands
 
173
        }
 
174
}
 
175
 
 
176
func (c *RunCommand) executeNoContext() (*exec.ExecResponse, error) {
 
177
        // Acquire the uniter hook execution lock to make sure we don't
 
178
        // stomp on each other.
 
179
        spec := mutex.Spec{
 
180
                Name:  c.MachineLockName,
 
181
                Clock: clock.WallClock,
 
182
                Delay: 250 * time.Millisecond,
 
183
        }
 
184
        releaser, err := mutex.Acquire(spec)
 
185
        if err != nil {
 
186
                return nil, errors.Trace(err)
 
187
        }
 
188
        defer releaser.Release()
 
189
 
 
190
        runCmd := c.appendProxyToCommands()
 
191
 
 
192
        return exec.RunCommands(
 
193
                exec.RunParams{
 
194
                        Commands: runCmd,
 
195
                })
 
196
}
 
197
 
 
198
// checkRelationId verifies that the relationId
 
199
// given by the user is of a valid syntax, it does
 
200
// not check that the relationId is a valid one. This
 
201
// is done by the NewRunner method that is part of
 
202
// the worker/uniter/runner/factory package.
 
203
func checkRelationId(value string) (int, error) {
 
204
        if len(value) == 0 {
 
205
                return -1, nil
 
206
        }
 
207
 
 
208
        trim := value
 
209
        if idx := strings.LastIndex(trim, ":"); idx != -1 {
 
210
                trim = trim[idx+1:]
 
211
        }
 
212
        id, err := strconv.Atoi(trim)
 
213
        if err != nil {
 
214
                return -1, errors.Errorf("invalid relation id")
 
215
        }
 
216
        return id, nil
 
217
}