move conversion functions into separate package

Managing which function need to be shared between the terraform plugin
and the helper plugin without creating cycles was becoming difficult.
Move all functions related to converting between terraform and proto
type into plugin/convert.
This commit is contained in:
James Bardin 2018-08-15 12:03:39 -04:00 committed by Martin Atkins
parent 88bfbeb9c5
commit c07ce1cd4b
16 changed files with 678 additions and 1537 deletions

View File

@ -1,41 +0,0 @@
package plugin
import (
"github.com/hashicorp/terraform/plugin/proto"
)
// diagsFromWarnsErrs converts the warnings and errors return by the lagacy
// provider to diagnostics.
func diagsFromWarnsErrs(warns []string, errs []error) (diags []*proto.Diagnostic) {
for _, w := range warns {
diags = appendDiag(diags, w)
}
for _, e := range errs {
diags = appendDiag(diags, e)
}
return diags
}
// appendDiag appends a new diagnostic from a warning string or an error. This
// panics if d is not a string or error.
func appendDiag(diags []*proto.Diagnostic, d interface{}) []*proto.Diagnostic {
switch d := d.(type) {
case error:
diags = append(diags, &proto.Diagnostic{
Severity: proto.Diagnostic_ERROR,
Summary: d.Error(),
})
case string:
diags = append(diags, &proto.Diagnostic{
Severity: proto.Diagnostic_WARNING,
Summary: d,
})
case *proto.Diagnostic:
diags = append(diags, d)
case []*proto.Diagnostic:
diags = append(diags, d...)
}
return diags
}

View File

@ -1,45 +0,0 @@
package plugin
import (
"errors"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform/plugin/proto"
)
func TestDiagnostics(t *testing.T) {
diags := diagsFromWarnsErrs(
[]string{
"warning 1",
"warning 2",
},
[]error{
errors.New("error 1"),
errors.New("error 2"),
},
)
expected := []*proto.Diagnostic{
{
Severity: proto.Diagnostic_WARNING,
Summary: "warning 1",
},
{
Severity: proto.Diagnostic_WARNING,
Summary: "warning 2",
},
{
Severity: proto.Diagnostic_ERROR,
Summary: "error 1",
},
{
Severity: proto.Diagnostic_ERROR,
Summary: "error 2",
},
}
if !cmp.Equal(expected, diags) {
t.Fatal(cmp.Diff(expected, diags))
}
}

View File

