github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/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/names"
    13  	"gopkg.in/juju/charm.v5/hooks"
    14  
    15  	unitdebug "github.com/juju/juju/worker/uniter/runner/debug"
    16  )
    17  
    18  // DebugHooksCommand is responsible for launching a ssh shell on a given unit or machine.
    19  type DebugHooksCommand struct {
    20  	SSHCommand
    21  	hooks []string
    22  }
    23  
    24  const debugHooksDoc = `
    25  Interactively debug a hook remotely on a service unit.
    26  `
    27  
    28  func (c *DebugHooksCommand) Info() *cmd.Info {
    29  	return &cmd.Info{
    30  		Name:    "debug-hooks",
    31  		Args:    "<unit name> [hook names]",
    32  		Purpose: "launch a tmux session to debug a hook",
    33  		Doc:     debugHooksDoc,
    34  	}
    35  }
    36  
    37  func (c *DebugHooksCommand) Init(args []string) error {
    38  	if len(args) < 1 {
    39  		return fmt.Errorf("no unit name specified")
    40  	}
    41  	c.Target = args[0]
    42  	if !names.IsValidUnit(c.Target) {
    43  		return fmt.Errorf("%q is not a valid unit name", c.Target)
    44  	}
    45  
    46  	// If any of the hooks is "*", then debug all hooks.
    47  	c.hooks = append([]string{}, args[1:]...)
    48  	for _, h := range c.hooks {
    49  		if h == "*" {
    50  			c.hooks = nil
    51  			break
    52  		}
    53  	}
    54  	return nil
    55  }
    56  
    57  func (c *DebugHooksCommand) validateHooks() error {
    58  	if len(c.hooks) == 0 {
    59  		return nil
    60  	}
    61  	service, err := names.UnitService(c.Target)
    62  	if err != nil {
    63  		return err
    64  	}
    65  	relations, err := c.apiClient.ServiceCharmRelations(service)
    66  	if err != nil {
    67  		return err
    68  	}
    69  
    70  	validHooks := make(map[string]bool)
    71  	for _, hook := range hooks.UnitHooks() {
    72  		validHooks[string(hook)] = true
    73  	}
    74  	for _, relation := range relations {
    75  		for _, hook := range hooks.RelationHooks() {
    76  			hook := fmt.Sprintf("%s-%s", relation, hook)
    77  			validHooks[hook] = true
    78  		}
    79  	}
    80  	for _, hook := range c.hooks {
    81  		if !validHooks[hook] {
    82  			names := make([]string, 0, len(validHooks))
    83  			for hookName := range validHooks {
    84  				names = append(names, hookName)
    85  			}
    86  			sort.Strings(names)
    87  			logger.Infof("unknown hook %s, valid hook names: %v", hook, names)
    88  			return fmt.Errorf("unit %q does not contain hook %q", c.Target, hook)
    89  		}
    90  	}
    91  	return nil
    92  }
    93  
    94  // Run ensures c.Target is a unit, and resolves its address,
    95  // and connects to it via SSH to execute the debug-hooks
    96  // script.
    97  func (c *DebugHooksCommand) Run(ctx *cmd.Context) error {
    98  	var err error
    99  	c.apiClient, err = c.initAPIClient()
   100  	if err != nil {
   101  		return err
   102  	}
   103  	defer c.apiClient.Close()
   104  	err = c.validateHooks()
   105  	if err != nil {
   106  		return err
   107  	}
   108  	debugctx := unitdebug.NewHooksContext(c.Target)
   109  	script := base64.StdEncoding.EncodeToString([]byte(unitdebug.ClientScript(debugctx, c.hooks)))
   110  	innercmd := fmt.Sprintf(`F=$(mktemp); echo %s | base64 -d > $F; . $F`, script)
   111  	args := []string{fmt.Sprintf("sudo /bin/bash -c '%s'", innercmd)}
   112  	c.Args = args
   113  	return c.SSHCommand.Run(ctx)
   114  }