Insert default values into provider config

Add any top-level default attributes from the provider schema into Null
config values.
This commit is contained in:
James Bardin 2018-10-18 11:40:47 -04:00
parent ac5f08c5d8
commit e077c9ce95
2 changed files with 210 additions and 2 deletions

View File

@ -6,6 +6,7 @@ import (
"strconv"
"github.com/zclconf/go-cty/cty"
ctyconvert "github.com/zclconf/go-cty/cty/convert"
"github.com/zclconf/go-cty/cty/msgpack"
context "golang.org/x/net/context"
@ -88,13 +89,67 @@ func (s *GRPCProviderServer) PrepareProviderConfig(_ context.Context, req *proto
return resp, nil
}
// lookup any required, top-level attributes that are Null, and see if we
// have a Default value available.
configVal, _ = cty.Transform(configVal, func(path cty.Path, val cty.Value) (cty.Value, error) {
// we're only looking for top-level attributes
if len(path) != 1 {
return val, nil
}
// nothing to do if we already have a value
if !val.IsNull() {
return val, nil
}
// get the Schema definition for this attribute
getAttr, ok := path[0].(cty.GetAttrStep)
// these should all exist, but just ignore anything strange
if !ok {
return val, nil
}
attrSchema := s.provider.Schema[getAttr.Name]
// continue to ignore anything that doesn't match
if attrSchema == nil {
return val, nil
}
// find a default value if it exists
def, err := attrSchema.DefaultValue()
if err != nil {
return val, err
}
// no default
if def == nil {
return val, err
}
// create a cty.Value and make sure it's the correct type
tmpVal := hcl2shim.HCL2ValueFromConfigValue(def)
val, err = ctyconvert.Convert(tmpVal, val.Type())
return val, err
})
configVal, err = block.CoerceValue(configVal)
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
config := terraform.NewResourceConfigShimmed(configVal, block)
warns, errs := s.provider.Validate(config)
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, convert.WarnsAndErrsToProto(warns, errs))
// TODO: set defaults
resp.PreparedConfig = req.Config
preparedConfigMP, err := msgpack.Marshal(configVal, block.ImpliedType())
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
resp.PreparedConfig = &proto.DynamicValue{Msgpack: preparedConfigMP}
return resp, nil
}

View File

@ -3,6 +3,7 @@ package plugin
import (
"context"
"fmt"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
@ -425,3 +426,155 @@ func TestApplyResourceChange(t *testing.T) {
t.Fatalf("incorrect final state: %#v\n", newStateVal)
}
}
func TestPrepareProviderConfig(t *testing.T) {
for _, tc := range []struct {
Name string
Schema map[string]*schema.Schema
ConfigVal cty.Value
ExpectError string
ExpectConfig cty.Value
}{
{
Name: "test prepare",
Schema: map[string]*schema.Schema{
"foo": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
},
ConfigVal: cty.ObjectVal(map[string]cty.Value{
"foo": cty.StringVal("bar"),
}),
ExpectConfig: cty.ObjectVal(map[string]cty.Value{
"foo": cty.StringVal("bar"),
}),
},
{
Name: "test default",
Schema: map[string]*schema.Schema{
"foo": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "default",
},
},
ConfigVal: cty.ObjectVal(map[string]cty.Value{
"foo": cty.NullVal(cty.String),
}),
ExpectConfig: cty.ObjectVal(map[string]cty.Value{
"foo": cty.StringVal("default"),
}),
},
{
Name: "test defaultfunc",
Schema: map[string]*schema.Schema{
"foo": &schema.Schema{
Type: schema.TypeString,
Optional: true,
DefaultFunc: func() (interface{}, error) {
return "defaultfunc", nil
},
},
},
ConfigVal: cty.ObjectVal(map[string]cty.Value{
"foo": cty.NullVal(cty.String),
}),
ExpectConfig: cty.ObjectVal(map[string]cty.Value{
"foo": cty.StringVal("defaultfunc"),
}),
},
{
Name: "test default required",
Schema: map[string]*schema.Schema{
"foo": &schema.Schema{
Type: schema.TypeString,
Required: true,
DefaultFunc: func() (interface{}, error) {
return "defaultfunc", nil
},
},
},
ConfigVal: cty.ObjectVal(map[string]cty.Value{
"foo": cty.NullVal(cty.String),
}),
ExpectConfig: cty.ObjectVal(map[string]cty.Value{
"foo": cty.StringVal("defaultfunc"),
}),
},
{
Name: "test incorrect type",
Schema: map[string]*schema.Schema{
"foo": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
},
ConfigVal: cty.ObjectVal(map[string]cty.Value{
"foo": cty.NumberIntVal(3),
}),
ExpectConfig: cty.ObjectVal(map[string]cty.Value{
"foo": cty.StringVal("3"),
}),
},
{
Name: "test incorrect default type",
Schema: map[string]*schema.Schema{
"foo": &schema.Schema{
Type: schema.TypeString,
Required: true,
Default: true,
},
},
ConfigVal: cty.ObjectVal(map[string]cty.Value{
"foo": cty.NullVal(cty.String),
}),
ExpectConfig: cty.ObjectVal(map[string]cty.Value{
"foo": cty.StringVal("true"),
}),
},
} {
t.Run(tc.Name, func(t *testing.T) {
server := &GRPCProviderServer{
provider: &schema.Provider{
Schema: tc.Schema,
},
}
block := schema.InternalMap(tc.Schema).CoreConfigSchema()
rawConfig, err := msgpack.Marshal(tc.ConfigVal, block.ImpliedType())
if err != nil {
t.Fatal(err)
}
testReq := &proto.PrepareProviderConfig_Request{
Config: &proto.DynamicValue{
Msgpack: rawConfig,
},
}
resp, err := server.PrepareProviderConfig(nil, testReq)
if err != nil {
t.Fatal(err)
}
if tc.ExpectError == "" && len(resp.Diagnostics) > 0 {
for _, d := range resp.Diagnostics {
if !strings.Contains(d.Summary, tc.ExpectError) {
t.Fatalf("Unexpected error: %s/%s", d.Summary, d.Detail)
}
}
}
val, err := msgpack.Unmarshal(resp.PreparedConfig.Msgpack, block.ImpliedType())
if err != nil {
t.Fatal(err)
}
if tc.ExpectConfig.GoString() != val.GoString() {
t.Fatalf("\nexpected: %#v\ngot: %#v", tc.ExpectConfig, val)
}
})
}
}