github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/cmd/juju/commands/debughooks.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package commands
     5  
     6  import (
     7  	"encoding/base64"
     8  	"fmt"
     9  	"sort"
    10  
    11  	"github.com/juju/cmd"
    12  	"github.com/juju/errors"
    13  	"gopkg.in/juju/charm.v6-unstable/hooks"
    14  	"gopkg.in/juju/names.v2"
    15  
    16  	"github.com/juju/juju/api/application"
    17  	"github.com/juju/juju/cmd/modelcmd"
    18  	unitdebug "github.com/juju/juju/worker/uniter/runner/debug"
    19  )
    20  
    21  func newDebugHooksCommand() cmd.Command {
    22  	return modelcmd.Wrap(&debugHooksCommand{})
    23  }
    24  
    25  // debugHooksCommand is responsible for launching a ssh shell on a given unit or machine.
    26  type debugHooksCommand struct {
    27  	sshCommand
    28  	hooks []string
    29  }
    30  
    31  const debugHooksDoc = `
    32  Interactively debug a hook remotely on an application unit.
    33  
    34  See the "juju help ssh" for information about SSH related options
    35  accepted by the debug-hooks command.
    36  `
    37  
    38  func (c *debugHooksCommand) Info() *cmd.Info {
    39  	return &cmd.Info{
    40  		Name:    "debug-hooks",
    41  		Args:    "<unit name> [hook names]",
    42  		Purpose: "Launch a tmux session to debug a hook.",
    43  		Doc:     debugHooksDoc,
    44  	}
    45  }
    46  
    47  func (c *debugHooksCommand) Init(args []string) error {
    48  	if len(args) < 1 {
    49  		return errors.Errorf("no unit name specified")
    50  	}
    51  	c.Target = args[0]
    52  	if !names.IsValidUnit(c.Target) {
    53  		return errors.Errorf("%q is not a valid unit name", c.Target)
    54  	}
    55  
    56  	// If any of the hooks is "*", then debug all hooks.
    57  	c.hooks = append([]string{}, args[1:]...)
    58  	for _, h := range c.hooks {
    59  		if h == "*" {
    60  			c.hooks = nil
    61  			break
    62  		}
    63  	}
    64  	return nil
    65  }
    66  
    67  type charmRelationsAPI interface {
    68  	CharmRelations(serviceName string) ([]string, error)
    69  }
    70  
    71  func (c *debugHooksCommand) getServiceAPI() (charmRelationsAPI, error) {
    72  	root, err := c.NewAPIRoot()
    73  	if err != nil {
    74  		return nil, errors.Trace(err)
    75  	}
    76  	return application.NewClient(root), nil
    77  }
    78  
    79  func (c *debugHooksCommand) validateHooks() error {
    80  	if len(c.hooks) == 0 {
    81  		return nil
    82  	}
    83  	service, err := names.UnitApplication(c.Target)
    84  	if err != nil {
    85  		return err
    86  	}
    87  	serviceAPI, err := c.getServiceAPI()
    88  	if err != nil {
    89  		return err
    90  	}
    91  	relations, err := serviceAPI.CharmRelations(service)
    92  	if err != nil {
    93  		return err
    94  	}
    95  
    96  	validHooks := make(map[string]bool)
    97  	for _, hook := range hooks.UnitHooks() {
    98  		validHooks[string(hook)] = true
    99  	}
   100  	for _, relation := range relations {
   101  		for _, hook := range hooks.RelationHooks() {
   102  			hook := fmt.Sprintf("%s-%s", relation, hook)
   103  			validHooks[hook] = true
   104  		}
   105  	}
   106  	for _, hook := range c.hooks {
   107  		if !validHooks[hook] {
   108  			names := make([]string, 0, len(validHooks))
   109  			for hookName := range validHooks {
   110  				names = append(names, hookName)
   111  			}
   112  			sort.Strings(names)
   113  			logger.Infof("unknown hook %s, valid hook names: %v", hook, names)
   114  			return errors.Errorf("unit %q does not contain hook %q", c.Target, hook)
   115  		}
   116  	}
   117  	return nil
   118  }
   119  
   120  // Run ensures c.Target is a unit, and resolves its address,
   121  // and connects to it via SSH to execute the debug-hooks
   122  // script.
   123  func (c *debugHooksCommand) Run(ctx *cmd.Context) error {
   124  	err := c.initRun()
   125  	if err != nil {
   126  		return err
   127  	}
   128  	defer c.cleanupRun()
   129  	err = c.validateHooks()
   130  	if err != nil {
   131  		return err
   132  	}
   133  	debugctx := unitdebug.NewHooksContext(c.Target)
   134  	script := base64.StdEncoding.EncodeToString([]byte(unitdebug.ClientScript(debugctx, c.hooks)))
   135  	innercmd := fmt.Sprintf(`F=$(mktemp); echo %s | base64 -d > $F; . $F`, script)
   136  	args := []string{fmt.Sprintf("sudo /bin/bash -c '%s'", innercmd)}
   137  	c.Args = args
   138  	return c.sshCommand.Run(ctx)
   139  }