1
// Copyright 2013 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
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"
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"
28
type RunCommand struct {
30
MachineLockName string
40
const runCommandDoc = `
41
Run the specified commands in the hook context for the unit.
43
unit-name can be either the unit tag:
48
If --no-context is specified, the <unit-name> positional
49
argument is not needed.
51
The commands are executed with '/bin/bash -s', and the output returned.
54
// Info returns usage information for the command.
55
func (c *RunCommand) Info() *cmd.Info {
58
Args: "<unit-name> <commands>",
59
Purpose: "run commands in a unit's hook context",
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")
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)
79
return fmt.Errorf("missing unit-name")
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)
90
c.unit, err = names.ParseUnitTag(unitName)
92
return errors.Trace(err)
97
return fmt.Errorf("missing commands")
99
c.commands, args = args[0], args[1:]
100
return cmd.CheckEmpty(args)
103
func (c *RunCommand) Run(ctx *cmd.Context) error {
104
var result *exec.ExecResponse
107
result, err = c.executeNoContext()
109
result, err = c.executeInUnitContext()
112
return errors.Trace(err)
115
ctx.Stdout.Write(result.Stdout)
116
ctx.Stderr.Write(result.Stderr)
117
return cmd.NewRcPassthroughError(result.Code)
120
func (c *RunCommand) socketPath() string {
121
paths := uniter.NewPaths(cmdutil.DataDir, c.unit)
122
return paths.Runtime.JujuRunSocket
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)
136
relationId, err := checkRelationId(c.relationId)
138
return nil, errors.Trace(err)
141
if len(c.remoteUnitName) > 0 && relationId == -1 {
142
return nil, errors.Errorf("remote unit: %s, provided without a relation", c.remoteUnitName)
144
client, err := sockets.Dial(c.socketPath())
146
return nil, errors.Trace(err)
150
var result exec.ExecResponse
151
args := uniter.RunCommandsArgs{
152
Commands: c.commands,
153
RelationId: relationId,
154
RemoteUnitName: c.remoteUnitName,
155
ForceRemoteUnit: c.forceRemoteUnit,
157
err = client.Call(uniter.JujuRunEndpoint, args, &result)
158
return &result, errors.Trace(err)
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 {
170
return `[ -f "/home/ubuntu/.juju-proxy" ] && . "/home/ubuntu/.juju-proxy"` + "\n" + c.commands
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.
180
Name: c.MachineLockName,
181
Clock: clock.WallClock,
182
Delay: 250 * time.Millisecond,
184
releaser, err := mutex.Acquire(spec)
186
return nil, errors.Trace(err)
188
defer releaser.Release()
190
runCmd := c.appendProxyToCommands()
192
return exec.RunCommands(
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) {
209
if idx := strings.LastIndex(trim, ":"); idx != -1 {
212
id, err := strconv.Atoi(trim)
214
return -1, errors.Errorf("invalid relation id")