github.com/hashicorp/go-plugin@v1.6.0/grpc_server.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package plugin
     5  
     6  import (
     7  	"bytes"
     8  	"crypto/tls"
     9  	"encoding/json"
    10  	"fmt"
    11  	"io"
    12  	"net"
    13  
    14  	hclog "github.com/hashicorp/go-hclog"
    15  	"github.com/hashicorp/go-plugin/internal/grpcmux"
    16  	"github.com/hashicorp/go-plugin/internal/plugin"
    17  	"google.golang.org/grpc"
    18  	"google.golang.org/grpc/credentials"
    19  	"google.golang.org/grpc/health"
    20  	"google.golang.org/grpc/health/grpc_health_v1"
    21  	"google.golang.org/grpc/reflection"
    22  )
    23  
    24  // GRPCServiceName is the name of the service that the health check should
    25  // return as passing.
    26  const GRPCServiceName = "plugin"
    27  
    28  // DefaultGRPCServer can be used with the "GRPCServer" field for Server
    29  // as a default factory method to create a gRPC server with no extra options.
    30  func DefaultGRPCServer(opts []grpc.ServerOption) *grpc.Server {
    31  	return grpc.NewServer(opts...)
    32  }
    33  
    34  // GRPCServer is a ServerType implementation that serves plugins over
    35  // gRPC. This allows plugins to easily be written for other languages.
    36  //
    37  // The GRPCServer outputs a custom configuration as a base64-encoded
    38  // JSON structure represented by the GRPCServerConfig config structure.
    39  type GRPCServer struct {
    40  	// Plugins are the list of plugins to serve.
    41  	Plugins map[string]Plugin
    42  
    43  	// Server is the actual server that will accept connections. This
    44  	// will be used for plugin registration as well.
    45  	Server func([]grpc.ServerOption) *grpc.Server
    46  
    47  	// TLS should be the TLS configuration if available. If this is nil,
    48  	// the connection will not have transport security.
    49  	TLS *tls.Config
    50  
    51  	// DoneCh is the channel that is closed when this server has exited.
    52  	DoneCh chan struct{}
    53  
    54  	// Stdout/StderrLis are the readers for stdout/stderr that will be copied
    55  	// to the stdout/stderr connection that is output.
    56  	Stdout io.Reader
    57  	Stderr io.Reader
    58  
    59  	config      GRPCServerConfig
    60  	server      *grpc.Server
    61  	broker      *GRPCBroker
    62  	stdioServer *grpcStdioServer
    63  
    64  	logger hclog.Logger
    65  
    66  	muxer *grpcmux.GRPCServerMuxer
    67  }
    68  
    69  // ServerProtocol impl.
    70  func (s *GRPCServer) Init() error {
    71  	// Create our server
    72  	var opts []grpc.ServerOption
    73  	if s.TLS != nil {
    74  		opts = append(opts, grpc.Creds(credentials.NewTLS(s.TLS)))
    75  	}
    76  	s.server = s.Server(opts)
    77  
    78  	// Register the health service
    79  	healthCheck := health.NewServer()
    80  	healthCheck.SetServingStatus(
    81  		GRPCServiceName, grpc_health_v1.HealthCheckResponse_SERVING)
    82  	grpc_health_v1.RegisterHealthServer(s.server, healthCheck)
    83  
    84  	// Register the reflection service
    85  	reflection.Register(s.server)
    86  
    87  	// Register the broker service
    88  	brokerServer := newGRPCBrokerServer()
    89  	plugin.RegisterGRPCBrokerServer(s.server, brokerServer)
    90  	s.broker = newGRPCBroker(brokerServer, s.TLS, unixSocketConfigFromEnv(), nil, s.muxer)
    91  	go s.broker.Run()
    92  
    93  	// Register the controller
    94  	controllerServer := &grpcControllerServer{server: s}
    95  	plugin.RegisterGRPCControllerServer(s.server, controllerServer)
    96  
    97  	// Register the stdio service
    98  	s.stdioServer = newGRPCStdioServer(s.logger, s.Stdout, s.Stderr)
    99  	plugin.RegisterGRPCStdioServer(s.server, s.stdioServer)
   100  
   101  	// Register all our plugins onto the gRPC server.
   102  	for k, raw := range s.Plugins {
   103  		p, ok := raw.(GRPCPlugin)
   104  		if !ok {
   105  			return fmt.Errorf("%q is not a GRPC-compatible plugin", k)
   106  		}
   107  
   108  		if err := p.GRPCServer(s.broker, s.server); err != nil {
   109  			return fmt.Errorf("error registering %q: %s", k, err)
   110  		}
   111  	}
   112  
   113  	return nil
   114  }
   115  
   116  // Stop calls Stop on the underlying grpc.Server and Close on the underlying
   117  // grpc.Broker if present.
   118  func (s *GRPCServer) Stop() {
   119  	s.server.Stop()
   120  
   121  	if s.broker != nil {
   122  		s.broker.Close()
   123  		s.broker = nil
   124  	}
   125  }
   126  
   127  // GracefulStop calls GracefulStop on the underlying grpc.Server and Close on
   128  // the underlying grpc.Broker if present.
   129  func (s *GRPCServer) GracefulStop() {
   130  	s.server.GracefulStop()
   131  
   132  	if s.broker != nil {
   133  		s.broker.Close()
   134  		s.broker = nil
   135  	}
   136  }
   137  
   138  // Config is the GRPCServerConfig encoded as JSON then base64.
   139  func (s *GRPCServer) Config() string {
   140  	// Create a buffer that will contain our final contents
   141  	var buf bytes.Buffer
   142  
   143  	// Wrap the base64 encoding with JSON encoding.
   144  	if err := json.NewEncoder(&buf).Encode(s.config); err != nil {
   145  		// We panic since ths shouldn't happen under any scenario. We
   146  		// carefully control the structure being encoded here and it should
   147  		// always be successful.
   148  		panic(err)
   149  	}
   150  
   151  	return buf.String()
   152  }
   153  
   154  func (s *GRPCServer) Serve(lis net.Listener) {
   155  	defer close(s.DoneCh)
   156  	err := s.server.Serve(lis)
   157  	if err != nil {
   158  		s.logger.Error("grpc server", "error", err)
   159  	}
   160  }
   161  
   162  // GRPCServerConfig is the extra configuration passed along for consumers
   163  // to facilitate using GRPC plugins.
   164  type GRPCServerConfig struct {
   165  	StdoutAddr string `json:"stdout_addr"`
   166  	StderrAddr string `json:"stderr_addr"`
   167  }