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  `