github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/cmd/jujud/run.go (about) 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/gnuflag" 16 "github.com/juju/mutex" 17 "github.com/juju/utils/clock" 18 "github.com/juju/utils/exec" 19 "gopkg.in/juju/names.v2" 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/worker/uniter" 25 jujuos "github.com/juju/utils/os" 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-application-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 jujuos.HostOS() { 169 case jujuos.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 logger.Debugf("acquire lock %q for juju-run", c.MachineLockName) 185 releaser, err := mutex.Acquire(spec) 186 if err != nil { 187 return nil, errors.Trace(err) 188 } 189 logger.Debugf("lock %q acquired", c.MachineLockName) 190 191 // Defer the logging first so it is executed after the Release. LIFO. 192 defer logger.Debugf("release lock %q for juju-run", c.MachineLockName) 193 defer releaser.Release() 194 195 runCmd := c.appendProxyToCommands() 196 197 return exec.RunCommands( 198 exec.RunParams{ 199 Commands: runCmd, 200 }) 201 } 202 203 // checkRelationId verifies that the relationId 204 // given by the user is of a valid syntax, it does 205 // not check that the relationId is a valid one. This 206 // is done by the NewRunner method that is part of 207 // the worker/uniter/runner/factory package. 208 func checkRelationId(value string) (int, error) { 209 if len(value) == 0 { 210 return -1, nil 211 } 212 213 trim := value 214 if idx := strings.LastIndex(trim, ":"); idx != -1 { 215 trim = trim[idx+1:] 216 } 217 id, err := strconv.Atoi(trim) 218 if err != nil { 219 return -1, errors.Errorf("invalid relation id") 220 } 221 return id, nil 222 }