github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/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.v1" 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 100 More help and info is available in the online documentation: 101 https://juju.ubuntu.com/docs/authors-hook-debug.html 102 103 END 104 105 cat > $JUJU_DEBUG/init.sh <<END 106 #!/bin/bash 107 cat $JUJU_DEBUG/welcome.msg 108 END 109 chmod +x $JUJU_DEBUG/init.sh 110 111 # Create an internal script which will load the hook environment. 112 cat > $JUJU_DEBUG/hook.sh <<END 113 #!/bin/bash 114 . $JUJU_DEBUG/env.sh 115 echo \$\$ > $JUJU_DEBUG/hook.pid 116 exec /bin/bash --noprofile --init-file $JUJU_DEBUG/init.sh 117 END 118 chmod +x $JUJU_DEBUG/hook.sh 119 120 tmux new-window -t $JUJU_UNIT_NAME -n $JUJU_HOOK_NAME "$JUJU_DEBUG/hook.sh" 121 122 # If we exit for whatever reason, kill the hook shell. 123 exit_handler() { 124 if [ -f $JUJU_DEBUG/hook.pid ]; then 125 kill -9 $(cat $JUJU_DEBUG/hook.pid) || true 126 fi 127 } 128 trap exit_handler EXIT 129 130 # Wait for the hook shell to start, and then wait for it to exit. 131 while [ ! -f $JUJU_DEBUG/hook.pid ]; do 132 sleep 1 133 done 134 HOOK_PID=$(cat $JUJU_DEBUG/hook.pid) 135 while kill -0 "$HOOK_PID" 2> /dev/null; do 136 sleep 1 137 done 138 `