github.com/cloudbase/juju-core@v0.0.0-20140504232958-a7271ac7912f/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 "launchpad.net/goyaml" 14 15 "launchpad.net/juju-core/utils/set" 16 ) 17 18 // ServerSession represents a "juju debug-hooks" session. 19 type ServerSession struct { 20 *HooksContext 21 hooks set.Strings 22 } 23 24 // MatchHook returns true if the specified hook name matches 25 // the hook specified by the debug-hooks client. 26 func (s *ServerSession) MatchHook(hookName string) bool { 27 return s.hooks.IsEmpty() || s.hooks.Contains(hookName) 28 } 29 30 // waitClientExit executes flock, waiting for the SSH client to exit. 31 // This is a var so it can be replaced for testing. 32 var waitClientExit = func(s *ServerSession) { 33 path := s.ClientExitFileLock() 34 exec.Command("flock", path, "-c", "true").Run() 35 } 36 37 // RunHook "runs" the hook with the specified name via debug-hooks. 38 func (s *ServerSession) RunHook(hookName, charmDir string, env []string) error { 39 env = append(env, "JUJU_HOOK_NAME="+hookName) 40 cmd := exec.Command("/bin/bash", "-s") 41 cmd.Env = env 42 cmd.Dir = charmDir 43 cmd.Stdin = bytes.NewBufferString(debugHooksServerScript) 44 if err := cmd.Start(); err != nil { 45 return err 46 } 47 go func(proc *os.Process) { 48 // Wait for the SSH client to exit (i.e. release the flock), 49 // then kill the server hook process in case the client 50 // exited uncleanly. 51 waitClientExit(s) 52 proc.Kill() 53 }(cmd.Process) 54 return cmd.Wait() 55 } 56 57 // FindSession attempts to find a debug hooks session for the unit specified 58 // in the context, and returns a new ServerSession structure for it. 59 func (c *HooksContext) FindSession() (*ServerSession, error) { 60 cmd := exec.Command("tmux", "has-session", "-t", c.tmuxSessionName()) 61 out, err := cmd.CombinedOutput() 62 if err != nil { 63 if len(out) != 0 { 64 return nil, errors.New(string(out)) 65 } else { 66 return nil, err 67 } 68 } 69 // Parse the debug-hooks file for an optional hook name. 70 data, err := ioutil.ReadFile(c.ClientFileLock()) 71 if err != nil { 72 return nil, err 73 } 74 var args hookArgs 75 err = goyaml.Unmarshal(data, &args) 76 if err != nil { 77 return nil, err 78 } 79 hooks := set.NewStrings(args.Hooks...) 80 session := &ServerSession{c, hooks} 81 return session, nil 82 } 83 84 const debugHooksServerScript = `set -e 85 export JUJU_DEBUG=$(mktemp -d) 86 exec > $JUJU_DEBUG/debug.log >&1 87 88 # Set a useful prompt. 89 export PS1="$JUJU_UNIT_NAME:$JUJU_HOOK_NAME % " 90 91 # Save environment variables and export them for sourcing. 92 FILTER='^\(LS_COLORS\|LESSOPEN\|LESSCLOSE\|PWD\)=' 93 export | grep -v $FILTER > $JUJU_DEBUG/env.sh 94 95 # Create an internal script which will load the hook environment. 96 cat > $JUJU_DEBUG/hook.sh <<END 97 #!/bin/bash 98 . $JUJU_DEBUG/env.sh 99 echo \$\$ > $JUJU_DEBUG/hook.pid 100 exec /bin/bash --noprofile --norc 101 END 102 chmod +x $JUJU_DEBUG/hook.sh 103 104 tmux new-window -t $JUJU_UNIT_NAME -n $JUJU_HOOK_NAME "$JUJU_DEBUG/hook.sh" 105 106 # If we exit for whatever reason, kill the hook shell. 107 exit_handler() { 108 if [ -f $JUJU_DEBUG/hook.pid ]; then 109 kill -9 $(cat $JUJU_DEBUG/hook.pid) || true 110 fi 111 } 112 trap exit_handler EXIT 113 114 # Wait for the hook shell to start, and then wait for it to exit. 115 while [ ! -f $JUJU_DEBUG/hook.pid ]; do 116 sleep 1 117 done 118 HOOK_PID=$(cat $JUJU_DEBUG/hook.pid) 119 while kill -0 "$HOOK_PID" 2> /dev/null; do 120 sleep 1 121 done 122 `