229 lines
6.9 KiB
Go
229 lines
6.9 KiB
Go
package plugin
|
|
|
|
import (
|
|
"errors"
|
|
"log"
|
|
|
|
hclog "github.com/hashicorp/go-hclog"
|
|
"github.com/hashicorp/go-plugin"
|
|
testing "github.com/mitchellh/go-testing-interface"
|
|
|
|
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
|
|
"github.com/hashicorp/terraform-plugin-go/tfprotov5/tf5server"
|
|
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
|
|
"github.com/hashicorp/terraform-plugin-go/tfprotov6/tf6server"
|
|
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
|
)
|
|
|
|
const (
|
|
// The constants below are the names of the plugins that can be dispensed
|
|
// from the plugin server.
|
|
//
|
|
// Deprecated: This is no longer used, but left for backwards compatibility
|
|
// since it is exported. It will be removed in the next major version.
|
|
ProviderPluginName = "provider"
|
|
)
|
|
|
|
// Handshake is the HandshakeConfig used to configure clients and servers.
|
|
//
|
|
// Deprecated: This is no longer used, but left for backwards compatibility
|
|
// since it is exported. It will be removed in the next major version.
|
|
var Handshake = plugin.HandshakeConfig{
|
|
// The magic cookie values should NEVER be changed.
|
|
MagicCookieKey: "TF_PLUGIN_MAGIC_COOKIE",
|
|
MagicCookieValue: "d602bf8f470bc67ca7faa0386276bbdd4330efaf76d1a219cb4d6991ca9872b2",
|
|
}
|
|
|
|
type ProviderFunc func() *schema.Provider
|
|
type GRPCProviderFunc func() tfprotov5.ProviderServer
|
|
type GRPCProviderV6Func func() tfprotov6.ProviderServer
|
|
|
|
// ServeOpts are the configurations to serve a plugin.
|
|
type ServeOpts struct {
|
|
ProviderFunc ProviderFunc
|
|
|
|
// Wrapped versions of the above plugins will automatically shimmed and
|
|
// added to the GRPC functions when possible.
|
|
GRPCProviderFunc GRPCProviderFunc
|
|
|
|
GRPCProviderV6Func GRPCProviderV6Func
|
|
|
|
// Logger is the logger that go-plugin will use.
|
|
Logger hclog.Logger
|
|
|
|
// Debug starts a debug server and controls its lifecycle, printing the
|
|
// information needed for Terraform to connect to the provider to stdout.
|
|
// os.Interrupt will be captured and used to stop the server.
|
|
//
|
|
// This option cannot be combined with TestConfig.
|
|
Debug bool
|
|
|
|
// TestConfig should only be set when the provider is being tested; it
|
|
// will opt out of go-plugin's lifecycle management and other features,
|
|
// and will use the supplied configuration options to control the
|
|
// plugin's lifecycle and communicate connection information. See the
|
|
// go-plugin GoDoc for more information.
|
|
//
|
|
// This option cannot be combined with Debug.
|
|
TestConfig *plugin.ServeTestConfig
|
|
|
|
// Set NoLogOutputOverride to not override the log output with an hclog
|
|
// adapter. This should only be used when running the plugin in
|
|
// acceptance tests.
|
|
NoLogOutputOverride bool
|
|
|
|
// UseTFLogSink is the testing.T for a test function that will turn on
|
|
// the terraform-plugin-log logging sink.
|
|
UseTFLogSink testing.T
|
|
|
|
// ProviderAddr is the address of the provider under test, like
|
|
// registry.terraform.io/hashicorp/random.
|
|
ProviderAddr string
|
|
}
|
|
|
|
// Serve serves a plugin. This function never returns and should be the final
|
|
// function called in the main function of the plugin.
|
|
func Serve(opts *ServeOpts) {
|
|
if opts.Debug && opts.TestConfig != nil {
|
|
log.Printf("[ERROR] Error starting provider: cannot set both Debug and TestConfig")
|
|
return
|
|
}
|
|
|
|
if !opts.NoLogOutputOverride {
|
|
// In order to allow go-plugin to correctly pass log-levels through to
|
|
// terraform, we need to use an hclog.Logger with JSON output. We can
|
|
// inject this into the std `log` package here, so existing providers will
|
|
// make use of it automatically.
|
|
logger := hclog.New(&hclog.LoggerOptions{
|
|
// We send all output to terraform. Go-plugin will take the output and
|
|
// pass it through another hclog.Logger on the client side where it can
|
|
// be filtered.
|
|
Level: hclog.Trace,
|
|
JSONFormat: true,
|
|
})
|
|
log.SetOutput(logger.StandardWriter(&hclog.StandardLoggerOptions{InferLevels: true}))
|
|
}
|
|
|
|
if opts.ProviderAddr == "" {
|
|
opts.ProviderAddr = "provider"
|
|
}
|
|
|
|
var err error
|
|
|
|
switch {
|
|
case opts.ProviderFunc != nil && opts.GRPCProviderFunc == nil:
|
|
opts.GRPCProviderFunc = func() tfprotov5.ProviderServer {
|
|
return schema.NewGRPCProviderServer(opts.ProviderFunc())
|
|
}
|
|
err = tf5serverServe(opts)
|
|
case opts.GRPCProviderFunc != nil:
|
|
err = tf5serverServe(opts)
|
|
case opts.GRPCProviderV6Func != nil:
|
|
err = tf6serverServe(opts)
|
|
default:
|
|
err = errors.New("no provider server defined in ServeOpts")
|
|
}
|
|
|
|
if err != nil {
|
|
log.Printf("[ERROR] Error starting provider: %s", err)
|
|
}
|
|
}
|
|
|
|
func tf5serverServe(opts *ServeOpts) error {
|
|
var tf5serveOpts []tf5server.ServeOpt
|
|
|
|
if opts.Debug {
|
|
tf5serveOpts = append(tf5serveOpts, tf5server.WithManagedDebug())
|
|
}
|
|
|
|
if opts.Logger != nil {
|
|
tf5serveOpts = append(tf5serveOpts, tf5server.WithGoPluginLogger(opts.Logger))
|
|
}
|
|
|
|
if opts.TestConfig != nil {
|
|
// Convert send-only channels to bi-directional channels to appease
|
|
// the compiler. WithDebug is errantly defined to require
|
|
// bi-directional when send-only is actually needed, which may be
|
|
// fixed in the future so the opts.TestConfig channels can be passed
|
|
// through directly.
|
|
closeCh := make(chan struct{})
|
|
reattachConfigCh := make(chan *plugin.ReattachConfig)
|
|
|
|
go func() {
|
|
// Always forward close channel receive, since its signaling that
|
|
// the channel is closed.
|
|
val := <-closeCh
|
|
opts.TestConfig.CloseCh <- val
|
|
}()
|
|
|
|
go func() {
|
|
val, ok := <-reattachConfigCh
|
|
|
|
if ok {
|
|
opts.TestConfig.ReattachConfigCh <- val
|
|
}
|
|
}()
|
|
|
|
tf5serveOpts = append(tf5serveOpts, tf5server.WithDebug(
|
|
opts.TestConfig.Context,
|
|
reattachConfigCh,
|
|
closeCh),
|
|
)
|
|
}
|
|
|
|
if opts.UseTFLogSink != nil {
|
|
tf5serveOpts = append(tf5serveOpts, tf5server.WithLoggingSink(opts.UseTFLogSink))
|
|
}
|
|
|
|
return tf5server.Serve(opts.ProviderAddr, opts.GRPCProviderFunc, tf5serveOpts...)
|
|
}
|
|
|
|
func tf6serverServe(opts *ServeOpts) error {
|
|
var tf6serveOpts []tf6server.ServeOpt
|
|
|
|
if opts.Debug {
|
|
tf6serveOpts = append(tf6serveOpts, tf6server.WithManagedDebug())
|
|
}
|
|
|
|
if opts.Logger != nil {
|
|
tf6serveOpts = append(tf6serveOpts, tf6server.WithGoPluginLogger(opts.Logger))
|
|
}
|
|
|
|
if opts.TestConfig != nil {
|
|
// Convert send-only channels to bi-directional channels to appease
|
|
// the compiler. WithDebug is errantly defined to require
|
|
// bi-directional when send-only is actually needed, which may be
|
|
// fixed in the future so the opts.TestConfig channels can be passed
|
|
// through directly.
|
|
closeCh := make(chan struct{})
|
|
reattachConfigCh := make(chan *plugin.ReattachConfig)
|
|
|
|
go func() {
|
|
// Always forward close channel receive, since its signaling that
|
|
// the channel is closed.
|
|
val := <-closeCh
|
|
opts.TestConfig.CloseCh <- val
|
|
}()
|
|
|
|
go func() {
|
|
val, ok := <-reattachConfigCh
|
|
|
|
if ok {
|
|
opts.TestConfig.ReattachConfigCh <- val
|
|
}
|
|
}()
|
|
|
|
tf6serveOpts = append(tf6serveOpts, tf6server.WithDebug(
|
|
opts.TestConfig.Context,
|
|
reattachConfigCh,
|
|
closeCh),
|
|
)
|
|
}
|
|
|
|
if opts.UseTFLogSink != nil {
|
|
tf6serveOpts = append(tf6serveOpts, tf6server.WithLoggingSink(opts.UseTFLogSink))
|
|
}
|
|
|
|
return tf6server.Serve(opts.ProviderAddr, opts.GRPCProviderV6Func, tf6serveOpts...)
|
|
}
|