github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/worker/uniter/runner/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  	goyaml "gopkg.in/yaml.v2"
    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 welcome message display for the hook environment.
    95  cat > $JUJU_DEBUG/welcome.msg <<END
    96  This is a Juju debug-hooks tmux session. Remember:
    97  1. You need to execute hooks manually if you want them to run for trapped events.
    98  2. When you are finished with an event, you can run 'exit' to close the current window and allow Juju to continue running.
    99  3. CTRL+a is tmux prefix.
   100  
   101  More help and info is available in the online documentation:
   102  https://juju.ubuntu.com/docs/authors-hook-debug.html
   103  
   104  END
   105  
   106  cat > $JUJU_DEBUG/init.sh <<END
   107  #!/bin/bash
   108  cat $JUJU_DEBUG/welcome.msg
   109  trap 'echo \$? > $JUJU_DEBUG/hook_exit_status' EXIT
   110  END
   111  chmod +x $JUJU_DEBUG/init.sh
   112  
   113  # Create an internal script which will load the hook environment.
   114  cat > $JUJU_DEBUG/hook.sh <<END
   115  #!/bin/bash
   116  . $JUJU_DEBUG/env.sh
   117  echo \$\$ > $JUJU_DEBUG/hook.pid
   118  exec /bin/bash --noprofile --init-file $JUJU_DEBUG/init.sh
   119  END
   120  chmod +x $JUJU_DEBUG/hook.sh
   121  
   122  tmux new-window -t $JUJU_UNIT_NAME -n $JUJU_HOOK_NAME "$JUJU_DEBUG/hook.sh"
   123  
   124  # If we exit for whatever reason, kill the hook shell.
   125  exit_handler() {
   126      if [ -f $JUJU_DEBUG/hook.pid ]; then
   127          kill -9 $(cat $JUJU_DEBUG/hook.pid) || true
   128      fi
   129  }
   130  trap exit_handler EXIT
   131  
   132  # Wait for the hook shell to start, and then wait for it to exit.
   133  while [ ! -f $JUJU_DEBUG/hook.pid ]; do
   134      sleep 1
   135  done
   136  HOOK_PID=$(cat $JUJU_DEBUG/hook.pid)
   137  while kill -0 "$HOOK_PID" 2> /dev/null; do
   138      sleep 1
   139  done
   140  typeset -i exitstatus=$(cat $JUJU_DEBUG/hook_exit_status)
   141  exit $exitstatus
   142  `