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  `