]>
Commit | Line | Data |
---|---|---|
15c0b25d AP |
1 | package plugin |
2 | ||
3 | import ( | |
4 | "bytes" | |
5 | "crypto/tls" | |
6 | "encoding/json" | |
7 | "fmt" | |
8 | "io" | |
9 | "net" | |
10 | ||
107c1cdb ND |
11 | hclog "github.com/hashicorp/go-hclog" |
12 | "github.com/hashicorp/go-plugin/internal/plugin" | |
15c0b25d AP |
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. | |
21 | const 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. | |
25 | func 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. | |
34 | type 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 | |
107c1cdb ND |
57 | |
58 | logger hclog.Logger | |
15c0b25d AP |
59 | } |
60 | ||
61 | // ServerProtocol impl. | |
62 | func (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() | |
107c1cdb | 78 | plugin.RegisterGRPCBrokerServer(s.server, brokerServer) |
15c0b25d AP |
79 | s.broker = newGRPCBroker(brokerServer, s.TLS) |
80 | go s.broker.Run() | |
81 | ||
107c1cdb ND |
82 | // Register the controller |
83 | controllerServer := &grpcControllerServer{ | |
84 | server: s, | |
85 | } | |
86 | plugin.RegisterGRPCControllerServer(s.server, controllerServer) | |
87 | ||
15c0b25d AP |
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 { | |
107c1cdb | 96 | return fmt.Errorf("error registering %q: %s", k, err) |
15c0b25d AP |
97 | } |
98 | } | |
99 | ||
100 | return nil | |
101 | } | |
102 | ||
103 | // Stop calls Stop on the underlying grpc.Server | |
104 | func (s *GRPCServer) Stop() { | |
105 | s.server.Stop() | |
106 | } | |
107 | ||
108 | // GracefulStop calls GracefulStop on the underlying grpc.Server | |
109 | func (s *GRPCServer) GracefulStop() { | |
110 | s.server.GracefulStop() | |
111 | } | |
112 | ||
113 | // Config is the GRPCServerConfig encoded as JSON then base64. | |
114 | func (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 | ||
129 | func (s *GRPCServer) Serve(lis net.Listener) { | |
107c1cdb ND |
130 | defer close(s.DoneCh) |
131 | err := s.server.Serve(lis) | |
132 | if err != nil { | |
133 | s.logger.Error("grpc server", "error", err) | |
134 | } | |
15c0b25d AP |
135 | } |
136 | ||
137 | // GRPCServerConfig is the extra configuration passed along for consumers | |
138 | // to facilitate using GRPC plugins. | |
139 | type GRPCServerConfig struct { | |
140 | StdoutAddr string `json:"stdout_addr"` | |
141 | StderrAddr string `json:"stderr_addr"` | |
142 | } |