github.com/cloudbase/juju-core@v0.0.0-20140504232958-a7271ac7912f/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 "errors" 9 "fmt" 10 "sort" 11 12 "launchpad.net/juju-core/charm/hooks" 13 "launchpad.net/juju-core/cmd" 14 "launchpad.net/juju-core/names" 15 "launchpad.net/juju-core/state/api/params" 16 unitdebug "launchpad.net/juju-core/worker/uniter/debug" 17 ) 18 19 // DebugHooksCommand is responsible for launching a ssh shell on a given unit or machine. 20 type DebugHooksCommand struct { 21 SSHCommand 22 hooks []string 23 } 24 25 const debugHooksDoc = ` 26 Interactively debug a hook remotely on a service unit. 27 ` 28 29 func (c *DebugHooksCommand) Info() *cmd.Info { 30 return &cmd.Info{ 31 Name: "debug-hooks", 32 Args: "<unit name> [hook names]", 33 Purpose: "launch a tmux session to debug a hook", 34 Doc: debugHooksDoc, 35 } 36 } 37 38 func (c *DebugHooksCommand) Init(args []string) error { 39 if len(args) < 1 { 40 return errors.New("no unit name specified") 41 } 42 c.Target = args[0] 43 if !names.IsUnit(c.Target) { 44 return fmt.Errorf("%q is not a valid unit name", c.Target) 45 } 46 47 // If any of the hooks is "*", then debug all hooks. 48 c.hooks = append([]string{}, args[1:]...) 49 for _, h := range c.hooks { 50 if h == "*" { 51 c.hooks = nil 52 break 53 } 54 } 55 return nil 56 } 57 58 // getRelationNames1dot16 gets the list of relation hooks directly from the 59 // database, in a fashion compatible with the API server in Juju 1.16 (which 60 // doesn't have the ServiceCharmRelations API). This function can be removed 61 // when we no longer maintain compatibility with 1.16 62 func (c *DebugHooksCommand) getRelationNames1dot16() ([]string, error) { 63 err := c.ensureRawConn() 64 if err != nil { 65 return nil, err 66 } 67 unit, err := c.rawConn.State.Unit(c.Target) 68 if err != nil { 69 return nil, err 70 } 71 service, err := unit.Service() 72 if err != nil { 73 return nil, err 74 } 75 endpoints, err := service.Endpoints() 76 if err != nil { 77 return nil, err 78 } 79 relations := make([]string, len(endpoints)) 80 for i, endpoint := range endpoints { 81 relations[i] = endpoint.Relation.Name 82 } 83 return relations, nil 84 } 85 86 func (c *DebugHooksCommand) getRelationNames(serviceName string) ([]string, error) { 87 relations, err := c.apiClient.ServiceCharmRelations(serviceName) 88 if params.IsCodeNotImplemented(err) { 89 logger.Infof("API server does not support Client.ServiceCharmRelations falling back to 1.16 compatibility mode (direct DB access)") 90 return c.getRelationNames1dot16() 91 } 92 if err != nil { 93 return nil, err 94 } 95 return relations, err 96 } 97 98 func (c *DebugHooksCommand) validateHooks() error { 99 if len(c.hooks) == 0 { 100 return nil 101 } 102 service := names.UnitService(c.Target) 103 relations, err := c.getRelationNames(service) 104 if err != nil { 105 return err 106 } 107 108 validHooks := make(map[string]bool) 109 for _, hook := range hooks.UnitHooks() { 110 validHooks[string(hook)] = true 111 } 112 for _, relation := range relations { 113 for _, hook := range hooks.RelationHooks() { 114 hook := fmt.Sprintf("%s-%s", relation, hook) 115 validHooks[hook] = true 116 } 117 } 118 for _, hook := range c.hooks { 119 if !validHooks[hook] { 120 names := make([]string, 0, len(validHooks)) 121 for hookName, _ := range validHooks { 122 names = append(names, hookName) 123 } 124 sort.Strings(names) 125 logger.Infof("unknown hook %s, valid hook names: %v", hook, names) 126 return fmt.Errorf("unit %q does not contain hook %q", c.Target, hook) 127 } 128 } 129 return nil 130 } 131 132 // Run ensures c.Target is a unit, and resolves its address, 133 // and connects to it via SSH to execute the debug-hooks 134 // script. 135 func (c *DebugHooksCommand) Run(ctx *cmd.Context) error { 136 var err error 137 c.apiClient, err = c.initAPIClient() 138 if err != nil { 139 return err 140 } 141 defer c.apiClient.Close() 142 err = c.validateHooks() 143 if err != nil { 144 return err 145 } 146 debugctx := unitdebug.NewHooksContext(c.Target) 147 script := base64.StdEncoding.EncodeToString([]byte(unitdebug.ClientScript(debugctx, c.hooks))) 148 innercmd := fmt.Sprintf(`F=$(mktemp); echo %s | base64 -d > $F; . $F`, script) 149 args := []string{fmt.Sprintf("sudo /bin/bash -c '%s'", innercmd)} 150 c.Args = args 151 return c.SSHCommand.Run(ctx) 152 }