catch conversion errors in PrepareProviderConfig

Errors were being ignore with the intention that they would be caught
later in validation, but it turns out we nee dto catch those earlier.

The legacy schemas also allowed providers to set and empty string for a
bool value, which we need to handle here, since it's not being handled
from user input like a normal config value.
This commit is contained in:
James Bardin 2018-11-30 14:51:52 -05:00
parent 92e83e3bcf
commit 5f9b189fcf
2 changed files with 46 additions and 5 deletions

View File

@ -3,6 +3,7 @@ package plugin
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt"
"regexp" "regexp"
"sort" "sort"
"strconv" "strconv"
@ -94,7 +95,7 @@ func (s *GRPCProviderServer) PrepareProviderConfig(_ context.Context, req *proto
// lookup any required, top-level attributes that are Null, and see if we // lookup any required, top-level attributes that are Null, and see if we
// have a Default value available. // have a Default value available.
configVal, _ = cty.Transform(configVal, func(path cty.Path, val cty.Value) (cty.Value, error) { configVal, err = cty.Transform(configVal, func(path cty.Path, val cty.Value) (cty.Value, error) {
// we're only looking for top-level attributes // we're only looking for top-level attributes
if len(path) != 1 { if len(path) != 1 {
return val, nil return val, nil
@ -126,20 +127,36 @@ func (s *GRPCProviderServer) PrepareProviderConfig(_ context.Context, req *proto
// find a default value if it exists // find a default value if it exists
def, err := attrSchema.DefaultValue() def, err := attrSchema.DefaultValue()
if err != nil { if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, fmt.Errorf("error getting default for %q: %s", getAttr.Name, err))
return val, err return val, err
} }
// no default // no default
if def == nil { if def == nil {
return val, err return val, nil
} }
// create a cty.Value and make sure it's the correct type // create a cty.Value and make sure it's the correct type
tmpVal := hcl2shim.HCL2ValueFromConfigValue(def) tmpVal := hcl2shim.HCL2ValueFromConfigValue(def)
// helper/schema used to allow setting "" to a bool
if val.Type() == cty.Bool && tmpVal.RawEquals(cty.StringVal("")) {
// return a warning about the conversion
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, "provider set empty string as default value for bool "+getAttr.Name)
tmpVal = cty.False
}
val, err = ctyconvert.Convert(tmpVal, val.Type()) val, err = ctyconvert.Convert(tmpVal, val.Type())
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, fmt.Errorf("error setting default for %q: %s", getAttr.Name, err))
}
return val, err return val, err
}) })
if err != nil {
// any error here was already added to the diagnostics
return resp, nil
}
configVal, err = block.CoerceValue(configVal) configVal, err = block.CoerceValue(configVal)
if err != nil { if err != nil {

View File

@ -526,7 +526,7 @@ func TestPrepareProviderConfig(t *testing.T) {
Schema: map[string]*schema.Schema{ Schema: map[string]*schema.Schema{
"foo": &schema.Schema{ "foo": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Required: true, Optional: true,
Default: true, Default: true,
}, },
}, },
@ -537,12 +537,28 @@ func TestPrepareProviderConfig(t *testing.T) {
"foo": cty.StringVal("true"), "foo": cty.StringVal("true"),
}), }),
}, },
{
Name: "test incorrect default bool type",
Schema: map[string]*schema.Schema{
"foo": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: "",
},
},
ConfigVal: cty.ObjectVal(map[string]cty.Value{
"foo": cty.NullVal(cty.Bool),
}),
ExpectConfig: cty.ObjectVal(map[string]cty.Value{
"foo": cty.False,
}),
},
{ {
Name: "test deprecated default", Name: "test deprecated default",
Schema: map[string]*schema.Schema{ Schema: map[string]*schema.Schema{
"foo": &schema.Schema{ "foo": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Required: true, Optional: true,
Default: "do not use", Default: "do not use",
Removed: "don't use this", Removed: "don't use this",
}, },
@ -580,12 +596,20 @@ func TestPrepareProviderConfig(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
if tc.ExpectError == "" && len(resp.Diagnostics) > 0 { if tc.ExpectError != "" && len(resp.Diagnostics) > 0 {
for _, d := range resp.Diagnostics { for _, d := range resp.Diagnostics {
if !strings.Contains(d.Summary, tc.ExpectError) { if !strings.Contains(d.Summary, tc.ExpectError) {
t.Fatalf("Unexpected error: %s/%s", d.Summary, d.Detail) t.Fatalf("Unexpected error: %s/%s", d.Summary, d.Detail)
} }
} }
return
}
// we should have no errors past this point
for _, d := range resp.Diagnostics {
if d.Severity == proto.Diagnostic_ERROR {
t.Fatal(resp.Diagnostics)
}
} }
val, err := msgpack.Unmarshal(resp.PreparedConfig.Msgpack, block.ImpliedType()) val, err := msgpack.Unmarshal(resp.PreparedConfig.Msgpack, block.ImpliedType())