Merge pull request #9096 from hashicorp/f-resource-config

ResourceConfig.Equal, DeepCopy
This commit is contained in:
Mitchell Hashimoto 2016-09-28 15:27:18 -07:00 committed by GitHub
commit 88c3554dda
7 changed files with 121 additions and 24 deletions

View File

@ -179,13 +179,15 @@ func TestInterpolationWalker_replace(t *testing.T) {
return tc.Value, nil return tc.Value, nil
} }
w := &interpolationWalker{F: fn, Replace: true} t.Run(fmt.Sprintf("walk-%d", i), func(t *testing.T) {
if err := reflectwalk.Walk(tc.Input, w); err != nil { w := &interpolationWalker{F: fn, Replace: true}
t.Fatalf("err: %s", err) if err := reflectwalk.Walk(tc.Input, w); err != nil {
} t.Fatalf("err: %s", err)
}
if !reflect.DeepEqual(tc.Input, tc.Output) { if !reflect.DeepEqual(tc.Input, tc.Output) {
t.Fatalf("%d: bad:\n\nexpected:%#v\ngot:%#v", i, tc.Output, tc.Input) t.Fatalf("%d: bad:\n\nexpected:%#v\ngot:%#v", i, tc.Output, tc.Input)
} }
})
} }
} }

View File

@ -7,6 +7,7 @@ import (
"strings" "strings"
"github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/config"
"github.com/mitchellh/copystructure"
) )
// ResourceProvisionerConfig is used to pair a provisioner // ResourceProvisionerConfig is used to pair a provisioner
@ -93,6 +94,45 @@ func NewResourceConfig(c *config.RawConfig) *ResourceConfig {
return result return result
} }
// DeepCopy performs a deep copy of the configuration. This makes it safe
// to modify any of the structures that are part of the resource config without
// affecting the original configuration.
func (c *ResourceConfig) DeepCopy() *ResourceConfig {
// Copy, this will copy all the exported attributes
copy, err := copystructure.Config{Lock: true}.Copy(c)
if err != nil {
panic(err)
}
// Force the type
result := copy.(*ResourceConfig)
// For the raw configuration, we can just use its own copy method
result.raw = c.raw.Copy()
return result
}
// Equal checks the equality of two resource configs.
func (c *ResourceConfig) Equal(c2 *ResourceConfig) bool {
// Two resource configs if their exported properties are equal.
// We don't compare "raw" because it is never used again after
// initialization and for all intents and purposes they are equal
// if the exported properties are equal.
check := [][2]interface{}{
{c.ComputedKeys, c2.ComputedKeys},
{c.Raw, c2.Raw},
{c.Config, c2.Config},
}
for _, pair := range check {
if !reflect.DeepEqual(pair[0], pair[1]) {
return false
}
}
return true
}
// CheckSet checks that the given list of configuration keys is // CheckSet checks that the given list of configuration keys is
// properly set. If not, errors are returned for each unset key. // properly set. If not, errors are returned for each unset key.
// //

View File

@ -1,6 +1,7 @@
package terraform package terraform
import ( import (
"fmt"
"reflect" "reflect"
"testing" "testing"
@ -208,10 +209,33 @@ func TestResourceConfigGet(t *testing.T) {
rc := NewResourceConfig(rawC) rc := NewResourceConfig(rawC)
rc.interpolateForce() rc.interpolateForce()
v, _ := rc.Get(tc.Key) // Test getting a key
if !reflect.DeepEqual(v, tc.Value) { t.Run(fmt.Sprintf("get-%d", i), func(t *testing.T) {
t.Fatalf("%d bad: %#v", i, v) v, _ := rc.Get(tc.Key)
if !reflect.DeepEqual(v, tc.Value) {
t.Fatalf("%d bad: %#v", i, v)
}
})
// If we have vars, we don't test copying
if len(tc.Vars) > 0 {
continue
} }
// Test copying and equality
t.Run(fmt.Sprintf("copy-and-equal-%d", i), func(t *testing.T) {
copy := rc.DeepCopy()
if !reflect.DeepEqual(copy, rc) {
t.Fatalf("bad:\n\n%#v\n\n%#v", copy, rc)
}
if !copy.Equal(rc) {
t.Fatalf("copy != rc:\n\n%#v\n\n%#v", copy, rc)
}
if !rc.Equal(copy) {
t.Fatalf("rc != copy:\n\n%#v\n\n%#v", copy, rc)
}
})
} }
} }

View File

@ -3,6 +3,7 @@ package terraform
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"fmt"
"reflect" "reflect"
"strings" "strings"
"testing" "testing"
@ -272,11 +273,13 @@ func TestStateDeepCopy(t *testing.T) {
} }
for i, tc := range cases { for i, tc := range cases {
actual := tc.F(tc.One.DeepCopy()) t.Run(fmt.Sprintf("copy-%d", i), func(t *testing.T) {
expected := tc.F(tc.Two) actual := tc.F(tc.One.DeepCopy())
if !reflect.DeepEqual(actual, expected) { expected := tc.F(tc.Two)
t.Fatalf("Bad: %d\n\n%s\n\n%s", i, actual, expected) if !reflect.DeepEqual(actual, expected) {
} t.Fatalf("Bad: %d\n\n%s\n\n%s", i, actual, expected)
}
})
} }
} }