@ -10,6 +10,7 @@ import (
"github.com/hashicorp/terraform/config/hcl2shim"
"github.com/hashicorp/terraform/configs/configschema"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/plugin/convert"
"github.com/hashicorp/terraform/plugin/proto"
"github.com/hashicorp/terraform/terraform"
"github.com/zclconf/go-cty/cty"
@ -42,20 +43,20 @@ func (s *GRPCProviderServer) GetSchema(_ context.Context, req *proto.GetProvider
}
resp.Provider = &proto.Schema{
Block: protoSchemaBlock(s.getProviderSchemaBlock()),
Block: convert.ConfigSchemaToProto(s.getProviderSchemaBlock()),
}
for typ, res := range s.provider.ResourcesMap {
resp.ResourceSchemas[typ] = &proto.Schema{
Version: int64(res.SchemaVersion),
Block: protoSchemaBlock(res.CoreConfigSchema()),
Block: convert.ConfigSchemaToProto(res.CoreConfigSchema()),
}
}
for typ, dat := range s.provider.DataSourcesMap {
resp.DataSourceSchemas[typ] = &proto.Schema{
Version: int64(dat.SchemaVersion),
Block: protoSchemaBlock(dat.CoreConfigSchema()),
Block: convert.ConfigSchemaToProto(dat.CoreConfigSchema()),
}
}
@ -83,14 +84,14 @@ func (s *GRPCProviderServer) ValidateProviderConfig(_ context.Context, req *prot
configVal, err := msgpack.Unmarshal(req.Config.Msgpack, block.ImpliedType())
if err != nil {
resp.Diagnostics = appendDiag(resp.Diagnostics, err)
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
config := terraform.NewResourceConfigShimmed(configVal, block)
warns, errs := s.provider.Validate(config)
resp.Diagnostics = appendDiag(resp.Diagnostics, diagsFromWarnsErrs(warns, errs))
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, convert.WarnsAndErrsToProto(warns, errs))
return resp, nil
}
@ -102,14 +103,14 @@ func (s *GRPCProviderServer) ValidateResourceTypeConfig(_ context.Context, req *
configVal, err := msgpack.Unmarshal(req.Config.Msgpack, block.ImpliedType())
if err != nil {
resp.Diagnostics = appendDiag(resp.Diagnostics, err)
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
config := terraform.NewResourceConfigShimmed(configVal, block)
warns, errs := s.provider.ValidateResource(req.TypeName, config)
resp.Diagnostics = appendDiag(resp.Diagnostics, diagsFromWarnsErrs(warns, errs))
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, convert.WarnsAndErrsToProto(warns, errs))
return resp, nil
}
@ -121,14 +122,14 @@ func (s *GRPCProviderServer) ValidateDataSourceConfig(_ context.Context, req *pr
configVal, err := msgpack.Unmarshal(req.Config.Msgpack, block.ImpliedType())
if err != nil {
resp.Diagnostics = appendDiag(resp.Diagnostics, err)
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
config := terraform.NewResourceConfigShimmed(configVal, block)
warns, errs := s.provider.ValidateResource(req.TypeName, config)
resp.Diagnostics = appendDiag(resp.Diagnostics, diagsFromWarnsErrs(warns, errs))
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, convert.WarnsAndErrsToProto(warns, errs))
return resp, nil
}
@ -148,7 +149,7 @@ func (s *GRPCProviderServer) UpgradeResourceState(_ context.Context, req *proto.
if req.RawState.Json != nil {
err = json.Unmarshal(req.RawState.Json, &jsonMap)
if err != nil {
resp.Diagnostics = appendDiag(resp.Diagnostics, err)
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
}
@ -158,7 +159,7 @@ func (s *GRPCProviderServer) UpgradeResourceState(_ context.Context, req *proto.
if req.RawState.Flatmap != nil {
jsonMap, version, err = s.upgradeFlatmapState(version, req.RawState.Flatmap, res)
if err != nil {
resp.Diagnostics = appendDiag(resp.Diagnostics, err)
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
}
@ -166,7 +167,7 @@ func (s *GRPCProviderServer) UpgradeResourceState(_ context.Context, req *proto.
// complete the upgrade of the JSON states
jsonMap, err = s.upgradeJSONState(version, jsonMap, res)
if err != nil {
resp.Diagnostics = appendDiag(resp.Diagnostics, err)
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
@ -174,14 +175,14 @@ func (s *GRPCProviderServer) UpgradeResourceState(_ context.Context, req *proto.
// that it can be re-decoded using the actual schema.
val, err := schema.JSONMapToStateValue(jsonMap, block)
if err != nil {
resp.Diagnostics = appendDiag(resp.Diagnostics, err)
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
// encode the final state to the expected msgpack format
newStateMP, err := msgpack.Marshal(val, block.ImpliedType())
if err != nil {
resp.Diagnostics = appendDiag(resp.Diagnostics, err)
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
@ -299,13 +300,13 @@ func (s *GRPCProviderServer) Configure(_ context.Context, req *proto.Configure_R
configVal, err := msgpack.Unmarshal(req.Config.Msgpack, block.ImpliedType())
if err != nil {
resp.Diagnostics = appendDiag(resp.Diagnostics, err)
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
config := terraform.NewResourceConfigShimmed(configVal, block)
err = s.provider.Configure(config)
resp.Diagnostics = appendDiag(resp.Diagnostics, err)
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
@ -318,7 +319,7 @@ func (s *GRPCProviderServer) ReadResource(_ context.Context, req *proto.ReadReso
stateVal, err := msgpack.Unmarshal(req.CurrentState.Msgpack, block.ImpliedType())
if err != nil {
resp.Diagnostics = appendDiag(resp.Diagnostics, err)
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
@ -326,7 +327,7 @@ func (s *GRPCProviderServer) ReadResource(_ context.Context, req *proto.ReadReso
newInstanceState, err := res.RefreshWithoutUpgrade(instanceState, s.provider.Meta())
if err != nil {
resp.Diagnostics = appendDiag(resp.Diagnostics, err)
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
@ -335,13 +336,13 @@ func (s *GRPCProviderServer) ReadResource(_ context.Context, req *proto.ReadReso
newConfigVal, err := hcl2shim.HCL2ValueFromFlatmap(newInstanceState.Attributes, block.ImpliedType())
if err != nil {
resp.Diagnostics = appendDiag(resp.Diagnostics, err)
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
newConfigMP, err := msgpack.Marshal(newConfigVal, block.ImpliedType())
if err != nil {
resp.Diagnostics = appendDiag(resp.Diagnostics, err)
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
@ -360,13 +361,13 @@ func (s *GRPCProviderServer) PlanResourceChange(_ context.Context, req *proto.Pl
priorStateVal, err := msgpack.Unmarshal(req.PriorState.Msgpack, block.ImpliedType())
if err != nil {
resp.Diagnostics = appendDiag(resp.Diagnostics, err)
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
proposedNewStateVal, err := msgpack.Unmarshal(req.ProposedNewState.Msgpack, block.ImpliedType())
if err != nil {
resp.Diagnostics = appendDiag(resp.Diagnostics, err)
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
@ -381,20 +382,20 @@ func (s *GRPCProviderServer) PlanResourceChange(_ context.Context, req *proto.Pl
diff, err := s.provider.Diff(info, priorState, config)
if err != nil {
resp.Diagnostics = appendDiag(resp.Diagnostics, err)
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
// now we need to apply the diff to the prior state, so get the planned state
plannedStateVal, err := schema.ApplyDiff(priorStateVal, diff, block)
if err != nil {
resp.Diagnostics = appendDiag(resp.Diagnostics, err)
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
plannedMP, err := msgpack.Marshal(plannedStateVal, block.ImpliedType())
if err != nil {
resp.Diagnostics = appendDiag(resp.Diagnostics, err)
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
resp.PlannedState.Msgpack = plannedMP
@ -402,7 +403,7 @@ func (s *GRPCProviderServer) PlanResourceChange(_ context.Context, req *proto.Pl
// the Meta field gets encoded into PlannedPrivate
plannedPrivate, err := json.Marshal(diff.Meta)
if err != nil {
resp.Diagnostics = appendDiag(resp.Diagnostics, err)
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
resp.PlannedPrivate = plannedPrivate
@ -418,7 +419,7 @@ func (s *GRPCProviderServer) PlanResourceChange(_ context.Context, req *proto.Pl
requiresReplace, err := hcl2shim.RequiresReplace(requiresNew, block.ImpliedType())
if err != nil {
resp.Diagnostics = appendDiag(resp.Diagnostics, err)
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
@ -438,13 +439,13 @@ func (s *GRPCProviderServer) ApplyResourceChange(_ context.Context, req *proto.A
priorStateVal, err := msgpack.Unmarshal(req.PriorState.Msgpack, block.ImpliedType())
if err != nil {
resp.Diagnostics = appendDiag(resp.Diagnostics, err)
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
plannedStateVal, err := msgpack.Unmarshal(req.PlannedState.Msgpack, block.ImpliedType())
if err != nil {
resp.Diagnostics = appendDiag(resp.Diagnostics, err)
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
@ -456,38 +457,38 @@ func (s *GRPCProviderServer) ApplyResourceChange(_ context.Context, req *proto.A
var private map[string]interface{}
if err := json.Unmarshal(req.PlannedPrivate, &private); err != nil {
resp.Diagnostics = appendDiag(resp.Diagnostics, err)
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
diff, err := schema.DiffFromValues(priorStateVal, plannedStateVal, res)
if err != nil {
resp.Diagnostics = appendDiag(resp.Diagnostics, err)
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
newInstanceState, err := s.provider.Apply(info, priorState, diff)
if err != nil {
resp.Diagnostics = appendDiag(resp.Diagnostics, err)
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
newStateVal, err := schema.StateValueFromInstanceState(newInstanceState, block.ImpliedType())
if err != nil {
resp.Diagnostics = appendDiag(resp.Diagnostics, err)
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
newStateMP, err := msgpack.Marshal(newStateVal, block.ImpliedType())
if err != nil {
resp.Diagnostics = appendDiag(resp.Diagnostics, err)
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
resp.NewState.Msgpack = newStateMP
meta, err := json.Marshal(newInstanceState.Meta)
if err != nil {
resp.Diagnostics = appendDiag(resp.Diagnostics, err)
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
resp.Private = meta
@ -506,7 +507,7 @@ func (s *GRPCProviderServer) ImportResourceState(_ context.Context, req *proto.I
newInstanceStates, err := s.provider.ImportState(info, req.Id)
if err != nil {
resp.Diagnostics = appendDiag(resp.Diagnostics, err)
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
@ -516,19 +517,19 @@ func (s *GRPCProviderServer) ImportResourceState(_ context.Context, req *proto.I
newStateVal, err := hcl2shim.HCL2ValueFromFlatmap(is.Attributes, block.ImpliedType())
if err != nil {
resp.Diagnostics = appendDiag(resp.Diagnostics, err)
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
newStateMP, err := msgpack.Marshal(newStateVal, block.ImpliedType())
if err != nil {
resp.Diagnostics = appendDiag(resp.Diagnostics, err)
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
meta, err := json.Marshal(is.Meta)
if err != nil {
resp.Diagnostics = appendDiag(resp.Diagnostics, err)
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
@ -555,7 +556,7 @@ func (s *GRPCProviderServer) ReadDataSource(_ context.Context, req *proto.ReadDa
configVal, err := msgpack.Unmarshal(req.Config.Msgpack, block.ImpliedType())
if err != nil {
resp.Diagnostics = appendDiag(resp.Diagnostics, err)
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
@ -569,26 +570,26 @@ func (s *GRPCProviderServer) ReadDataSource(_ context.Context, req *proto.ReadDa
// the old behavior
diff, err := s.provider.ReadDataDiff(info, config)
if err != nil {
resp.Diagnostics = appendDiag(resp.Diagnostics, err)
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
// now we can get the new complete data source
newInstanceState, err := s.provider.ReadDataApply(info, diff)
if err != nil {
resp.Diagnostics = appendDiag(resp.Diagnostics, err)
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
newStateVal, err := schema.StateValueFromInstanceState(newInstanceState, block.ImpliedType())
if err != nil {
resp.Diagnostics = appendDiag(resp.Diagnostics, err)
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
newStateMP, err := msgpack.Marshal(newStateVal, block.ImpliedType())
if err != nil {
resp.Diagnostics = appendDiag(resp.Diagnostics, err)
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
resp.State.Msgpack = newStateMP

View File

@ -4,10 +4,11 @@ import (
"log"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/plugin/convert"
"github.com/hashicorp/terraform/plugin/proto"
"github.com/hashicorp/terraform/terraform"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/convert"
ctyconvert "github.com/zclconf/go-cty/cty/convert"
"github.com/zclconf/go-cty/cty/msgpack"
context "golang.org/x/net/context"
)
@ -33,7 +34,7 @@ func (s *GRPCProvisionerServer) GetSchema(_ context.Context, req *proto.GetProvi
resp := &proto.GetProvisionerSchema_Response{}
resp.Provisioner = &proto.Schema{
Block: protoSchemaBlock(schema.InternalMap(s.provisioner.Schema).CoreConfigSchema()),
Block: convert.ConfigSchemaToProto(schema.InternalMap(s.provisioner.Schema).CoreConfigSchema()),
}
return resp, nil
@ -46,14 +47,14 @@ func (s *GRPCProvisionerServer) ValidateProvisionerConfig(_ context.Context, req
configVal, err := msgpack.Unmarshal(req.Config.Msgpack, cfgSchema.ImpliedType())
if err != nil {
resp.Diagnostics = appendDiag(resp.Diagnostics, err)
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
config := terraform.NewResourceConfigShimmed(configVal, cfgSchema)
warns, errs := s.provisioner.Validate(config)
resp.Diagnostics = appendDiag(resp.Diagnostics, diagsFromWarnsErrs(warns, errs))
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, convert.WarnsAndErrsToProto(warns, errs))
return resp, nil
}
@ -74,7 +75,7 @@ func stringMapFromValue(val cty.Value) map[string]string {
continue
}
av, _ = convert.Convert(av, cty.String)
av, _ = ctyconvert.Convert(av, cty.String)
m[name] = av.AsString()
}
@ -104,7 +105,7 @@ func (s *GRPCProvisionerServer) ProvisionResource(req *proto.ProvisionResource_R
cfgSchema := schema.InternalMap(s.provisioner.Schema).CoreConfigSchema()
cfgVal, err := msgpack.Unmarshal(req.Config.Msgpack, cfgSchema.ImpliedType())
if err != nil {
srvResp.Diagnostics = appendDiag(srvResp.Diagnostics, err)
srvResp.Diagnostics = convert.AppendProtoDiag(srvResp.Diagnostics, err)
srv.Send(srvResp)
return nil
}
@ -112,7 +113,7 @@ func (s *GRPCProvisionerServer) ProvisionResource(req *proto.ProvisionResource_R
connVal, err := msgpack.Unmarshal(req.Connection.Msgpack, cty.Map(cty.String))
if err != nil {
srvResp.Diagnostics = appendDiag(srvResp.Diagnostics, err)
srvResp.Diagnostics = convert.AppendProtoDiag(srvResp.Diagnostics, err)
srv.Send(srvResp)
return nil
}
@ -127,7 +128,7 @@ func (s *GRPCProvisionerServer) ProvisionResource(req *proto.ProvisionResource_R
err = s.provisioner.Apply(uiOutput{srv}, instanceState, resourceConfig)
if err != nil {
srvResp.Diagnostics = appendDiag(srvResp.Diagnostics, err)
srvResp.Diagnostics = convert.AppendProtoDiag(srvResp.Diagnostics, err)
srv.Send(srvResp)
}
return nil

View File

@ -1,70 +0,0 @@
package plugin
import (
"encoding/json"
"reflect"
"sort"
"github.com/hashicorp/terraform/configs/configschema"
"github.com/hashicorp/terraform/plugin/proto"
)
// protoSchemaBlock takes a *configschema.Block and converts it to a
// proto.Schema_Block for a grpc response.
func protoSchemaBlock(b *configschema.Block) *proto.Schema_Block {
block := &proto.Schema_Block{}
for _, name := range sortedKeys(b.Attributes) {
a := b.Attributes[name]
attr := &proto.Schema_Attribute{
Name: name,
Description: a.Description,
Optional: a.Optional,
Computed: a.Computed,
Required: a.Required,
Sensitive: a.Sensitive,
}
ty, err := json.Marshal(a.Type)
if err != nil {
panic(err)
}
attr.Type = ty
block.Attributes = append(block.Attributes, attr)
}
for _, name := range sortedKeys(b.BlockTypes) {
b := b.BlockTypes[name]
block.BlockTypes = append(block.BlockTypes, protoSchemaNestedBlock(name, b))
}
return block
}
func protoSchemaNestedBlock(name string, b *configschema.NestedBlock) *proto.Schema_NestedBlock {
return &proto.Schema_NestedBlock{
TypeName: name,
Block: protoSchemaBlock(&b.Block),
Nesting: proto.Schema_NestedBlock_NestingMode(b.Nesting),
MinItems: int64(b.MinItems),
MaxItems: int64(b.MaxItems),
}
}
// sortedKeys returns the lexically sorted keys from the given map. This is
// used to make schema conversions are deterministic. This panics if map keys
// are not a string.
func sortedKeys(m interface{}) []string {
v := reflect.ValueOf(m)
keys := make([]string, v.Len())
mapKeys := v.MapKeys()
for i, k := range mapKeys {
keys[i] = k.Interface().(string)
}
sort.Strings(keys)
return keys
}

View File

@ -1,516 +0,0 @@
package plugin
import (
"errors"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform/config/hcl2shim"
"github.com/hashicorp/terraform/configs/configschema"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/plugin"
"github.com/hashicorp/terraform/plugin/proto"
"github.com/hashicorp/terraform/providers"
"github.com/hashicorp/terraform/tfdiags"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/msgpack"
)
// the TestProvider functions have been adapted from the helper/schema fixtures
func TestProviderGetSchema(t *testing.T) {
p := &schema.Provider{
Schema: map[string]*schema.Schema{
"bar": {
Type: schema.TypeString,
Required: true,
},
},
ResourcesMap: map[string]*schema.Resource{
"foo": &schema.Resource{
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"bar": {
Type: schema.TypeString,
Required: true,
},
},
},
},
DataSourcesMap: map[string]*schema.Resource{
"baz": &schema.Resource{
SchemaVersion: 2,
Schema: map[string]*schema.Schema{
"bur": {
Type: schema.TypeString,
Required: true,
},
},
},
},
}
want := providers.GetSchemaResponse{
Provider: providers.Schema{
Version: 0,
Block: schema.InternalMap(p.Schema).CoreConfigSchema(),
},
ResourceTypes: map[string]providers.Schema{
"foo": {
Version: 1,
Block: p.ResourcesMap["foo"].CoreConfigSchema(),
},
},
DataSources: map[string]providers.Schema{
"baz": {
Version: 2,
Block: p.DataSourcesMap["baz"].CoreConfigSchema(),
},
},
}
provider := &GRPCProviderServer{
provider: p,
}
resp, err := provider.GetSchema(nil, nil)
if err != nil {
t.Fatalf("unexpected error %s", err)
}
diags := plugin.ProtoToDiagnostics(resp.Diagnostics)
if diags.HasErrors() {
t.Fatal(diags.Err())
}
schemaResp := providers.GetSchemaResponse{
Provider: plugin.ProtoToProviderSchema(resp.Provider),
ResourceTypes: map[string]providers.Schema{
"foo": plugin.ProtoToProviderSchema(resp.ResourceSchemas["foo"]),
},
DataSources: map[string]providers.Schema{
"baz": plugin.ProtoToProviderSchema(resp.DataSourceSchemas["baz"]),
},
}
if !cmp.Equal(schemaResp, want, equateEmpty, typeComparer) {
t.Error("wrong result:\n", cmp.Diff(schemaResp, want, equateEmpty, typeComparer))
}
}
func TestProviderValidate(t *testing.T) {
cases := []struct {
Name string
P *schema.Provider
Err bool
Warn bool
}{
{
Name: "warning",
P: &schema.Provider{
Schema: map[string]*schema.Schema{
"foo": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: func(_ interface{}, _ string) ([]string, []error) {
return []string{"warning"}, nil
},
},
},
},
Warn: true,
},
{
Name: "error",
P: &schema.Provider{
Schema: map[string]*schema.Schema{
"foo": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: func(_ interface{}, _ string) ([]string, []error) {
return nil, []error{errors.New("error")}
},
},
},
},
Err: true,
},
}
for _, tc := range cases {
t.Run(tc.Name, func(t *testing.T) {
provider := &GRPCProviderServer{
provider: tc.P,
}
cfgSchema := schema.InternalMap(tc.P.Schema).CoreConfigSchema()
val := hcl2shim.HCL2ValueFromConfigValue(map[string]interface{}{"foo": "bar"})
val, err := cfgSchema.CoerceValue(val)
if err != nil {
t.Fatal(err)
}
mp, err := msgpack.Marshal(val, cfgSchema.ImpliedType())
if err != nil {
t.Fatal(err)
}
req := &proto.ValidateProviderConfig_Request{
Config: &proto.DynamicValue{Msgpack: mp},
}
resp, err := provider.ValidateProviderConfig(nil, req)
if err != nil {
t.Fatal(err)
}
diags := plugin.ProtoToDiagnostics(resp.Diagnostics)
var warn tfdiags.Diagnostic
for _, d := range diags {
if d.Severity() == tfdiags.Warning {
warn = d
}
}
switch {
case tc.Err:
if !diags.HasErrors() {
t.Fatal("expected error")
}
case !tc.Err:
if diags.HasErrors() {
t.Fatal(diags.Err())
}
case tc.Warn:
if warn == nil {
t.Fatal("expected warning")
}
case !tc.Warn:
if warn != nil {
t.Fatal("unexpected warning", warn)
}
}
})
}
}
func TestProviderValidateResource(t *testing.T) {
cases := []struct {
Name string
P *schema.Provider
Type string
Config map[string]interface{}
Err bool
Warn bool
}{
{
Name: "error",
P: &schema.Provider{
ResourcesMap: map[string]*schema.Resource{
"foo": &schema.Resource{
Schema: map[string]*schema.Schema{
"attr": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: func(_ interface{}, _ string) ([]string, []error) {
return nil, []error{errors.New("warn")}
},
},
},
},
},
},
Type: "foo",
Err: true,
},
{
Name: "ok",
P: &schema.Provider{
ResourcesMap: map[string]*schema.Resource{
"foo": &schema.Resource{
Schema: map[string]*schema.Schema{
"attr": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
},
},
},
},
Config: map[string]interface{}{"attr": "bar"},
Type: "foo",
},
{
Name: "warn",
P: &schema.Provider{
ResourcesMap: map[string]*schema.Resource{
"foo": &schema.Resource{
Schema: map[string]*schema.Schema{
"attr": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: func(_ interface{}, _ string) ([]string, []error) {
return []string{"warn"}, nil
},
},
},
},
},
},
Type: "foo",
Config: map[string]interface{}{"attr": "bar"},
Err: false,
},
}
for _, tc := range cases {
t.Run(tc.Name, func(t *testing.T) {
provider := &GRPCProviderServer{
provider: tc.P,
}
cfgSchema := tc.P.ResourcesMap[tc.Type].CoreConfigSchema()
val := hcl2shim.HCL2ValueFromConfigValue(tc.Config)
val, err := cfgSchema.CoerceValue(val)
if err != nil {
t.Fatal(err)
}
mp, err := msgpack.Marshal(val, cfgSchema.ImpliedType())
if err != nil {
t.Fatal(err)
}
req := &proto.ValidateResourceTypeConfig_Request{
TypeName: tc.Type,
Config: &proto.DynamicValue{Msgpack: mp},
}
resp, err := provider.ValidateResourceTypeConfig(nil, req)
if err != nil {
t.Fatal(err)
}
diags := plugin.ProtoToDiagnostics(resp.Diagnostics)
var warn tfdiags.Diagnostic
for _, d := range diags {
if d.Severity() == tfdiags.Warning {
warn = d
}
}
switch {
case tc.Err:
if !diags.HasErrors() {
t.Fatal("expected error")
}
case !tc.Err:
if diags.HasErrors() {
t.Fatal(diags.Err())
}
case tc.Warn:
if warn == nil {
t.Fatal("expected warning")
}
case !tc.Warn:
if warn != nil {
t.Fatal("unexpected warning", warn)
}
}
})
}
}
func TestProviderImportState_default(t *testing.T) {
p := &GRPCProviderServer{
provider: &schema.Provider{
ResourcesMap: map[string]*schema.Resource{
"foo": &schema.Resource{
Importer: &schema.ResourceImporter{},
},
},
},
}
req := &proto.ImportResourceState_Request{
TypeName: "foo",
Id: "bar",
}
resp, err := p.ImportResourceState(nil, req)
if err != nil {
t.Fatal(err)
}
diags := plugin.ProtoToDiagnostics(resp.Diagnostics)
if diags.HasErrors() {
t.Fatal(diags.Err())
}
if len(resp.ImportedResources) != 1 {
t.Fatalf("expected 1 import, git %#v", resp.ImportedResources)
}
}
func TestProviderImportState_setsId(t *testing.T) {
var val string
stateFunc := func(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
val = d.Id()
return []*schema.ResourceData{d}, nil
}
p := &GRPCProviderServer{
provider: &schema.Provider{
ResourcesMap: map[string]*schema.Resource{
"foo": &schema.Resource{
Importer: &schema.ResourceImporter{
State: stateFunc,
},
},
},
},
}
req := &proto.ImportResourceState_Request{
TypeName: "foo",
Id: "bar",
}
resp, err := p.ImportResourceState(nil, req)
if err != nil {
t.Fatal(err)
}
diags := plugin.ProtoToDiagnostics(resp.Diagnostics)
if diags.HasErrors() {
t.Fatal(diags.Err())
}
if len(resp.ImportedResources) != 1 {
t.Fatalf("expected 1 import, git %#v", resp.ImportedResources)
}
if val != "bar" {
t.Fatal("should set id")
}
}
func TestProviderImportState_setsType(t *testing.T) {
var tVal string
stateFunc := func(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
d.SetId("foo")
tVal = d.State().Ephemeral.Type
return []*schema.ResourceData{d}, nil
}
p := &GRPCProviderServer{
provider: &schema.Provider{
ResourcesMap: map[string]*schema.Resource{
"foo": &schema.Resource{
Importer: &schema.ResourceImporter{
State: stateFunc,
},
},
},
},
}
req := &proto.ImportResourceState_Request{
TypeName: "foo",
Id: "bar",
}
resp, err := p.ImportResourceState(nil, req)
if err != nil {
t.Fatal(err)
}
diags := plugin.ProtoToDiagnostics(resp.Diagnostics)
if diags.HasErrors() {
t.Fatal(diags.Err())
}
if tVal != "foo" {
t.Fatal("should set type")
}
}
func TestProviderStop(t *testing.T) {
var p schema.Provider
if p.Stopped() {
t.Fatal("should not be stopped")
}
// Verify stopch blocks
ch := p.StopContext().Done()
select {
case <-ch:
t.Fatal("should not be stopped")
case <-time.After(10 * time.Millisecond):
}
provider := &GRPCProviderServer{
provider: &p,
}
// Stop it
if _, err := provider.Stop(nil, &proto.Stop_Request{}); err != nil {
t.Fatal(err)
}
// Verify
if !p.Stopped() {
t.Fatal("should be stopped")
}
select {
case <-ch:
case <-time.After(10 * time.Millisecond):
t.Fatal("should be stopped")
}
}
func TestProviderStop_stopFirst(t *testing.T) {
var p schema.Provider
provider := &GRPCProviderServer{
provider: &p,
}
// Stop it
_, err := provider.Stop(nil, &proto.Stop_Request{})
if err != nil {
t.Fatal(err)
}
// Verify
if !p.Stopped() {
t.Fatal("should be stopped")
}
select {
case <-p.StopContext().Done():
case <-time.After(10 * time.Millisecond):
t.Fatal("should be stopped")
}
}
// add the implicit "id" attribute for test resources
func testResource(block *configschema.Block) *configschema.Block {
if block.Attributes == nil {
block.Attributes = make(map[string]*configschema.Attribute)
}
if block.BlockTypes == nil {
block.BlockTypes = make(map[string]*configschema.NestedBlock)
}
if block.Attributes["id"] == nil {
block.Attributes["id"] = &configschema.Attribute{
Type: cty.String,
Optional: true,
Computed: true,
}
}
return block
}

View File

@ -1,338 +0,0 @@
package plugin
import (
"context"
"fmt"
"reflect"
"testing"
"time"
"github.com/golang/mock/gomock"
"github.com/hashicorp/terraform/config/hcl2shim"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/plugin"
"github.com/hashicorp/terraform/plugin/proto"
"github.com/hashicorp/terraform/terraform"
"github.com/hashicorp/terraform/tfdiags"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/msgpack"
mockproto "github.com/hashicorp/terraform/plugin/mock_proto"
)
// TestProvisioner functions in this file have been adapted from the
// helper/schema tests.
func noopApply(ctx context.Context) error {
return nil
}
func TestProvisionerValidate(t *testing.T) {
cases := []struct {
Name string
P *schema.Provisioner
Config map[string]interface{}
Err bool
Warns []string
}{
{
Name: "No ApplyFunc",
P: &schema.Provisioner{},
Config: map[string]interface{}{},
Err: true,
},
{
"Basic required field set",
&schema.Provisioner{
Schema: map[string]*schema.Schema{
"foo": &schema.Schema{
Required: true,
Type: schema.TypeString,
},
},
ApplyFunc: noopApply,
},
map[string]interface{}{
"foo": "bar",
},
false,
nil,
},
{
Name: "Warning from property validation",
P: &schema.Provisioner{
Schema: map[string]*schema.Schema{
"foo": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
ws = append(ws, "Simple warning from property validation")
return
},
},
},
ApplyFunc: noopApply,
},
Config: map[string]interface{}{
"foo": "",
},
Err: false,
Warns: []string{"Simple warning from property validation"},
},
{
Name: "No schema",
P: &schema.Provisioner{
Schema: nil,
ApplyFunc: noopApply,
},
Config: map[string]interface{}{},
Err: false,
},
{
Name: "Warning from provisioner ValidateFunc",
P: &schema.Provisioner{
Schema: nil,
ApplyFunc: noopApply,
ValidateFunc: func(*terraform.ResourceConfig) (ws []string, errors []error) {
ws = append(ws, "Simple warning from provisioner ValidateFunc")
return
},
},
Config: map[string]interface{}{},
Err: false,
Warns: []string{"Simple warning from provisioner ValidateFunc"},
},
}
for i, tc := range cases {
t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) {
p := &GRPCProvisionerServer{
provisioner: tc.P,
}
cfgSchema := schema.InternalMap(tc.P.Schema).CoreConfigSchema()
val := hcl2shim.HCL2ValueFromConfigValue(tc.Config)
val, err := cfgSchema.CoerceValue(val)
if err != nil {
t.Fatal(err)
}
mp, err := msgpack.Marshal(val, cfgSchema.ImpliedType())
if err != nil {
t.Fatal(err)
}
req := &proto.ValidateProvisionerConfig_Request{
Config: &proto.DynamicValue{Msgpack: mp},
}
resp, err := p.ValidateProvisionerConfig(nil, req)
if err != nil {
t.Fatal(err)
}
diags := plugin.ProtoToDiagnostics(resp.Diagnostics)
if diags.HasErrors() != tc.Err {
t.Fatal(diags.Err())
}
var ws []string
for _, d := range diags {
if d.Severity() == tfdiags.Warning {
ws = append(ws, d.Description().Summary)
}
}
if (tc.Warns != nil || len(ws) != 0) && !reflect.DeepEqual(ws, tc.Warns) {
t.Fatalf("%d: warnings mismatch, actual: %#v", i, ws)
}
})
}
}
func TestProvisionerApply(t *testing.T) {
cases := []struct {
Name string
P *schema.Provisioner
Conn map[string]interface{}
Config map[string]interface{}
Err bool
}{
{
Name: "Basic config",
P: &schema.Provisioner{
ConnSchema: map[string]*schema.Schema{
"foo": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
},
Schema: map[string]*schema.Schema{
"foo": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
},
},
ApplyFunc: func(ctx context.Context) error {
cd := ctx.Value(schema.ProvConnDataKey).(*schema.ResourceData)
d := ctx.Value(schema.ProvConfigDataKey).(*schema.ResourceData)
if d.Get("foo").(int) != 42 {
return fmt.Errorf("bad config data")
}
if cd.Get("foo").(string) != "bar" {
return fmt.Errorf("bad conn data")
}
return nil
},
},
Conn: map[string]interface{}{
"foo": "bar",
},
Config: map[string]interface{}{
"foo": 42,
},
Err: false,
},
}
for i, tc := range cases {
t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) {
p := &GRPCProvisionerServer{
provisioner: tc.P,
}
cfgSchema := schema.InternalMap(tc.P.Schema).CoreConfigSchema()
val := hcl2shim.HCL2ValueFromConfigValue(tc.Config)
val, err := cfgSchema.CoerceValue(val)
if err != nil {
t.Fatal(err)
}
cfgMP, err := msgpack.Marshal(val, cfgSchema.ImpliedType())
if err != nil {
t.Fatal(err)
}
connVal := hcl2shim.HCL2ValueFromConfigValue(tc.Conn)
connMP, err := msgpack.Marshal(connVal, cty.Map(cty.String))
if err != nil {
t.Fatal(err)
}
req := &proto.ProvisionResource_Request{
Config: &proto.DynamicValue{Msgpack: cfgMP},
Connection: &proto.DynamicValue{Msgpack: connMP},
}
ctrl := gomock.NewController(t)
srv := mockproto.NewMockProvisioner_ProvisionResourceServer(ctrl)
srv.EXPECT().Send(gomock.Any()).Return(nil)
err = p.ProvisionResource(req, srv)
if err != nil && !tc.Err {
t.Fatal(err)
}
})
}
}
func TestProvisionerStop(t *testing.T) {
p := &GRPCProvisionerServer{
provisioner: &schema.Provisioner{},
}
// Verify stopch blocks
ch := p.provisioner.StopContext().Done()
select {
case <-ch:
t.Fatal("should not be stopped")
case <-time.After(10 * time.Millisecond):
}
// Stop it
resp, err := p.Stop(nil, nil)
if err != nil {
t.Fatal(err)
}
if resp.Error != "" {
t.Fatal(resp.Error)
}
select {
case <-ch:
case <-time.After(10 * time.Millisecond):
t.Fatal("should be stopped")
}
}
func TestProvisionerStop_apply(t *testing.T) {
p := &schema.Provisioner{
ConnSchema: map[string]*schema.Schema{
"foo": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
},
Schema: map[string]*schema.Schema{
"foo": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
},
},
ApplyFunc: func(ctx context.Context) error {
<-ctx.Done()
return nil
},
}
s := &GRPCProvisionerServer{
provisioner: p,
}
srv := mockproto.NewMockProvisioner_ProvisionResourceServer(gomock.NewController(t))
srv.EXPECT().Send(gomock.Any()).Return(nil)
// Run the apply in a goroutine
doneCh := make(chan struct{})
go func() {
req := &proto.ProvisionResource_Request{
Config: &proto.DynamicValue{Msgpack: []byte("\201\243foo*")},
Connection: &proto.DynamicValue{Msgpack: []byte("\201\243foo\243bar")},
}
err := s.ProvisionResource(req, srv)
if err != nil {
t.Fatal(err)
}
close(doneCh)
}()
// Should block
select {
case <-doneCh:
t.Fatal("should not be done")
case <-time.After(10 * time.Millisecond):
}
resp, err := s.Stop(nil, nil)
if err != nil {
t.Fatal(err)
}
if resp.Error != "" {
t.Fatal(resp.Error)
}
select {
case <-doneCh:
case <-time.After(10 * time.Millisecond):
t.Fatal("should be done")
}
}

View File

@ -1,182 +0,0 @@
package plugin
import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform/configs/configschema"
"github.com/hashicorp/terraform/plugin/proto"
"github.com/zclconf/go-cty/cty"
)
// Test that we can convert configschema to protobuf types and back again.
func TestConvertSchemaBlocks(t *testing.T) {
tests := map[string]struct {
Want *proto.Schema_Block
Block *configschema.Block
}{
"attributes": {
&proto.Schema_Block{
Attributes: []*proto.Schema_Attribute{
{
Name: "computed",
Type: []byte(`["list","bool"]`),
Computed: true,
},
{
Name: "optional",
Type: []byte(`"string"`),
Optional: true,
},
{
Name: "optional_computed",
Type: []byte(`["map","bool"]`),
Optional: true,
Computed: true,
},
{
Name: "required",
Type: []byte(`"number"`),
Required: true,
},
},
},
&configschema.Block{
Attributes: map[string]*configschema.Attribute{
"computed": {
Type: cty.List(cty.Bool),
Computed: true,
},
"optional": {
Type: cty.String,
Optional: true,
},
"optional_computed": {
Type: cty.Map(cty.Bool),
Optional: true,
Computed: true,
},
"required": {
Type: cty.Number,
Required: true,
},
},
},
},
"blocks": {
&proto.Schema_Block{
BlockTypes: []*proto.Schema_NestedBlock{
{
TypeName: "list",
Nesting: proto.Schema_NestedBlock_LIST,
Block: &proto.Schema_Block{},
},
{
TypeName: "map",
Nesting: proto.Schema_NestedBlock_MAP,
Block: &proto.Schema_Block{},
},
{
TypeName: "set",
Nesting: proto.Schema_NestedBlock_SET,
Block: &proto.Schema_Block{},
},
{
TypeName: "single",
Nesting: proto.Schema_NestedBlock_SINGLE,
Block: &proto.Schema_Block{
Attributes: []*proto.Schema_Attribute{
{
Name: "foo",
Type: []byte(`"dynamic"`),
Required: true,
},
},
},
},
},
},
&configschema.Block{
BlockTypes: map[string]*configschema.NestedBlock{
"list": &configschema.NestedBlock{
Nesting: configschema.NestingList,
},
"map": &configschema.NestedBlock{
Nesting: configschema.NestingMap,
},
"set": &configschema.NestedBlock{
Nesting: configschema.NestingSet,
},
"single": &configschema.NestedBlock{
Nesting: configschema.NestingSingle,
Block: configschema.Block{
Attributes: map[string]*configschema.Attribute{
"foo": {
Type: cty.DynamicPseudoType,
Required: true,
},
},
},
},
},
},
},
"deep block nesting": {
&proto.Schema_Block{
BlockTypes: []*proto.Schema_NestedBlock{
{
TypeName: "single",
Nesting: proto.Schema_NestedBlock_SINGLE,
Block: &proto.Schema_Block{
BlockTypes: []*proto.Schema_NestedBlock{
{
TypeName: "list",
Nesting: proto.Schema_NestedBlock_LIST,
Block: &proto.Schema_Block{
BlockTypes: []*proto.Schema_NestedBlock{
{
TypeName: "set",
Nesting: proto.Schema_NestedBlock_SET,
Block: &proto.Schema_Block{},
},
},
},
},
},
},
},
},
},
&configschema.Block{
BlockTypes: map[string]*configschema.NestedBlock{
"single": &configschema.NestedBlock{
Nesting: configschema.NestingSingle,
Block: configschema.Block{
BlockTypes: map[string]*configschema.NestedBlock{
"list": &configschema.NestedBlock{
Nesting: configschema.NestingList,
Block: configschema.Block{
BlockTypes: map[string]*configschema.NestedBlock{
"set": &configschema.NestedBlock{
Nesting: configschema.NestingSet,
},
},
},
},
},
},
},
},
},
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
converted := protoSchemaBlock(tc.Block)
if !cmp.Equal(converted, tc.Want, typeComparer, equateEmpty) {
t.Fatal(cmp.Diff(converted, tc.Want, typeComparer, equateEmpty))
}
})
}
}

View File

@ -1,107 +0,0 @@
package plugin
import (
"encoding/json"
"github.com/hashicorp/terraform/configs/configschema"
"github.com/hashicorp/terraform/plugin/proto"
"github.com/hashicorp/terraform/providers"
"github.com/hashicorp/terraform/tfdiags"
"github.com/zclconf/go-cty/cty"
)
// ProtoToProviderSchema takes a proto.Schema and converts it to a providers.Schema.
func ProtoToProviderSchema(s *proto.Schema) providers.Schema {
return providers.Schema{
Version: uint64(s.Version),
Block: schemaBlock(s.Block),
}
}
// schemaBlock takes the GetSchcema_Block from a grpc response and converts it
// to a terraform *configschema.Block.
func schemaBlock(b *proto.Schema_Block) *configschema.Block {
block := &configschema.Block{
Attributes: make(map[string]*configschema.Attribute),
BlockTypes: make(map[string]*configschema.NestedBlock),
}
for _, a := range b.Attributes {
attr := &configschema.Attribute{
Description: a.Description,
Required: a.Required,
Optional: a.Optional,
Computed: a.Computed,
Sensitive: a.Sensitive,
}
if err := json.Unmarshal(a.Type, &attr.Type); err != nil {
panic(err)
}
block.Attributes[a.Name] = attr
}
for _, b := range b.BlockTypes {
block.BlockTypes[b.TypeName] = schemaNestedBlock(b)
}
return block
}
func schemaNestedBlock(b *proto.Schema_NestedBlock) *configschema.NestedBlock {
nb := &configschema.NestedBlock{
Nesting: configschema.NestingMode(b.Nesting),
MinItems: int(b.MinItems),
MaxItems: int(b.MaxItems),
}
nested := schemaBlock(b.Block)
nb.Block = *nested
return nb
}
// ProtoToDiagnostics converts a list of proto.Diagnostics to a tf.Diagnostics.
func ProtoToDiagnostics(ds []*proto.Diagnostic) tfdiags.Diagnostics {
var diags tfdiags.Diagnostics
for _, d := range ds {
var severity tfdiags.Severity
switch d.Severity {
case proto.Diagnostic_ERROR:
severity = tfdiags.Error
case proto.Diagnostic_WARNING:
severity = tfdiags.Warning
}
var newDiag tfdiags.Diagnostic
// if there's an attribute path, we need to create a AttributeValue diagnostic
if d.Attribute != nil {
path := attributePath(d.Attribute)
newDiag = tfdiags.AttributeValue(severity, d.Summary, d.Detail, path)
} else {
newDiag = tfdiags.Sourceless(severity, d.Summary, d.Detail)
}
diags = diags.Append(newDiag)
}
return diags
}
// attributePath takes the proto encoded path and converts it to a cty.Path
func attributePath(ap *proto.AttributePath) cty.Path {
var p cty.Path
for _, step := range ap.Steps {
switch selector := step.Selector.(type) {
case *proto.AttributePath_Step_AttributeName:
p = p.GetAttr(selector.AttributeName)
case *proto.AttributePath_Step_ElementKeyString:
p = p.Index(cty.StringVal(selector.ElementKeyString))
case *proto.AttributePath_Step_ElementKeyInt:
p = p.Index(cty.NumberIntVal(selector.ElementKeyInt))
}
}
return p
}

View File

@ -0,0 +1,88 @@
package convert
import (
"github.com/hashicorp/terraform/plugin/proto"
"github.com/hashicorp/terraform/tfdiags"
"github.com/zclconf/go-cty/cty"
)
// WarnsAndErrorsToProto converts the warnings and errors return by the legacy
// provider to protobuf diagnostics.
func WarnsAndErrsToProto(warns []string, errs []error) (diags []*proto.Diagnostic) {
for _, w := range warns {
diags = AppendProtoDiag(diags, w)
}
for _, e := range errs {
diags = AppendProtoDiag(diags, e)
}
return diags
}
// AppendProtoDiag appends a new diagnostic from a warning string or an error.
// This panics if d is not a string or error.
func AppendProtoDiag(diags []*proto.Diagnostic, d interface{}) []*proto.Diagnostic {
switch d := d.(type) {
case error:
diags = append(diags, &proto.Diagnostic{
Severity: proto.Diagnostic_ERROR,
Summary: d.Error(),
})
case string:
diags = append(diags, &proto.Diagnostic{
Severity: proto.Diagnostic_WARNING,
Summary: d,
})
case *proto.Diagnostic:
diags = append(diags, d)
case []*proto.Diagnostic:
diags = append(diags, d...)
}
return diags
}
// ProtoToDiagnostics converts a list of proto.Diagnostics to a tf.Diagnostics.
func ProtoToDiagnostics(ds []*proto.Diagnostic) tfdiags.Diagnostics {
var diags tfdiags.Diagnostics
for _, d := range ds {
var severity tfdiags.Severity
switch d.Severity {
case proto.Diagnostic_ERROR:
severity = tfdiags.Error
case proto.Diagnostic_WARNING:
severity = tfdiags.Warning
}
var newDiag tfdiags.Diagnostic
// if there's an attribute path, we need to create a AttributeValue diagnostic
if d.Attribute != nil {
path := AttributePathToPath(d.Attribute)
newDiag = tfdiags.AttributeValue(severity, d.Summary, d.Detail, path)
} else {
newDiag = tfdiags.Sourceless(severity, d.Summary, d.Detail)
}
diags = diags.Append(newDiag)
}
return diags
}
// AttributePathToPath takes the proto encoded path and converts it to a cty.Path
func AttributePathToPath(ap *proto.AttributePath) cty.Path {
var p cty.Path
for _, step := range ap.Steps {
switch selector := step.Selector.(type) {
case *proto.AttributePath_Step_AttributeName:
p = p.GetAttr(selector.AttributeName)
case *proto.AttributePath_Step_ElementKeyString:
p = p.Index(cty.StringVal(selector.ElementKeyString))
case *proto.AttributePath_Step_ElementKeyInt:
p = p.Index(cty.NumberIntVal(selector.ElementKeyInt))
}
}
return p
}

View File

@ -1,191 +1,48 @@
package plugin
package convert
import (
"errors"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/hashicorp/terraform/configs/configschema"
"github.com/hashicorp/terraform/plugin/proto"
"github.com/hashicorp/terraform/tfdiags"
"github.com/zclconf/go-cty/cty"
)
var (
equateEmpty = cmpopts.EquateEmpty()
typeComparer = cmp.Comparer(cty.Type.Equals)
valueComparer = cmp.Comparer(cty.Value.RawEquals)
)
func TestProtoDiagnostics(t *testing.T) {
diags := WarnsAndErrsToProto(
[]string{
"warning 1",
"warning 2",
},
[]error{
errors.New("error 1"),
errors.New("error 2"),
},
)
// Test that we can convert configschema to protobuf types and back again.
func TestConvertSchemaBlocks(t *testing.T) {
tests := map[string]struct {
Block *proto.Schema_Block
Want *configschema.Block
}{
"attributes": {
&proto.Schema_Block{
Attributes: []*proto.Schema_Attribute{
{
Name: "computed",
Type: []byte(`["list","bool"]`),
Computed: true,
},
{
Name: "optional",
Type: []byte(`"string"`),
Optional: true,
},
{
Name: "optional_computed",
Type: []byte(`["map","bool"]`),
Optional: true,
Computed: true,
},
{
Name: "required",
Type: []byte(`"number"`),
Required: true,
},
},
},
&configschema.Block{
Attributes: map[string]*configschema.Attribute{
"computed": {
Type: cty.List(cty.Bool),
Computed: true,
},
"optional": {
Type: cty.String,
Optional: true,
},
"optional_computed": {
Type: cty.Map(cty.Bool),
Optional: true,
Computed: true,
},
"required": {
Type: cty.Number,
Required: true,
},
},
},
expected := []*proto.Diagnostic{
{
Severity: proto.Diagnostic_WARNING,
Summary: "warning 1",
},
"blocks": {
&proto.Schema_Block{
BlockTypes: []*proto.Schema_NestedBlock{
{
TypeName: "list",
Nesting: proto.Schema_NestedBlock_LIST,
Block: &proto.Schema_Block{},
},
{
TypeName: "map",
Nesting: proto.Schema_NestedBlock_MAP,
Block: &proto.Schema_Block{},
},
{
TypeName: "set",
Nesting: proto.Schema_NestedBlock_SET,
Block: &proto.Schema_Block{},
},
{
TypeName: "single",
Nesting: proto.Schema_NestedBlock_SINGLE,
Block: &proto.Schema_Block{
Attributes: []*proto.Schema_Attribute{
{
Name: "foo",
Type: []byte(`"dynamic"`),
Required: true,
},
},
},
},
},
},
&configschema.Block{
BlockTypes: map[string]*configschema.NestedBlock{
"list": &configschema.NestedBlock{
Nesting: configschema.NestingList,
},
"map": &configschema.NestedBlock{
Nesting: configschema.NestingMap,
},
"set": &configschema.NestedBlock{
Nesting: configschema.NestingSet,
},
"single": &configschema.NestedBlock{
Nesting: configschema.NestingSingle,
Block: configschema.Block{
Attributes: map[string]*configschema.Attribute{
"foo": {
Type: cty.DynamicPseudoType,
Required: true,
},
},
},
},
},
},
{
Severity: proto.Diagnostic_WARNING,
Summary: "warning 2",
},
"deep block nesting": {
&proto.Schema_Block{
BlockTypes: []*proto.Schema_NestedBlock{
{
TypeName: "single",
Nesting: proto.Schema_NestedBlock_SINGLE,
Block: &proto.Schema_Block{
BlockTypes: []*proto.Schema_NestedBlock{
{
TypeName: "list",
Nesting: proto.Schema_NestedBlock_LIST,
Block: &proto.Schema_Block{
BlockTypes: []*proto.Schema_NestedBlock{
{
TypeName: "set",
Nesting: proto.Schema_NestedBlock_SET,
Block: &proto.Schema_Block{},
},
},
},
},
},
},
},
},
},
&configschema.Block{
BlockTypes: map[string]*configschema.NestedBlock{
"single": &configschema.NestedBlock{
Nesting: configschema.NestingSingle,
Block: configschema.Block{
BlockTypes: map[string]*configschema.NestedBlock{
"list": &configschema.NestedBlock{
Nesting: configschema.NestingList,
Block: configschema.Block{
BlockTypes: map[string]*configschema.NestedBlock{
"set": &configschema.NestedBlock{
Nesting: configschema.NestingSet,
},
},
},
},
},
},
},
},
},
{
Severity: proto.Diagnostic_ERROR,
Summary: "error 1",
},
{
Severity: proto.Diagnostic_ERROR,
Summary: "error 2",
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
converted := schemaBlock(tc.Block)
if !cmp.Equal(converted, tc.Want, typeComparer, valueComparer, equateEmpty) {
t.Fatal(cmp.Diff(converted, tc.Want, typeComparer, valueComparer, equateEmpty))
}
})
if !cmp.Equal(expected, diags) {
t.Fatal(cmp.Diff(expected, diags))
}
}

122
plugin/convert/schema.go Normal file
View File

@ -0,0 +1,122 @@
package convert
import (
"encoding/json"
"reflect"
"sort"
"github.com/hashicorp/terraform/configs/configschema"
"github.com/hashicorp/terraform/plugin/proto"
"github.com/hashicorp/terraform/providers"
)
// ConfigSchemaToProto takes a *configschema.Block and converts it to a
// proto.Schema_Block for a grpc response.
func ConfigSchemaToProto(b *configschema.Block) *proto.Schema_Block {
block := &proto.Schema_Block{}
for _, name := range sortedKeys(b.Attributes) {
a := b.Attributes[name]
attr := &proto.Schema_Attribute{
Name: name,
Description: a.Description,
Optional: a.Optional,
Computed: a.Computed,
Required: a.Required,
Sensitive: a.Sensitive,
}
ty, err := json.Marshal(a.Type)
if err != nil {
panic(err)
}
attr.Type = ty
block.Attributes = append(block.Attributes, attr)
}
for _, name := range sortedKeys(b.BlockTypes) {
b := b.BlockTypes[name]
block.BlockTypes = append(block.BlockTypes, protoSchemaNestedBlock(name, b))
}
return block
}
func protoSchemaNestedBlock(name string, b *configschema.NestedBlock) *proto.Schema_NestedBlock {
return &proto.Schema_NestedBlock{
TypeName: name,
Block: ConfigSchemaToProto(&b.Block),
Nesting: proto.Schema_NestedBlock_NestingMode(b.Nesting),
MinItems: int64(b.MinItems),
MaxItems: int64(b.MaxItems),
}
}
// ProtoToProviderSchema takes a proto.Schema and converts it to a providers.Schema.
func ProtoToProviderSchema(s *proto.Schema) providers.Schema {
return providers.Schema{
Version: uint64(s.Version),
Block: ProtoToConfigSchema(s.Block),
}
}
// ProtoToConfigSchema takes the GetSchcema_Block from a grpc response and converts it
// to a terraform *configschema.Block.
func ProtoToConfigSchema(b *proto.Schema_Block) *configschema.Block {
block := &configschema.Block{
Attributes: make(map[string]*configschema.Attribute),
BlockTypes: make(map[string]*configschema.NestedBlock),
}
for _, a := range b.Attributes {
attr := &configschema.Attribute{
Description: a.Description,
Required: a.Required,
Optional: a.Optional,
Computed: a.Computed,
Sensitive: a.Sensitive,
}
if err := json.Unmarshal(a.Type, &attr.Type); err != nil {
panic(err)
}
block.Attributes[a.Name] = attr
}
for _, b := range b.BlockTypes {
block.BlockTypes[b.TypeName] = schemaNestedBlock(b)
}
return block
}
func schemaNestedBlock(b *proto.Schema_NestedBlock) *configschema.NestedBlock {
nb := &configschema.NestedBlock{
Nesting: configschema.NestingMode(b.Nesting),
MinItems: int(b.MinItems),
MaxItems: int(b.MaxItems),
}
nested := ProtoToConfigSchema(b.Block)
nb.Block = *nested
return nb
}
// sortedKeys returns the lexically sorted keys from the given map. This is
// used to make schema conversions are deterministic. This panics if map keys
// are not a string.
func sortedKeys(m interface{}) []string {
v := reflect.ValueOf(m)
keys := make([]string, v.Len())
mapKeys := v.MapKeys()
for i, k := range mapKeys {
keys[i] = k.Interface().(string)
}
sort.Strings(keys)
return keys
}

View File

@ -0,0 +1,361 @@
package convert
import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/hashicorp/terraform/configs/configschema"
"github.com/hashicorp/terraform/plugin/proto"
"github.com/zclconf/go-cty/cty"
)
var (
equateEmpty = cmpopts.EquateEmpty()
typeComparer = cmp.Comparer(cty.Type.Equals)
valueComparer = cmp.Comparer(cty.Value.RawEquals)
)
// Test that we can convert configschema to protobuf types and back again.
func TestConvertSchemaBlocks(t *testing.T) {
tests := map[string]struct {
Block *proto.Schema_Block
Want *configschema.Block
}{
"attributes": {
&proto.Schema_Block{
Attributes: []*proto.Schema_Attribute{
{
Name: "computed",
Type: []byte(`["list","bool"]`),
Computed: true,
},
{
Name: "optional",
Type: []byte(`"string"`),
Optional: true,
},
{
Name: "optional_computed",
Type: []byte(`["map","bool"]`),
Optional: true,
Computed: true,
},
{
Name: "required",
Type: []byte(`"number"`),
Required: true,
},
},
},
&configschema.Block{
Attributes: map[string]*configschema.Attribute{
"computed": {
Type: cty.List(cty.Bool),
Computed: true,
},
"optional": {
Type: cty.String,
Optional: true,
},
"optional_computed": {
Type: cty.Map(cty.Bool),
Optional: true,
Computed: true,
},
"required": {
Type: cty.Number,
Required: true,
},
},
},
},
"blocks": {
&proto.Schema_Block{
BlockTypes: []*proto.Schema_NestedBlock{
{
TypeName: "list",
Nesting: proto.Schema_NestedBlock_LIST,
Block: &proto.Schema_Block{},
},
{
TypeName: "map",
Nesting: proto.Schema_NestedBlock_MAP,
Block: &proto.Schema_Block{},
},
{
TypeName: "set",
Nesting: proto.Schema_NestedBlock_SET,
Block: &proto.Schema_Block{},
},
{
TypeName: "single",
Nesting: proto.Schema_NestedBlock_SINGLE,
Block: &proto.Schema_Block{
Attributes: []*proto.Schema_Attribute{
{
Name: "foo",
Type: []byte(`"dynamic"`),
Required: true,
},
},
},
},
},
},
&configschema.Block{
BlockTypes: map[string]*configschema.NestedBlock{
"list": &configschema.NestedBlock{
Nesting: configschema.NestingList,
},
"map": &configschema.NestedBlock{
Nesting: configschema.NestingMap,
},
"set": &configschema.NestedBlock{
Nesting: configschema.NestingSet,
},
"single": &configschema.NestedBlock{
Nesting: configschema.NestingSingle,
Block: configschema.Block{
Attributes: map[string]*configschema.Attribute{
"foo": {
Type: cty.DynamicPseudoType,
Required: true,
},
},
},
},
},
},
},
"deep block nesting": {
&proto.Schema_Block{
BlockTypes: []*proto.Schema_NestedBlock{
{
TypeName: "single",
Nesting: proto.Schema_NestedBlock_SINGLE,
Block: &proto.Schema_Block{
BlockTypes: []*proto.Schema_NestedBlock{
{
TypeName: "list",
Nesting: proto.Schema_NestedBlock_LIST,
Block: &proto.Schema_Block{
BlockTypes: []*proto.Schema_NestedBlock{
{
TypeName: "set",
Nesting: proto.Schema_NestedBlock_SET,
Block: &proto.Schema_Block{},
},
},
},
},
},
},
},
},
},
&configschema.Block{
BlockTypes: map[string]*configschema.NestedBlock{
"single": &configschema.NestedBlock{
Nesting: configschema.NestingSingle,
Block: configschema.Block{
BlockTypes: map[string]*configschema.NestedBlock{
"list": &configschema.NestedBlock{
Nesting: configschema.NestingList,
Block: configschema.Block{
BlockTypes: map[string]*configschema.NestedBlock{
"set": &configschema.NestedBlock{
Nesting: configschema.NestingSet,
},
},
},
},
},
},
},
},
},
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
converted := ProtoToConfigSchema(tc.Block)
if !cmp.Equal(converted, tc.Want, typeComparer, valueComparer, equateEmpty) {
t.Fatal(cmp.Diff(converted, tc.Want, typeComparer, valueComparer, equateEmpty))
}
})
}
}
// Test that we can convert configschema to protobuf types and back again.
func TestConvertProtoSchemaBlocks(t *testing.T) {
tests := map[string]struct {
Want *proto.Schema_Block
Block *configschema.Block
}{
"attributes": {
&proto.Schema_Block{
Attributes: []*proto.Schema_Attribute{
{
Name: "computed",
Type: []byte(`["list","bool"]`),
Computed: true,
},
{
Name: "optional",
Type: []byte(`"string"`),
Optional: true,
},
{
Name: "optional_computed",
Type: []byte(`["map","bool"]`),
Optional: true,
Computed: true,
},
{
Name: "required",
Type: []byte(`"number"`),
Required: true,
},
},
},
&configschema.Block{
Attributes: map[string]*configschema.Attribute{
"computed": {
Type: cty.List(cty.Bool),
Computed: true,
},
"optional": {
Type: cty.String,
Optional: true,
},
"optional_computed": {
Type: cty.Map(cty.Bool),
Optional: true,
Computed: true,
},
"required": {
Type: cty.Number,
Required: true,
},
},
},
},
"blocks": {
&proto.Schema_Block{
BlockTypes: []*proto.Schema_NestedBlock{
{
TypeName: "list",
Nesting: proto.Schema_NestedBlock_LIST,
Block: &proto.Schema_Block{},
},
{
TypeName: "map",
Nesting: proto.Schema_NestedBlock_MAP,
Block: &proto.Schema_Block{},
},
{
TypeName: "set",
Nesting: proto.Schema_NestedBlock_SET,
Block: &proto.Schema_Block{},
},
{
TypeName: "single",
Nesting: proto.Schema_NestedBlock_SINGLE,
Block: &proto.Schema_Block{
Attributes: []*proto.Schema_Attribute{
{
Name: "foo",
Type: []byte(`"dynamic"`),
Required: true,
},
},
},
},
},
},
&configschema.Block{
BlockTypes: map[string]*configschema.NestedBlock{
"list": &configschema.NestedBlock{
Nesting: configschema.NestingList,
},
"map": &configschema.NestedBlock{
Nesting: configschema.NestingMap,
},
"set": &configschema.NestedBlock{
Nesting: configschema.NestingSet,
},
"single": &configschema.NestedBlock{
Nesting: configschema.NestingSingle,
Block: configschema.Block{
Attributes: map[string]*configschema.Attribute{
"foo": {
Type: cty.DynamicPseudoType,
Required: true,
},
},
},
},
},
},
},
"deep block nesting": {
&proto.Schema_Block{
BlockTypes: []*proto.Schema_NestedBlock{
{
TypeName: "single",
Nesting: proto.Schema_NestedBlock_SINGLE,
Block: &proto.Schema_Block{
BlockTypes: []*proto.Schema_NestedBlock{
{
TypeName: "list",
Nesting: proto.Schema_NestedBlock_LIST,
Block: &proto.Schema_Block{
BlockTypes: []*proto.Schema_NestedBlock{
{
TypeName: "set",
Nesting: proto.Schema_NestedBlock_SET,
Block: &proto.Schema_Block{},
},
},
},
},
},
},
},
},
},
&configschema.Block{
BlockTypes: map[string]*configschema.NestedBlock{
"single": &configschema.NestedBlock{
Nesting: configschema.NestingSingle,
Block: configschema.Block{
BlockTypes: map[string]*configschema.NestedBlock{
"list": &configschema.NestedBlock{
Nesting: configschema.NestingList,
Block: configschema.Block{
BlockTypes: map[string]*configschema.NestedBlock{
"set": &configschema.NestedBlock{
Nesting: configschema.NestingSet,
},
},
},
},
},
},
},
},
},
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
converted := ConfigSchemaToProto(tc.Block)
if !cmp.Equal(converted, tc.Want, typeComparer, equateEmpty) {
t.Fatal(cmp.Diff(converted, tc.Want, typeComparer, equateEmpty))
}
})
}
}

View File

@ -6,6 +6,7 @@ import (
"sync"
plugin "github.com/hashicorp/go-plugin"
"github.com/hashicorp/terraform/plugin/convert"
"github.com/hashicorp/terraform/plugin/proto"
"github.com/hashicorp/terraform/providers"
"github.com/hashicorp/terraform/version"
@ -116,14 +117,14 @@ func (p *GRPCProvider) GetSchema() (resp providers.GetSchemaResponse) {
return resp
}
resp.Provider = ProtoToProviderSchema(protoResp.Provider)
resp.Provider = convert.ProtoToProviderSchema(protoResp.Provider)
for name, res := range protoResp.ResourceSchemas {
resp.ResourceTypes[name] = ProtoToProviderSchema(res)
resp.ResourceTypes[name] = convert.ProtoToProviderSchema(res)
}
for name, data := range protoResp.DataSourceSchemas {
resp.DataSources[name] = ProtoToProviderSchema(data)
resp.DataSources[name] = convert.ProtoToProviderSchema(data)
}
p.schemas = resp
@ -148,7 +149,7 @@ func (p *GRPCProvider) ValidateProviderConfig(r providers.ValidateProviderConfig
resp.Diagnostics = resp.Diagnostics.Append(err)
return resp
}
resp.Diagnostics = resp.Diagnostics.Append(ProtoToDiagnostics(protoResp.Diagnostics))
resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics))
return resp
}
@ -172,7 +173,7 @@ func (p *GRPCProvider) ValidateResourceTypeConfig(r providers.ValidateResourceTy
return resp
}
resp.Diagnostics = resp.Diagnostics.Append(ProtoToDiagnostics(protoResp.Diagnostics))
resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics))
return resp
}
@ -195,7 +196,7 @@ func (p *GRPCProvider) ValidateDataSourceConfig(r providers.ValidateDataSourceCo
resp.Diagnostics = resp.Diagnostics.Append(err)
return resp
}
resp.Diagnostics = resp.Diagnostics.Append(ProtoToDiagnostics(protoResp.Diagnostics))
resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics))
return resp
}
@ -225,7 +226,7 @@ func (p *GRPCProvider) UpgradeResourceState(r providers.UpgradeResourceStateRequ
resp.UpgradedState = state
resp.Diagnostics = resp.Diagnostics.Append(ProtoToDiagnostics(protoResp.Diagnostics))
resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics))
return resp
}
@ -253,7 +254,7 @@ func (p *GRPCProvider) Configure(r providers.ConfigureRequest) (resp providers.C
resp.Diagnostics = resp.Diagnostics.Append(err)
return resp
}
resp.Diagnostics = resp.Diagnostics.Append(ProtoToDiagnostics(protoResp.Diagnostics))
resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics))
return resp
}
@ -289,7 +290,7 @@ func (p *GRPCProvider) ReadResource(r providers.ReadResourceRequest) (resp provi
return resp
}
resp.Diagnostics = resp.Diagnostics.Append(ProtoToDiagnostics(protoResp.Diagnostics))
resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics))
state, err := msgpack.Unmarshal(protoResp.NewState.Msgpack, resSchema.Block.ImpliedType())
if err != nil {
@ -328,7 +329,7 @@ func (p *GRPCProvider) PlanResourceChange(r providers.PlanResourceChangeRequest)
return resp
}
resp.Diagnostics = resp.Diagnostics.Append(ProtoToDiagnostics(protoResp.Diagnostics))
resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics))
state, err := msgpack.Unmarshal(protoResp.PlannedState.Msgpack, resSchema.Block.ImpliedType())
if err != nil {
@ -339,7 +340,7 @@ func (p *GRPCProvider) PlanResourceChange(r providers.PlanResourceChangeRequest)
resp.PlannedState = state
for _, p := range protoResp.RequiresReplace {
resp.RequiresReplace = append(resp.RequiresReplace, attributePath(p))
resp.RequiresReplace = append(resp.RequiresReplace, convert.AttributePathToPath(p))
}
resp.PlannedPrivate = protoResp.PlannedPrivate

View File

@ -8,6 +8,7 @@ import (
plugin "github.com/hashicorp/go-plugin"
"github.com/hashicorp/terraform/configs/configschema"
"github.com/hashicorp/terraform/plugin/convert"
"github.com/hashicorp/terraform/plugin/proto"
"github.com/hashicorp/terraform/provisioners"
"github.com/zclconf/go-cty/cty"
@ -66,7 +67,7 @@ func (p *GRPCProvisioner) GetSchema() (resp provisioners.GetSchemaResponse) {
return resp
}
resp.Provisioner = schemaBlock(protoResp.Provisioner.Block)
resp.Provisioner = convert.ProtoToConfigSchema(protoResp.Provisioner.Block)
p.schema = resp.Provisioner
@ -94,7 +95,7 @@ func (p *GRPCProvisioner) ValidateProvisionerConfig(r provisioners.ValidateProvi
resp.Diagnostics = resp.Diagnostics.Append(err)
return resp
}
resp.Diagnostics = resp.Diagnostics.Append(ProtoToDiagnostics(protoResp.Diagnostics))
resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics))
return resp
}
@ -142,7 +143,7 @@ func (p *GRPCProvisioner) ProvisionResource(r provisioners.ProvisionResourceRequ
}
if len(rcv.Diagnostics) > 0 {
resp.Diagnostics = resp.Diagnostics.Append(ProtoToDiagnostics(rcv.Diagnostics))
resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(rcv.Diagnostics))
break
}
}

View File

@ -5,6 +5,8 @@ import (
"testing"
"github.com/golang/mock/gomock"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/hashicorp/terraform/config/hcl2shim"
"github.com/hashicorp/terraform/plugin/proto"
"github.com/hashicorp/terraform/provisioners"
@ -15,6 +17,12 @@ import (
var _ provisioners.Interface = (*GRPCProvisioner)(nil)
var (
equateEmpty = cmpopts.EquateEmpty()
typeComparer = cmp.Comparer(cty.Type.Equals)
valueComparer = cmp.Comparer(cty.Value.RawEquals)
)
func mockProvisionerClient(t *testing.T) *mockproto.MockProvisionerClient {
ctrl := gomock.NewController(t)
client := mockproto.NewMockProvisionerClient(ctrl)