From e077c9ce95ef1b8caf1b653b5c2a4a8740ab5b7b Mon Sep 17 00:00:00 2001 From: James Bardin Date: Thu, 18 Oct 2018 11:40:47 -0400 Subject: [PATCH] Insert default values into provider config Add any top-level default attributes from the provider schema into Null config values. --- helper/plugin/grpc_provider.go | 59 ++++++++++- helper/plugin/grpc_provider_test.go | 153 ++++++++++++++++++++++++++++ 2 files changed, 210 insertions(+), 2 deletions(-) diff --git a/helper/plugin/grpc_provider.go b/helper/plugin/grpc_provider.go index 1d48c9182..5cac34c35 100644 --- a/helper/plugin/grpc_provider.go +++ b/helper/plugin/grpc_provider.go @@ -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 } diff --git a/helper/plugin/grpc_provider_test.go b/helper/plugin/grpc_provider_test.go index 4e8c518ad..7b4f3c4d4 100644 --- a/helper/plugin/grpc_provider_test.go +++ b/helper/plugin/grpc_provider_test.go @@ -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) + } + }) + } +}