github.com/hashicorp/go-plugin@v1.6.0/grpc_client.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package plugin 5 6 import ( 7 "context" 8 "crypto/tls" 9 "fmt" 10 "math" 11 "net" 12 "time" 13 14 "github.com/hashicorp/go-plugin/internal/plugin" 15 "google.golang.org/grpc" 16 "google.golang.org/grpc/credentials" 17 "google.golang.org/grpc/health/grpc_health_v1" 18 ) 19 20 func dialGRPCConn(tls *tls.Config, dialer func(string, time.Duration) (net.Conn, error), dialOpts ...grpc.DialOption) (*grpc.ClientConn, error) { 21 // Build dialing options. 22 opts := make([]grpc.DialOption, 0) 23 24 // We use a custom dialer so that we can connect over unix domain sockets. 25 opts = append(opts, grpc.WithDialer(dialer)) 26 27 // Fail right away 28 opts = append(opts, grpc.FailOnNonTempDialError(true)) 29 30 // If we have no TLS configuration set, we need to explicitly tell grpc 31 // that we're connecting with an insecure connection. 32 if tls == nil { 33 opts = append(opts, grpc.WithInsecure()) 34 } else { 35 opts = append(opts, grpc.WithTransportCredentials( 36 credentials.NewTLS(tls))) 37 } 38 39 opts = append(opts, 40 grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(math.MaxInt32)), 41 grpc.WithDefaultCallOptions(grpc.MaxCallSendMsgSize(math.MaxInt32))) 42 43 // Add our custom options if we have any 44 opts = append(opts, dialOpts...) 45 46 // Connect. Note the first parameter is unused because we use a custom 47 // dialer that has the state to see the address. 48 conn, err := grpc.Dial("unused", opts...) 49 if err != nil { 50 return nil, err 51 } 52 53 return conn, nil 54 } 55 56 // newGRPCClient creates a new GRPCClient. The Client argument is expected 57 // to be successfully started already with a lock held. 58 func newGRPCClient(doneCtx context.Context, c *Client) (*GRPCClient, error) { 59 conn, err := dialGRPCConn(c.config.TLSConfig, c.dialer, c.config.GRPCDialOptions...) 60 if err != nil { 61 return nil, err 62 } 63 64 muxer, err := c.getGRPCMuxer(c.address) 65 if err != nil { 66 return nil, err 67 } 68 69 // Start the broker. 70 brokerGRPCClient := newGRPCBrokerClient(conn) 71 broker := newGRPCBroker(brokerGRPCClient, c.config.TLSConfig, c.unixSocketCfg, c.runner, muxer) 72 go broker.Run() 73 go brokerGRPCClient.StartStream() 74 75 // Start the stdio client 76 stdioClient, err := newGRPCStdioClient(doneCtx, c.logger.Named("stdio"), conn) 77 if err != nil { 78 return nil, err 79 } 80 go stdioClient.Run(c.config.SyncStdout, c.config.SyncStderr) 81 82 cl := &GRPCClient{ 83 Conn: conn, 84 Plugins: c.config.Plugins, 85 doneCtx: doneCtx, 86 broker: broker, 87 controller: plugin.NewGRPCControllerClient(conn), 88 } 89 90 return cl, nil 91 } 92 93 // GRPCClient connects to a GRPCServer over gRPC to dispense plugin types. 94 type GRPCClient struct { 95 Conn *grpc.ClientConn 96 Plugins map[string]Plugin 97 98 doneCtx context.Context 99 broker *GRPCBroker 100 101 controller plugin.GRPCControllerClient 102 } 103 104 // ClientProtocol impl. 105 func (c *GRPCClient) Close() error { 106 c.broker.Close() 107 c.controller.Shutdown(c.doneCtx, &plugin.Empty{}) 108 return c.Conn.Close() 109 } 110 111 // ClientProtocol impl. 112 func (c *GRPCClient) Dispense(name string) (interface{}, error) { 113 raw, ok := c.Plugins[name] 114 if !ok { 115 return nil, fmt.Errorf("unknown plugin type: %s", name) 116 } 117 118 p, ok := raw.(GRPCPlugin) 119 if !ok { 120 return nil, fmt.Errorf("plugin %q doesn't support gRPC", name) 121 } 122 123 return p.GRPCClient(c.doneCtx, c.broker, c.Conn) 124 } 125 126 // ClientProtocol impl. 127 func (c *GRPCClient) Ping() error { 128 client := grpc_health_v1.NewHealthClient(c.Conn) 129 _, err := client.Check(context.Background(), &grpc_health_v1.HealthCheckRequest{ 130 Service: GRPCServiceName, 131 }) 132 133 return err 134 }