Skip to content

Commit

Permalink
Merge pull request #2 from hashicorp/kmoe-helper-resource
Browse files Browse the repository at this point in the history
Allow plugin-go provider factories in acc tests
  • Loading branch information
kmoe authored Oct 30, 2020
2 parents 82e03dc + 7ff69a0 commit 9f0b119
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 32 deletions.
82 changes: 81 additions & 1 deletion helper/resource/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import (
testing "github.com/mitchellh/go-testing-interface"
)

func runProviderCommand(t testing.T, f func() error, wd *plugintest.WorkingDir, factories map[string]func() (*schema.Provider, error)) error {
func runProviderCommand(t testing.T, f func() error, wd *plugintest.WorkingDir, factories map[string]func() (*schema.Provider, error), v5factories map[string]func() (proto.ProviderServer, error)) error {
// don't point to this as a test failure location
// point to whatever called it
t.Helper()
Expand Down Expand Up @@ -121,6 +121,86 @@ func runProviderCommand(t testing.T, f func() error, wd *plugintest.WorkingDir,
}
}

// Now spin up gRPC servers for every plugin-go provider factory
// in the same way.
for providerName, factory := range v5factories {
// providerName may be returned as terraform-provider-foo, and
// we need just foo. So let's fix that.
providerName = strings.TrimPrefix(providerName, "terraform-provider-")

// If the user has supplied the same provider in both
// ProviderFactories and ProtoV5ProviderFactories, they made a
// mistake and we should exit early.
for _, ns := range namespaces {
reattachString := strings.TrimSuffix(host, "/") + "/" +
strings.TrimSuffix(ns, "/") + "/" +
providerName
if _, ok := reattachInfo[reattachString]; ok {
return fmt.Errorf("Provider %s registered in both TestCase.ProviderFactories and TestCase.ProtoV5ProviderFactories: please use one or the other, or supply a muxed provider to TestCase.ProtoV5ProviderFactories.", providerName)
}
}

provider, err := factory()
if err != nil {
return fmt.Errorf("unable to create provider %q from factory: %w", providerName, err)
}

// keep track of the running factory, so we can make sure it's
// shut down.
wg.Add(1)

// configure the settings our plugin will be served with
// the GRPCProviderFunc wraps a non-gRPC provider server
// into a gRPC interface, and the logger just discards logs
// from go-plugin.
opts := &plugin.ServeOpts{
GRPCProviderFunc: func() proto.ProviderServer {
return provider
},
Logger: hclog.New(&hclog.LoggerOptions{
Name: "plugintest",
Level: hclog.Trace,
Output: ioutil.Discard,
}),
}

// let's actually start the provider server
config, closeCh, err := plugin.DebugServe(ctx, opts)
if err != nil {
return fmt.Errorf("unable to serve provider %q: %w", providerName, err)
}
tfexecConfig := tfexec.ReattachConfig{
Protocol: config.Protocol,
Pid: config.Pid,
Test: config.Test,
Addr: tfexec.ReattachConfigAddr{
Network: config.Addr.Network,
String: config.Addr.String,
},
}

// plugin.DebugServe hijacks our log output location, so let's
// reset it
logging.SetOutput(t)

// when the provider exits, remove one from the waitgroup
// so we can track when everything is done
go func(c <-chan struct{}) {
<-c
wg.Done()
}(closeCh)

// set our provider's reattachinfo in our map, once
// for every namespace that different Terraform versions
// may expect.
for _, ns := range namespaces {
reattachString := strings.TrimSuffix(host, "/") + "/" +
strings.TrimSuffix(ns, "/") + "/" +
providerName
reattachInfo[reattachString] = tfexecConfig
}
}

// set the working directory reattach info that will tell Terraform how to
// connect to our various running servers.
wd.SetReattachInfo(reattachInfo)
Expand Down
6 changes: 6 additions & 0 deletions helper/resource/testing.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/hashicorp/go-multierror"
testing "github.com/mitchellh/go-testing-interface"

proto "github.com/hashicorp/terraform-plugin-go/tfprotov5"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/logging"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/internal/addrs"
Expand Down Expand Up @@ -296,6 +297,11 @@ type TestCase struct {
// }
ProviderFactories map[string]func() (*schema.Provider, error)

// ProtoV5ProviderFactories serves the same purpose as ProviderFactories,
// but for protocol v5 providers defined using the terraform-plugin-go
// ProviderServer interface.
ProtoV5ProviderFactories map[string]func() (proto.ProviderServer, error)

// Providers is the ResourceProvider that will be under test.
//
// Deprecated: Providers is deprecated, please use ProviderFactories
Expand Down
13 changes: 7 additions & 6 deletions helper/resource/testing_new.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,18 @@ import (
tfjson "github.com/hashicorp/terraform-json"
testing "github.com/mitchellh/go-testing-interface"

proto "github.com/hashicorp/terraform-plugin-go/tfprotov5"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/internal/plugintest"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
)

func runPostTestDestroy(t testing.T, c TestCase, wd *plugintest.WorkingDir, factories map[string]func() (*schema.Provider, error), statePreDestroy *terraform.State) error {
func runPostTestDestroy(t testing.T, c TestCase, wd *plugintest.WorkingDir, factories map[string]func() (*schema.Provider, error), v5factories map[string]func() (proto.ProviderServer, error), statePreDestroy *terraform.State) error {
t.Helper()

err := runProviderCommand(t, func() error {
return wd.Destroy()
}, wd, factories)
}, wd, factories, v5factories)
if err != nil {
return err
}
Expand Down Expand Up @@ -50,14 +51,14 @@ func runNewTest(t testing.T, c TestCase, helper *plugintest.Helper) {
return err
}
return nil
}, wd, c.ProviderFactories)
}, wd, c.ProviderFactories, c.ProtoV5ProviderFactories)
if err != nil {
t.Fatalf("Error retrieving state, there may be dangling resources: %s", err.Error())
return
}

