// Copyright IBM Corp. 2014, 2026 // 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", }