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