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