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 }