github.com/rogpeppe/juju@v0.0.0-20140613142852-6337964b789e/worker/uniter/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 "launchpad.net/goyaml" 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 an internal script which will load the hook environment. 95 cat > $JUJU_DEBUG/hook.sh <<END 96 #!/bin/bash 97 . $JUJU_DEBUG/env.sh 98 echo \$\$ > $JUJU_DEBUG/hook.pid 99 exec /bin/bash --noprofile --norc 100 END 101 chmod +x $JUJU_DEBUG/hook.sh 102 103 tmux new-window -t $JUJU_UNIT_NAME -n $JUJU_HOOK_NAME "$JUJU_DEBUG/hook.sh" 104 105 # If we exit for whatever reason, kill the hook shell. 106 exit_handler() { 107 if [ -f $JUJU_DEBUG/hook.pid ]; then 108 kill -9 $(cat $JUJU_DEBUG/hook.pid) || true 109 fi 110 } 111 trap exit_handler EXIT 112 113 # Wait for the hook shell to start, and then wait for it to exit. 114 while [ ! -f $JUJU_DEBUG/hook.pid ]; do 115 sleep 1 116 done 117 HOOK_PID=$(cat $JUJU_DEBUG/hook.pid) 118 while kill -0 "$HOOK_PID" 2> /dev/null; do 119 sleep 1 120 done 121 `