Add support for plugin protocol v6 (#27826)

* Add support for plugin protocol v6

This PR turns on support for plugin protocol v6. A provider can
advertise itself as supporting protocol version 6 and terraform will
use the correct client.

Todo:

The "unmanaged" providers functionality does not support protocol
version, so at the moment terraform will continue to assume that
"unmanaged" providers are on protocol v5. This will require some
upstream work on go-plugin (I believe).

I would like to convert the builtin providers to use protocol v6 in a
future PR; however it is not necessary until we remove protocol v6.

* add e2e test for using both plugin protocol versions

- copied grpcwrap and made a version that returns protocol v6 provider
- copied the test provider, provider-simple, and made a version that's
  using protocol v6 with the above fun
- added an e2etest
This commit is contained in:
Kristin Laemmert 2021-02-22 10:22:45 -05:00 committed by GitHub
parent 1de8216a9e
commit 7b7273e3aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 1690 additions and 28 deletions

View File

@ -0,0 +1,70 @@
package e2etest
import (
"os"
"path/filepath"
"strings"
"testing"
"github.com/hashicorp/terraform/e2e"
"github.com/hashicorp/terraform/internal/getproviders"
)
// TestProviderProtocols verifies that Terraform can execute provider plugins
// with both supported protocol versions.
func TestProviderProtocols(t *testing.T) {
t.Parallel()
tf := e2e.NewBinary(terraformBin, "testdata/provider-plugin")
defer tf.Close()
// In order to do a decent end-to-end test for this case we will need a real
// enough provider plugin to try to run and make sure we are able to
// actually run it. Here will build the simple and simple6 (built with
// protocol v6) providers.
simple6Provider := filepath.Join(tf.WorkDir(), "terraform-provider-simple6")
simple6ProviderExe := e2e.GoBuild("github.com/hashicorp/terraform/internal/provider-simple-v6/main", simple6Provider)
simpleProvider := filepath.Join(tf.WorkDir(), "terraform-provider-simple")
simpleProviderExe := e2e.GoBuild("github.com/hashicorp/terraform/internal/provider-simple/main", simpleProvider)
// Move the provider binaries into a directory that we will point terraform
// to using the -plugin-dir cli flag.
platform := getproviders.CurrentPlatform.String()
hashiDir := "cache/registry.terraform.io/hashicorp/"
if err := os.MkdirAll(tf.Path(hashiDir, "simple6/0.0.1/", platform), os.ModePerm); err != nil {
t.Fatal(err)
}
if err := os.Rename(simple6ProviderExe, tf.Path(hashiDir, "simple6/0.0.1/", platform, "terraform-provider-simple6")); err != nil {
t.Fatal(err)
}
if err := os.MkdirAll(tf.Path(hashiDir, "simple/0.0.1/", platform), os.ModePerm); err != nil {
t.Fatal(err)
}
if err := os.Rename(simpleProviderExe, tf.Path(hashiDir, "simple/0.0.1/", platform, "terraform-provider-simple")); err != nil {
t.Fatal(err)
}
//// INIT
_, stderr, err := tf.Run("init", "-plugin-dir=cache")
if err != nil {
t.Fatalf("unexpected init error: %s\nstderr:\n%s", err, stderr)
}
//// PLAN
_, stderr, err = tf.Run("plan", "-out=tfplan")
if err != nil {
t.Fatalf("unexpected plan error: %s\nstderr:\n%s", err, stderr)
}
//// APPLY
stdout, stderr, err := tf.Run("apply", "tfplan")
if err != nil {
t.Fatalf("unexpected apply error: %s\nstderr:\n%s", err, stderr)
}
if !strings.Contains(stdout, "Apply complete! Resources: 2 added, 0 changed, 0 destroyed.") {
t.Fatalf("wrong output:\n%s", stdout)
}
}

View File

@ -9,7 +9,7 @@ import (
"github.com/hashicorp/terraform/e2e"
)
// TestProviderDevOverrides is a test that terraform can execute a 3rd party
// TestProvisionerPlugin is a test that terraform can execute a 3rd party
// provisioner plugin.
func TestProvisionerPlugin(t *testing.T) {
t.Parallel()

View File

@ -0,0 +1,20 @@
// the provider-plugin tests uses the -plugin-cache flag so terraform pulls the
// test binaries instead of reaching out to the registry.
terraform {
required_providers {
simple5 = {
source = "registry.terraform.io/hashicorp/simple"
}
simple6 = {
source = "registry.terraform.io/hashicorp/simple6"
}
}
}
resource "simple_resource" "test-proto5" {
provider = simple5
}
resource "simple_resource" "test-proto6" {
provider = simple6
}

View File

@ -17,6 +17,7 @@ import (
"github.com/hashicorp/terraform/internal/logging"
"github.com/hashicorp/terraform/internal/providercache"
tfplugin "github.com/hashicorp/terraform/plugin"
tfplugin6 "github.com/hashicorp/terraform/plugin6"
"github.com/hashicorp/terraform/providers"
"github.com/hashicorp/terraform/tfdiags"
)
@ -360,10 +361,19 @@ func providerFactory(meta *providercache.CachedProvider) providers.Factory {
}
// store the client so that the plugin can kill the child process
p := raw.(*tfplugin.GRPCProvider)
p.PluginClient = client
return p, nil
protoVer := client.NegotiatedVersion()
switch protoVer {
case 5:
p := raw.(*tfplugin.GRPCProvider)
p.PluginClient = client
return p, nil
case 6:
p := raw.(*tfplugin6.GRPCProvider)
p.PluginClient = client
return p, nil
default:
panic("unsupported protocol version")
}
}
}

View File

