github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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  	"github.com/juju/names"
    14  	"gopkg.in/juju/charm.v6-unstable/hooks"
    15  
    16  	"github.com/juju/juju/api/service"
    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 a service unit.
    33  `
    34  
    35  func (c *debugHooksCommand) Info() *cmd.Info {
    36  	return &cmd.Info{
    37  		Name:    "debug-hooks",
    38  		Args:    "<unit name> [hook names]",
    39  		Purpose: "launch a tmux session to debug a hook",
    40  		Doc:     debugHooksDoc,
    41  	}
    42  }
    43  
    44  func (c *debugHooksCommand) Init(args []string) error {
    45  	if len(args) < 1 {
    46  		return fmt.Errorf("no unit name specified")
    47  	}
    48  	c.Target = args[0]
    49  	if !names.IsValidUnit(c.Target) {
    50  		return fmt.Errorf("%q is not a valid unit name", c.Target)
    51  	}
    52  
    53  	// If any of the hooks is "*", then debug all hooks.
    54  	c.hooks = append([]string{}, args[1:]...)
    55  	for _, h := range c.hooks {
    56  		if h == "*" {
    57  			c.hooks = nil
    58  			break
    59  		}
    60  	}
    61  	return nil
    62  }
    63  
    64  type charmRelationsApi interface {
    65  	CharmRelations(serviceName string) ([]string, error)
    66  }
    67  
    68  func (c *debugHooksCommand) getServiceAPI() (charmRelationsApi, error) {
    69  	root, err := c.NewAPIRoot()
    70  	if err != nil {
    71  		return nil, errors.Trace(err)
    72  	}
    73  	return service.NewClient(root), nil
    74  }
    75  
    76  func (c *debugHooksCommand) validateHooks() error {
    77  	if len(c.hooks) == 0 {
    78  		return nil
    79  	}
    80  	service, err := names.UnitService(c.Target)
    81  	if err != nil {
    82  		return err
    83  	}
    84  	serviceApi, err := c.getServiceAPI()
    85  	if err != nil {
    86  		return err
    87  	}
    88  	relations, err := serviceApi.CharmRelations(service)
    89  	if err != nil {
    90  		return err
    91  	}
    92  
    93  	validHooks := make(map[string]bool)
    94  	for _, hook := range hooks.UnitHooks() {
    95  		validHooks[string(hook)] = true
    96  	}
    97  	for _, relation := range relations {
    98  		for _, hook := range hooks.RelationHooks() {
    99  			hook := fmt.Sprintf("%s-%s", relation, hook)
   100  			validHooks[hook] = true
   101  		}
   102  	}
   103  	for _, hook := range c.hooks {
   104  		if !validHooks[hook] {
   105  			names := make([]string, 0, len(validHooks))
   106  			for hookName := range validHooks {
   107  				names = append(names, hookName)
   108  			}
   109  			sort.Strings(names)
   110  			logger.Infof("unknown hook %s, valid hook names: %v", hook, names)
   111  			return fmt.Errorf("unit %q does not contain hook %q", c.Target, hook)
   112  		}
   113  	}
   114  	return nil
   115  }
   116  
   117  // Run ensures c.Target is a unit, and resolves its address,
   118  // and connects to it via SSH to execute the debug-hooks
   119  // script.
   120  func (c *debugHooksCommand) Run(ctx *cmd.Context) error {
   121  	var err error
   122  	c.apiClient, err = c.initAPIClient()
   123  	if err != nil {
   124  		return err
   125  	}
   126  	defer c.apiClient.Close()
   127  	err = c.validateHooks()
   128  	if err != nil {
   129  		return err
   130  	}
   131  	debugctx := unitdebug.NewHooksContext(c.Target)
   132  	script := base64.StdEncoding.EncodeToString([]byte(unitdebug.ClientScript(debugctx, c.hooks)))
   133  	innercmd := fmt.Sprintf(`F=$(mktemp); echo %s | base64 -d > $F; . $F`, script)
   134  	args := []string{fmt.Sprintf("sudo /bin/bash -c '%s'", innercmd)}
   135  	c.Args = args
   136  	return c.sshCommand.Run(ctx)
   137  }