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