]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/hashicorp/go-plugin/server.go
Initial transfer of provider code
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / go-plugin / server.go
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
16 // CoreProtocolVersion is the ProtocolVersion of the plugin system itself.
17 // We will increment this whenever we change any protocol behavior. This
18 // will invalidate any prior plugins but will at least allow us to iterate
19 // on the core in a safe way. We will do our best to do this very
20 // infrequently.
21 const CoreProtocolVersion = 1
22
23 // HandshakeConfig is the configuration used by client and servers to
24 // handshake before starting a plugin connection. This is embedded by
25 // both ServeConfig and ClientConfig.
26 //
27 // In practice, the plugin host creates a HandshakeConfig that is exported
28 // and plugins then can easily consume it.
29 type HandshakeConfig struct {
30 // ProtocolVersion is the version that clients must match on to
31 // agree they can communicate. This should match the ProtocolVersion
32 // set on ClientConfig when using a plugin.
33 ProtocolVersion uint
34
35 // MagicCookieKey and value are used as a very basic verification
36 // that a plugin is intended to be launched. This is not a security
37 // measure, just a UX feature. If the magic cookie doesn't match,
38 // we show human-friendly output.
39 MagicCookieKey string
40 MagicCookieValue string
41 }
42
43 // ServeConfig configures what sorts of plugins are served.
44 type ServeConfig struct {
45 // HandshakeConfig is the configuration that must match clients.
46 HandshakeConfig
47
48 // Plugins are the plugins that are served.
49 Plugins map[string]Plugin
50 }
51
52 // Serve serves the plugins given by ServeConfig.
53 //
54 // Serve doesn't return until the plugin is done being executed. Any
55 // errors will be outputted to the log.
56 //
57 // This is the method that plugins should call in their main() functions.
58 func Serve(opts *ServeConfig) {
59 // Validate the handshake config
60 if opts.MagicCookieKey == "" || opts.MagicCookieValue == "" {
61 fmt.Fprintf(os.Stderr,
62 "Misconfigured ServeConfig given to serve this plugin: no magic cookie\n"+
63 "key or value was set. Please notify the plugin author and report\n"+
64 "this as a bug.\n")
65 os.Exit(1)
66 }
67
68 // First check the cookie
69 if os.Getenv(opts.MagicCookieKey) != opts.MagicCookieValue {
70 fmt.Fprintf(os.Stderr,
71 "This binary is a plugin. These are not meant to be executed directly.\n"+
72 "Please execute the program that consumes these plugins, which will\n"+
73 "load any plugins automatically\n")
74 os.Exit(1)
75 }
76
77 // Logging goes to the original stderr
78 log.SetOutput(os.Stderr)
79
80 // Create our new stdout, stderr files. These will override our built-in
81 // stdout/stderr so that it works across the stream boundary.
82 stdout_r, stdout_w, err := os.Pipe()
83 if err != nil {
84 fmt.Fprintf(os.Stderr, "Error preparing plugin: %s\n", err)
85 os.Exit(1)
86 }
87 stderr_r, stderr_w, err := os.Pipe()
88 if err != nil {
89 fmt.Fprintf(os.Stderr, "Error preparing plugin: %s\n", err)
90 os.Exit(1)
91 }
92
93 // Register a listener so we can accept a connection
94 listener, err := serverListener()
95 if err != nil {
96 log.Printf("[ERR] plugin: plugin init: %s", err)
97 return
98 }
99 defer listener.Close()
100
101 // Create the channel to tell us when we're done
102 doneCh := make(chan struct{})
103
104 // Create the RPC server to dispense
105 server := &RPCServer{
106 Plugins: opts.Plugins,
107 Stdout: stdout_r,
108 Stderr: stderr_r,
109 DoneCh: doneCh,
110 }
111
112 // Output the address and service name to stdout so that core can bring it up.
113 log.Printf("[DEBUG] plugin: plugin address: %s %s\n",
114 listener.Addr().Network(), listener.Addr().String())
115 fmt.Printf("%d|%d|%s|%s\n",
116 CoreProtocolVersion,
117 opts.ProtocolVersion,
118 listener.Addr().Network(),
119 listener.Addr().String())
120 os.Stdout.Sync()
121
122 // Eat the interrupts
123 ch := make(chan os.Signal, 1)
124 signal.Notify(ch, os.Interrupt)
125 go func() {
126 var count int32 = 0
127 for {
128 <-ch
129 newCount := atomic.AddInt32(&count, 1)
130 log.Printf(
131 "[DEBUG] plugin: received interrupt signal (count: %d). Ignoring.",
132 newCount)
133 }
134 }()
135
136 // Set our new out, err
137 os.Stdout = stdout_w
138 os.Stderr = stderr_w
139
140 // Serve
141 go server.Accept(listener)
142
143 // Wait for the graceful exit
144 <-doneCh
145 }
146
147 func serverListener() (net.Listener, error) {
148 if runtime.GOOS == "windows" {
149 return serverListener_tcp()
150 }
151
152 return serverListener_unix()
153 }
154
155 func serverListener_tcp() (net.Listener, error) {
156 minPort, err := strconv.ParseInt(os.Getenv("PLUGIN_MIN_PORT"), 10, 32)
157 if err != nil {
158 return nil, err
159 }
160
161 maxPort, err := strconv.ParseInt(os.Getenv("PLUGIN_MAX_PORT"), 10, 32)
162 if err != nil {
163 return nil, err
164 }
165
166 for port := minPort; port <= maxPort; port++ {
167 address := fmt.Sprintf("127.0.0.1:%d", port)
168 listener, err := net.Listen("tcp", address)
169 if err == nil {
170 return listener, nil
171 }
172 }
173
174 return nil, errors.New("Couldn't bind plugin TCP listener")
175 }
176
177 func serverListener_unix() (net.Listener, error) {
178 tf, err := ioutil.TempFile("", "plugin")
179 if err != nil {
180 return nil, err
181 }
182 path := tf.Name()
183
184 // Close the file and remove it because it has to not exist for
185 // the domain socket.
186 if err := tf.Close(); err != nil {
187 return nil, err
188 }
189 if err := os.Remove(path); err != nil {
190 return nil, err
191 }
192
193 l, err := net.Listen("unix", path)
194 if err != nil {
195 return nil, err
196 }
197
198 // Wrap the listener in rmListener so that the Unix domain socket file
199 // is removed on close.
200 return &rmListener{
201 Listener: l,
202 Path: path,
203 }, nil
204 }
205
206 // rmListener is an implementation of net.Listener that forwards most
207 // calls to the listener but also removes a file as part of the close. We
208 // use this to cleanup the unix domain socket on close.
209 type rmListener struct {
210 net.Listener
211 Path string
212 }
213
214 func (l *rmListener) Close() error {
215 // Close the listener itself
216 if err := l.Listener.Close(); err != nil {
217 return err
218 }
219
220 // Remove the file
221 return os.Remove(l.Path)
222 }