launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/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 "io/ioutil" 9 "launchpad.net/errgo/errors" 10 "os" 11 "os/exec" 12 13 "launchpad.net/goyaml" 14 15 "launchpad.net/juju-core/utils/set" 16 ) 17 18 var mask = errors.Mask 19 20 // ServerSession represents a "juju debug-hooks" session. 21 type ServerSession struct { 22 *HooksContext 23 hooks set.Strings 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 err := cmd.Start(); err != nil { 47 return mask(err) 48 } 49 go func(proc *os.Process) { 50 // Wait for the SSH client to exit (i.e. release the flock), 51 // then kill the server hook process in case the client 52 // exited uncleanly. 53 waitClientExit(s) 54 proc.Kill() 55 }(cmd.Process) 56 return cmd.Wait() 57 } 58 59 // FindSession attempts to find a debug hooks session for the unit specified 60 // in the context, and returns a new ServerSession structure for it. 61 func (c *HooksContext) FindSession() (*ServerSession, error) { 62 cmd := exec.Command("tmux", "has-session", "-t", c.tmuxSessionName()) 63 out, err := cmd.CombinedOutput() 64 if err != nil { 65 if len(out) != 0 { 66 return nil, errors.New(string(out)) 67 } else { 68 return nil, mask(err) 69 } 70 } 71 // Parse the debug-hooks file for an optional hook name. 72 data, err := ioutil.ReadFile(c.ClientFileLock()) 73 if err != nil { 74 return nil, mask(err, os.IsNotExist) 75 } 76 var args hookArgs 77 err = goyaml.Unmarshal(data, &args) 78 if err != nil { 79 return nil, mask(err) 80 } 81 hooks := set.NewStrings(args.Hooks...) 82 session := &ServerSession{c, hooks} 83 return session, nil 84 } 85 86 const debugHooksServerScript = `set -e 87 export JUJU_DEBUG=$(mktemp -d) 88 exec > $JUJU_DEBUG/debug.log >&1 89 90 # Set a useful prompt. 91 export PS1="$JUJU_UNIT_NAME:$JUJU_HOOK_NAME % " 92 93 # Save environment variables and export them for sourcing. 94 FILTER='^\(LS_COLORS\|LESSOPEN\|LESSCLOSE\|PWD\)=' 95 export | grep -v $FILTER > $JUJU_DEBUG/env.sh 96 97 # Create an internal script which will load the hook environment. 98 cat > $JUJU_DEBUG/hook.sh <<END 99 #!/bin/bash 100 . $JUJU_DEBUG/env.sh 101 echo \$\$ > $JUJU_DEBUG/hook.pid 102 exec /bin/bash --noprofile --norc 103 END 104 chmod +x $JUJU_DEBUG/hook.sh 105 106 tmux new-window -t $JUJU_UNIT_NAME -n $JUJU_HOOK_NAME "$JUJU_DEBUG/hook.sh" 107 108 # If we exit for whatever reason, kill the hook shell. 109 exit_handler() { 110 if [ -f $JUJU_DEBUG/hook.pid ]; then 111 kill -9 $(cat $JUJU_DEBUG/hook.pid) || true 112 fi 113 } 114 trap exit_handler EXIT 115 116 # Wait for the hook shell to start, and then wait for it to exit. 117 while [ ! -f $JUJU_DEBUG/hook.pid ]; do 118 sleep 1 119 done 120 HOOK_PID=$(cat $JUJU_DEBUG/hook.pid) 121 while kill -0 "$HOOK_PID" 2> /dev/null; do 122 sleep 1 123 done 124 `