github.com/leowmjw/otto@v0.2.1-0.20160126165905-6400716cf085/plugin/server.go (about)

     1  package plugin
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"log"
     8  	"net"
     9  	"os"
    10  	"os/signal"
    11  	"runtime"
    12  	"strconv"
    13  	"sync/atomic"
    14  
    15  	pluginrpc "github.com/hashicorp/otto/rpc"
    16  )
    17  
    18  // The APIVersion is outputted along with the RPC address. The plugin
    19  // client validates this API version and will show an error if it doesn't
    20  // know how to speak it.
    21  const APIVersion = "1"
    22  
    23  // The "magic cookie" is used to verify that the user intended to
    24  // actually run this binary. If this cookie isn't present as an
    25  // environmental variable, then we bail out early with an error.
    26  const MagicCookieKey = "OTTO_PLUGIN_MAGIC_COOKIE"
    27  const MagicCookieValue = "11aab7ff21cb9ff7b0e9975d53f17a8dab571eac9b5ff0191730046698f07b7f"
    28  
    29  // ServeOpts configures what sorts of plugins are served.
    30  type ServeOpts struct {
    31  	AppFunc pluginrpc.AppFunc
    32  }
    33  
    34  // Serve serves the plugins given by ServeOpts.
    35  //
    36  // Serve doesn't return until the plugin is done being executed. Any
    37  // errors will be outputted to the log.
    38  func Serve(opts *ServeOpts) {
    39  	// First check the cookie
    40  	if os.Getenv(MagicCookieKey) != MagicCookieValue {
    41  		fmt.Fprintf(os.Stderr,
    42  			"This binary is an Otto plugin. These are not meant to be\n"+
    43  				"executed directly. Please execute `otto`, which will load\n"+
    44  				"any plugins automatically.\n")
    45  		os.Exit(1)
    46  	}
    47  
    48  	// Logging goes to the original stderr
    49  	log.SetOutput(os.Stderr)
    50  
    51  	// Create our new stdout, stderr files. These will override our built-in
    52  	// stdout/stderr so that it works across the stream boundary.
    53  	stdout_r, stdout_w, err := os.Pipe()
    54  	if err != nil {
    55  		fmt.Fprintf(os.Stderr, "Error preparing Otto plugin: %s\n", err)
    56  		os.Exit(1)
    57  	}
    58  	stderr_r, stderr_w, err := os.Pipe()
    59  	if err != nil {
    60  		fmt.Fprintf(os.Stderr, "Error preparing Otto plugin: %s\n", err)
    61  		os.Exit(1)
    62  	}
    63  
    64  	// Register a listener so we can accept a connection
    65  	listener, err := serverListener()
    66  	if err != nil {
    67  		log.Printf("[ERR] plugin init: %s", err)
    68  		return
    69  	}
    70  	defer listener.Close()
    71  
    72  	// Create the RPC server to dispense
    73  	server := &pluginrpc.Server{
    74  		AppFunc: opts.AppFunc,
    75  		Stdout:  stdout_r,
    76  		Stderr:  stderr_r,
    77  	}
    78  
    79  	// Output the address and service name to stdout so that core can bring it up.
    80  	log.Printf("Plugin address: %s %s\n",
    81  		listener.Addr().Network(), listener.Addr().String())
    82  	fmt.Printf("%s|%s|%s\n",
    83  		APIVersion,
    84  		listener.Addr().Network(),
    85  		listener.Addr().String())
    86  	os.Stdout.Sync()
    87  
    88  	// Eat the interrupts
    89  	ch := make(chan os.Signal, 1)
    90  	signal.Notify(ch, os.Interrupt)
    91  	go func() {
    92  		var count int32 = 0
    93  		for {
    94  			<-ch
    95  			newCount := atomic.AddInt32(&count, 1)
    96  			log.Printf(
    97  				"Received interrupt signal (count: %d). Ignoring.",
    98  				newCount)
    99  		}
   100  	}()
   101  
   102  	// Set our new out, err
   103  	os.Stdout = stdout_w
   104  	os.Stderr = stderr_w
   105  
   106  	// Serve
   107  	server.Accept(listener)
   108  }
   109  
   110  func serverListener() (net.Listener, error) {
   111  	if runtime.GOOS == "windows" {
   112  		return serverListener_tcp()
   113  	}
   114  
   115  	return serverListener_unix()
   116  }
   117  
   118  func serverListener_tcp() (net.Listener, error) {
   119  	minPort, err := strconv.ParseInt(os.Getenv("OTTO_PLUGIN_MIN_PORT"), 10, 32)
   120  	if err != nil {
   121  		return nil, err
   122  	}
   123  
   124  	maxPort, err := strconv.ParseInt(os.Getenv("OTTO_PLUGIN_MAX_PORT"), 10, 32)
   125  	if err != nil {
   126  		return nil, err
   127  	}
   128  
   129  	for port := minPort; port <= maxPort; port++ {
   130  		address := fmt.Sprintf("127.0.0.1:%d", port)
   131  		listener, err := net.Listen("tcp", address)
   132  		if err == nil {
   133  			return listener, nil
   134  		}
   135  	}
   136  
   137  	return nil, errors.New("Couldn't bind plugin TCP listener")
   138  }
   139  
   140  func serverListener_unix() (net.Listener, error) {
   141  	tf, err := ioutil.TempFile("", "otto-plugin")
   142  	if err != nil {
   143  		return nil, err
   144  	}
   145  	path := tf.Name()
   146  
   147  	// Close the file and remove it because it has to not exist for
   148  	// the domain socket.
   149  	if err := tf.Close(); err != nil {
   150  		return nil, err
   151  	}
   152  	if err := os.Remove(path); err != nil {
   153  		return nil, err
   154  	}
   155  
   156  	return net.Listen("unix", path)
   157  }