From c07ce1cd4bdb767a1fda02f8e1f32bcf32decbd6 Mon Sep 17 00:00:00 2001 From: James Bardin Date: Wed, 15 Aug 2018 12:03:39 -0400 Subject: [PATCH] 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. --- helper/plugin/diagnostics.go | 41 -- helper/plugin/diagnostics_test.go | 45 -- helper/plugin/grpc_provider.go | 89 +-- helper/plugin/grpc_provisioner.go | 17 +- helper/plugin/schema.go | 70 --- helper/plugin/schema_provider_test.go | 516 ------------------ helper/plugin/schema_provisioner_test.go | 338 ------------ helper/plugin/schema_test.go | 182 ------ plugin/conversions.go | 107 ---- plugin/convert/diagnostics.go | 88 +++ .../diagnostics_test.go} | 201 +------ plugin/convert/schema.go | 122 +++++ plugin/convert/schema_test.go | 361 ++++++++++++ plugin/grpc_provider.go | 23 +- plugin/grpc_provisioner.go | 7 +- plugin/grpc_provisioner_test.go | 8 + 16 files changed, 678 insertions(+), 1537 deletions(-) delete mode 100644 helper/plugin/diagnostics.go delete mode 100644 helper/plugin/diagnostics_test.go delete mode 100644 helper/plugin/schema.go delete mode 100644 helper/plugin/schema_provider_test.go delete mode 100644 helper/plugin/schema_provisioner_test.go delete mode 100644 helper/plugin/schema_test.go delete mode 100644 plugin/conversions.go create mode 100644 plugin/convert/diagnostics.go rename plugin/{conversions_test.go => convert/diagnostics_test.go} (62%) create mode 100644 plugin/convert/schema.go create mode 100644 plugin/convert/schema_test.go diff --git a/helper/plugin/diagnostics.go b/helper/plugin/diagnostics.go deleted file mode 100644 index b5be91db24..0000000000 --- a/helper/plugin/diagnostics.go +++ /dev/null @@ -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 -} diff --git a/helper/plugin/diagnostics_test.go b/helper/plugin/diagnostics_test.go deleted file mode 100644 index 22e50815b2..0000000000 --- a/helper/plugin/diagnostics_test.go +++ /dev/null @@ -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)) - } -} diff --git a/helper/plugin/grpc_provider.go b/helper/plugin/grpc_provider.go index fd08b39103..f12551d765 100644 --- a/helper/plugin/grpc_provider.go +++ b/helper/plugin/grpc_provider.go @@ -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 diff --git a/helper/plugin/grpc_provisioner.go b/helper/plugin/grpc_provisioner.go index f9036240b3..ec0f130e43 100644 --- a/helper/plugin/grpc_provisioner.go +++ b/helper/plugin/grpc_provisioner.go @@ -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 diff --git a/helper/plugin/schema.go b/helper/plugin/schema.go deleted file mode 100644 index d5a12db717..0000000000 --- a/helper/plugin/schema.go +++ /dev/null @@ -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 -} diff --git a/helper/plugin/schema_provider_test.go b/helper/plugin/schema_provider_test.go deleted file mode 100644 index d8c855e047..0000000000 --- a/helper/plugin/schema_provider_test.go +++ /dev/null @@ -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 -} diff --git a/helper/plugin/schema_provisioner_test.go b/helper/plugin/schema_provisioner_test.go deleted file mode 100644 index 5e9dd47793..0000000000 --- a/helper/plugin/schema_provisioner_test.go +++ /dev/null @@ -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") - } -} diff --git a/helper/plugin/schema_test.go b/helper/plugin/schema_test.go deleted file mode 100644 index d46924a59b..0000000000 --- a/helper/plugin/schema_test.go +++ /dev/null @@ -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)) - } - }) - } -} diff --git a/plugin/conversions.go b/plugin/conversions.go deleted file mode 100644 index f169faf70d..0000000000 --- a/plugin/conversions.go +++ /dev/null @@ -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 -} diff --git a/plugin/convert/diagnostics.go b/plugin/convert/diagnostics.go new file mode 100644 index 0000000000..e712b57be8 --- /dev/null +++ b/plugin/convert/diagnostics.go @@ -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 +} diff --git a/plugin/conversions_test.go b/plugin/convert/diagnostics_test.go similarity index 62% rename from plugin/conversions_test.go rename to plugin/convert/diagnostics_test.go index f9af3ca77e..fe5c59d8f1 100644 --- a/plugin/conversions_test.go +++ b/plugin/convert/diagnostics_test.go @@ -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)) } } diff --git a/plugin/convert/schema.go b/plugin/convert/schema.go new file mode 100644 index 0000000000..b29e475774 --- /dev/null +++ b/plugin/convert/schema.go @@ -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 +} diff --git a/plugin/convert/schema_test.go b/plugin/convert/schema_test.go new file mode 100644 index 0000000000..ba40e0a1bb --- /dev/null +++ b/plugin/convert/schema_test.go @@ -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)) + } + }) + } +} diff --git a/plugin/grpc_provider.go b/plugin/grpc_provider.go index 9da5331b96..443226417b 100644 --- a/plugin/grpc_provider.go +++ b/plugin/grpc_provider.go @@ -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 diff --git a/plugin/grpc_provisioner.go b/plugin/grpc_provisioner.go index 0f015c72f3..45a5db886d 100644 --- a/plugin/grpc_provisioner.go +++ b/plugin/grpc_provisioner.go @@ -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 } } diff --git a/plugin/grpc_provisioner_test.go b/plugin/grpc_provisioner_test.go index c26b3edef2..80329e8ee3 100644 --- a/plugin/grpc_provisioner_test.go +++ b/plugin/grpc_provisioner_test.go @@ -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)