github.com/rogpeppe/juju@v0.0.0-20140613142852-6337964b789e/cmd/juju/debughooks.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package main 5 6 import ( 7 "encoding/base64" 8 "fmt" 9 "sort" 10 11 "github.com/juju/charm/hooks" 12 "github.com/juju/cmd" 13 "github.com/juju/names" 14 15 unitdebug "github.com/juju/juju/worker/uniter/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.IsUnit(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 := names.UnitService(c.Target) 62 relations, err := c.apiClient.ServiceCharmRelations(service) 63 if err != nil { 64 return err 65 } 66 67 validHooks := make(map[string]bool) 68 for _, hook := range hooks.UnitHooks() { 69 validHooks[string(hook)] = true 70 } 71 for _, relation := range relations { 72 for _, hook := range hooks.RelationHooks() { 73 hook := fmt.Sprintf("%s-%s", relation, hook) 74 validHooks[hook] = true 75 } 76 } 77 for _, hook := range c.hooks { 78 if !validHooks[hook] { 79 names := make([]string, 0, len(validHooks)) 80 for hookName, _ := range validHooks { 81 names = append(names, hookName) 82 } 83 sort.Strings(names) 84 logger.Infof("unknown hook %s, valid hook names: %v", hook, names) 85 return fmt.Errorf("unit %q does not contain hook %q", c.Target, hook) 86 } 87 } 88 return nil 89 } 90 91 // Run ensures c.Target is a unit, and resolves its address, 92 // and connects to it via SSH to execute the debug-hooks 93 // script. 94 func (c *DebugHooksCommand) Run(ctx *cmd.Context) error { 95 var err error 96 c.apiClient, err = c.initAPIClient() 97 if err != nil { 98 return err 99 } 100 defer c.apiClient.Close() 101 err = c.validateHooks() 102 if err != nil { 103 return err 104 } 105 debugctx := unitdebug.NewHooksContext(c.Target) 106 script := base64.StdEncoding.EncodeToString([]byte(unitdebug.ClientScript(debugctx, c.hooks))) 107 innercmd := fmt.Sprintf(`F=$(mktemp); echo %s | base64 -d > $F; . $F`, script) 108 args := []string{fmt.Sprintf("sudo /bin/bash -c '%s'", innercmd)} 109 c.Args = args 110 return c.SSHCommand.Run(ctx) 111 }