@ -0,0 +1,415 @@
package grpcwrap
import (
"context"
"github.com/hashicorp/terraform/internal/tfplugin6"
"github.com/hashicorp/terraform/plugin6/convert"
"github.com/hashicorp/terraform/providers"
"github.com/zclconf/go-cty/cty"
ctyjson "github.com/zclconf/go-cty/cty/json"
"github.com/zclconf/go-cty/cty/msgpack"
)
// New wraps a providers.Interface to implement a grpc ProviderServer using
// plugin protocol v6. This is useful for creating a test binary out of an
// internal provider implementation.
func Provider6(p providers.Interface) tfplugin6.ProviderServer {
return &provider6{
provider: p,
schema: p.GetProviderSchema(),
}
}
type provider6 struct {
provider providers.Interface
schema providers.GetProviderSchemaResponse
}
func (p *provider6) GetProviderSchema(_ context.Context, req *tfplugin6.GetProviderSchema_Request) (*tfplugin6.GetProviderSchema_Response, error) {
resp := &tfplugin6.GetProviderSchema_Response{
ResourceSchemas: make(map[string]*tfplugin6.Schema),
DataSourceSchemas: make(map[string]*tfplugin6.Schema),
}
resp.Provider = &tfplugin6.Schema{
Block: &tfplugin6.Schema_Block{},
}
if p.schema.Provider.Block != nil {
resp.Provider.Block = convert.ConfigSchemaToProto(p.schema.Provider.Block)
}
resp.ProviderMeta = &tfplugin6.Schema{
Block: &tfplugin6.Schema_Block{},
}
if p.schema.ProviderMeta.Block != nil {
resp.ProviderMeta.Block = convert.ConfigSchemaToProto(p.schema.ProviderMeta.Block)
}
for typ, res := range p.schema.ResourceTypes {
resp.ResourceSchemas[typ] = &tfplugin6.Schema{
Version: res.Version,
Block: convert.ConfigSchemaToProto(res.Block),
}
}
for typ, dat := range p.schema.DataSources {
resp.DataSourceSchemas[typ] = &tfplugin6.Schema{
Version: dat.Version,
Block: convert.ConfigSchemaToProto(dat.Block),
}
}
// include any diagnostics from the original GetSchema call
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, p.schema.Diagnostics)
return resp, nil
}
func (p *provider6) ValidateProviderConfig(_ context.Context, req *tfplugin6.ValidateProviderConfig_Request) (*tfplugin6.ValidateProviderConfig_Response, error) {
resp := &tfplugin6.ValidateProviderConfig_Response{}
ty := p.schema.Provider.Block.ImpliedType()
configVal, err := decodeDynamicValue6(req.Config, ty)
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
prepareResp := p.provider.ValidateProviderConfig(providers.ValidateProviderConfigRequest{
Config: configVal,
})
// the PreparedConfig value is no longer used
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, prepareResp.Diagnostics)
return resp, nil
}
func (p *provider6) ValidateResourceConfig(_ context.Context, req *tfplugin6.ValidateResourceConfig_Request) (*tfplugin6.ValidateResourceConfig_Response, error) {
resp := &tfplugin6.ValidateResourceConfig_Response{}
ty := p.schema.ResourceTypes[req.TypeName].Block.ImpliedType()
configVal, err := decodeDynamicValue6(req.Config, ty)
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
validateResp := p.provider.ValidateResourceConfig(providers.ValidateResourceConfigRequest{
TypeName: req.TypeName,
Config: configVal,
})
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, validateResp.Diagnostics)
return resp, nil
}
func (p *provider6) ValidateDataSourceConfig(_ context.Context, req *tfplugin6.ValidateDataSourceConfig_Request) (*tfplugin6.ValidateDataSourceConfig_Response, error) {
resp := &tfplugin6.ValidateDataSourceConfig_Response{}
ty := p.schema.DataSources[req.TypeName].Block.ImpliedType()
configVal, err := decodeDynamicValue6(req.Config, ty)
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
validateResp := p.provider.ValidateDataSourceConfig(providers.ValidateDataSourceConfigRequest{
TypeName: req.TypeName,
Config: configVal,
})
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, validateResp.Diagnostics)
return resp, nil
}
func (p *provider6) UpgradeResourceState(_ context.Context, req *tfplugin6.UpgradeResourceState_Request) (*tfplugin6.UpgradeResourceState_Response, error) {
resp := &tfplugin6.UpgradeResourceState_Response{}
ty := p.schema.ResourceTypes[req.TypeName].Block.ImpliedType()
upgradeResp := p.provider.UpgradeResourceState(providers.UpgradeResourceStateRequest{
TypeName: req.TypeName,
Version: req.Version,
RawStateJSON: req.RawState.Json,
})
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, upgradeResp.Diagnostics)
if upgradeResp.Diagnostics.HasErrors() {
return resp, nil
}
dv, err := encodeDynamicValue6(upgradeResp.UpgradedState, ty)
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
resp.UpgradedState = dv
return resp, nil
}
func (p *provider6) ConfigureProvider(_ context.Context, req *tfplugin6.ConfigureProvider_Request) (*tfplugin6.ConfigureProvider_Response, error) {
resp := &tfplugin6.ConfigureProvider_Response{}
ty := p.schema.Provider.Block.ImpliedType()
configVal, err := decodeDynamicValue6(req.Config, ty)
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
configureResp := p.provider.ConfigureProvider(providers.ConfigureProviderRequest{
TerraformVersion: req.TerraformVersion,
Config: configVal,
})
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, configureResp.Diagnostics)
return resp, nil
}
func (p *provider6) ReadResource(_ context.Context, req *tfplugin6.ReadResource_Request) (*tfplugin6.ReadResource_Response, error) {
resp := &tfplugin6.ReadResource_Response{}
ty := p.schema.ResourceTypes[req.TypeName].Block.ImpliedType()
stateVal, err := decodeDynamicValue6(req.CurrentState, ty)
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
metaTy := p.schema.ProviderMeta.Block.ImpliedType()
metaVal, err := decodeDynamicValue6(req.ProviderMeta, metaTy)
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
readResp := p.provider.ReadResource(providers.ReadResourceRequest{
TypeName: req.TypeName,
PriorState: stateVal,
Private: req.Private,
ProviderMeta: metaVal,
})
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, readResp.Diagnostics)
if readResp.Diagnostics.HasErrors() {
return resp, nil
}
resp.Private = readResp.Private
dv, err := encodeDynamicValue6(readResp.NewState, ty)
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
resp.NewState = dv
return resp, nil
}
func (p *provider6) PlanResourceChange(_ context.Context, req *tfplugin6.PlanResourceChange_Request) (*tfplugin6.PlanResourceChange_Response, error) {
resp := &tfplugin6.PlanResourceChange_Response{}
ty := p.schema.ResourceTypes[req.TypeName].Block.ImpliedType()
priorStateVal, err := decodeDynamicValue6(req.PriorState, ty)
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
proposedStateVal, err := decodeDynamicValue6(req.ProposedNewState, ty)
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
configVal, err := decodeDynamicValue6(req.Config, ty)
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
metaTy := p.schema.ProviderMeta.Block.ImpliedType()
metaVal, err := decodeDynamicValue6(req.ProviderMeta, metaTy)
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
planResp := p.provider.PlanResourceChange(providers.PlanResourceChangeRequest{
TypeName: req.TypeName,
PriorState: priorStateVal,
ProposedNewState: proposedStateVal,
Config: configVal,
PriorPrivate: req.PriorPrivate,
ProviderMeta: metaVal,
})
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, planResp.Diagnostics)
if planResp.Diagnostics.HasErrors() {
return resp, nil
}
resp.PlannedPrivate = planResp.PlannedPrivate
resp.PlannedState, err = encodeDynamicValue6(planResp.PlannedState, ty)
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
for _, path := range planResp.RequiresReplace {
resp.RequiresReplace = append(resp.RequiresReplace, convert.PathToAttributePath(path))
}
return resp, nil
}
func (p *provider6) ApplyResourceChange(_ context.Context, req *tfplugin6.ApplyResourceChange_Request) (*tfplugin6.ApplyResourceChange_Response, error) {
resp := &tfplugin6.ApplyResourceChange_Response{}
ty := p.schema.ResourceTypes[req.TypeName].Block.ImpliedType()
priorStateVal, err := decodeDynamicValue6(req.PriorState, ty)
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
plannedStateVal, err := decodeDynamicValue6(req.PlannedState, ty)
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
configVal, err := decodeDynamicValue6(req.Config, ty)
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
metaTy := p.schema.ProviderMeta.Block.ImpliedType()
metaVal, err := decodeDynamicValue6(req.ProviderMeta, metaTy)
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
applyResp := p.provider.ApplyResourceChange(providers.ApplyResourceChangeRequest{
TypeName: req.TypeName,
PriorState: priorStateVal,
PlannedState: plannedStateVal,
Config: configVal,
PlannedPrivate: req.PlannedPrivate,
ProviderMeta: metaVal,
})
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, applyResp.Diagnostics)
if applyResp.Diagnostics.HasErrors() {
return resp, nil
}
resp.Private = applyResp.Private
resp.NewState, err = encodeDynamicValue6(applyResp.NewState, ty)
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
return resp, nil
}
func (p *provider6) ImportResourceState(_ context.Context, req *tfplugin6.ImportResourceState_Request) (*tfplugin6.ImportResourceState_Response, error) {
resp := &tfplugin6.ImportResourceState_Response{}
importResp := p.provider.ImportResourceState(providers.ImportResourceStateRequest{
TypeName: req.TypeName,
ID: req.Id,
})
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, importResp.Diagnostics)
for _, res := range importResp.ImportedResources {
ty := p.schema.ResourceTypes[res.TypeName].Block.ImpliedType()
state, err := encodeDynamicValue6(res.State, ty)
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
continue
}
resp.ImportedResources = append(resp.ImportedResources, &tfplugin6.ImportResourceState_ImportedResource{
TypeName: res.TypeName,
State: state,
Private: res.Private,
})
}
return resp, nil
}
func (p *provider6) ReadDataSource(_ context.Context, req *tfplugin6.ReadDataSource_Request) (*tfplugin6.ReadDataSource_Response, error) {
resp := &tfplugin6.ReadDataSource_Response{}
ty := p.schema.DataSources[req.TypeName].Block.ImpliedType()
configVal, err := decodeDynamicValue6(req.Config, ty)
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
metaTy := p.schema.ProviderMeta.Block.ImpliedType()
metaVal, err := decodeDynamicValue6(req.ProviderMeta, metaTy)
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
readResp := p.provider.ReadDataSource(providers.ReadDataSourceRequest{
TypeName: req.TypeName,
Config: configVal,
ProviderMeta: metaVal,
})
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, readResp.Diagnostics)
if readResp.Diagnostics.HasErrors() {
return resp, nil
}
resp.State, err = encodeDynamicValue6(readResp.State, ty)
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
return resp, nil
}
func (p *provider6) StopProvider(context.Context, *tfplugin6.StopProvider_Request) (*tfplugin6.StopProvider_Response, error) {
resp := &tfplugin6.StopProvider_Response{}
err := p.provider.Stop()
if err != nil {
resp.Error = err.Error()
}
return resp, nil
}
// decode a DynamicValue from either the JSON or MsgPack encoding.
func decodeDynamicValue6(v *tfplugin6.DynamicValue, ty cty.Type) (cty.Value, error) {
// always return a valid value
var err error
res := cty.NullVal(ty)
if v == nil {
return res, nil
}
switch {
case len(v.Msgpack) > 0:
res, err = msgpack.Unmarshal(v.Msgpack, ty)
case len(v.Json) > 0:
res, err = ctyjson.Unmarshal(v.Json, ty)
}
return res, err
}
// encode a cty.Value into a DynamicValue msgpack payload.
func encodeDynamicValue6(v cty.Value, ty cty.Type) (*tfplugin6.DynamicValue, error) {
mp, err := msgpack.Marshal(v, ty)
return &tfplugin6.DynamicValue{
Msgpack: mp,
}, err
}

