forked from juju/juju
/
server.go
149 lines (129 loc) · 4.06 KB
/
server.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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
// Copyright 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.
package debug
import (
"bytes"
"errors"
"io"
"io/ioutil"
"os"
"os/exec"
"github.com/juju/utils/set"
goyaml "gopkg.in/yaml.v2"
)
// ServerSession represents a "juju debug-hooks" session.
type ServerSession struct {
*HooksContext
hooks set.Strings
output io.Writer
}
// MatchHook returns true if the specified hook name matches
// the hook specified by the debug-hooks client.
func (s *ServerSession) MatchHook(hookName string) bool {
return s.hooks.IsEmpty() || s.hooks.Contains(hookName)
}
// waitClientExit executes flock, waiting for the SSH client to exit.
// This is a var so it can be replaced for testing.
var waitClientExit = func(s *ServerSession) {
path := s.ClientExitFileLock()
exec.Command("flock", path, "-c", "true").Run()
}
// RunHook "runs" the hook with the specified name via debug-hooks.
func (s *ServerSession) RunHook(hookName, charmDir string, env []string) error {
env = append(env, "JUJU_HOOK_NAME="+hookName)
cmd := exec.Command("/bin/bash", "-s")
cmd.Env = env
cmd.Dir = charmDir
cmd.Stdin = bytes.NewBufferString(debugHooksServerScript)
if s.output != nil {
cmd.Stdout = s.output
cmd.Stderr = s.output
}
if err := cmd.Start(); err != nil {
return err
}
go func(proc *os.Process) {
// Wait for the SSH client to exit (i.e. release the flock),
// then kill the server hook process in case the client
// exited uncleanly.
waitClientExit(s)
proc.Kill()
}(cmd.Process)
return cmd.Wait()
}
// FindSession attempts to find a debug hooks session for the unit specified
// in the context, and returns a new ServerSession structure for it.
func (c *HooksContext) FindSession() (*ServerSession, error) {
cmd := exec.Command("tmux", "has-session", "-t", c.tmuxSessionName())
out, err := cmd.CombinedOutput()
if err != nil {
if len(out) != 0 {
return nil, errors.New(string(out))
} else {
return nil, err
}
}
// Parse the debug-hooks file for an optional hook name.
data, err := ioutil.ReadFile(c.ClientFileLock())
if err != nil {
return nil, err
}
var args hookArgs
err = goyaml.Unmarshal(data, &args)
if err != nil {
return nil, err
}
hooks := set.NewStrings(args.Hooks...)
session := &ServerSession{HooksContext: c, hooks: hooks}
return session, nil
}
const debugHooksServerScript = `set -e
export JUJU_DEBUG=$(mktemp -d)
exec > $JUJU_DEBUG/debug.log >&1
# Set a useful prompt.
export PS1="$JUJU_UNIT_NAME:$JUJU_HOOK_NAME % "
# Save environment variables and export them for sourcing.
FILTER='^\(LS_COLORS\|LESSOPEN\|LESSCLOSE\|PWD\)='
export | grep -v $FILTER > $JUJU_DEBUG/env.sh
# Create welcome message display for the hook environment.
cat > $JUJU_DEBUG/welcome.msg <<END
This is a Juju debug-hooks tmux session. Remember:
1. You need to execute hooks manually if you want them to run for trapped events.
2. When you are finished with an event, you can run 'exit' to close the current window and allow Juju to continue running.
3. CTRL+a is tmux prefix.
More help and info is available in the online documentation:
https://jujucharms.com/docs/authors-hook-debug.html
END
cat > $JUJU_DEBUG/init.sh <<END
#!/bin/bash
cat $JUJU_DEBUG/welcome.msg
trap 'echo \$? > $JUJU_DEBUG/hook_exit_status' EXIT
END
chmod +x $JUJU_DEBUG/init.sh
# Create an internal script which will load the hook environment.
cat > $JUJU_DEBUG/hook.sh <<END
#!/bin/bash
. $JUJU_DEBUG/env.sh
echo \$\$ > $JUJU_DEBUG/hook.pid
exec /bin/bash --noprofile --init-file $JUJU_DEBUG/init.sh
END
chmod +x $JUJU_DEBUG/hook.sh
tmux new-window -t $JUJU_UNIT_NAME -n $JUJU_HOOK_NAME "$JUJU_DEBUG/hook.sh"
# If we exit for whatever reason, kill the hook shell.
exit_handler() {
if [ -f $JUJU_DEBUG/hook.pid ]; then
kill -9 $(cat $JUJU_DEBUG/hook.pid) || true
fi
}
trap exit_handler EXIT
# Wait for the hook shell to start, and then wait for it to exit.
while [ ! -f $JUJU_DEBUG/hook.pid ]; do
sleep 1
done
HOOK_PID=$(cat $JUJU_DEBUG/hook.pid)
while kill -0 "$HOOK_PID" 2> /dev/null; do
sleep 1
done
typeset -i exitstatus=$(cat $JUJU_DEBUG/hook_exit_status)
exit $exitstatus
`