]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/hashicorp/go-plugin/server.go
Merge pull request #27 from terraform-providers/go-modules-2019-02-22
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / go-plugin / server.go
1 package plugin
2
3 import (
4 "crypto/tls"
5 "encoding/base64"
6 "errors"
7 "fmt"
8 "io/ioutil"
9 "log"
10 "net"
11 "os"
12 "os/signal"
13 "runtime"
14 "strconv"
15 "sync/atomic"
16
17 "github.com/hashicorp/go-hclog"
18
19 "google.golang.org/grpc"
20 )
21
22 // CoreProtocolVersion is the ProtocolVersion of the plugin system itself.
23 // We will increment this whenever we change any protocol behavior. This
24 // will invalidate any prior plugins but will at least allow us to iterate
25 // on the core in a safe way. We will do our best to do this very
26 // infrequently.
27 const CoreProtocolVersion = 1
28
29 // HandshakeConfig is the configuration used by client and servers to
30 // handshake before starting a plugin connection. This is embedded by
31 // both ServeConfig and ClientConfig.
32 //
33 // In practice, the plugin host creates a HandshakeConfig that is exported
34 // and plugins then can easily consume it.
35 type HandshakeConfig struct {
36 // ProtocolVersion is the version that clients must match on to
37 // agree they can communicate. This should match the ProtocolVersion
38 // set on ClientConfig when using a plugin.
39 ProtocolVersion uint
40
41 // MagicCookieKey and value are used as a very basic verification
42 // that a plugin is intended to be launched. This is not a security
43 // measure, just a UX feature. If the magic cookie doesn't match,
44 // we show human-friendly output.
45 MagicCookieKey string
46 MagicCookieValue string
47 }
48
49 // ServeConfig configures what sorts of plugins are served.
50 type ServeConfig struct {
51 // HandshakeConfig is the configuration that must match clients.
52 HandshakeConfig
53
54 // TLSProvider is a function that returns a configured tls.Config.
55 TLSProvider func() (*tls.Config, error)
56
57 // Plugins are the plugins that are served.
58 Plugins map[string]Plugin
59
60 // GRPCServer should be non-nil to enable serving the plugins over
61 // gRPC. This is a function to create the server when needed with the
62 // given server options. The server options populated by go-plugin will
63 // be for TLS if set. You may modify the input slice.
64 //
65 // Note that the grpc.Server will automatically be registered with
66 // the gRPC health checking service. This is not optional since go-plugin
67 // relies on this to implement Ping().
68 GRPCServer func([]grpc.ServerOption) *grpc.Server
69
70 // Logger is used to pass a logger into the server. If none is provided the
71 // server will create a default logger.
72 Logger hclog.Logger
73 }
74
75 // Protocol returns the protocol that this server should speak.
76 func (c *ServeConfig) Protocol() Protocol {
77 result := ProtocolNetRPC
78 if c.GRPCServer != nil {
79 result = ProtocolGRPC
80 }
81
82 return result
83 }
84
85 // Serve serves the plugins given by ServeConfig.
86 //
87 // Serve doesn't return until the plugin is done being executed. Any
88 // errors will be outputted to os.Stderr.
89 //
90 // This is the method that plugins should call in their main() functions.
91 func Serve(opts *ServeConfig) {
92 // Validate the handshake config
93 if opts.MagicCookieKey == "" || opts.MagicCookieValue == "" {
94 fmt.Fprintf(os.Stderr,
95 "Misconfigured ServeConfig given to serve this plugin: no magic cookie\n"+
96 "key or value was set. Please notify the plugin author and report\n"+
97 "this as a bug.\n")
98 os.Exit(1)
99 }
100
101 // First check the cookie
102 if os.Getenv(opts.MagicCookieKey) != opts.MagicCookieValue {
103 fmt.Fprintf(os.Stderr,
104 "This binary is a plugin. These are not meant to be executed directly.\n"+
105 "Please execute the program that consumes these plugins, which will\n"+
106 "load any plugins automatically\n")
107 os.Exit(1)
108 }
109
110 // Logging goes to the original stderr
111 log.SetOutput(os.Stderr)
112
113 logger := opts.Logger
114 if logger == nil {
115 // internal logger to os.Stderr
116 logger = hclog.New(&hclog.LoggerOptions{
117 Level: hclog.Trace,
118 Output: os.Stderr,
119 JSONFormat: true,
120 })
121 }
122
123 // Create our new stdout, stderr files. These will override our built-in
124 // stdout/stderr so that it works across the stream boundary.
125 stdout_r, stdout_w, err := os.Pipe()
126 if err != nil {
127 fmt.Fprintf(os.Stderr, "Error preparing plugin: %s\n", err)
128 os.Exit(1)
129 }
130 stderr_r, stderr_w, err := os.Pipe()
131 if err != nil {
132 fmt.Fprintf(os.Stderr, "Error preparing plugin: %s\n", err)
133 os.Exit(1)
134 }
135
136 // Register a listener so we can accept a connection
137 listener, err := serverListener()
138 if err != nil {
139 logger.Error("plugin init error", "error", err)
140 return
141 }
142
143 // Close the listener on return. We wrap this in a func() on purpose
144 // because the "listener" reference may change to TLS.
145 defer func() {
146 listener.Close()
147 }()
148
149 var tlsConfig *tls.Config
150 if opts.TLSProvider != nil {
151 tlsConfig, err = opts.TLSProvider()
152 if err != nil {
153 logger.Error("plugin tls init", "error", err)
154 return
155 }
156 }
157
158 // Create the channel to tell us when we're done
159 doneCh := make(chan struct{})
160
161 // Build the server type
162 var server ServerProtocol
163 switch opts.Protocol() {
164 case ProtocolNetRPC:
165 // If we have a TLS configuration then we wrap the listener
166 // ourselves and do it at that level.
167 if tlsConfig != nil {
168 listener = tls.NewListener(listener, tlsConfig)
169 }
170
171 // Create the RPC server to dispense
172 server = &RPCServer{
173 Plugins: opts.Plugins,
174 Stdout: stdout_r,
175 Stderr: stderr_r,
176 DoneCh: doneCh,
177 }
178
179 case ProtocolGRPC:
180 // Create the gRPC server
181 server = &GRPCServer{
182 Plugins: opts.Plugins,
183 Server: opts.GRPCServer,
184 TLS: tlsConfig,
185 Stdout: stdout_r,
186 Stderr: stderr_r,
187 DoneCh: doneCh,
188 }
189
190 default:
191 panic("unknown server protocol: " + opts.Protocol())
192 }
193
194 // Initialize the servers
195 if err := server.Init(); err != nil {
196 logger.Error("protocol init", "error", err)
197 return
198 }
199
200 // Build the extra configuration
201 extra := ""
202 if v := server.Config(); v != "" {
203 extra = base64.StdEncoding.EncodeToString([]byte(v))
204 }
205 if extra != "" {
206 extra = "|" + extra
207 }
208
209 logger.Debug("plugin address", "network", listener.Addr().Network(), "address", listener.Addr().String())
210
211 // Output the address and service name to stdout so that core can bring it up.
212 fmt.Printf("%d|%d|%s|%s|%s%s\n",
213 CoreProtocolVersion,
214 opts.ProtocolVersion,
215 listener.Addr().Network(),
216 listener.Addr().String(),
217 opts.Protocol(),
218 extra)
219 os.Stdout.Sync()
220
221 // Eat the interrupts
222 ch := make(chan os.Signal, 1)
223 signal.Notify(ch, os.Interrupt)
224 go func() {
225 var count int32 = 0
226 for {
227 <-ch
228 newCount := atomic.AddInt32(&count, 1)
229 logger.Debug("plugin received interrupt signal, ignoring", "count", newCount)
230 }
231 }()
232
233 // Set our new out, err
234 os.Stdout = stdout_w
235 os.Stderr = stderr_w
236
237 // Accept connections and wait for completion
238 go server.Serve(listener)
239 <-doneCh
240 }
241
242 func serverListener() (net.Listener, error) {
243 if runtime.GOOS == "windows" {
244 return serverListener_tcp()
245 }
246
247 return serverListener_unix()
248 }
249
250 func serverListener_tcp() (net.Listener, error) {
251 minPort, err := strconv.ParseInt(os.Getenv("PLUGIN_MIN_PORT"), 10, 32)
252 if err != nil {
253 return nil, err
254 }
255
256 maxPort, err := strconv.ParseInt(os.Getenv("PLUGIN_MAX_PORT"), 10, 32)
257 if err != nil {
258 return nil, err
259 }
260
261 for port := minPort; port <= maxPort; port++ {
262 address := fmt.Sprintf("127.0.0.1:%d", port)
263 listener, err := net.Listen("tcp", address)
264 if err == nil {
265 return listener, nil
266 }
267 }
268
269 return nil, errors.New("Couldn't bind plugin TCP listener")
270 }
271
272 func serverListener_unix() (net.Listener, error) {
273 tf, err := ioutil.TempFile("", "plugin")
274 if err != nil {
275 return nil, err
276 }
277 path := tf.Name()
278
279 // Close the file and remove it because it has to not exist for
280 // the domain socket.
281 if err := tf.Close(); err != nil {
282 return nil, err
283 }
284 if err := os.Remove(path); err != nil {
285 return nil, err
286 }
287
288 l, err := net.Listen("unix", path)
289 if err != nil {
290 return nil, err
291 }
292
293 // Wrap the listener in rmListener so that the Unix domain socket file
294 // is removed on close.
295 return &rmListener{
296 Listener: l,
297 Path: path,
298 }, nil
299 }
300
301 // rmListener is an implementation of net.Listener that forwards most
302 // calls to the listener but also removes a file as part of the close. We
303 // use this to cleanup the unix domain socket on close.
304 type rmListener struct {
305 net.Listener
306 Path string
307 }
308
309 func (l *rmListener) Close() error {
310 // Close the listener itself
311 if err := l.Listener.Close(); err != nil {
312 return err
313 }
314
315 // Remove the file
316 return os.Remove(l.Path)
317 }