github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/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  }