aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com/hashicorp/go-plugin/client.go
diff options
context:
space:
mode:
authorAlex Pilon <apilon@hashicorp.com>2019-02-22 18:24:37 -0500
committerAlex Pilon <apilon@hashicorp.com>2019-02-22 18:24:37 -0500
commit15c0b25d011f37e7c20aeca9eaf461f78285b8d9 (patch)
tree255c250a5c9d4801c74092d33b7337d8c14438ff /vendor/github.com/hashicorp/go-plugin/client.go
parent07971ca38143c5faf951d152fba370ddcbe26ad5 (diff)
downloadterraform-provider-statuscake-15c0b25d011f37e7c20aeca9eaf461f78285b8d9.tar.gz
terraform-provider-statuscake-15c0b25d011f37e7c20aeca9eaf461f78285b8d9.tar.zst
terraform-provider-statuscake-15c0b25d011f37e7c20aeca9eaf461f78285b8d9.zip
deps: github.com/hashicorp/terraform@sdk-v0.11-with-go-modules
Updated via: go get github.com/hashicorp/terraform@sdk-v0.11-with-go-modules and go mod tidy
Diffstat (limited to 'vendor/github.com/hashicorp/go-plugin/client.go')
-rw-r--r--vendor/github.com/hashicorp/go-plugin/client.go293
1 files changed, 253 insertions, 40 deletions
diff --git a/vendor/github.com/hashicorp/go-plugin/client.go b/vendor/github.com/hashicorp/go-plugin/client.go
index 9f8a0f2..b3e3b78 100644
--- a/vendor/github.com/hashicorp/go-plugin/client.go
+++ b/vendor/github.com/hashicorp/go-plugin/client.go
@@ -2,8 +2,12 @@ package plugin
2 2
3import ( 3import (
4 "bufio" 4 "bufio"
5 "context"
6 "crypto/subtle"
7 "crypto/tls"
5 "errors" 8 "errors"
6 "fmt" 9 "fmt"
10 "hash"
7 "io" 11 "io"
8 "io/ioutil" 12 "io/ioutil"
9 "log" 13 "log"
@@ -17,6 +21,8 @@ import (
17 "sync/atomic" 21 "sync/atomic"
18 "time" 22 "time"
19 "unicode" 23 "unicode"
24
25 hclog "github.com/hashicorp/go-hclog"
20) 26)
21 27
22// If this is 1, then we've called CleanupClients. This can be used 28// If this is 1, then we've called CleanupClients. This can be used
@@ -35,6 +41,22 @@ var (
35 // ErrProcessNotFound is returned when a client is instantiated to 41 // ErrProcessNotFound is returned when a client is instantiated to
36 // reattach to an existing process and it isn't found. 42 // reattach to an existing process and it isn't found.
37 ErrProcessNotFound = errors.New("Reattachment process not found") 43 ErrProcessNotFound = errors.New("Reattachment process not found")
44
45 // ErrChecksumsDoNotMatch is returned when binary's checksum doesn't match
46 // the one provided in the SecureConfig.
47 ErrChecksumsDoNotMatch = errors.New("checksums did not match")
48
49 // ErrSecureNoChecksum is returned when an empty checksum is provided to the
50 // SecureConfig.
51 ErrSecureConfigNoChecksum = errors.New("no checksum provided")
52
53 // ErrSecureNoHash is returned when a nil Hash object is provided to the
54 // SecureConfig.
55 ErrSecureConfigNoHash = errors.New("no hash implementation provided")
56
57 // ErrSecureConfigAndReattach is returned when both Reattach and
58 // SecureConfig are set.
59 ErrSecureConfigAndReattach = errors.New("only one of Reattach or SecureConfig can be set")
38) 60)
39 61
40// Client handles the lifecycle of a plugin application. It launches 62// Client handles the lifecycle of a plugin application. It launches
@@ -55,7 +77,10 @@ type Client struct {
55 l sync.Mutex 77 l sync.Mutex
56 address net.Addr 78 address net.Addr
57 process *os.Process 79 process *os.Process
58 client *RPCClient 80 client ClientProtocol
81 protocol Protocol
82 logger hclog.Logger
83 doneCtx context.Context
59} 84}
60 85
61// ClientConfig is the configuration used to initialize a new 86// ClientConfig is the configuration used to initialize a new
@@ -79,6 +104,13 @@ type ClientConfig struct {
79 Cmd *exec.Cmd 104 Cmd *exec.Cmd
80 Reattach *ReattachConfig 105 Reattach *ReattachConfig
81 106
107 // SecureConfig is configuration for verifying the integrity of the
108 // executable. It can not be used with Reattach.
109 SecureConfig *SecureConfig
110
111 // TLSConfig is used to enable TLS on the RPC client.
112 TLSConfig *tls.Config
113
82 // Managed represents if the client should be managed by the 114 // Managed represents if the client should be managed by the
83 // plugin package or not. If true, then by calling CleanupClients, 115 // plugin package or not. If true, then by calling CleanupClients,
84 // it will automatically be cleaned up. Otherwise, the client 116 // it will automatically be cleaned up. Otherwise, the client
@@ -109,14 +141,74 @@ type ClientConfig struct {
109 // sync any of these streams. 141 // sync any of these streams.
110 SyncStdout io.Writer 142 SyncStdout io.Writer
111 SyncStderr io.Writer 143 SyncStderr io.Writer
144
145 // AllowedProtocols is a list of allowed protocols. If this isn't set,
146 // then only netrpc is allowed. This is so that older go-plugin systems
147 // can show friendly errors if they see a plugin with an unknown
148 // protocol.
149 //
150 // By setting this, you can cause an error immediately on plugin start
151 // if an unsupported protocol is used with a good error message.
152 //
153 // If this isn't set at all (nil value), then only net/rpc is accepted.
154 // This is done for legacy reasons. You must explicitly opt-in to
155 // new protocols.
156 AllowedProtocols []Protocol
157
158 // Logger is the logger that the client will used. If none is provided,
159 // it will default to hclog's default logger.
160 Logger hclog.Logger
112} 161}
113 162
114// ReattachConfig is used to configure a client to reattach to an 163// ReattachConfig is used to configure a client to reattach to an
115// already-running plugin process. You can retrieve this information by 164// already-running plugin process. You can retrieve this information by
116// calling ReattachConfig on Client. 165// calling ReattachConfig on Client.
117type ReattachConfig struct { 166type ReattachConfig struct {
118 Addr net.Addr 167 Protocol Protocol
119 Pid int 168 Addr net.Addr
169 Pid int
170}
171
172// SecureConfig is used to configure a client to verify the integrity of an
173// executable before running. It does this by verifying the checksum is
174// expected. Hash is used to specify the hashing method to use when checksumming
175// the file. The configuration is verified by the client by calling the
176// SecureConfig.Check() function.
177//
178// The host process should ensure the checksum was provided by a trusted and
179// authoritative source. The binary should be installed in such a way that it
180// can not be modified by an unauthorized user between the time of this check
181// and the time of execution.
182type SecureConfig struct {
183 Checksum []byte
184 Hash hash.Hash
185}
186
187// Check takes the filepath to an executable and returns true if the checksum of
188// the file matches the checksum provided in the SecureConfig.
189func (s *SecureConfig) Check(filePath string) (bool, error) {
190 if len(s.Checksum) == 0 {
191 return false, ErrSecureConfigNoChecksum
192 }
193
194 if s.Hash == nil {
195 return false, ErrSecureConfigNoHash
196 }
197
198 file, err := os.Open(filePath)
199 if err != nil {
200 return false, err
201 }
202 defer file.Close()
203
204 _, err = io.Copy(s.Hash, file)
205 if err != nil {
206 return false, err
207 }
208
209 sum := s.Hash.Sum(nil)
210
211 return subtle.ConstantTimeCompare(sum, s.Checksum) == 1, nil
120} 212}
121 213
122// This makes sure all the managed subprocesses are killed and properly 214// This makes sure all the managed subprocesses are killed and properly
@@ -174,7 +266,22 @@ func NewClient(config *ClientConfig) (c *Client) {
174 config.SyncStderr = ioutil.Discard 266 config.SyncStderr = ioutil.Discard
175 } 267 }
176 268
177 c = &Client{config: config} 269 if config.AllowedProtocols == nil {
270 config.AllowedProtocols = []Protocol{ProtocolNetRPC}
271 }
272
273 if config.Logger == nil {
274 config.Logger = hclog.New(&hclog.LoggerOptions{
275 Output: hclog.DefaultOutput,
276 Level: hclog.Trace,
277 Name: "plugin",
278 })
279 }
280
281 c = &Client{
282 config: config,
283 logger: config.Logger,
284 }
178 if config.Managed { 285 if config.Managed {
179 managedClientsLock.Lock() 286 managedClientsLock.Lock()
180 managedClients = append(managedClients, c) 287 managedClients = append(managedClients, c)
@@ -184,11 +291,11 @@ func NewClient(config *ClientConfig) (c *Client) {
184 return 291 return
185} 292}
186 293
187// Client returns an RPC client for the plugin. 294// Client returns the protocol client for this connection.
188// 295//
189// Subsequent calls to this will return the same RPC client. 296// Subsequent calls to this will return the same client.
190func (c *Client) Client() (*RPCClient, error) { 297func (c *Client) Client() (ClientProtocol, error) {
191 addr, err := c.Start() 298 _, err := c.Start()
192 if err != nil { 299 if err != nil {
193 return nil, err 300 return nil, err
194 } 301 }
@@ -200,29 +307,18 @@ func (c *Client) Client() (*RPCClient, error) {
200 return c.client, nil 307 return c.client, nil
201 } 308 }
202 309
203 // Connect to the client 310 switch c.protocol {
204 conn, err := net.Dial(addr.Network(), addr.String()) 311 case ProtocolNetRPC:
205 if err != nil { 312 c.client, err = newRPCClient(c)
206 return nil, err
207 }
208 if tcpConn, ok := conn.(*net.TCPConn); ok {
209 // Make sure to set keep alive so that the connection doesn't die
210 tcpConn.SetKeepAlive(true)
211 }
212 313
213 // Create the actual RPC client 314 case ProtocolGRPC:
214 c.client, err = NewRPCClient(conn, c.config.Plugins) 315 c.client, err = newGRPCClient(c.doneCtx, c)
215 if err != nil { 316
216 conn.Close() 317 default:
217 return nil, err 318 return nil, fmt.Errorf("unknown server protocol: %s", c.protocol)
218 } 319 }
219 320
220 // Begin the stream syncing so that stdin, out, err work properly
221 err = c.client.SyncStreams(
222 c.config.SyncStdout,
223 c.config.SyncStderr)
224 if err != nil { 321 if err != nil {
225 c.client.Close()
226 c.client = nil 322 c.client = nil
227 return nil, err 323 return nil, err
228 } 324 }
@@ -274,8 +370,7 @@ func (c *Client) Kill() {
274 if err != nil { 370 if err != nil {
275 // If there was an error just log it. We're going to force 371 // If there was an error just log it. We're going to force
276 // kill in a moment anyways. 372 // kill in a moment anyways.
277 log.Printf( 373 c.logger.Warn("error closing client during Kill", "err", err)
278 "[WARN] plugin: error closing client during Kill: %s", err)
279 } 374 }
280 } 375 }
281 } 376 }
@@ -318,13 +413,21 @@ func (c *Client) Start() (addr net.Addr, err error) {
318 { 413 {
319 cmdSet := c.config.Cmd != nil 414 cmdSet := c.config.Cmd != nil
320 attachSet := c.config.Reattach != nil 415 attachSet := c.config.Reattach != nil
416 secureSet := c.config.SecureConfig != nil
321 if cmdSet == attachSet { 417 if cmdSet == attachSet {
322 return nil, fmt.Errorf("Only one of Cmd or Reattach must be set") 418 return nil, fmt.Errorf("Only one of Cmd or Reattach must be set")
323 } 419 }
420
421 if secureSet && attachSet {
422 return nil, ErrSecureConfigAndReattach
423 }
324 } 424 }
325 425
326 // Create the logging channel for when we kill 426 // Create the logging channel for when we kill
327 c.doneLogging = make(chan struct{}) 427 c.doneLogging = make(chan struct{})
428 // Create a context for when we kill
429 var ctxCancel context.CancelFunc
430 c.doneCtx, ctxCancel = context.WithCancel(context.Background())
328 431
329 if c.config.Reattach != nil { 432 if c.config.Reattach != nil {
330 // Verify the process still exists. If not, then it is an error 433 // Verify the process still exists. If not, then it is an error
@@ -350,7 +453,7 @@ func (c *Client) Start() (addr net.Addr, err error) {
350 pidWait(pid) 453 pidWait(pid)
351 454
352 // Log so we can see it 455 // Log so we can see it
353 log.Printf("[DEBUG] plugin: reattached plugin process exited\n") 456 c.logger.Debug("reattached plugin process exited")
354 457
355 // Mark it 458 // Mark it
356 c.l.Lock() 459 c.l.Lock()
@@ -359,11 +462,19 @@ func (c *Client) Start() (addr net.Addr, err error) {
359 462
360 // Close the logging channel since that doesn't work on reattach 463 // Close the logging channel since that doesn't work on reattach
361 close(c.doneLogging) 464 close(c.doneLogging)
465
466 // Cancel the context
467 ctxCancel()
362 }(p.Pid) 468 }(p.Pid)
363 469
364 // Set the address and process 470 // Set the address and process
365 c.address = c.config.Reattach.Addr 471 c.address = c.config.Reattach.Addr
366 c.process = p 472 c.process = p
473 c.protocol = c.config.Reattach.Protocol
474 if c.protocol == "" {
475 // Default the protocol to net/rpc for backwards compatibility
476 c.protocol = ProtocolNetRPC
477 }
367 478
368 return c.address, nil 479 return c.address, nil
369 } 480 }
@@ -384,7 +495,15 @@ func (c *Client) Start() (addr net.Addr, err error) {
384 cmd.Stderr = stderr_w 495 cmd.Stderr = stderr_w
385 cmd.Stdout = stdout_w 496 cmd.Stdout = stdout_w
386 497
387 log.Printf("[DEBUG] plugin: starting plugin: %s %#v", cmd.Path, cmd.Args) 498 if c.config.SecureConfig != nil {
499 if ok, err := c.config.SecureConfig.Check(cmd.Path); err != nil {
500 return nil, fmt.Errorf("error verifying checksum: %s", err)
501 } else if !ok {
502 return nil, ErrChecksumsDoNotMatch
503 }
504 }
505
506 c.logger.Debug("starting plugin", "path", cmd.Path, "args", cmd.Args)
388 err = cmd.Start() 507 err = cmd.Start()
389 if err != nil { 508 if err != nil {
390 return 509 return
@@ -418,12 +537,15 @@ func (c *Client) Start() (addr net.Addr, err error) {
418 cmd.Wait() 537 cmd.Wait()
419 538
420 // Log and make sure to flush the logs write away 539 // Log and make sure to flush the logs write away
421 log.Printf("[DEBUG] plugin: %s: plugin process exited\n", cmd.Path) 540 c.logger.Debug("plugin process exited", "path", cmd.Path)
422 os.Stderr.Sync() 541 os.Stderr.Sync()
423 542
424 // Mark that we exited 543 // Mark that we exited
425 close(exitCh) 544 close(exitCh)
426 545
546 // Cancel the context, marking that we exited
547 ctxCancel()
548
427 // Set that we exited, which takes a lock 549 // Set that we exited, which takes a lock
428 c.l.Lock() 550 c.l.Lock()
429 defer c.l.Unlock() 551 defer c.l.Unlock()
@@ -465,7 +587,7 @@ func (c *Client) Start() (addr net.Addr, err error) {
465 timeout := time.After(c.config.StartTimeout) 587 timeout := time.After(c.config.StartTimeout)
466 588
467 // Start looking for the address 589 // Start looking for the address
468 log.Printf("[DEBUG] plugin: waiting for RPC address for: %s", cmd.Path) 590 c.logger.Debug("waiting for RPC address", "path", cmd.Path)
469 select { 591 select {
470 case <-timeout: 592 case <-timeout:
471 err = errors.New("timeout while waiting for plugin to start") 593 err = errors.New("timeout while waiting for plugin to start")
@@ -475,7 +597,7 @@ func (c *Client) Start() (addr net.Addr, err error) {
475 // Trim the line and split by "|" in order to get the parts of 597 // Trim the line and split by "|" in order to get the parts of
476 // the output. 598 // the output.
477 line := strings.TrimSpace(string(lineBytes)) 599 line := strings.TrimSpace(string(lineBytes))
478 parts := strings.SplitN(line, "|", 4) 600 parts := strings.SplitN(line, "|", 6)
479 if len(parts) < 4 { 601 if len(parts) < 4 {
480 err = fmt.Errorf( 602 err = fmt.Errorf(
481 "Unrecognized remote plugin message: %s\n\n"+ 603 "Unrecognized remote plugin message: %s\n\n"+
@@ -495,7 +617,7 @@ func (c *Client) Start() (addr net.Addr, err error) {
495 617
496 if int(coreProtocol) != CoreProtocolVersion { 618 if int(coreProtocol) != CoreProtocolVersion {
497 err = fmt.Errorf("Incompatible core API version with plugin. "+ 619 err = fmt.Errorf("Incompatible core API version with plugin. "+
498 "Plugin version: %s, Ours: %d\n\n"+ 620 "Plugin version: %s, Core version: %d\n\n"+
499 "To fix this, the plugin usually only needs to be recompiled.\n"+ 621 "To fix this, the plugin usually only needs to be recompiled.\n"+
500 "Please report this to the plugin author.", parts[0], CoreProtocolVersion) 622 "Please report this to the plugin author.", parts[0], CoreProtocolVersion)
501 return 623 return
@@ -513,7 +635,7 @@ func (c *Client) Start() (addr net.Addr, err error) {
513 // Test the API version 635 // Test the API version
514 if uint(protocol) != c.config.ProtocolVersion { 636 if uint(protocol) != c.config.ProtocolVersion {
515 err = fmt.Errorf("Incompatible API version with plugin. "+ 637 err = fmt.Errorf("Incompatible API version with plugin. "+
516 "Plugin version: %s, Ours: %d", parts[1], c.config.ProtocolVersion) 638 "Plugin version: %s, Core version: %d", parts[1], c.config.ProtocolVersion)
517 return 639 return
518 } 640 }
519 641
@@ -525,6 +647,27 @@ func (c *Client) Start() (addr net.Addr, err error) {
525 default: 647 default:
526 err = fmt.Errorf("Unknown address type: %s", parts[3]) 648 err = fmt.Errorf("Unknown address type: %s", parts[3])
527 } 649 }
650
651 // If we have a server type, then record that. We default to net/rpc
652 // for backwards compatibility.
653 c.protocol = ProtocolNetRPC
654 if len(parts) >= 5 {
655 c.protocol = Protocol(parts[4])
656 }
657
658 found := false
659 for _, p := range c.config.AllowedProtocols {
660 if p == c.protocol {
661 found = true
662 break
663 }
664 }
665 if !found {
666 err = fmt.Errorf("Unsupported plugin protocol %q. Supported: %v",
667 c.protocol, c.config.AllowedProtocols)
668 return
669 }
670
528 } 671 }
529 672
530 c.address = addr 673 c.address = addr
@@ -555,9 +698,57 @@ func (c *Client) ReattachConfig() *ReattachConfig {
555 } 698 }
556 699
557 return &ReattachConfig{ 700 return &ReattachConfig{
558 Addr: c.address, 701 Protocol: c.protocol,
559 Pid: c.config.Cmd.Process.Pid, 702 Addr: c.address,
703 Pid: c.config.Cmd.Process.Pid,
704 }
705}
706
707// Protocol returns the protocol of server on the remote end. This will
708// start the plugin process if it isn't already started. Errors from
709// starting the plugin are surpressed and ProtocolInvalid is returned. It
710// is recommended you call Start explicitly before calling Protocol to ensure
711// no errors occur.
712func (c *Client) Protocol() Protocol {
713 _, err := c.Start()
714 if err != nil {
715 return ProtocolInvalid
716 }
717
718 return c.protocol
719}
720
721func netAddrDialer(addr net.Addr) func(string, time.Duration) (net.Conn, error) {
722 return func(_ string, _ time.Duration) (net.Conn, error) {
723 // Connect to the client
724 conn, err := net.Dial(addr.Network(), addr.String())
725 if err != nil {
726 return nil, err
727 }
728 if tcpConn, ok := conn.(*net.TCPConn); ok {
729 // Make sure to set keep alive so that the connection doesn't die
730 tcpConn.SetKeepAlive(true)
731 }
732
733 return conn, nil
734 }
735}
736
737// dialer is compatible with grpc.WithDialer and creates the connection
738// to the plugin.
739func (c *Client) dialer(_ string, timeout time.Duration) (net.Conn, error) {
740 conn, err := netAddrDialer(c.address)("", timeout)
741 if err != nil {
742 return nil, err
560 } 743 }
744
745 // If we have a TLS config we wrap our connection. We only do this
746 // for net/rpc since gRPC uses its own mechanism for TLS.
747 if c.protocol == ProtocolNetRPC && c.config.TLSConfig != nil {
748 conn = tls.Client(conn, c.config.TLSConfig)
749 }
750
751 return conn, nil
561} 752}
562 753
563func (c *Client) logStderr(r io.Reader) { 754func (c *Client) logStderr(r io.Reader) {
@@ -566,9 +757,31 @@ func (c *Client) logStderr(r io.Reader) {
566 line, err := bufR.ReadString('\n') 757 line, err := bufR.ReadString('\n')
567 if line != "" { 758 if line != "" {
568 c.config.Stderr.Write([]byte(line)) 759 c.config.Stderr.Write([]byte(line))
569
570 line = strings.TrimRightFunc(line, unicode.IsSpace) 760 line = strings.TrimRightFunc(line, unicode.IsSpace)
571 log.Printf("[DEBUG] plugin: %s: %s", filepath.Base(c.config.Cmd.Path), line) 761
762 l := c.logger.Named(filepath.Base(c.config.Cmd.Path))
763
764 entry, err := parseJSON(line)
765 // If output is not JSON format, print directly to Debug
766 if err != nil {
767 l.Debug(line)
768 } else {
769 out := flattenKVPairs(entry.KVPairs)
770
771 l = l.With("timestamp", entry.Timestamp.Format(hclog.TimeFormat))
772 switch hclog.LevelFromString(entry.Level) {
773 case hclog.Trace:
774 l.Trace(entry.Message, out...)
775 case hclog.Debug:
776 l.Debug(entry.Message, out...)
777 case hclog.Info:
778 l.Info(entry.Message, out...)
779 case hclog.Warn:
780 l.Warn(entry.Message, out...)
781 case hclog.Error:
782 l.Error(entry.Message, out...)
783 }
784 }
572 } 785 }
573 786
574 if err == io.EOF { 787 if err == io.EOF {