Merge pull request #16957 from hashicorp/jbardin/computed-diff

Restore ability for an empty string to be considered unset in a computed value
This commit is contained in:
James Bardin 2017-12-20 09:20:43 -05:00 committed by GitHub
commit a262a0e046
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 106 additions and 50 deletions

View File

@ -445,7 +445,7 @@ func (d *ResourceData) init() {
} }
func (d *ResourceData) diffChange( func (d *ResourceData) diffChange(
k string) (interface{}, interface{}, bool, bool) { k string) (interface{}, interface{}, bool, bool, bool) {
// Get the change between the state and the config. // Get the change between the state and the config.
o, n := d.getChange(k, getSourceState, getSourceConfig|getSourceExact) o, n := d.getChange(k, getSourceState, getSourceConfig|getSourceExact)
if !o.Exists { if !o.Exists {
@ -456,7 +456,7 @@ func (d *ResourceData) diffChange(
} }
// Return the old, new, and whether there is a change // Return the old, new, and whether there is a change
return o.Value, n.Value, !reflect.DeepEqual(o.Value, n.Value), n.Computed return o.Value, n.Value, !reflect.DeepEqual(o.Value, n.Value), n.Computed, false
} }
func (d *ResourceData) getChange( func (d *ResourceData) getChange(

View File

@ -236,8 +236,8 @@ func (d *ResourceDiff) clear(key string) error {
// diffChange helps to implement resourceDiffer and derives its change values // diffChange helps to implement resourceDiffer and derives its change values
// from ResourceDiff's own change data, in addition to existing diff, config, and state. // from ResourceDiff's own change data, in addition to existing diff, config, and state.
func (d *ResourceDiff) diffChange(key string) (interface{}, interface{}, bool, bool) { func (d *ResourceDiff) diffChange(key string) (interface{}, interface{}, bool, bool, bool) {
old, new := d.getChange(key) old, new, customized := d.getChange(key)
if !old.Exists { if !old.Exists {
old.Value = nil old.Value = nil
@ -246,7 +246,7 @@ func (d *ResourceDiff) diffChange(key string) (interface{}, interface{}, bool, b
new.Value = nil new.Value = nil
} }
return old.Value, new.Value, !reflect.DeepEqual(old.Value, new.Value), new.Computed return old.Value, new.Value, !reflect.DeepEqual(old.Value, new.Value), new.Computed, customized
} }
// SetNew is used to set a new diff value for the mentioned key. The value must // SetNew is used to set a new diff value for the mentioned key. The value must
@ -327,7 +327,7 @@ func (d *ResourceDiff) Get(key string) interface{} {
// results from the exact levels for the new diff, then from state and diff as // results from the exact levels for the new diff, then from state and diff as
// per normal. // per normal.
func (d *ResourceDiff) GetChange(key string) (interface{}, interface{}) { func (d *ResourceDiff) GetChange(key string) (interface{}, interface{}) {
old, new := d.getChange(key) old, new, _ := d.getChange(key)
return old.Value, new.Value return old.Value, new.Value
} }
@ -387,18 +387,17 @@ func (d *ResourceDiff) Id() string {
// This implementation differs from ResourceData's in the way that we first get // This implementation differs from ResourceData's in the way that we first get
// results from the exact levels for the new diff, then from state and diff as // results from the exact levels for the new diff, then from state and diff as
// per normal. // per normal.
func (d *ResourceDiff) getChange(key string) (getResult, getResult) { func (d *ResourceDiff) getChange(key string) (getResult, getResult, bool) {
old := d.get(strings.Split(key, "."), "state") old := d.get(strings.Split(key, "."), "state")
var new getResult var new getResult
for p := range d.updatedKeys { for p := range d.updatedKeys {
if childAddrOf(key, p) { if childAddrOf(key, p) {
new = d.getExact(strings.Split(key, "."), "newDiff") new = d.getExact(strings.Split(key, "."), "newDiff")
goto done return old, new, true
} }
} }
new = d.get(strings.Split(key, "."), "newDiff") new = d.get(strings.Split(key, "."), "newDiff")
done: return old, new, false
return old, new
} }
// get performs the appropriate multi-level reader logic for ResourceDiff, // get performs the appropriate multi-level reader logic for ResourceDiff,

View File

@ -296,8 +296,7 @@ func (s *Schema) ZeroValue() interface{} {
} }
} }
func (s *Schema) finalizeDiff( func (s *Schema) finalizeDiff(d *terraform.ResourceAttrDiff, customized bool) *terraform.ResourceAttrDiff {
d *terraform.ResourceAttrDiff) *terraform.ResourceAttrDiff {
if d == nil { if d == nil {
return d return d
} }
@ -337,14 +336,21 @@ func (s *Schema) finalizeDiff(
return d return d
} }
if s.Computed && !d.NewComputed { if s.Computed {
// FIXME: This is where the customized bool from getChange finally
// comes into play. It allows the previously incorrect behavior
// of an empty string being used as "unset" when the value is
// computed. This should be removed once we can properly
// represent an unset/nil value from the configuration.
if !customized {
if d.Old != "" && d.New == "" { if d.Old != "" && d.New == "" {
// This is a computed value with an old value set already, // This is a computed value with an old value set already,
// just let it go. // just let it go.
return nil return nil
} }
}
if d.New == "" { if d.New == "" && !d.NewComputed {
// Computed attribute without a new value set // Computed attribute without a new value set
d.NewComputed = true d.NewComputed = true
} }
@ -744,7 +750,7 @@ func isValidFieldName(name string) bool {
// This helps facilitate diff logic for both ResourceData and ResoureDiff with // This helps facilitate diff logic for both ResourceData and ResoureDiff with
// minimal divergence in code. // minimal divergence in code.
type resourceDiffer interface { type resourceDiffer interface {
diffChange(string) (interface{}, interface{}, bool, bool) diffChange(string) (interface{}, interface{}, bool, bool, bool)
Get(string) interface{} Get(string) interface{}
GetChange(string) (interface{}, interface{}) GetChange(string) (interface{}, interface{})
GetOk(string) (interface{}, bool) GetOk(string) (interface{}, bool)
@ -797,7 +803,7 @@ func (m schemaMap) diffList(
diff *terraform.InstanceDiff, diff *terraform.InstanceDiff,
d resourceDiffer, d resourceDiffer,
all bool) error { all bool) error {
o, n, _, computedList := d.diffChange(k) o, n, _, computedList, customized := d.diffChange(k)
if computedList { if computedList {
n = nil n = nil
} }
@ -864,10 +870,13 @@ func (m schemaMap) diffList(
oldStr = "" oldStr = ""
} }
diff.Attributes[k+".#"] = countSchema.finalizeDiff(&terraform.ResourceAttrDiff{ diff.Attributes[k+".#"] = countSchema.finalizeDiff(
&terraform.ResourceAttrDiff{
Old: oldStr, Old: oldStr,
New: newStr, New: newStr,
}) },
customized,
)
} }
// Figure out the maximum // Figure out the maximum
@ -920,7 +929,7 @@ func (m schemaMap) diffMap(
// First get all the values from the state // First get all the values from the state
var stateMap, configMap map[string]string var stateMap, configMap map[string]string
o, n, _, nComputed := d.diffChange(k) o, n, _, nComputed, customized := d.diffChange(k)
if err := mapstructure.WeakDecode(o, &stateMap); err != nil { if err := mapstructure.WeakDecode(o, &stateMap); err != nil {
return fmt.Errorf("%s: %s", k, err) return fmt.Errorf("%s: %s", k, err)
} }
@ -972,6 +981,7 @@ func (m schemaMap) diffMap(
Old: oldStr, Old: oldStr,
New: newStr, New: newStr,
}, },
customized,
) )
} }
@ -989,16 +999,22 @@ func (m schemaMap) diffMap(
continue continue
} }
diff.Attributes[prefix+k] = schema.finalizeDiff(&terraform.ResourceAttrDiff{ diff.Attributes[prefix+k] = schema.finalizeDiff(
&terraform.ResourceAttrDiff{
Old: old, Old: old,
New: v, New: v,
}) },
customized,
)
} }
for k, v := range stateMap { for k, v := range stateMap {
diff.Attributes[prefix+k] = schema.finalizeDiff(&terraform.ResourceAttrDiff{ diff.Attributes[prefix+k] = schema.finalizeDiff(
&terraform.ResourceAttrDiff{
Old: v, Old: v,
NewRemoved: true, NewRemoved: true,
}) },
customized,
)
} }
return nil return nil
@ -1011,7 +1027,7 @@ func (m schemaMap) diffSet(
d resourceDiffer, d resourceDiffer,
all bool) error { all bool) error {
o, n, _, computedSet := d.diffChange(k) o, n, _, computedSet, customized := d.diffChange(k)
if computedSet { if computedSet {
n = nil n = nil
} }
@ -1070,20 +1086,26 @@ func (m schemaMap) diffSet(
countStr = "" countStr = ""
} }
diff.Attributes[k+".#"] = countSchema.finalizeDiff(&terraform.ResourceAttrDiff{ diff.Attributes[k+".#"] = countSchema.finalizeDiff(
&terraform.ResourceAttrDiff{
Old: countStr, Old: countStr,
NewComputed: true, NewComputed: true,
}) },
customized,
)
return nil return nil
} }
// If the counts are not the same, then record that diff // If the counts are not the same, then record that diff
changed := oldLen != newLen changed := oldLen != newLen
if changed || all { if changed || all {
diff.Attributes[k+".#"] = countSchema.finalizeDiff(&terraform.ResourceAttrDiff{ diff.Attributes[k+".#"] = countSchema.finalizeDiff(
&terraform.ResourceAttrDiff{
Old: oldStr, Old: oldStr,
New: newStr, New: newStr,
}) },
customized,
)
} }
// Build the list of codes that will make up our set. This is the // Build the list of codes that will make up our set. This is the
@ -1133,7 +1155,7 @@ func (m schemaMap) diffString(
all bool) error { all bool) error {
var originalN interface{} var originalN interface{}
var os, ns string var os, ns string
o, n, _, computed := d.diffChange(k) o, n, _, computed, customized := d.diffChange(k)
if schema.StateFunc != nil && n != nil { if schema.StateFunc != nil && n != nil {
originalN = n originalN = n
n = schema.StateFunc(n) n = schema.StateFunc(n)
@ -1170,13 +1192,16 @@ func (m schemaMap) diffString(
return nil return nil
} }
diff.Attributes[k] = schema.finalizeDiff(&terraform.ResourceAttrDiff{ diff.Attributes[k] = schema.finalizeDiff(
&terraform.ResourceAttrDiff{
Old: os, Old: os,
New: ns, New: ns,
NewExtra: originalN, NewExtra: originalN,
NewRemoved: removed, NewRemoved: removed,
NewComputed: computed, NewComputed: computed,
}) },
customized,
)
return nil return nil
} }

View File

@ -3110,6 +3110,38 @@ func TestSchemaMap_Diff(t *testing.T) {
Err: true, Err: true,
}, },
// A lot of resources currently depended on using the empty string as a
// nil/unset value.
// FIXME: We want this to eventually produce a diff, since there
// technically is a new value in the config.
{
Name: "optional, computed, empty string",
Schema: map[string]*Schema{
"attr": &Schema{
Type: TypeString,
Optional: true,
Computed: true,
},
},
State: &terraform.InstanceState{
Attributes: map[string]string{
"attr": "bar",
},
},
// this does necessarily depend on an interpolated value, but this
// is often how it comes about in a configuration, otherwise the
// value would be unset.
Config: map[string]interface{}{
"attr": "${var.foo}",
},
ConfigVariables: map[string]ast.Variable{
"var.foo": interfaceToVariableSwallowError(""),
},
},
} }
for i, tc := range cases { for i, tc := range cases {