2017-08-11 19:19:46 +02:00
|
|
|
package plugin
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"crypto/tls"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"net"
|
|
|
|
|
2018-12-05 20:27:05 +01:00
|
|
|
hclog "github.com/hashicorp/go-hclog"
|
2019-02-13 00:27:56 +01:00
|
|
|
"github.com/hashicorp/go-plugin/internal/plugin"
|
2017-08-11 19:19:46 +02:00
|
|
|
"google.golang.org/grpc"
|
|
|
|
"google.golang.org/grpc/credentials"
|
|
|
|
"google.golang.org/grpc/health"
|
|
|
|
"google.golang.org/grpc/health/grpc_health_v1"
|
|
|
|
)
|
|
|
|
|
|
|
|
// GRPCServiceName is the name of the service that the health check should
|
|
|
|
// return as passing.
|
|
|
|
const GRPCServiceName = "plugin"
|
|
|
|
|
|
|
|
// DefaultGRPCServer can be used with the "GRPCServer" field for Server
|
|
|
|
// as a default factory method to create a gRPC server with no extra options.
|
|
|
|
func DefaultGRPCServer(opts []grpc.ServerOption) *grpc.Server {
|
|
|
|
return grpc.NewServer(opts...)
|
|
|
|
}
|
|
|
|
|
|
|
|
// GRPCServer is a ServerType implementation that serves plugins over
|
|
|
|
// gRPC. This allows plugins to easily be written for other languages.
|
|
|
|
//
|
|
|
|
// The GRPCServer outputs a custom configuration as a base64-encoded
|
|
|
|
// JSON structure represented by the GRPCServerConfig config structure.
|
|
|
|
type GRPCServer struct {
|
|
|
|
// Plugins are the list of plugins to serve.
|
|
|
|
Plugins map[string]Plugin
|
|
|
|
|
|
|
|
// Server is the actual server that will accept connections. This
|
|
|
|
// will be used for plugin registration as well.
|
|
|
|
Server func([]grpc.ServerOption) *grpc.Server
|
|
|
|
|
|
|
|
// TLS should be the TLS configuration if available. If this is nil,
|
|
|
|
// the connection will not have transport security.
|
|
|
|
TLS *tls.Config
|
|
|
|
|
|
|
|
// DoneCh is the channel that is closed when this server has exited.
|
|
|
|
DoneCh chan struct{}
|
|
|
|
|
|
|
|
// Stdout/StderrLis are the readers for stdout/stderr that will be copied
|
|
|
|
// to the stdout/stderr connection that is output.
|
|
|
|
Stdout io.Reader
|
|
|
|
Stderr io.Reader
|
|
|
|
|
|
|
|
config GRPCServerConfig
|
|
|
|
server *grpc.Server
|
2018-01-25 20:32:05 +01:00
|
|
|
broker *GRPCBroker
|
2018-12-05 20:27:05 +01:00
|
|
|
|
|
|
|
logger hclog.Logger
|
2017-08-11 19:19:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// ServerProtocol impl.
|
|
|
|
func (s *GRPCServer) Init() error {
|
|
|
|
// Create our server
|
|
|
|
var opts []grpc.ServerOption
|
|
|
|
if s.TLS != nil {
|
|
|
|
opts = append(opts, grpc.Creds(credentials.NewTLS(s.TLS)))
|
|
|
|
}
|
|
|
|
s.server = s.Server(opts)
|
|
|
|
|
|
|
|
// Register the health service
|
|
|
|
healthCheck := health.NewServer()
|
|
|
|
healthCheck.SetServingStatus(
|
|
|
|
GRPCServiceName, grpc_health_v1.HealthCheckResponse_SERVING)
|
|
|
|
grpc_health_v1.RegisterHealthServer(s.server, healthCheck)
|
|
|
|
|
2018-01-25 20:32:05 +01:00
|
|
|
// Register the broker service
|
|
|
|
brokerServer := newGRPCBrokerServer()
|
2019-02-13 00:27:56 +01:00
|
|
|
plugin.RegisterGRPCBrokerServer(s.server, brokerServer)
|
2018-01-25 20:32:05 +01:00
|
|
|
s.broker = newGRPCBroker(brokerServer, s.TLS)
|
|
|
|
go s.broker.Run()
|
|
|
|
|
2018-12-05 20:27:05 +01:00
|
|
|
// Register the controller
|
|
|
|
controllerServer := &grpcControllerServer{
|
|
|
|
server: s,
|
|
|
|
}
|
2019-02-13 00:27:56 +01:00
|
|
|
plugin.RegisterGRPCControllerServer(s.server, controllerServer)
|
2018-12-05 20:27:05 +01:00
|
|
|
|
2017-08-11 19:19:46 +02:00
|
|
|
// Register all our plugins onto the gRPC server.
|
|
|
|
for k, raw := range s.Plugins {
|
|
|
|
p, ok := raw.(GRPCPlugin)
|
|
|
|
if !ok {
|
2018-01-25 20:32:05 +01:00
|
|
|
return fmt.Errorf("%q is not a GRPC-compatible plugin", k)
|
2017-08-11 19:19:46 +02:00
|
|
|
}
|
|
|
|
|
2018-01-25 20:32:05 +01:00
|
|
|
if err := p.GRPCServer(s.broker, s.server); err != nil {
|
2018-12-05 20:27:05 +01:00
|
|
|
return fmt.Errorf("error registering %q: %s", k, err)
|
2017-08-11 19:19:46 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-01-25 20:32:05 +01:00
|
|
|
// Stop calls Stop on the underlying grpc.Server
|
|
|
|
func (s *GRPCServer) Stop() {
|
|
|
|
s.server.Stop()
|
|
|
|
}
|
|
|
|
|
|
|
|
// GracefulStop calls GracefulStop on the underlying grpc.Server
|
|
|
|
func (s *GRPCServer) GracefulStop() {
|
|
|
|
s.server.GracefulStop()
|
|
|
|
}
|
|
|
|
|
2017-08-11 19:19:46 +02:00
|
|
|
// Config is the GRPCServerConfig encoded as JSON then base64.
|
|
|
|
func (s *GRPCServer) Config() string {
|
|
|
|
// Create a buffer that will contain our final contents
|
|
|
|
var buf bytes.Buffer
|
|
|
|
|
|
|
|
// Wrap the base64 encoding with JSON encoding.
|
|
|
|
if err := json.NewEncoder(&buf).Encode(s.config); err != nil {
|
|
|
|
// We panic since ths shouldn't happen under any scenario. We
|
|
|
|
// carefully control the structure being encoded here and it should
|
|
|
|
// always be successful.
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return buf.String()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *GRPCServer) Serve(lis net.Listener) {
|
2018-12-05 20:27:05 +01:00
|
|
|
defer close(s.DoneCh)
|
|
|
|
err := s.server.Serve(lis)
|
|
|
|
if err != nil {
|
|
|
|
s.logger.Error("grpc server", "error", err)
|
|
|
|
}
|
2017-08-11 19:19:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// GRPCServerConfig is the extra configuration passed along for consumers
|
|
|
|
// to facilitate using GRPC plugins.
|
|
|
|
type GRPCServerConfig struct {
|
|
|
|
StdoutAddr string `json:"stdout_addr"`
|
|
|
|
StderrAddr string `json:"stderr_addr"`
|
|
|
|
}
|