github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/worker/uniter/runner/debug/server.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package debug 5 6 import ( 7 "bytes" 8 "errors" 9 "io/ioutil" 10 "os" 11 "os/exec" 12 13 "github.com/juju/utils/set" 14 goyaml "gopkg.in/yaml.v2" 15 ) 16 17 // ServerSession represents a "juju debug-hooks" session. 18 type ServerSession struct { 19 *HooksContext 20 hooks set.Strings 21 } 22 23 // MatchHook returns true if the specified hook name matches 24 // the hook specified by the debug-hooks client. 25 func (s *ServerSession) MatchHook(hookName string) bool { 26 return s.hooks.IsEmpty() || s.hooks.Contains(hookName) 27 } 28 29 // waitClientExit executes flock, waiting for the SSH client to exit. 30 // This is a var so it can be replaced for testing. 31 var waitClientExit = func(s *ServerSession) { 32 path := s.ClientExitFileLock() 33 exec.Command("flock", path, "-c", "true").Run() 34 } 35 36 // RunHook "runs" the hook with the specified name via debug-hooks. 37 func (s *ServerSession) RunHook(hookName, charmDir string, env []string) error { 38 env = append(env, "JUJU_HOOK_NAME="+hookName) 39 cmd := exec.Command("/bin/bash", "-s") 40 cmd.Env = env 41 cmd.Dir = charmDir 42 cmd.Stdin = bytes.NewBufferString(debugHooksServerScript) 43 if err := cmd.Start(); err != nil { 44 return err 45 } 46 go func(proc *os.Process) { 47 // Wait for the SSH client to exit (i.e. release the flock), 48 // then kill the server hook process in case the client 49 // exited uncleanly. 50 waitClientExit(s) 51 proc.Kill() 52 }(cmd.Process) 53 return cmd.Wait() 54 } 55 56 // FindSession attempts to find a debug hooks session for the unit specified 57 // in the context, and returns a new ServerSession structure for it. 58 func (c *HooksContext) FindSession() (*ServerSession, error) { 59 cmd := exec.Command("tmux", "has-session", "-t", c.tmuxSessionName()) 60 out, err := cmd.CombinedOutput() 61 if err != nil { 62 if len(out) != 0 { 63 return nil, errors.New(string(out)) 64 } else { 65 return nil, err 66 } 67 } 68 // Parse the debug-hooks file for an optional hook name. 69 data, err := ioutil.ReadFile(c.ClientFileLock()) 70 if err != nil { 71 return nil, err 72 } 73 var args hookArgs 74 err = goyaml.Unmarshal(data, &args) 75 if err != nil { 76 return nil, err 77 } 78 hooks := set.NewStrings(args.Hooks...) 79 session := &ServerSession{c, hooks} 80 return session, nil 81 } 82 83 const debugHooksServerScript = `set -e 84 export JUJU_DEBUG=$(mktemp -d) 85 exec > $JUJU_DEBUG/debug.log >&1 86 87 # Set a useful prompt. 88 export PS1="$JUJU_UNIT_NAME:$JUJU_HOOK_NAME % " 89 90 # Save environment variables and export them for sourcing. 91 FILTER='^\(LS_COLORS\|LESSOPEN\|LESSCLOSE\|PWD\)=' 92 export | grep -v $FILTER > $JUJU_DEBUG/env.sh 93 94 # Create welcome message display for the hook environment. 95 cat > $JUJU_DEBUG/welcome.msg <<END 96 This is a Juju debug-hooks tmux session. Remember: 97 1. You need to execute hooks manually if you want them to run for trapped events. 98 2. When you are finished with an event, you can run 'exit' to close the current window and allow Juju to continue running. 99 3. CTRL+a is tmux prefix. 100 101 More help and info is available in the online documentation: 102 https://juju.ubuntu.com/docs/authors-hook-debug.html 103 104 END 105 106 cat > $JUJU_DEBUG/init.sh <<END 107 #!/bin/bash 108 cat $JUJU_DEBUG/welcome.msg 109 trap 'echo \$? > $JUJU_DEBUG/hook_exit_status' EXIT 110 END 111 chmod +x $JUJU_DEBUG/init.sh 112 113 # Create an internal script which will load the hook environment. 114 cat > $JUJU_DEBUG/hook.sh <<END 115 #!/bin/bash 116 . $JUJU_DEBUG/env.sh 117 echo \$\$ > $JUJU_DEBUG/hook.pid 118 exec /bin/bash --noprofile --init-file $JUJU_DEBUG/init.sh 119 END 120 chmod +x $JUJU_DEBUG/hook.sh 121 122 tmux new-window -t $JUJU_UNIT_NAME -n $JUJU_HOOK_NAME "$JUJU_DEBUG/hook.sh" 123 124 # If we exit for whatever reason, kill the hook shell. 125 exit_handler() { 126 if [ -f $JUJU_DEBUG/hook.pid ]; then 127 kill -9 $(cat $JUJU_DEBUG/hook.pid) || true 128 fi 129 } 130 trap exit_handler EXIT 131 132 # Wait for the hook shell to start, and then wait for it to exit. 133 while [ ! -f $JUJU_DEBUG/hook.pid ]; do 134 sleep 1 135 done 136 HOOK_PID=$(cat $JUJU_DEBUG/hook.pid) 137 while kill -0 "$HOOK_PID" 2> /dev/null; do 138 sleep 1 139 done 140 typeset -i exitstatus=$(cat $JUJU_DEBUG/hook_exit_status) 141 exit $exitstatus 142 `