/
debughooks.go
114 lines (102 loc) · 2.74 KB
/
debughooks.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
// Copyright 2012, 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.
package commands
import (
"encoding/base64"
"fmt"
"sort"
"github.com/juju/cmd"
"github.com/juju/names"
"gopkg.in/juju/charm.v5/hooks"
unitdebug "github.com/juju/juju/worker/uniter/runner/debug"
)
// DebugHooksCommand is responsible for launching a ssh shell on a given unit or machine.
type DebugHooksCommand struct {
SSHCommand
hooks []string
}
const debugHooksDoc = `
Interactively debug a hook remotely on a service unit.
`
func (c *DebugHooksCommand) Info() *cmd.Info {
return &cmd.Info{
Name: "debug-hooks",
Args: "<unit name> [hook names]",
Purpose: "launch a tmux session to debug a hook",
Doc: debugHooksDoc,
}
}
func (c *DebugHooksCommand) Init(args []string) error {
if len(args) < 1 {
return fmt.Errorf("no unit name specified")
}
c.Target = args[0]
if !names.IsValidUnit(c.Target) {
return fmt.Errorf("%q is not a valid unit name", c.Target)
}
// If any of the hooks is "*", then debug all hooks.
c.hooks = append([]string{}, args[1:]...)
for _, h := range c.hooks {
if h == "*" {
c.hooks = nil
break
}
}
return nil
}
func (c *DebugHooksCommand) validateHooks() error {
if len(c.hooks) == 0 {
return nil
}
service, err := names.UnitService(c.Target)
if err != nil {
return err
}
relations, err := c.apiClient.ServiceCharmRelations(service)
if err != nil {
return err
}
validHooks := make(map[string]bool)
for _, hook := range hooks.UnitHooks() {
validHooks[string(hook)] = true
}
for _, relation := range relations {
for _, hook := range hooks.RelationHooks() {
hook := fmt.Sprintf("%s-%s", relation, hook)
validHooks[hook] = true
}
}
for _, hook := range c.hooks {
if !validHooks[hook] {
names := make([]string, 0, len(validHooks))
for hookName := range validHooks {
names = append(names, hookName)
}
sort.Strings(names)
logger.Infof("unknown hook %s, valid hook names: %v", hook, names)
return fmt.Errorf("unit %q does not contain hook %q", c.Target, hook)
}
}
return nil
}
// Run ensures c.Target is a unit, and resolves its address,
// and connects to it via SSH to execute the debug-hooks
// script.
func (c *DebugHooksCommand) Run(ctx *cmd.Context) error {
var err error
c.apiClient, err = c.initAPIClient()
if err != nil {
return err
}
defer c.apiClient.Close()
err = c.validateHooks()
if err != nil {
return err
}
debugctx := unitdebug.NewHooksContext(c.Target)
script := base64.StdEncoding.EncodeToString([]byte(unitdebug.ClientScript(debugctx, c.hooks)))
innercmd := fmt.Sprintf(`F=$(mktemp); echo %s | base64 -d > $F; . $F`, script)
args := []string{fmt.Sprintf("sudo /bin/bash -c '%s'", innercmd)}
c.Args = args
return c.SSHCommand.Run(ctx)
}