if !stateIsEmpty(statePreDestroy) {
err := runPostTestDestroy(t, c, wd, c.ProviderFactories, statePreDestroy)
err := runPostTestDestroy(t, c, wd, c.ProviderFactories, c.ProtoV5ProviderFactories, statePreDestroy)
if err != nil {
t.Fatalf("Error running post-test destroy, there may be dangling resources: %s", err.Error())
}
Expand All @@ -77,7 +78,7 @@ func runNewTest(t testing.T, c TestCase, helper *plugintest.Helper) {
}
err = runProviderCommand(t, func() error {
return wd.Init()
}, wd, c.ProviderFactories)
}, wd, c.ProviderFactories, c.ProtoV5ProviderFactories)
if err != nil {
t.Fatalf("Error running init: %s", err.Error())
return
Expand Down Expand Up @@ -211,7 +212,7 @@ func testIDRefresh(c TestCase, t testing.T, wd *plugintest.WorkingDir, step Test
return err
}
return nil
}, wd, c.ProviderFactories)
}, wd, c.ProviderFactories, c.ProtoV5ProviderFactories)
if err != nil {
return err
}
Expand Down
28 changes: 14 additions & 14 deletions helper/resource/testing_new_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func testStepNewConfig(t testing.T, c TestCase, wd *plugintest.WorkingDir, step
return err
}
return nil
}, wd, c.ProviderFactories)
}, wd, c.ProviderFactories, c.ProtoV5ProviderFactories)
if err != nil {
return err
}
Expand All @@ -43,7 +43,7 @@ func testStepNewConfig(t testing.T, c TestCase, wd *plugintest.WorkingDir, step
// failing to do this will result in data sources not being updated
err = runProviderCommand(t, func() error {
return wd.Refresh()
}, wd, c.ProviderFactories)
}, wd, c.ProviderFactories, c.ProtoV5ProviderFactories)
if err != nil {
return fmt.Errorf("Error running pre-apply refresh: %w", err)
}
Expand All @@ -58,7 +58,7 @@ func testStepNewConfig(t testing.T, c TestCase, wd *plugintest.WorkingDir, step
return wd.CreateDestroyPlan()
}
return wd.CreatePlan()
}, wd, c.ProviderFactories)
}, wd, c.ProviderFactories, c.ProtoV5ProviderFactories)
if err != nil {
return fmt.Errorf("Error running pre-apply plan: %w", err)
}
Expand All @@ -73,15 +73,15 @@ func testStepNewConfig(t testing.T, c TestCase, wd *plugintest.WorkingDir, step
return err
}
return nil
}, wd, c.ProviderFactories)
}, wd, c.ProviderFactories, c.ProtoV5ProviderFactories)
if err != nil {
return fmt.Errorf("Error retrieving pre-apply state: %w", err)
}

