terraform: use ProtocolVersion from unmanaged providers' reattachConfig to chose the correct PluginClient (#28190)

* add/use ProtocolVersion with unmanaged providers reattach config
This commit is contained in:
Kristin Laemmert 2021-05-18 10:59:14 -04:00 committed by GitHub
parent bd4f8d7dea
commit 4928e1dd01
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 215 additions and 29 deletions

View File

@ -13,9 +13,12 @@ import (
"github.com/hashicorp/go-plugin"
"github.com/hashicorp/terraform/internal/e2e"
"github.com/hashicorp/terraform/internal/grpcwrap"
tfplugin "github.com/hashicorp/terraform/internal/plugin"
simple "github.com/hashicorp/terraform/internal/provider-simple"
proto "github.com/hashicorp/terraform/internal/tfplugin5"
tfplugin5 "github.com/hashicorp/terraform/internal/plugin"
tfplugin "github.com/hashicorp/terraform/internal/plugin6"
simple5 "github.com/hashicorp/terraform/internal/provider-simple"
simple "github.com/hashicorp/terraform/internal/provider-simple-v6"
proto5 "github.com/hashicorp/terraform/internal/tfplugin5"
proto "github.com/hashicorp/terraform/internal/tfplugin6"
)
// The tests in this file are for the "unmanaged provider workflow", which
@ -29,10 +32,11 @@ import (
// checked for correctness of the operations themselves.
type reattachConfig struct {
Protocol string
Pid int
Test bool
Addr reattachConfigAddr
Protocol string
ProtocolVersion int
Pid int
Test bool
Addr reattachConfigAddr
}
type reattachConfigAddr struct {
@ -89,6 +93,55 @@ func (p *providerServer) ResetApplyResourceChangeCalled() {
p.applyResourceChangeCalled = false
}
type providerServer5 struct {
sync.Mutex
proto5.ProviderServer
planResourceChangeCalled bool
applyResourceChangeCalled bool
}
func (p *providerServer5) PlanResourceChange(ctx context.Context, req *proto5.PlanResourceChange_Request) (*proto5.PlanResourceChange_Response, error) {
p.Lock()
defer p.Unlock()
p.planResourceChangeCalled = true
return p.ProviderServer.PlanResourceChange(ctx, req)
}
func (p *providerServer5) ApplyResourceChange(ctx context.Context, req *proto5.ApplyResourceChange_Request) (*proto5.ApplyResourceChange_Response, error) {
p.Lock()
defer p.Unlock()
p.applyResourceChangeCalled = true
return p.ProviderServer.ApplyResourceChange(ctx, req)
}
func (p *providerServer5) PlanResourceChangeCalled() bool {
p.Lock()
defer p.Unlock()
return p.planResourceChangeCalled
}
func (p *providerServer5) ResetPlanResourceChangeCalled() {
p.Lock()
defer p.Unlock()
p.planResourceChangeCalled = false
}
func (p *providerServer5) ApplyResourceChangeCalled() bool {
p.Lock()
defer p.Unlock()
return p.applyResourceChangeCalled
}
func (p *providerServer5) ResetApplyResourceChangeCalled() {
p.Lock()
defer p.Unlock()
p.applyResourceChangeCalled = false
}
func TestUnmanagedSeparatePlan(t *testing.T) {
t.Parallel()
@ -99,7 +152,7 @@ func TestUnmanagedSeparatePlan(t *testing.T) {
reattachCh := make(chan *plugin.ReattachConfig)
closeCh := make(chan struct{})
provider := &providerServer{
ProviderServer: grpcwrap.Provider(simple.Provider()),
ProviderServer: grpcwrap.Provider6(simple.Provider()),
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@ -116,7 +169,7 @@ func TestUnmanagedSeparatePlan(t *testing.T) {
},
GRPCServer: plugin.DefaultGRPCServer,
VersionedPlugins: map[int]plugin.PluginSet{
5: plugin.PluginSet{
6: {
"provider": &tfplugin.GRPCProviderPlugin{
GRPCProvider: func() proto.ProviderServer {
return provider
@ -130,10 +183,117 @@ func TestUnmanagedSeparatePlan(t *testing.T) {
t.Fatalf("no reattach config received")
}
reattachStr, err := json.Marshal(map[string]reattachConfig{
"hashicorp/test": reattachConfig{
Protocol: string(config.Protocol),
Pid: config.Pid,
Test: true,
"hashicorp/test": {
Protocol: string(config.Protocol),
ProtocolVersion: 6,
Pid: config.Pid,
Test: true,
Addr: reattachConfigAddr{
Network: config.Addr.Network(),
String: config.Addr.String(),
},
},
})
if err != nil {
t.Fatal(err)
}
tf.AddEnv("TF_REATTACH_PROVIDERS=" + string(reattachStr))
//// INIT
stdout, stderr, err := tf.Run("init")
if err != nil {
t.Fatalf("unexpected init error: %s\nstderr:\n%s", err, stderr)
}
// Make sure we didn't download the binary
if strings.Contains(stdout, "Installing hashicorp/test v") {
t.Errorf("test provider download message is present in init output:\n%s", stdout)
}
if tf.FileExists(filepath.Join(".terraform", "plugins", "registry.terraform.io", "hashicorp", "test")) {
t.Errorf("test provider binary found in .terraform dir")
}
//// PLAN
_, stderr, err = tf.Run("plan", "-out=tfplan")
if err != nil {
t.Fatalf("unexpected plan error: %s\nstderr:\n%s", err, stderr)
}
if !provider.PlanResourceChangeCalled() {
t.Error("PlanResourceChange not called on un-managed provider")
}
//// APPLY
_, stderr, err = tf.Run("apply", "tfplan")
if err != nil {
t.Fatalf("unexpected apply error: %s\nstderr:\n%s", err, stderr)
}
if !provider.ApplyResourceChangeCalled() {
t.Error("ApplyResourceChange not called on un-managed provider")
}
provider.ResetApplyResourceChangeCalled()
//// DESTROY
_, stderr, err = tf.Run("destroy", "-auto-approve")
if err != nil {
t.Fatalf("unexpected destroy error: %s\nstderr:\n%s", err, stderr)
}
if !provider.ApplyResourceChangeCalled() {
t.Error("ApplyResourceChange (destroy) not called on in-process provider")
}
cancel()
<-closeCh
}
func TestUnmanagedSeparatePlan_proto5(t *testing.T) {
t.Parallel()
fixturePath := filepath.Join("testdata", "test-provider")
tf := e2e.NewBinary(terraformBin, fixturePath)
defer tf.Close()
reattachCh := make(chan *plugin.ReattachConfig)
closeCh := make(chan struct{})
provider := &providerServer5{
ProviderServer: grpcwrap.Provider(simple5.Provider()),
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go plugin.Serve(&plugin.ServeConfig{
Logger: hclog.New(&hclog.LoggerOptions{
Name: "plugintest",
Level: hclog.Trace,
Output: ioutil.Discard,
}),
Test: &plugin.ServeTestConfig{
Context: ctx,
ReattachConfigCh: reattachCh,
CloseCh: closeCh,
},
GRPCServer: plugin.DefaultGRPCServer,
VersionedPlugins: map[int]plugin.PluginSet{
5: {
"provider": &tfplugin5.GRPCProviderPlugin{
GRPCProvider: func() proto5.ProviderServer {
return provider
},
},
},
},
})
config := <-reattachCh
if config == nil {
t.Fatalf("no reattach config received")
}
reattachStr, err := json.Marshal(map[string]reattachConfig{
"hashicorp/test": {
Protocol: string(config.Protocol),
ProtocolVersion: 5,
Pid: config.Pid,
Test: true,
Addr: reattachConfigAddr{
Network: config.Addr.Network(),
String: config.Addr.String(),
@ -145,7 +305,6 @@ func TestUnmanagedSeparatePlan(t *testing.T) {
}
tf.AddEnv("TF_REATTACH_PROVIDERS=" + string(reattachStr))
tf.AddEnv("PLUGIN_PROTOCOL_VERSION=5")
//// INIT
stdout, stderr, err := tf.Run("init")

View File

@ -1,6 +1,7 @@
package command
import (
"errors"
"fmt"
"log"
"os"
@ -401,7 +402,6 @@ func devOverrideProviderFactory(provider addrs.Provider, localDir getproviders.P
// running, and implements providers.Interface against it.
func unmanagedProviderFactory(provider addrs.Provider, reattach *plugin.ReattachConfig) providers.Factory {
return func() (providers.Interface, error) {
config := &plugin.ClientConfig{
HandshakeConfig: tfplugin.Handshake,
Logger: logging.NewProviderLogger("unmanaged."),
@ -411,12 +411,20 @@ func unmanagedProviderFactory(provider addrs.Provider, reattach *plugin.Reattach
SyncStdout: logging.PluginOutputMonitor(fmt.Sprintf("%s:stdout", provider)),
SyncStderr: logging.PluginOutputMonitor(fmt.Sprintf("%s:stderr", provider)),
}
// TODO: we probably shouldn't hardcode the protocol version
// here, but it'll do for now, because only one protocol
// version is supported. Eventually, we'll probably want to
// sneak it into the JSON ReattachConfigs.
if plugins, ok := tfplugin.VersionedPlugins[5]; !ok {
return nil, fmt.Errorf("no supported plugins for protocol 5")
if reattach.ProtocolVersion == 0 {
// As of the 0.15 release, sdk.v2 doesn't include the protocol
// version in the ReattachConfig (only recently added to
// go-plugin), so client.NegotiatedVersion() always returns 0. We
// assume that an unmanaged provider reporting protocol version 0 is
// actually using proto v5 for backwards compatibility.
if defaultPlugins, ok := tfplugin.VersionedPlugins[5]; ok {
config.Plugins = defaultPlugins
} else {
return nil, errors.New("no supported plugins for protocol 0")
}
} else if plugins, ok := tfplugin.VersionedPlugins[reattach.ProtocolVersion]; !ok {
return nil, fmt.Errorf("no supported plugins for protocol %d", reattach.ProtocolVersion)
} else {
config.Plugins = plugins
}
@ -432,8 +440,25 @@ func unmanagedProviderFactory(provider addrs.Provider, reattach *plugin.Reattach
return nil, err
}
p := raw.(*tfplugin.GRPCProvider)
return p, nil
// store the client so that the plugin can kill the child process
protoVer := client.NegotiatedVersion()
switch protoVer {
case 0, 5:
// As of the 0.15 release, sdk.v2 doesn't include the protocol
// version in the ReattachConfig (only recently added to
// go-plugin), so client.NegotiatedVersion() always returns 0. We
// assume that an unmanaged provider reporting protocol version 0 is
// actually using proto v5 for backwards compatibility.
p := raw.(*tfplugin.GRPCProvider)
p.PluginClient = client
return p, nil
case 6:
p := raw.(*tfplugin6.GRPCProvider)
p.PluginClient = client
return p, nil
default:
return nil, fmt.Errorf("unsupported protocol version %d", protoVer)
}
}
}

14
main.go
View File

@ -450,8 +450,9 @@ func parseReattachProviders(in string) (map[addrs.Provider]*plugin.ReattachConfi
unmanagedProviders := map[addrs.Provider]*plugin.ReattachConfig{}
if in != "" {
type reattachConfig struct {
Protocol string
Addr struct {
Protocol string
ProtocolVersion int
Addr struct {
Network string
String string
}
@ -484,10 +485,11 @@ func parseReattachProviders(in string) (map[addrs.Provider]*plugin.ReattachConfi
return unmanagedProviders, fmt.Errorf("Unknown address type %q for %q", c.Addr.Network, p)
}
unmanagedProviders[a] = &plugin.ReattachConfig{
Protocol: plugin.Protocol(c.Protocol),
Pid: c.Pid,
Test: c.Test,
Addr: addr,
Protocol: plugin.Protocol(c.Protocol),
ProtocolVersion: c.ProtocolVersion,
Pid: c.Pid,
Test: c.Test,
Addr: addr,
}
}
}