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  }