blob: 630e36daa0ecae8fc23f6ea5980b81820ef605b9 [file] [log] [blame]
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package rpcapi
import (
"context"
"errors"
"os"
"github.com/hashicorp/go-plugin"
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
"google.golang.org/grpc"
)
// ServePlugin attempts to complete the go-plugin protocol handshake, and then
// if successful starts the plugin server and blocks until the given context
// is cancelled.
//
// Returns [ErrNotPluginClient] if this program doesn't seem to be running as
// the child of a plugin client, which is detected based on a magic environment
// variable that the client ought to have set.
func ServePlugin(ctx context.Context, opts ServerOpts) error {
// go-plugin has its own check for the environment variable magic cookie
// but it returns a generic error message. We'll pre-check it out here
// instead so we can return a more specific error message.
if os.Getenv(handshake.MagicCookieKey) != handshake.MagicCookieValue {
return ErrNotPluginClient
}
plugin.Serve(&plugin.ServeConfig{
HandshakeConfig: handshake,
VersionedPlugins: map[int]plugin.PluginSet{
1: {
"tfcore": &corePlugin{
experimentsAllowed: opts.ExperimentsAllowed,
},
},
},
GRPCServer: func(opts []grpc.ServerOption) *grpc.Server {
fullOpts := []grpc.ServerOption{
grpc.UnaryInterceptor(otelgrpc.UnaryServerInterceptor()),
grpc.StreamInterceptor(otelgrpc.StreamServerInterceptor()),
}
fullOpts = append(fullOpts, opts...)
server := grpc.NewServer(fullOpts...)
// We'll also monitor the given context for cancellation
// and terminate the server gracefully if we get cancelled.
go func() {
<-ctx.Done()
server.GracefulStop()
// The above will block until all of the pending RPCs have
// finished.
os.Exit(0)
}()
return server
},
})
return nil
}
var ErrNotPluginClient = errors.New("caller is not a plugin client")
type ServerOpts struct {
ExperimentsAllowed bool
}
// 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",
}