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 }