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  }