View File

@ -13,7 +13,7 @@ import (
"github.com/hashicorp/terraform/provisioners"
)
// New wraps a providers.Interface to implement a grpc ProviderServer.
// New wraps a provisioners.Interface to implement a grpc ProviderServer.
// This is useful for creating a test binary out of an internal provider
// implementation.
func Provisioner(p provisioners.Interface) tfplugin5.ProvisionerServer {

View File

@ -0,0 +1,16 @@
package main
import (
"github.com/hashicorp/terraform/internal/grpcwrap"
simple "github.com/hashicorp/terraform/internal/provider-simple-v6"
"github.com/hashicorp/terraform/internal/tfplugin6"
plugin "github.com/hashicorp/terraform/plugin6"
)
func main() {
plugin.Serve(&plugin.ServeOpts{
GRPCProviderFunc: func() tfplugin6.ProviderServer {
return grpcwrap.Provider6(simple.Provider())
},
})
}

View File

@ -0,0 +1,128 @@
// simple provider a minimal provider implementation for testing
package simple
import (
"errors"
"time"
"github.com/hashicorp/terraform/configs/configschema"
"github.com/hashicorp/terraform/providers"
"github.com/zclconf/go-cty/cty"
ctyjson "github.com/zclconf/go-cty/cty/json"
)
type simple struct {
schema providers.GetProviderSchemaResponse
}
func Provider() providers.Interface {
simpleResource := providers.Schema{
Block: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"id": {
Computed: true,
Type: cty.String,
},
"value": {
Optional: true,
Type: cty.String,
},
},
},
}
return simple{
schema: providers.GetProviderSchemaResponse{
Provider: providers.Schema{
Block: nil,
},
ResourceTypes: map[string]providers.Schema{
"simple_resource": simpleResource,
},
DataSources: map[string]providers.Schema{
"simple_resource": simpleResource,
},
},
}
}
func (s simple) GetProviderSchema() providers.GetProviderSchemaResponse {
return s.schema
}
func (s simple) ValidateProviderConfig(req providers.ValidateProviderConfigRequest) (resp providers.ValidateProviderConfigResponse) {
return resp
}
func (s simple) ValidateResourceConfig(req providers.ValidateResourceConfigRequest) (resp providers.ValidateResourceConfigResponse) {
return resp
}
func (s simple) ValidateDataSourceConfig(req providers.ValidateDataSourceConfigRequest) (resp providers.ValidateDataSourceConfigResponse) {
return resp
}
func (p simple) UpgradeResourceState(req providers.UpgradeResourceStateRequest) (resp providers.UpgradeResourceStateResponse) {
ty := p.schema.ResourceTypes[req.TypeName].Block.ImpliedType()
val, err := ctyjson.Unmarshal(req.RawStateJSON, ty)
resp.Diagnostics = resp.Diagnostics.Append(err)
resp.UpgradedState = val
return resp
}
func (s simple) ConfigureProvider(providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) {
return resp
}
func (s simple) Stop() error {
return nil
}
func (s simple) ReadResource(req providers.ReadResourceRequest) (resp providers.ReadResourceResponse) {
// just return the same state we received
resp.NewState = req.PriorState
return resp
}
func (s simple) PlanResourceChange(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
m := req.ProposedNewState.AsValueMap()
_, ok := m["id"]
if !ok {
m["id"] = cty.UnknownVal(cty.String)
}
resp.PlannedState = cty.ObjectVal(m)
return resp
}
func (s simple) ApplyResourceChange(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) {
if req.PlannedState.IsNull() {
resp.NewState = req.PlannedState
return resp
}
m := req.PlannedState.AsValueMap()
_, ok := m["id"]
if !ok {
m["id"] = cty.StringVal(time.Now().String())
}
resp.NewState = cty.ObjectVal(m)
return resp
}
func (s simple) ImportResourceState(providers.ImportResourceStateRequest) (resp providers.ImportResourceStateResponse) {
resp.Diagnostics = resp.Diagnostics.Append(errors.New("unsupported"))
return resp
}
func (s simple) ReadDataSource(req providers.ReadDataSourceRequest) (resp providers.ReadDataSourceResponse) {
m := req.Config.AsValueMap()
m["id"] = cty.StringVal("static_id")
resp.State = cty.ObjectVal(m)
return resp
}
func (s simple) Close() error {
return nil
}

View File

