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 }