github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/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/version"
    22  	"github.com/juju/juju/worker/uniter"
    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  
   141  	client, err := sockets.Dial(c.socketPath())
   142  	if err != nil {
   143  		return nil, errors.Trace(err)
   144  	}
   145  	defer client.Close()
   146  
   147  	var result exec.ExecResponse
   148  	args := uniter.RunCommandsArgs{
   149  		Commands:        c.commands,
   150  		RelationId:      relationId,
   151  		RemoteUnitName:  c.remoteUnitName,
   152  		ForceRemoteUnit: c.forceRemoteUnit,
   153  	}
   154  	err = client.Call(uniter.JujuRunEndpoint, args, &result)
   155  	return &result, errors.Trace(err)
   156  }
   157  
   158  // appendProxyToCommands activates proxy settings on platforms
   159  // that support this feature via the command line. Currently this
   160  // will work on most GNU/Linux systems, but has no use in Windows
   161  // where the proxy settings are taken from the registry or from
   162  // application specific settings (proxy settings in firefox ignore
   163  // registry values on Windows).
   164  func (c *RunCommand) appendProxyToCommands() string {
   165  	switch version.Current.OS {
   166  	case version.Ubuntu:
   167  		return `[ -f "/home/ubuntu/.juju-proxy" ] && . "/home/ubuntu/.juju-proxy"` + "\n" + c.commands
   168  	default:
   169  		return c.commands
   170  	}
   171  }
   172  
   173  func (c *RunCommand) executeNoContext() (*exec.ExecResponse, error) {
   174  	// Acquire the uniter hook execution lock to make sure we don't
   175  	// stomp on each other.
   176  	lock, err := cmdutil.HookExecutionLock(cmdutil.DataDir)
   177  	if err != nil {
   178  		return nil, errors.Trace(err)
   179  	}
   180  	err = lock.Lock("juju-run")
   181  	if err != nil {
   182  		return nil, errors.Trace(err)
   183  	}
   184  	defer lock.Unlock()
   185  
   186  	runCmd := c.appendProxyToCommands()
   187  
   188  	return exec.RunCommands(
   189  		exec.RunParams{
   190  			Commands: runCmd,
   191  		})
   192  }
   193  
   194  // checkRelationId verifies that the relationId
   195  // given by the user is of a valid syntax, it does
   196  // not check that the relationId is a valid one. This
   197  // is done by the NewRunner method that is part of
   198  // the worker/uniter/runner/factory package.
   199  func checkRelationId(value string) (int, error) {
   200  	if len(value) == 0 {
   201  		return -1, nil
   202  	}
   203  
   204  	trim := value
   205  	if idx := strings.LastIndex(trim, ":"); idx != -1 {
   206  		trim = trim[idx+1:]
   207  	}
   208  	id, err := strconv.Atoi(trim)
   209  	if err != nil {
   210  		return -1, errors.Errorf("invalid relation id")
   211  	}
   212  	return id, nil
   213  }