@ -2,11 +2,18 @@ package plugin
import (
"github.com/hashicorp/go-plugin"
"github.com/hashicorp/terraform/plugin6"
)
// VersionedPlugins includes both protocol 5 and 6 because this is the function
// called in providerFactory (command/meta_providers.go) to set up the initial
// plugin client config.
var VersionedPlugins = map[int]plugin.PluginSet{
5: {
"provider": &GRPCProviderPlugin{},
"provisioner": &GRPCProvisionerPlugin{},
},
6: {
"provider": &plugin6.GRPCProviderPlugin{},
},
}

View File

@ -39,7 +39,7 @@ func (p *GRPCProviderPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Serve
// GRPCProvider handles the client, or core side of the plugin rpc connection.
// The GRPCProvider methods are mostly a translation layer between the
// terraform provioders types and the grpc proto types, directly converting
// terraform providers types and the grpc proto types, directly converting
// between the two.
type GRPCProvider struct {
// PluginClient provides a reference to the plugin.Client which controls the plugin process.
@ -72,10 +72,10 @@ func New(client proto6.ProviderClient, ctx context.Context) GRPCProvider {
// getSchema is used internally to get the saved provider schema. The schema
// should have already been fetched from the provider, but we have to
// synchronize access to avoid being called concurrently with GetSchema.
// synchronize access to avoid being called concurrently with GetProviderSchema.
func (p *GRPCProvider) getSchema() providers.GetProviderSchemaResponse {
p.mu.Lock()
// unlock inline in case GetSchema needs to be called
// unlock inline in case GetProviderSchema needs to be called
if p.schemas.Provider.Block != nil {
p.mu.Unlock()
return p.schemas
@ -85,7 +85,7 @@ func (p *GRPCProvider) getSchema() providers.GetProviderSchemaResponse {
// the schema should have been fetched already, but give it another shot
// just in case things are being called out of order. This may happen for
// tests.
schemas := p.GetSchema()
schemas := p.GetProviderSchema()
if schemas.Diagnostics.HasErrors() {
panic(schemas.Diagnostics.Err())
}
@ -122,8 +122,8 @@ func (p *GRPCProvider) getProviderMetaSchema() providers.Schema {
return schema.ProviderMeta
}
func (p *GRPCProvider) GetSchema() (resp providers.GetProviderSchemaResponse) {
logger.Trace("GRPCProvider.v6: GetSchema")
func (p *GRPCProvider) GetProviderSchema() (resp providers.GetProviderSchemaResponse) {
logger.Trace("GRPCProvider.v6: GetProviderSchema")
p.mu.Lock()
defer p.mu.Unlock()
@ -286,8 +286,8 @@ func (p *GRPCProvider) UpgradeResourceState(r providers.UpgradeResourceStateRequ
return resp
}
func (p *GRPCProvider) Configure(r providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) {
logger.Trace("GRPCProvider.v6: Configure")
func (p *GRPCProvider) ConfigureProvider(r providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) {
logger.Trace("GRPCProvider.v6: ConfigureProvider")
schema := p.getSchema()

View File

@ -0,0 +1,722 @@
package plugin6
import (
"bytes"
"fmt"
"testing"
"github.com/golang/mock/gomock"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/hashicorp/terraform/configs/hcl2shim"
"github.com/hashicorp/terraform/providers"
"github.com/hashicorp/terraform/tfdiags"
"github.com/zclconf/go-cty/cty"
proto "github.com/hashicorp/terraform/internal/tfplugin6"
mockproto "github.com/hashicorp/terraform/plugin6/mock_proto"
)
var _ providers.Interface = (*GRPCProvider)(nil)
var (
equateEmpty = cmpopts.EquateEmpty()
typeComparer = cmp.Comparer(cty.Type.Equals)
valueComparer = cmp.Comparer(cty.Value.RawEquals)
)
func mockProviderClient(t *testing.T) *mockproto.MockProviderClient {
ctrl := gomock.NewController(t)
client := mockproto.NewMockProviderClient(ctrl)
// we always need a GetSchema method
client.EXPECT().GetProviderSchema(
gomock.Any(),
gomock.Any(),
gomock.Any(),
).Return(providerProtoSchema(), nil)
return client
}
func checkDiags(t *testing.T, d tfdiags.Diagnostics) {
t.Helper()
if d.HasErrors() {
t.Fatal(d.Err())
}
}
func providerProtoSchema() *proto.GetProviderSchema_Response {
return &proto.GetProviderSchema_Response{
Provider: &proto.Schema{
Block: &proto.Schema_Block{
Attributes: []*proto.Schema_Attribute{
{
Name: "attr",
Type: []byte(`"string"`),
Required: true,
},
},
},
},
ResourceSchemas: map[string]*proto.Schema{
"resource": {
Version: 1,
Block: &proto.Schema_Block{
Attributes: []*proto.Schema_Attribute{
{
Name: "attr",
Type: []byte(`"string"`),
Required: true,
},
},
},
},
},
DataSourceSchemas: map[string]*proto.Schema{
"data": {
Version: 1,
Block: &proto.Schema_Block{
Attributes: []*proto.Schema_Attribute{
{
Name: "attr",
Type: []byte(`"string"`),
Required: true,
},
},
},
},
},
}
}
func TestGRPCProvider_GetSchema(t *testing.T) {
p := &GRPCProvider{
client: mockProviderClient(t),
}
resp := p.GetProviderSchema()
checkDiags(t, resp.Diagnostics)
}
func TestGRPCProvider_PrepareProviderConfig(t *testing.T) {
client := mockProviderClient(t)
p := &GRPCProvider{
client: client,
}
client.EXPECT().ValidateProviderConfig(
gomock.Any(),
gomock.Any(),
).Return(&proto.ValidateProviderConfig_Response{}, nil)
cfg := hcl2shim.HCL2ValueFromConfigValue(map[string]interface{}{"attr": "value"})
resp := p.ValidateProviderConfig(providers.ValidateProviderConfigRequest{Config: cfg})
checkDiags(t, resp.Diagnostics)
}
func TestGRPCProvider_ValidateResourceConfig(t *testing.T) {
client := mockProviderClient(t)
p := &GRPCProvider{
client: client,
}
client.EXPECT().ValidateResourceConfig(
gomock.Any(),
gomock.Any(),
).Return(&proto.ValidateResourceConfig_Response{}, nil)
cfg := hcl2shim.HCL2ValueFromConfigValue(map[string]interface{}{"attr": "value"})
resp := p.ValidateResourceConfig(providers.ValidateResourceConfigRequest{
TypeName: "resource",
Config: cfg,
})
checkDiags(t, resp.Diagnostics)
}
func TestGRPCProvider_ValidateDataSourceConfig(t *testing.T) {
client := mockProviderClient(t)
p := &GRPCProvider{
client: client,
}
client.EXPECT().ValidateDataSourceConfig(
gomock.Any(),
gomock.Any(),
).Return(&proto.ValidateDataSourceConfig_Response{}, nil)
cfg := hcl2shim.HCL2ValueFromConfigValue(map[string]interface{}{"attr": "value"})
resp := p.ValidateDataSourceConfig(providers.ValidateDataSourceConfigRequest{
TypeName: "data",
Config: cfg,
})
checkDiags(t, resp.Diagnostics)
}
func TestGRPCProvider_UpgradeResourceState(t *testing.T) {
client := mockProviderClient(t)
p := &GRPCProvider{
client: client,
}
client.EXPECT().UpgradeResourceState(
gomock.Any(),
gomock.Any(),
).Return(&proto.UpgradeResourceState_Response{
UpgradedState: &proto.DynamicValue{
Msgpack: []byte("\x81\xa4attr\xa3bar"),
},
}, nil)
resp := p.UpgradeResourceState(providers.UpgradeResourceStateRequest{
TypeName: "resource",
Version: 0,
RawStateJSON: []byte(`{"old_attr":"bar"}`),
})
checkDiags(t, resp.Diagnostics)
expected := cty.ObjectVal(map[string]cty.Value{
"attr": cty.StringVal("bar"),
})
if !cmp.Equal(expected, resp.UpgradedState, typeComparer, valueComparer, equateEmpty) {
t.Fatal(cmp.Diff(expected, resp.UpgradedState, typeComparer, valueComparer, equateEmpty))
}
}
func TestGRPCProvider_UpgradeResourceStateJSON(t *testing.T) {
client := mockProviderClient(t)
p := &GRPCProvider{
client: client,
}
client.EXPECT().UpgradeResourceState(
gomock.Any(),
gomock.Any(),
).Return(&proto.UpgradeResourceState_Response{
UpgradedState: &proto.DynamicValue{
Json: []byte(`{"attr":"bar"}`),
},
}, nil)
resp := p.UpgradeResourceState(providers.UpgradeResourceStateRequest{
TypeName: "resource",
Version: 0,
RawStateJSON: []byte(`{"old_attr":"bar"}`),
})
checkDiags(t, resp.Diagnostics)
expected := cty.ObjectVal(map[string]cty.Value{
"attr": cty.StringVal("bar"),
})
if !cmp.Equal(expected, resp.UpgradedState, typeComparer, valueComparer, equateEmpty) {
t.Fatal(cmp.Diff(expected, resp.UpgradedState, typeComparer, valueComparer, equateEmpty))
}
}
func TestGRPCProvider_Configure(t *testing.T) {
client := mockProviderClient(t)
p := &GRPCProvider{
client: client,
}
client.EXPECT().ConfigureProvider(
gomock.Any(),
gomock.Any(),
).Return(&proto.ConfigureProvider_Response{}, nil)
resp := p.ConfigureProvider(providers.ConfigureProviderRequest{
Config: cty.ObjectVal(map[string]cty.Value{
"attr": cty.StringVal("foo"),
}),
})
checkDiags(t, resp.Diagnostics)
}
func TestGRPCProvider_Stop(t *testing.T) {
client := mockProviderClient(t)
p := &GRPCProvider{
client: client,
}
client.EXPECT().StopProvider(
gomock.Any(),
gomock.Any(),
).Return(&proto.StopProvider_Response{}, nil)
err := p.Stop()
if err != nil {
t.Fatal(err)
}
}
func TestGRPCProvider_ReadResource(t *testing.T) {
client := mockProviderClient(t)
p := &GRPCProvider{
client: client,
}
client.EXPECT().ReadResource(
gomock.Any(),
gomock.Any(),
).Return(&proto.ReadResource_Response{
NewState: &proto.DynamicValue{
Msgpack: []byte("\x81\xa4attr\xa3bar"),
},
}, nil)
resp := p.ReadResource(providers.ReadResourceRequest{
TypeName: "resource",
PriorState: cty.ObjectVal(map[string]cty.Value{
"attr": cty.StringVal("foo"),
}),
})
checkDiags(t, resp.Diagnostics)
expected := cty.ObjectVal(map[string]cty.Value{
"attr": cty.StringVal("bar"),
})
if !cmp.Equal(expected, resp.NewState, typeComparer, valueComparer, equateEmpty) {
t.Fatal(cmp.Diff(expected, resp.NewState, typeComparer, valueComparer, equateEmpty))
}
}
func TestGRPCProvider_ReadResourceJSON(t *testing.T) {
client := mockProviderClient(t)
p := &GRPCProvider{
client: client,
}
client.EXPECT().ReadResource(
gomock.Any(),
gomock.Any(),
).Return(&proto.ReadResource_Response{
NewState: &proto.DynamicValue{
Json: []byte(`{"attr":"bar"}`),
},
}, nil)
resp := p.ReadResource(providers.ReadResourceRequest{
TypeName: "resource",
PriorState: cty.ObjectVal(map[string]cty.Value{
"attr": cty.StringVal("foo"),
}),
})
checkDiags(t, resp.Diagnostics)
expected := cty.ObjectVal(map[string]cty.Value{
"attr": cty.StringVal("bar"),
})
if !cmp.Equal(expected, resp.NewState, typeComparer, valueComparer, equateEmpty) {
t.Fatal(cmp.Diff(expected, resp.NewState, typeComparer, valueComparer, equateEmpty))
}
}
func TestGRPCProvider_ReadEmptyJSON(t *testing.T) {
client := mockProviderClient(t)
p := &GRPCProvider{
client: client,
}
client.EXPECT().ReadResource(
gomock.Any(),
gomock.Any(),
).Return(&proto.ReadResource_Response{
NewState: &proto.DynamicValue{
Json: []byte(``),
},
}, nil)
obj := cty.ObjectVal(map[string]cty.Value{
"attr": cty.StringVal("foo"),
})
resp := p.ReadResource(providers.ReadResourceRequest{
TypeName: "resource",
PriorState: obj,
})
checkDiags(t, resp.Diagnostics)
expected := cty.NullVal(obj.Type())
if !cmp.Equal(expected, resp.NewState, typeComparer, valueComparer, equateEmpty) {
t.Fatal(cmp.Diff(expected, resp.NewState, typeComparer, valueComparer, equateEmpty))
}
}
func TestGRPCProvider_PlanResourceChange(t *testing.T) {
client := mockProviderClient(t)
p := &GRPCProvider{
client: client,
}
expectedPrivate := []byte(`{"meta": "data"}`)
client.EXPECT().PlanResourceChange(
gomock.Any(),
gomock.Any(),
).Return(&proto.PlanResourceChange_Response{
PlannedState: &proto.DynamicValue{
Msgpack: []byte("\x81\xa4attr\xa3bar"),
},
RequiresReplace: []*proto.AttributePath{
{
Steps: []*proto.AttributePath_Step{
{
Selector: &proto.AttributePath_Step_AttributeName{
AttributeName: "attr",
},
},
},
},
},
PlannedPrivate: expectedPrivate,
}, nil)
resp := p.PlanResourceChange(providers.PlanResourceChangeRequest{
TypeName: "resource",
PriorState: cty.ObjectVal(map[string]cty.Value{
"attr": cty.StringVal("foo"),
}),
ProposedNewState: cty.ObjectVal(map[string]cty.Value{
"attr": cty.StringVal("bar"),
}),
Config: cty.ObjectVal(map[string]cty.Value{
"attr": cty.StringVal("bar"),
}),
})
checkDiags(t, resp.Diagnostics)
expectedState := cty.ObjectVal(map[string]cty.Value{
"attr": cty.StringVal("bar"),
})
if !cmp.Equal(expectedState, resp.PlannedState, typeComparer, valueComparer, equateEmpty) {
t.Fatal(cmp.Diff(expectedState, resp.PlannedState, typeComparer, valueComparer, equateEmpty))
}
expectedReplace := `[]cty.Path{cty.Path{cty.GetAttrStep{Name:"attr"}}}`
replace := fmt.Sprintf("%#v", resp.RequiresReplace)
if expectedReplace != replace {
t.Fatalf("expected %q, got %q", expectedReplace, replace)
}
if !bytes.Equal(expectedPrivate, resp.PlannedPrivate) {
t.Fatalf("expected %q, got %q", expectedPrivate, resp.PlannedPrivate)
}
}
func TestGRPCProvider_PlanResourceChangeJSON(t *testing.T) {
client := mockProviderClient(t)
p := &GRPCProvider{
client: client,
}
expectedPrivate := []byte(`{"meta": "data"}`)
client.EXPECT().PlanResourceChange(
gomock.Any(),
gomock.Any(),
).Return(&proto.PlanResourceChange_Response{
PlannedState: &proto.DynamicValue{
Json: []byte(`{"attr":"bar"}`),
},
RequiresReplace: []*proto.AttributePath{
{
Steps: []*proto.AttributePath_Step{
{
Selector: &proto.AttributePath_Step_AttributeName{
AttributeName: "attr",
},
},
},
},
},
PlannedPrivate: expectedPrivate,
}, nil)
resp := p.PlanResourceChange(providers.PlanResourceChangeRequest{
TypeName: "resource",
PriorState: cty.ObjectVal(map[string]cty.Value{
"attr": cty.StringVal("foo"),
}),
ProposedNewState: cty.ObjectVal(map[string]cty.Value{
"attr": cty.StringVal("bar"),
}),
Config: cty.ObjectVal(map[string]cty.Value{
"attr": cty.StringVal("bar"),
}),
})
checkDiags(t, resp.Diagnostics)
expectedState := cty.ObjectVal(map[string]cty.Value{
"attr": cty.StringVal("bar"),
})
if !cmp.Equal(expectedState, resp.PlannedState, typeComparer, valueComparer, equateEmpty) {
t.Fatal(cmp.Diff(expectedState, resp.PlannedState, typeComparer, valueComparer, equateEmpty))
}
expectedReplace := `[]cty.Path{cty.Path{cty.GetAttrStep{Name:"attr"}}}`
replace := fmt.Sprintf("%#v", resp.RequiresReplace)
if expectedReplace != replace {
t.Fatalf("expected %q, got %q", expectedReplace, replace)
}
if !bytes.Equal(expectedPrivate, resp.PlannedPrivate) {
t.Fatalf("expected %q, got %q", expectedPrivate, resp.PlannedPrivate)
}
}
func TestGRPCProvider_ApplyResourceChange(t *testing.T) {
client := mockProviderClient(t)
p := &GRPCProvider{
client: client,
}
expectedPrivate := []byte(`{"meta": "data"}`)
client.EXPECT().ApplyResourceChange(
gomock.Any(),
gomock.Any(),
).Return(&proto.ApplyResourceChange_Response{
NewState: &proto.DynamicValue{
Msgpack: []byte("\x81\xa4attr\xa3bar"),
},
Private: expectedPrivate,
}, nil)
resp := p.ApplyResourceChange(providers.ApplyResourceChangeRequest{
TypeName: "resource",
PriorState: cty.ObjectVal(map[string]cty.Value{
"attr": cty.StringVal("foo"),
}),
PlannedState: cty.ObjectVal(map[string]cty.Value{
"attr": cty.StringVal("bar"),
}),
Config: cty.ObjectVal(map[string]cty.Value{
"attr": cty.StringVal("bar"),
}),
PlannedPrivate: expectedPrivate,
})
checkDiags(t, resp.Diagnostics)
expectedState := cty.ObjectVal(map[string]cty.Value{
"attr": cty.StringVal("bar"),
})
if !cmp.Equal(expectedState, resp.NewState, typeComparer, valueComparer, equateEmpty) {
t.Fatal(cmp.Diff(expectedState, resp.NewState, typeComparer, valueComparer, equateEmpty))
}
if !bytes.Equal(expectedPrivate, resp.Private) {
t.Fatalf("expected %q, got %q", expectedPrivate, resp.Private)
}
}
func TestGRPCProvider_ApplyResourceChangeJSON(t *testing.T) {
client := mockProviderClient(t)
p := &GRPCProvider{
client: client,
}
expectedPrivate := []byte(`{"meta": "data"}`)
client.EXPECT().ApplyResourceChange(
gomock.Any(),
gomock.Any(),
).Return(&proto.ApplyResourceChange_Response{
NewState: &proto.DynamicValue{
Json: []byte(`{"attr":"bar"}`),
},
Private: expectedPrivate,
}, nil)
resp := p.ApplyResourceChange(providers.ApplyResourceChangeRequest{
TypeName: "resource",
PriorState: cty.ObjectVal(map[string]cty.Value{
"attr": cty.StringVal("foo"),
}),
PlannedState: cty.ObjectVal(map[string]cty.Value{
"attr": cty.StringVal("bar"),
}),
Config: cty.ObjectVal(map[string]cty.Value{
"attr": cty.StringVal("bar"),
}),
PlannedPrivate: expectedPrivate,
})
checkDiags(t, resp.Diagnostics)
expectedState := cty.ObjectVal(map[string]cty.Value{
"attr": cty.StringVal("bar"),
})
if !cmp.Equal(expectedState, resp.NewState, typeComparer, valueComparer, equateEmpty) {
t.Fatal(cmp.Diff(expectedState, resp.NewState, typeComparer, valueComparer, equateEmpty))
}
if !bytes.Equal(expectedPrivate, resp.Private) {
t.Fatalf("expected %q, got %q", expectedPrivate, resp.Private)
}
}
func TestGRPCProvider_ImportResourceState(t *testing.T) {
client := mockProviderClient(t)
p := &GRPCProvider{
client: client,
}
expectedPrivate := []byte(`{"meta": "data"}`)
client.EXPECT().ImportResourceState(
gomock.Any(),
gomock.Any(),
).Return(&proto.ImportResourceState_Response{
ImportedResources: []*proto.ImportResourceState_ImportedResource{
{
TypeName: "resource",
State: &proto.DynamicValue{
Msgpack: []byte("\x81\xa4attr\xa3bar"),
},
Private: expectedPrivate,
},
},
}, nil)
resp := p.ImportResourceState(providers.ImportResourceStateRequest{
TypeName: "resource",
ID: "foo",
})
checkDiags(t, resp.Diagnostics)
expectedResource := providers.ImportedResource{
TypeName: "resource",
State: cty.ObjectVal(map[string]cty.Value{
"attr": cty.StringVal("bar"),
}),
Private: expectedPrivate,
}
imported := resp.ImportedResources[0]
if !cmp.Equal(expectedResource, imported, typeComparer, valueComparer, equateEmpty) {
t.Fatal(cmp.Diff(expectedResource, imported, typeComparer, valueComparer, equateEmpty))
}
}
func TestGRPCProvider_ImportResourceStateJSON(t *testing.T) {
client := mockProviderClient(t)
p := &GRPCProvider{
client: client,
}
expectedPrivate := []byte(`{"meta": "data"}`)
client.EXPECT().ImportResourceState(
gomock.Any(),
gomock.Any(),
).Return(&proto.ImportResourceState_Response{
ImportedResources: []*proto.ImportResourceState_ImportedResource{
{
TypeName: "resource",
State: &proto.DynamicValue{
Json: []byte(`{"attr":"bar"}`),
},
Private: expectedPrivate,
},
},
}, nil)
resp := p.ImportResourceState(providers.ImportResourceStateRequest{
TypeName: "resource",
ID: "foo",
})
checkDiags(t, resp.Diagnostics)
expectedResource := providers.ImportedResource{
TypeName: "resource",
State: cty.ObjectVal(map[string]cty.Value{
"attr": cty.StringVal("bar"),
}),
Private: expectedPrivate,
}
imported := resp.ImportedResources[0]
if !cmp.Equal(expectedResource, imported, typeComparer, valueComparer, equateEmpty) {
t.Fatal(cmp.Diff(expectedResource, imported, typeComparer, valueComparer, equateEmpty))
}
}
func TestGRPCProvider_ReadDataSource(t *testing.T) {
client := mockProviderClient(t)
p := &GRPCProvider{
client: client,
}
client.EXPECT().ReadDataSource(
gomock.Any(),
gomock.Any(),
).Return(&proto.ReadDataSource_Response{
State: &proto.DynamicValue{
Msgpack: []byte("\x81\xa4attr\xa3bar"),
},
}, nil)
resp := p.ReadDataSource(providers.ReadDataSourceRequest{
TypeName: "data",
Config: cty.ObjectVal(map[string]cty.Value{
"attr": cty.StringVal("foo"),
}),
})
checkDiags(t, resp.Diagnostics)
expected := cty.ObjectVal(map[string]cty.Value{
"attr": cty.StringVal("bar"),
})
if !cmp.Equal(expected, resp.State, typeComparer, valueComparer, equateEmpty) {
t.Fatal(cmp.Diff(expected, resp.State, typeComparer, valueComparer, equateEmpty))
}
}
func TestGRPCProvider_ReadDataSourceJSON(t *testing.T) {
client := mockProviderClient(t)
p := &GRPCProvider{
client: client,
}
client.EXPECT().ReadDataSource(
gomock.Any(),
gomock.Any(),
).Return(&proto.ReadDataSource_Response{
State: &proto.DynamicValue{
Json: []byte(`{"attr":"bar"}`),
},
}, nil)
resp := p.ReadDataSource(providers.ReadDataSourceRequest{
TypeName: "data",
Config: cty.ObjectVal(map[string]cty.Value{
"attr": cty.StringVal("foo"),
}),
})
checkDiags(t, resp.Diagnostics)
expected := cty.ObjectVal(map[string]cty.Value{
"attr": cty.StringVal("bar"),
})
if !cmp.Equal(expected, resp.State, typeComparer, valueComparer, equateEmpty) {
t.Fatal(cmp.Diff(expected, resp.State, typeComparer, valueComparer, equateEmpty))
}
}

View File

@ -0,0 +1,3 @@
//go:generate go run github.com/golang/mock/mockgen -destination mock.go github.com/hashicorp/terraform/internal/tfplugin6 ProviderClient
package mock_tfplugin6

276
plugin6/mock_proto/mock.go Normal file
View File

@ -0,0 +1,276 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/hashicorp/terraform/internal/tfplugin6 (interfaces: ProviderClient)
// Package mock_tfplugin6 is a generated GoMock package.
package mock_tfplugin6
import (
context "context"
gomock "github.com/golang/mock/gomock"
tfplugin6 "github.com/hashicorp/terraform/internal/tfplugin6"
grpc "google.golang.org/grpc"
reflect "reflect"
)
// MockProviderClient is a mock of ProviderClient interface
type MockProviderClient struct {
ctrl *gomock.Controller
recorder *MockProviderClientMockRecorder
}
// MockProviderClientMockRecorder is the mock recorder for MockProviderClient
type MockProviderClientMockRecorder struct {
mock *MockProviderClient
}
// NewMockProviderClient creates a new mock instance
func NewMockProviderClient(ctrl *gomock.Controller) *MockProviderClient {
mock := &MockProviderClient{ctrl: ctrl}
mock.recorder = &MockProviderClientMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockProviderClient) EXPECT() *MockProviderClientMockRecorder {
return m.recorder
}
// ApplyResourceChange mocks base method
func (m *MockProviderClient) ApplyResourceChange(arg0 context.Context, arg1 *tfplugin6.ApplyResourceChange_Request, arg2 ...grpc.CallOption) (*tfplugin6.ApplyResourceChange_Response, error) {
m.ctrl.T.Helper()
varargs := []interface{}{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "ApplyResourceChange", varargs...)
ret0, _ := ret[0].(*tfplugin6.ApplyResourceChange_Response)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ApplyResourceChange indicates an expected call of ApplyResourceChange
func (mr *MockProviderClientMockRecorder) ApplyResourceChange(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0, arg1}, arg2...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ApplyResourceChange", reflect.TypeOf((*MockProviderClient)(nil).ApplyResourceChange), varargs...)
}
// ConfigureProvider mocks base method
func (m *MockProviderClient) ConfigureProvider(arg0 context.Context, arg1 *tfplugin6.ConfigureProvider_Request, arg2 ...grpc.CallOption) (*tfplugin6.ConfigureProvider_Response, error) {
m.ctrl.T.Helper()
varargs := []interface{}{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "ConfigureProvider", varargs...)
ret0, _ := ret[0].(*tfplugin6.ConfigureProvider_Response)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ConfigureProvider indicates an expected call of ConfigureProvider
func (mr *MockProviderClientMockRecorder) ConfigureProvider(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0, arg1}, arg2...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ConfigureProvider", reflect.TypeOf((*MockProviderClient)(nil).ConfigureProvider), varargs...)
}
// GetProviderSchema mocks base method
func (m *MockProviderClient) GetProviderSchema(arg0 context.Context, arg1 *tfplugin6.GetProviderSchema_Request, arg2 ...grpc.CallOption) (*tfplugin6.GetProviderSchema_Response, error) {
m.ctrl.T.Helper()
varargs := []interface{}{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "GetProviderSchema", varargs...)
ret0, _ := ret[0].(*tfplugin6.GetProviderSchema_Response)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetProviderSchema indicates an expected call of GetProviderSchema
func (mr *MockProviderClientMockRecorder) GetProviderSchema(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0, arg1}, arg2...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProviderSchema", reflect.TypeOf((*MockProviderClient)(nil).GetProviderSchema), varargs...)
}
// ImportResourceState mocks base method
func (m *MockProviderClient) ImportResourceState(arg0 context.Context, arg1 *tfplugin6.ImportResourceState_Request, arg2 ...grpc.CallOption) (*tfplugin6.ImportResourceState_Response, error) {
m.ctrl.T.Helper()
varargs := []interface{}{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "ImportResourceState", varargs...)
ret0, _ := ret[0].(*tfplugin6.ImportResourceState_Response)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ImportResourceState indicates an expected call of ImportResourceState
func (mr *MockProviderClientMockRecorder) ImportResourceState(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0, arg1}, arg2...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ImportResourceState", reflect.TypeOf((*MockProviderClient)(nil).ImportResourceState), varargs...)
}
// PlanResourceChange mocks base method
func (m *MockProviderClient) PlanResourceChange(arg0 context.Context, arg1 *tfplugin6.PlanResourceChange_Request, arg2 ...grpc.CallOption) (*tfplugin6.PlanResourceChange_Response, error) {
m.ctrl.T.Helper()
varargs := []interface{}{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "PlanResourceChange", varargs...)
ret0, _ := ret[0].(*tfplugin6.PlanResourceChange_Response)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// PlanResourceChange indicates an expected call of PlanResourceChange
func (mr *MockProviderClientMockRecorder) PlanResourceChange(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0, arg1}, arg2...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PlanResourceChange", reflect.TypeOf((*MockProviderClient)(nil).PlanResourceChange), varargs...)
}
// ReadDataSource mocks base method
func (m *MockProviderClient) ReadDataSource(arg0 context.Context, arg1 *tfplugin6.ReadDataSource_Request, arg2 ...grpc.CallOption) (*tfplugin6.ReadDataSource_Response, error) {
m.ctrl.T.Helper()
varargs := []interface{}{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "ReadDataSource", varargs...)
ret0, _ := ret[0].(*tfplugin6.ReadDataSource_Response)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ReadDataSource indicates an expected call of ReadDataSource
func (mr *MockProviderClientMockRecorder) ReadDataSource(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0, arg1}, arg2...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadDataSource", reflect.TypeOf((*MockProviderClient)(nil).ReadDataSource), varargs...)
}
// ReadResource mocks base method
func (m *MockProviderClient) ReadResource(arg0 context.Context, arg1 *tfplugin6.ReadResource_Request, arg2 ...grpc.CallOption) (*tfplugin6.ReadResource_Response, error) {
m.ctrl.T.Helper()
varargs := []interface{}{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "ReadResource", varargs...)
ret0, _ := ret[0].(*tfplugin6.ReadResource_Response)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ReadResource indicates an expected call of ReadResource
func (mr *MockProviderClientMockRecorder) ReadResource(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0, arg1}, arg2...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadResource", reflect.TypeOf((*MockProviderClient)(nil).ReadResource), varargs...)
}
// StopProvider mocks base method
func (m *MockProviderClient) StopProvider(arg0 context.Context, arg1 *tfplugin6.StopProvider_Request, arg2 ...grpc.CallOption) (*tfplugin6.StopProvider_Response, error) {
m.ctrl.T.Helper()
varargs := []interface{}{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "StopProvider", varargs...)
ret0, _ := ret[0].(*tfplugin6.StopProvider_Response)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// StopProvider indicates an expected call of StopProvider
func (mr *MockProviderClientMockRecorder) StopProvider(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0, arg1}, arg2...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StopProvider", reflect.TypeOf((*MockProviderClient)(nil).StopProvider), varargs...)
}
// UpgradeResourceState mocks base method
func (m *MockProviderClient) UpgradeResourceState(arg0 context.Context, arg1 *tfplugin6.UpgradeResourceState_Request, arg2 ...grpc.CallOption) (*tfplugin6.UpgradeResourceState_Response, error) {
m.ctrl.T.Helper()
varargs := []interface{}{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "UpgradeResourceState", varargs...)
ret0, _ := ret[0].(*tfplugin6.UpgradeResourceState_Response)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// UpgradeResourceState indicates an expected call of UpgradeResourceState
func (mr *MockProviderClientMockRecorder) UpgradeResourceState(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0, arg1}, arg2...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpgradeResourceState", reflect.TypeOf((*MockProviderClient)(nil).UpgradeResourceState), varargs...)
}
// ValidateDataSourceConfig mocks base method
func (m *MockProviderClient) ValidateDataSourceConfig(arg0 context.Context, arg1 *tfplugin6.ValidateDataSourceConfig_Request, arg2 ...grpc.CallOption) (*tfplugin6.ValidateDataSourceConfig_Response, error) {
m.ctrl.T.Helper()
varargs := []interface{}{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "ValidateDataSourceConfig", varargs...)
ret0, _ := ret[0].(*tfplugin6.ValidateDataSourceConfig_Response)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ValidateDataSourceConfig indicates an expected call of ValidateDataSourceConfig
func (mr *MockProviderClientMockRecorder) ValidateDataSourceConfig(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0, arg1}, arg2...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateDataSourceConfig", reflect.TypeOf((*MockProviderClient)(nil).ValidateDataSourceConfig), varargs...)
}
// ValidateProviderConfig mocks base method
func (m *MockProviderClient) ValidateProviderConfig(arg0 context.Context, arg1 *tfplugin6.ValidateProviderConfig_Request, arg2 ...grpc.CallOption) (*tfplugin6.ValidateProviderConfig_Response, error) {
m.ctrl.T.Helper()
varargs := []interface{}{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "ValidateProviderConfig", varargs...)
ret0, _ := ret[0].(*tfplugin6.ValidateProviderConfig_Response)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ValidateProviderConfig indicates an expected call of ValidateProviderConfig
func (mr *MockProviderClientMockRecorder) ValidateProviderConfig(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0, arg1}, arg2...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateProviderConfig", reflect.TypeOf((*MockProviderClient)(nil).ValidateProviderConfig), varargs...)
}
// ValidateResourceConfig mocks base method
func (m *MockProviderClient) ValidateResourceConfig(arg0 context.Context, arg1 *tfplugin6.ValidateResourceConfig_Request, arg2 ...grpc.CallOption) (*tfplugin6.ValidateResourceConfig_Response, error) {
m.ctrl.T.Helper()
varargs := []interface{}{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "ValidateResourceConfig", varargs...)
ret0, _ := ret[0].(*tfplugin6.ValidateResourceConfig_Response)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ValidateResourceConfig indicates an expected call of ValidateResourceConfig
func (mr *MockProviderClientMockRecorder) ValidateResourceConfig(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0, arg1}, arg2...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateResourceConfig", reflect.TypeOf((*MockProviderClient)(nil).ValidateResourceConfig), varargs...)
}

View File

@ -8,24 +8,21 @@ import (
const (
// The constants below are the names of the plugins that can be dispensed
// from the plugin server.
ProviderPluginName = "provider"
ProvisionerPluginName = "provisioner"
ProviderPluginName = "provider"
// DefaultProtocolVersion is the protocol version assumed for legacy clients that don't specify
// a particular version during their handshake. This is the version used when Terraform 0.10
// and 0.11 launch plugins that were built with support for both versions 4 and 5, and must
// stay unchanged at 4 until we intentionally build plugins that are not compatible with 0.10 and
// 0.11.
// DefaultProtocolVersion is the protocol version assumed for legacy clients
// that don't specify a particular version during their handshake. Since we
// explicitly set VersionedPlugins in Serve, this number does not need to
// change with the protocol version and can effectively stay 4 forever
// (unless we need the "biggest hammer" approach to break all provider
// compatibility).
DefaultProtocolVersion = 4
)
// Handshake is the HandshakeConfig used to configure clients and servers.
var Handshake = plugin.HandshakeConfig{
// The ProtocolVersion is the version that must match between TF core
// and TF plugins. This should be bumped whenever a change happens in
// one or the other that makes it so that they can't safely communicate.
// This could be adding a new interface value, it could be how
// helper/schema computes diffs, etc.
// and TF plugins.
ProtocolVersion: DefaultProtocolVersion,
// The magic cookie values should NEVER be changed.
@ -37,8 +34,6 @@ type GRPCProviderFunc func() proto.ProviderServer
// ServeOpts are the configurations to serve a plugin.
type ServeOpts struct {
// Wrapped versions of the above plugins will automatically shimmed and
// added to the GRPC functions when possible.
GRPCProviderFunc GRPCProviderFunc
}
@ -57,7 +52,7 @@ func pluginSet(opts *ServeOpts) map[int]plugin.PluginSet {
// add the new protocol versions if they're configured
if opts.GRPCProviderFunc != nil {
plugins[5] = plugin.PluginSet{}
plugins[6] = plugin.PluginSet{}
if opts.GRPCProviderFunc != nil {
plugins[6]["provider"] = &GRPCProviderPlugin{
GRPCProvider: opts.GRPCProviderFunc,