insert defaults during Backend.PrepareConfig

Lookup any defaults and insert them into the config value before
validation.
This commit is contained in:
James Bardin 2019-02-25 19:06:09 -05:00
parent c814f2da37
commit 37f391f1f7
2 changed files with 157 additions and 8 deletions

View File

@ -2,6 +2,7 @@ package schema
import (
"context"
"fmt"
"github.com/hashicorp/terraform/tfdiags"
"github.com/zclconf/go-cty/cty"
@ -9,6 +10,7 @@ import (
"github.com/hashicorp/terraform/config/hcl2shim"
"github.com/hashicorp/terraform/configs/configschema"
"github.com/hashicorp/terraform/terraform"
ctyconvert "github.com/zclconf/go-cty/cty/convert"
)
// Backend represents a partial backend.Backend implementation and simplifies
@ -49,13 +51,86 @@ func (b *Backend) ConfigSchema() *configschema.Block {
return b.CoreConfigSchema()
}
func (b *Backend) PrepareConfig(obj cty.Value) (cty.Value, tfdiags.Diagnostics) {
func (b *Backend) PrepareConfig(configVal cty.Value) (cty.Value, tfdiags.Diagnostics) {
if b == nil {
return obj, nil
return configVal, nil
}
var diags tfdiags.Diagnostics
var err error
// In order to use Transform below, this needs to be filled out completely
// according the schema.
configVal, err = b.CoreConfigSchema().CoerceValue(configVal)
if err != nil {
return configVal, diags.Append(err)
}
var diags tfdiags.Diagnostics
shimRC := b.shimConfig(obj)
// lookup any required, top-level attributes that are Null, and see if we
// have a Default value available.
configVal, err = 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 := b.Schema[getAttr.Name]
// continue to ignore anything that doesn't match
if attrSchema == nil {
return val, nil
}
// this is deprecated, so don't set it
if attrSchema.Deprecated != "" || attrSchema.Removed != "" {
return val, nil
}
// find a default value if it exists
def, err := attrSchema.DefaultValue()
if err != nil {
diags = diags.Append(fmt.Errorf("error getting default for %q: %s", getAttr.Name, err))
return val, err
}
// no default
if def == nil {
return val, nil
}
// create a cty.Value and make sure it's the correct type
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
diags = diags.Append("provider set empty string as default value for bool " + getAttr.Name)
tmpVal = cty.False
}
val, err = ctyconvert.Convert(tmpVal, val.Type())
if err != nil {
diags = diags.Append(fmt.Errorf("error setting default for %q: %s", getAttr.Name, err))
}
return val, err
})
if err != nil {
// any error here was already added to the diagnostics
return configVal, diags
}
shimRC := b.shimConfig(configVal)
warns, errs := schemaMap(b.Schema).Validate(shimRC)
for _, warn := range warns {
diags = diags.Append(tfdiags.SimpleWarning(warn))
@ -63,7 +138,7 @@ func (b *Backend) PrepareConfig(obj cty.Value) (cty.Value, tfdiags.Diagnostics)
for _, err := range errs {
diags = diags.Append(err)
}
return obj, diags
return configVal, diags
}
func (b *Backend) Configure(obj cty.Value) tfdiags.Diagnostics {

View File

@ -8,11 +8,12 @@ import (
"github.com/zclconf/go-cty/cty"
)
func TestBackendValidate(t *testing.T) {
func TestBackendPrepare(t *testing.T) {
cases := []struct {
Name string
B *Backend
Config map[string]cty.Value
Expect map[string]cty.Value
Err bool
}{
{
@ -26,6 +27,7 @@ func TestBackendValidate(t *testing.T) {
},
},
map[string]cty.Value{},
map[string]cty.Value{},
true,
},
@ -42,15 +44,87 @@ func TestBackendValidate(t *testing.T) {
map[string]cty.Value{
"foo": cty.StringVal("bar"),
},
map[string]cty.Value{
"foo": cty.StringVal("bar"),
},
false,
},
{
"unused default",
&Backend{
Schema: map[string]*Schema{
"foo": &Schema{
Optional: true,
Type: TypeString,
Default: "baz",
},
},
},
map[string]cty.Value{
"foo": cty.StringVal("bar"),
},
map[string]cty.Value{
"foo": cty.StringVal("bar"),
},
false,
},
{
"default",
&Backend{
Schema: map[string]*Schema{
"foo": &Schema{
Type: TypeString,
Optional: true,
Default: "baz",
},
},
},
map[string]cty.Value{},
map[string]cty.Value{
"foo": cty.StringVal("baz"),
},
false,
},
{
"default func",
&Backend{
Schema: map[string]*Schema{
"foo": &Schema{
Type: TypeString,
Optional: true,
DefaultFunc: func() (interface{}, error) {
return "baz", nil
},
},
},
},
map[string]cty.Value{},
map[string]cty.Value{
"foo": cty.StringVal("baz"),
},
false,
},
}
for i, tc := range cases {
t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) {
_, diags := tc.B.PrepareConfig(cty.ObjectVal(tc.Config))
configVal, diags := tc.B.PrepareConfig(cty.ObjectVal(tc.Config))
if diags.HasErrors() != tc.Err {
t.Errorf("wrong number of diagnostics")
for _, d := range diags {
t.Error(d.Description())
}
}
if tc.Err {
return
}
expect := cty.ObjectVal(tc.Expect)
if !expect.RawEquals(configVal) {
t.Fatalf("\nexpected: %#v\ngot: %#v\n", expect, configVal)
}
})
}