github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/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.v1"
    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  
   100  More help and info is available in the online documentation:
   101  https://juju.ubuntu.com/docs/authors-hook-debug.html
   102  
   103  END
   104  
   105  cat > $JUJU_DEBUG/init.sh <<END
   106  #!/bin/bash
   107  cat $JUJU_DEBUG/welcome.msg
   108  END
   109  chmod +x $JUJU_DEBUG/init.sh
   110  
   111  # Create an internal script which will load the hook environment.
   112  cat > $JUJU_DEBUG/hook.sh <<END
   113  #!/bin/bash
   114  . $JUJU_DEBUG/env.sh
   115  echo \$\$ > $JUJU_DEBUG/hook.pid
   116  exec /bin/bash --noprofile --init-file $JUJU_DEBUG/init.sh
   117  END
   118  chmod +x $JUJU_DEBUG/hook.sh
   119  
   120  tmux new-window -t $JUJU_UNIT_NAME -n $JUJU_HOOK_NAME "$JUJU_DEBUG/hook.sh"
   121  
   122  # If we exit for whatever reason, kill the hook shell.
   123  exit_handler() {
   124      if [ -f $JUJU_DEBUG/hook.pid ]; then
   125          kill -9 $(cat $JUJU_DEBUG/hook.pid) || true
   126      fi
   127  }
   128  trap exit_handler EXIT
   129  
   130  # Wait for the hook shell to start, and then wait for it to exit.
   131  while [ ! -f $JUJU_DEBUG/hook.pid ]; do
   132      sleep 1
   133  done
   134  HOOK_PID=$(cat $JUJU_DEBUG/hook.pid)
   135  while kill -0 "$HOOK_PID" 2> /dev/null; do
   136      sleep 1
   137  done
   138  `