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  `