View File

@ -295,12 +295,24 @@ func (w *walker) StructField(f reflect.StructField, v reflect.Value) error {
return nil return nil
} }
// If PkgPath is non-empty, this is a private (unexported) field.
// We do not set this unexported since the Go runtime doesn't allow us.
if f.PkgPath != "" {
w.ignore()
return nil
}
// Push the field onto the stack, we'll handle it when we exit // Push the field onto the stack, we'll handle it when we exit
// the struct field in Exit... // the struct field in Exit...
w.valPush(reflect.ValueOf(f)) w.valPush(reflect.ValueOf(f))
return nil return nil
} }
// ignore causes the walker to ignore any more values until we exit this on
func (w *walker) ignore() {
w.ignoreDepth = w.depth
}
func (w *walker) ignoring() bool { func (w *walker) ignoring() bool {
return w.ignoreDepth > 0 && w.depth >= w.ignoreDepth return w.ignoreDepth > 0 && w.depth >= w.ignoreDepth
} }

View File

@ -4,9 +4,7 @@
// those elements. // those elements.
package reflectwalk package reflectwalk
import ( import "reflect"
"reflect"
)
// PrimitiveWalker implementations are able to handle primitive values // PrimitiveWalker implementations are able to handle primitive values
// within complex structures. Primitive values are numbers, strings, // within complex structures. Primitive values are numbers, strings,
@ -79,10 +77,26 @@ func Walk(data, walker interface{}) (err error) {
func walk(v reflect.Value, w interface{}) (err error) { func walk(v reflect.Value, w interface{}) (err error) {
// Determine if we're receiving a pointer and if so notify the walker. // Determine if we're receiving a pointer and if so notify the walker.
// The logic here is convoluted but very important (tests will fail if
// almost any part is changed). I will try to explain here.
//
// First, we check if the value is an interface, if so, we really need
// to check the interface's VALUE to see whether it is a pointer (pointers
// to interfaces are not allowed).
//
// Check whether the value is then an interface. If so, then set pointer
// to true to notify the user.
//
// At this time, we also set "v" to be the dereferenced value. This is
// because once we've unwrapped the pointer we want to use that value.
pointer := false pointer := false
if v.Kind() == reflect.Ptr { pointerV := v
if pointerV.Kind() == reflect.Interface {
pointerV = pointerV.Elem()
}
if pointerV.Kind() == reflect.Ptr {
pointer = true pointer = true
v = reflect.Indirect(v) v = reflect.Indirect(pointerV)
} }
if pw, ok := w.(PointerWalker); ok { if pw, ok := w.(PointerWalker); ok {
if err = pw.PointerEnter(pointer); err != nil { if err = pw.PointerEnter(pointer); err != nil {

10
vendor/vendor.json vendored
View File

@ -1472,10 +1472,10 @@
"revision": "8631ce90f28644f54aeedcb3e389a85174e067d1" "revision": "8631ce90f28644f54aeedcb3e389a85174e067d1"
}, },
{ {
"checksumSHA1": "Vfkp+PcZ1wZ4+D6AsHTpKkdsQG0=", "checksumSHA1": "EDAtec3XSbTjw6gWG+NNScows9M=",
"path": "github.com/mitchellh/copystructure", "path": "github.com/mitchellh/copystructure",
"revision": "501dcbdc7c358c4d0bfa066018834bedca79fde3", "revision": "49a4444999946bce1882f9db0eb3ba0a44ed1fbb",
"revisionTime": "2016-09-16T19:51:24Z" "revisionTime": "2016-09-28T02:49:35Z"
}, },
{ {
"path": "github.com/mitchellh/go-homedir", "path": "github.com/mitchellh/go-homedir",
@ -1507,8 +1507,10 @@
"revision": "6e6954073784f7ee67b28f2d22749d6479151ed7" "revision": "6e6954073784f7ee67b28f2d22749d6479151ed7"
}, },
{ {
"checksumSHA1": "kXh6sdGViiRK0REpIWydJvpsyY0=",
"path": "github.com/mitchellh/reflectwalk", "path": "github.com/mitchellh/reflectwalk",
"revision": "eecf4c70c626c7cfbb95c90195bc34d386c74ac6" "revision": "0c9480f65513be815a88d6076a3d8d95d4274236",
"revisionTime": "2016-09-28T02:49:03Z"
}, },
{ {
"checksumSHA1": "/iig5lYSPCL3C8J7e4nTAevYNDE=", "checksumSHA1": "/iig5lYSPCL3C8J7e4nTAevYNDE=",