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 }