// Apply the diff, creating real resources
err = runProviderCommand(t, func() error {
return wd.Apply()
}, wd, c.ProviderFactories)
}, wd, c.ProviderFactories, c.ProtoV5ProviderFactories)
if err != nil {
if step.Destroy {
return fmt.Errorf("Error running destroy: %w", err)
Expand All @@ -97,7 +97,7 @@ func testStepNewConfig(t testing.T, c TestCase, wd *plugintest.WorkingDir, step
return err
}
return nil
}, wd, c.ProviderFactories)
}, wd, c.ProviderFactories, c.ProtoV5ProviderFactories)
if err != nil {
return fmt.Errorf("Error retrieving state after apply: %w", err)
}
Expand Down Expand Up @@ -125,7 +125,7 @@ func testStepNewConfig(t testing.T, c TestCase, wd *plugintest.WorkingDir, step
return wd.CreateDestroyPlan()
}
return wd.CreatePlan()
}, wd, c.ProviderFactories)
}, wd, c.ProviderFactories, c.ProtoV5ProviderFactories)
if err != nil {
return fmt.Errorf("Error running post-apply plan: %w", err)
}
Expand All @@ -135,7 +135,7 @@ func testStepNewConfig(t testing.T, c TestCase, wd *plugintest.WorkingDir, step
var err error
plan, err = wd.SavedPlan()
return err
}, wd, c.ProviderFactories)
}, wd, c.ProviderFactories, c.ProtoV5ProviderFactories)
if err != nil {
return fmt.Errorf("Error retrieving post-apply plan: %w", err)
}
Expand All @@ -146,7 +146,7 @@ func testStepNewConfig(t testing.T, c TestCase, wd *plugintest.WorkingDir, step
var err error
stdout, err = wd.SavedPlanRawStdout()
return err
}, wd, c.ProviderFactories)
}, wd, c.ProviderFactories, c.ProtoV5ProviderFactories)
if err != nil {
return fmt.Errorf("Error retrieving formatted plan output: %w", err)
}
Expand All @@ -157,7 +157,7 @@ func testStepNewConfig(t testing.T, c TestCase, wd *plugintest.WorkingDir, step
if !step.Destroy || (step.Destroy && !step.PreventPostDestroyRefresh) {
err := runProviderCommand(t, func() error {
return wd.Refresh()
}, wd, c.ProviderFactories)
}, wd, c.ProviderFactories, c.ProtoV5ProviderFactories)
if err != nil {
return fmt.Errorf("Error running post-apply refresh: %w", err)
}
Expand All @@ -169,7 +169,7 @@ func testStepNewConfig(t testing.T, c TestCase, wd *plugintest.WorkingDir, step
return wd.CreateDestroyPlan()
}
return wd.CreatePlan()
}, wd, c.ProviderFactories)
}, wd, c.ProviderFactories, c.ProtoV5ProviderFactories)
if err != nil {
return fmt.Errorf("Error running second post-apply plan: %w", err)
}
Expand All @@ -178,7 +178,7 @@ func testStepNewConfig(t testing.T, c TestCase, wd *plugintest.WorkingDir, step
var err error
plan, err = wd.SavedPlan()
return err
}, wd, c.ProviderFactories)
}, wd, c.ProviderFactories, c.ProtoV5ProviderFactories)
if err != nil {
return fmt.Errorf("Error retrieving second post-apply plan: %w", err)
}
Expand All @@ -190,7 +190,7 @@ func testStepNewConfig(t testing.T, c TestCase, wd *plugintest.WorkingDir, step
var err error
stdout, err = wd.SavedPlanRawStdout()
return err
}, wd, c.ProviderFactories)
}, wd, c.ProviderFactories, c.ProtoV5ProviderFactories)
if err != nil {
return fmt.Errorf("Error retrieving formatted second plan output: %w", err)
}
Expand All @@ -209,7 +209,7 @@ func testStepNewConfig(t testing.T, c TestCase, wd *plugintest.WorkingDir, step
return err
}
return nil
}, wd, c.ProviderFactories)
}, wd, c.ProviderFactories, c.ProtoV5ProviderFactories)
if err != nil {
return err
}
Expand Down
8 changes: 4 additions & 4 deletions helper/resource/testing_new_import_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func testStepNewImportState(t testing.T, c TestCase, helper *plugintest.Helper,
return err
}
return nil
}, wd, c.ProviderFactories)
}, wd, c.ProviderFactories, c.ProtoV5ProviderFactories)
if err != nil {
t.Fatalf("Error getting state: %s", err)
}
Expand Down Expand Up @@ -71,14 +71,14 @@ func testStepNewImportState(t testing.T, c TestCase, helper *plugintest.Helper,

err = runProviderCommand(t, func() error {
return importWd.Init()
}, importWd, c.ProviderFactories)
}, importWd, c.ProviderFactories, c.ProtoV5ProviderFactories)
if err != nil {
t.Fatalf("Error running init: %s", err)
}

err = runProviderCommand(t, func() error {
return importWd.Import(step.ResourceName, importId)
}, importWd, c.ProviderFactories)
}, importWd, c.ProviderFactories, c.ProtoV5ProviderFactories)
if err != nil {
return err
}
Expand All @@ -90,7 +90,7 @@ func testStepNewImportState(t testing.T, c TestCase, helper *plugintest.Helper,
return err
}
return nil
}, importWd, c.ProviderFactories)
}, importWd, c.ProviderFactories, c.ProtoV5ProviderFactories)
if err != nil {
t.Fatalf("Error getting state: %s", err)
}
Expand Down
9 changes: 2 additions & 7 deletions plugin/serve.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package plugin

import (
"context"

hclog "github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-plugin"
"google.golang.org/grpc"
Expand Down Expand Up @@ -63,18 +61,15 @@ func Serve(opts *ServeOpts) {
HandshakeConfig: Handshake,
VersionedPlugins: map[int]plugin.PluginSet{
5: {
ProviderPluginName: &tfprotov5server.GRPCProviderPlugin{
ProviderPluginName: &tf5server.GRPCProviderPlugin{
GRPCProvider: func() proto.ProviderServer {
return provider
},
},
},
},
GRPCServer: func(opts []grpc.ServerOption) *grpc.Server {
return grpc.NewServer(append(opts, grpc.UnaryInterceptor(func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
ctx = provider.(*schema.GRPCProviderServer).StopContext(ctx)
return handler(ctx, req)
}))...)
return grpc.NewServer(opts...)
},
Logger: opts.Logger,
Test: opts.TestConfig,
Expand Down

0 comments on commit 9f0b119

Please sign in to comment.