Skip to content

Commit

Permalink
Fix fallback logic for Terraform v0.11 compatibility
Browse files Browse the repository at this point in the history
  • Loading branch information
minamijoyo committed May 9, 2019
1 parent 3c5f7a2 commit b862f39
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 62 deletions.
1 change: 1 addition & 0 deletions go.mod
Expand Up @@ -5,6 +5,7 @@ go 1.12
require (
github.com/aws/aws-sdk-go v1.19.19 // indirect
github.com/goreleaser/goreleaser v0.106.0
github.com/hashicorp/go-hclog v0.0.0-20181001195459-61d530d6c27f
github.com/hashicorp/go-plugin v0.0.0-20190322172744-52e1c4730856
github.com/hashicorp/logutils v1.0.0
github.com/hashicorp/terraform v0.12.0-beta2
Expand Down
74 changes: 12 additions & 62 deletions tfschema/client.go
Expand Up @@ -9,8 +9,6 @@ import (
"runtime"
"strings"

plugin "github.com/hashicorp/go-plugin"
tfplugin "github.com/hashicorp/terraform/plugin"
"github.com/hashicorp/terraform/plugin/discovery"
"github.com/mitchellh/go-homedir"
)
Expand Down Expand Up @@ -41,71 +39,23 @@ type Client interface {

// NewClient creates a new Client instance.
func NewClient(providerName string) (Client, error) {
// find a provider plugin
pluginMeta, err := findPlugin("provider", providerName)
if err != nil {
return nil, err
}

// create a plugin client config
config := newClientConfig(pluginMeta)

// initialize a plugin client.
pluginClient := plugin.NewClient(config)
client, err := pluginClient.Client()
if err != nil {
return nil, fmt.Errorf("Failed to initialize plugin: %s", err)
// First, try to connect by GRPC protocol (version 5)
client, err := NewGRPCClient(providerName)
if err == nil {
return client, nil
}

// create a new resource provider.
raw, err := client.Dispense(tfplugin.ProviderPluginName)
// If failed, try to connect by NetRPC protocol (version 4)
// plugin.ClientConfig.AllowedProtocols has a protocol negotiation feature,
// but it doesn't seems to work with old providers.
// We guess it is for Terraform v0.11 to connect to with the latest provider.
// So we implement our own fallback logic here.
client, err = NewNetRPCClient(providerName)
if err != nil {
return nil, fmt.Errorf("Failed to dispense plugin: %s", err)
}

switch provider := raw.(type) {
// For Terraform v0.11
case *tfplugin.ResourceProvider:
return &NetRPCClient{
provider: provider,
pluginClient: pluginClient,
}, nil

// For Terraform v0.12+
case *tfplugin.GRPCProvider:
// To clean up the plugin process, we need to explicitly store references.
provider.PluginClient = pluginClient

return &GRPCClient{
provider: provider,
}, nil

default:
return nil, fmt.Errorf("Failed to type cast. unknown provider type: %+v", raw)
}
}

// newClientConfig returns a plugin client config.
func newClientConfig(pluginMeta *discovery.PluginMeta) *plugin.ClientConfig {
// create a default plugin client config.
config := tfplugin.ClientConfig(*pluginMeta)
// override config to dual protocols suport
config.AllowedProtocols = []plugin.Protocol{
plugin.ProtocolNetRPC,
plugin.ProtocolGRPC,
}
config.VersionedPlugins = map[int]plugin.PluginSet{
4: {
"provider": &tfplugin.ResourceProviderPlugin{},
"provisioner": &tfplugin.ResourceProvisionerPlugin{},
},
5: {
"provider": &tfplugin.GRPCProviderPlugin{},
"provisioner": &tfplugin.GRPCProvisionerPlugin{},
},
return nil, fmt.Errorf("Failed to NewClient: %s", err)
}

return config
return client, nil
}

// findPlugin finds a plugin with the name specified in the arguments.
Expand Down
47 changes: 47 additions & 0 deletions tfschema/grpc_client.go
Expand Up @@ -4,6 +4,9 @@ import (
"fmt"
"sort"

plugin "github.com/hashicorp/go-plugin"
tfplugin "github.com/hashicorp/terraform/plugin"
"github.com/hashicorp/terraform/plugin/discovery"
"github.com/hashicorp/terraform/providers"
)

Expand All @@ -14,6 +17,50 @@ type GRPCClient struct {
provider providers.Interface
}

// NewGRPCClient creates a new GRPCClient instance.
func NewGRPCClient(providerName string) (Client, error) {
// find a provider plugin
pluginMeta, err := findPlugin("provider", providerName)
if err != nil {
return nil, err
}

// create a plugin client config
config := newGRPCClientConfig(pluginMeta)

// initialize a plugin client.
pluginClient := plugin.NewClient(config)
client, err := pluginClient.Client()
if err != nil {
return nil, fmt.Errorf("Failed to initialize GRPC plugin: %s", err)
}

// create a new GRPCProvider.
raw, err := client.Dispense(tfplugin.ProviderPluginName)
if err != nil {
return nil, fmt.Errorf("Failed to dispense GRPC plugin: %s", err)
}

switch provider := raw.(type) {
// For Terraform v0.12+
case *tfplugin.GRPCProvider:
// To clean up the plugin process, we need to explicitly store references.
provider.PluginClient = pluginClient

return &GRPCClient{
provider: provider,
}, nil

default:
return nil, fmt.Errorf("Failed to type cast GRPC plugin: %+v", raw)
}
}

// newGRPCClientConfig returns a default plugin client config for Terraform v0.12+.
func newGRPCClientConfig(pluginMeta *discovery.PluginMeta) *plugin.ClientConfig {
return tfplugin.ClientConfig(*pluginMeta)
}

// getSchema is a helper function to get a schema from provider.
func (c *GRPCClient) getSchema() (providers.GetSchemaResponse, error) {
res := c.provider.GetSchema()
Expand Down
68 changes: 68 additions & 0 deletions tfschema/netrpc_client.go
Expand Up @@ -2,9 +2,15 @@ package tfschema

import (
"fmt"
"os"
"os/exec"
"reflect"
"sort"

"github.com/hashicorp/go-hclog"
plugin "github.com/hashicorp/go-plugin"
tfplugin "github.com/hashicorp/terraform/plugin"
"github.com/hashicorp/terraform/plugin/discovery"
"github.com/hashicorp/terraform/terraform"
)

Expand All @@ -21,6 +27,68 @@ type NetRPCClient struct {
pluginClient interface{}
}

// NewNetRPCClient creates a new NetRPCClient instance.
func NewNetRPCClient(providerName string) (Client, error) {
// find a provider plugin
pluginMeta, err := findPlugin("provider", providerName)
if err != nil {
return nil, err
}

// create a plugin client config
config := newNetRPCClientConfig(pluginMeta)

// initialize a plugin client.
pluginClient := plugin.NewClient(config)
client, err := pluginClient.Client()
if err != nil {
return nil, fmt.Errorf("Failed to initialize NetRPC plugin: %s", err)
}

// create a new ResourceProvider.
raw, err := client.Dispense(tfplugin.ProviderPluginName)
if err != nil {
return nil, fmt.Errorf("Failed to dispense NetRPC plugin: %s", err)
}

switch provider := raw.(type) {
// For Terraform v0.11
case *tfplugin.ResourceProvider:
return &NetRPCClient{
provider: provider,
pluginClient: pluginClient,
}, nil

default:
return nil, fmt.Errorf("Failed to type cast NetRPC plugin r: %+v", raw)
}
}

// newNetRPCClientConfig returns a default plugin client config for Terraform v0.11.
func newNetRPCClientConfig(pluginMeta *discovery.PluginMeta) *plugin.ClientConfig {
// Note that we depends on Terraform v0.12 library
// and cannot simply refer the v0.11 default config.
// So, we need to reproduce the v0.11 config manually.
logger := hclog.New(&hclog.LoggerOptions{
Name: "plugin",
Level: hclog.Trace,
Output: os.Stderr,
})

pluginMap := map[string]plugin.Plugin{
"provider": &tfplugin.ResourceProviderPlugin{},
"provisioner": &tfplugin.ResourceProvisionerPlugin{},
}

return &plugin.ClientConfig{
Cmd: exec.Command(pluginMeta.Path),
HandshakeConfig: tfplugin.Handshake,
Managed: true,
Plugins: pluginMap,
Logger: logger,
}
}

// GetProviderSchema returns a type definiton of provider schema.
func (c *NetRPCClient) GetProviderSchema() (*Block, error) {
req := &terraform.ProviderSchemaRequest{
Expand Down

0 comments on commit b862f39

Please sign in to comment.