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)