github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/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 "net/rpc" 9 "os" 10 "path/filepath" 11 12 "github.com/juju/names" 13 "github.com/juju/utils/exec" 14 "github.com/juju/utils/fslock" 15 "launchpad.net/gnuflag" 16 17 "github.com/juju/juju/cmd" 18 "github.com/juju/juju/worker/uniter" 19 ) 20 21 var ( 22 AgentDir = "/var/lib/juju/agents" 23 LockDir = "/var/lib/juju/locks" 24 ) 25 26 type RunCommand struct { 27 cmd.CommandBase 28 unit string 29 commands string 30 showHelp bool 31 noContext bool 32 } 33 34 const runCommandDoc = ` 35 Run the specified commands in the hook context for the unit. 36 37 unit-name can be either the unit tag: 38 i.e. unit-ubuntu-0 39 or the unit id: 40 i.e. ubuntu/0 41 42 If --no-context is specified, the <unit-name> positional 43 argument is not needed. 44 45 The commands are executed with '/bin/bash -s', and the output returned. 46 ` 47 48 // Info returns usage information for the command. 49 func (c *RunCommand) Info() *cmd.Info { 50 return &cmd.Info{ 51 Name: "juju-run", 52 Args: "<unit-name> <commands>", 53 Purpose: "run commands in a unit's hook context", 54 Doc: runCommandDoc, 55 } 56 } 57 58 func (c *RunCommand) SetFlags(f *gnuflag.FlagSet) { 59 f.BoolVar(&c.showHelp, "h", false, "show help on juju-run") 60 f.BoolVar(&c.showHelp, "help", false, "") 61 f.BoolVar(&c.noContext, "no-context", false, "do not run the command in a unit context") 62 } 63 64 func (c *RunCommand) Init(args []string) error { 65 // make sure we aren't in an existing hook context 66 if contextId, err := getenv("JUJU_CONTEXT_ID"); err == nil && contextId != "" { 67 return fmt.Errorf("juju-run cannot be called from within a hook, have context %q", contextId) 68 } 69 if !c.noContext { 70 if len(args) < 1 { 71 return fmt.Errorf("missing unit-name") 72 } 73 c.unit, args = args[0], args[1:] 74 // If the command line param is a unit id (like service/2) we need to 75 // change it to the unit tag as that is the format of the agent directory 76 // on disk (unit-service-2). 77 if names.IsUnit(c.unit) { 78 c.unit = names.UnitTag(c.unit) 79 } 80 } 81 if len(args) < 1 { 82 return fmt.Errorf("missing commands") 83 } 84 c.commands, args = args[0], args[1:] 85 return cmd.CheckEmpty(args) 86 } 87 88 func (c *RunCommand) Run(ctx *cmd.Context) error { 89 if c.showHelp { 90 return gnuflag.ErrHelp 91 } 92 93 var result *exec.ExecResponse 94 var err error 95 if c.noContext { 96 result, err = c.executeNoContext() 97 } else { 98 result, err = c.executeInUnitContext() 99 } 100 if err != nil { 101 return err 102 } 103 104 ctx.Stdout.Write(result.Stdout) 105 ctx.Stderr.Write(result.Stderr) 106 return cmd.NewRcPassthroughError(result.Code) 107 } 108 109 func (c *RunCommand) executeInUnitContext() (*exec.ExecResponse, error) { 110 unitDir := filepath.Join(AgentDir, c.unit) 111 logger.Debugf("looking for unit dir %s", unitDir) 112 // make sure the unit exists 113 _, err := os.Stat(unitDir) 114 if os.IsNotExist(err) { 115 return nil, fmt.Errorf("unit %q not found on this machine", c.unit) 116 } else if err != nil { 117 return nil, err 118 } 119 120 socketPath := filepath.Join(unitDir, uniter.RunListenerFile) 121 // make sure the socket exists 122 client, err := rpc.Dial("unix", socketPath) 123 if err != nil { 124 return nil, err 125 } 126 defer client.Close() 127 128 var result exec.ExecResponse 129 err = client.Call(uniter.JujuRunEndpoint, c.commands, &result) 130 return &result, err 131 } 132 133 func getLock() (*fslock.Lock, error) { 134 return fslock.NewLock(LockDir, "uniter-hook-execution") 135 } 136 137 func (c *RunCommand) executeNoContext() (*exec.ExecResponse, error) { 138 // Acquire the uniter hook execution lock to make sure we don't 139 // stomp on each other. 140 lock, err := getLock() 141 if err != nil { 142 return nil, err 143 } 144 err = lock.Lock("juju-run") 145 if err != nil { 146 return nil, err 147 } 148 defer lock.Unlock() 149 150 runCmd := `[ -f "/home/ubuntu/.juju-proxy" ] && . "/home/ubuntu/.juju-proxy"` + "\n" + c.commands 151 152 return exec.RunCommands( 153 exec.RunParams{ 154 Commands: runCmd, 155 }) 156 }