rpcapi: Initial stubbing of the plugin server setup

This commit is contained in:
Martin Atkins 2023-06-06 16:05:22 -07:00
parent 8f9b085501
commit 38d66ea74d
4 changed files with 136 additions and 0 deletions

View file

@ -0,0 +1,9 @@
package rpcapi
import (
"github.com/hashicorp/terraform/internal/rpcapi/terraform1"
)
type dependenciesServer struct {
terraform1.UnimplementedDependenciesServer
}

42
internal/rpcapi/plugin.go Normal file
View file

@ -0,0 +1,42 @@
package rpcapi
import (
"context"
"fmt"
"github.com/hashicorp/go-plugin"
"github.com/hashicorp/terraform/internal/rpcapi/terraform1"
"google.golang.org/grpc"
)
type corePlugin struct {
plugin.Plugin
}
func (p *corePlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error) {
// This codebase only provides a server implementation of this plugin.
// Clients must live elsewhere.
return nil, fmt.Errorf("there is no client implementation in this codebase")
}
func (p *corePlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error {
// We initially only register the setup server, because the registration
// of other services can vary depending on the capabilities negotiated
// during handshake.
setup := newSetupServer(p.handshakeFunc(s))
terraform1.RegisterSetupServer(s, setup)
return nil
}
func (p *corePlugin) handshakeFunc(s *grpc.Server) func(context.Context, *terraform1.ClientCapabilities) (*terraform1.ServerCapabilities, error) {
return func(ctx context.Context, clientCaps *terraform1.ClientCapabilities) (*terraform1.ServerCapabilities, error) {
// If handshaking is successful (which it currently always is, because
// we don't have any special capabilities to negotiate yet) then we
// will register all of the other services so the client can being
// doing real work. (In future the details of what we register here
// might vary based on the negotiated capabilities.)
terraform1.RegisterDependenciesServer(s, &dependenciesServer{})
return &terraform1.ServerCapabilities{}, nil
}
}

35
internal/rpcapi/server.go Normal file
View file

@ -0,0 +1,35 @@
package rpcapi
import (
"context"
"github.com/hashicorp/go-plugin"
)
// ServePlugin attempts to complete the go-plugin protocol handshake, and then
// if successful starts the plugin server and blocks until externally
// terminated.
func ServePlugin(ctx context.Context) error {
plugin.Serve(&plugin.ServeConfig{
HandshakeConfig: handshake,
VersionedPlugins: map[int]plugin.PluginSet{
1: plugin.PluginSet{
"tfcore": &corePlugin{},
},
},
GRPCServer: plugin.DefaultGRPCServer,
})
return nil
}
// handshake is the HandshakeConfig used to begin negotiation between client
// and server.
var handshake = plugin.HandshakeConfig{
// The ProtocolVersion is the version that must match between TF core
// and TF plugins.
ProtocolVersion: 1,
// The magic cookie values should NEVER be changed.
MagicCookieKey: "TERRAFORM_RPCAPI_COOKIE",
MagicCookieValue: "fba0991c9bcd453982f0d88e2da95940",
}

50
internal/rpcapi/setup.go Normal file
View file

@ -0,0 +1,50 @@
package rpcapi
import (
"context"
"sync"
"github.com/hashicorp/terraform/internal/rpcapi/terraform1"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
// setupServer is an implementation of the "Setup" service defined in our
// terraform1 package.
//
// This service is here only to offer the "Handshake" function, which clients
// must call to negotiate access to any other services. This is really just
// an adapter around a handshake function implemented on [corePlugin].
type setupServer struct {
terraform1.UnimplementedSetupServer
// initOthers is the callback used to perform the capability negotiation
// step and initialize all of the other API services based on what was
// negotiated.
initOthers func(context.Context, *terraform1.ClientCapabilities) (*terraform1.ServerCapabilities, error)
mu sync.Mutex
}
func newSetupServer(initOthers func(context.Context, *terraform1.ClientCapabilities) (*terraform1.ServerCapabilities, error)) terraform1.SetupServer {
return &setupServer{
initOthers: initOthers,
}
}
func (s *setupServer) Handshake(ctx context.Context, req *terraform1.Handshake_Request) (*terraform1.Handshake_Response, error) {
s.mu.Lock()
defer s.mu.Unlock()
if s.initOthers == nil {
return nil, status.Error(codes.FailedPrecondition, "handshake already completed")
}
serverCaps, err := s.initOthers(ctx, req.Capabilities)
s.initOthers = nil // cannot handshake again
if err != nil {
return nil, err
}
return &terraform1.Handshake_Response{
Capabilities: serverCaps,
}, nil
}