diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 75dcfe8bf..299ea8d3c 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -599,6 +599,10 @@ "ImportPath": "github.com/mitchellh/go-linereader", "Rev": "07bab5fdd9580500aea6ada0e09df4aa28e68abd" }, + { + "ImportPath": "github.com/mitchellh/hashstructure", + "Rev": "6b17d669fac5e2f71c16658d781ec3fdd3802b69" + }, { "ImportPath": "github.com/mitchellh/mapstructure", "Rev": "281073eb9eb092240d33ef253c404f1cca550309" diff --git a/Makefile b/Makefile index b1cba7070..1666d0967 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ TEST?=$$(GO15VENDOREXPERIMENT=1 go list ./... | grep -v /vendor/) -VETARGS?=-asmdecl -atomic -bool -buildtags -copylocks -methods -nilfunc -printf -rangeloops -shift -structtags -unsafeptr +VETARGS?=-asmdecl -atomic -bool -buildtags -composites -copylocks -methods -nilfunc -printf -rangeloops -shift -structtags -unreachable -unsafeptr -unusedresult default: test @@ -75,7 +75,7 @@ vet: go get golang.org/x/tools/cmd/vet; \ fi @echo "go tool vet $(VETARGS) ." - @GO15VENDOREXPERIMENT=1 go tool vet $(VETARGS) . ; if [ $$? -eq 1 ]; then \ + @GO15VENDOREXPERIMENT=1 go tool vet $(VETARGS) $$(ls -d */ | grep -v vendor) ; if [ $$? -eq 1 ]; then \ echo ""; \ echo "Vet found suspicious constructs. Please check the reported constructs"; \ echo "and fix them if necessary before submitting the code for review."; \ diff --git a/builtin/providers/aws/resource_aws_cloudtrail_test.go b/builtin/providers/aws/resource_aws_cloudtrail_test.go index 722bcaab1..39f2f9a62 100644 --- a/builtin/providers/aws/resource_aws_cloudtrail_test.go +++ b/builtin/providers/aws/resource_aws_cloudtrail_test.go @@ -266,7 +266,7 @@ func testAccCheckCloudTrailLogValidationEnabled(n string, desired bool, trail *c if *trail.LogFileValidationEnabled != desired { return fmt.Errorf("Expected log validation status %t, given %t", desired, - trail.LogFileValidationEnabled) + *trail.LogFileValidationEnabled) } // local state comparison @@ -277,8 +277,7 @@ func testAccCheckCloudTrailLogValidationEnabled(n string, desired bool, trail *c } desiredInString := fmt.Sprintf("%t", desired) if enabled != desiredInString { - return fmt.Errorf("Expected log validation status %t, saved %t", desiredInString, - enabled) + return fmt.Errorf("Expected log validation status %s, saved %s", desiredInString, enabled) } return nil diff --git a/builtin/providers/aws/resource_aws_iam_group_membership_test.go b/builtin/providers/aws/resource_aws_iam_group_membership_test.go index cf63a0588..9fd65b5a6 100644 --- a/builtin/providers/aws/resource_aws_iam_group_membership_test.go +++ b/builtin/providers/aws/resource_aws_iam_group_membership_test.go @@ -18,7 +18,7 @@ func TestAccAWSGroupMembership_basic(t *testing.T) { rString := acctest.RandStringFromCharSet(10, acctest.CharSetAlpha) configBase := fmt.Sprintf(testAccAWSGroupMemberConfig, rString, rString, rString) - configUpdate := fmt.Sprintf(testAccAWSGroupMemberConfigUpdate, rString, rString, rString, rString) + configUpdate := fmt.Sprintf(testAccAWSGroupMemberConfigUpdate, rString, rString, rString, rString, rString) configUpdateDown := fmt.Sprintf(testAccAWSGroupMemberConfigUpdateDown, rString, rString, rString) testUser := fmt.Sprintf("test-user-%s", rString) @@ -115,7 +115,7 @@ func testAccCheckAWSGroupMembershipExists(n string, g *iam.GetGroupOutput) resou func testAccCheckAWSGroupMembershipAttributes(group *iam.GetGroupOutput, users []string) resource.TestCheckFunc { return func(s *terraform.State) error { if !strings.Contains(*group.Group.GroupName, "test-group") { - return fmt.Errorf("Bad group membership: expected %d, got %d", "test-group", *group.Group.GroupName) + return fmt.Errorf("Bad group membership: expected %s, got %s", "test-group", *group.Group.GroupName) } uc := len(users) diff --git a/builtin/providers/azure/resource_azure_sql_database_server_test.go b/builtin/providers/azure/resource_azure_sql_database_server_test.go index cee7cb4ed..46f6ae1cb 100644 --- a/builtin/providers/azure/resource_azure_sql_database_server_test.go +++ b/builtin/providers/azure/resource_azure_sql_database_server_test.go @@ -81,7 +81,7 @@ func testAccCheckAzureSqlDatabaseServerDeleted(s *terraform.State) error { for _, srv := range servers.DatabaseServers { if srv.Name == resource.Primary.ID { - fmt.Errorf("SQL Server %s still exists.", resource.Primary.ID) + return fmt.Errorf("SQL Server %s still exists.", resource.Primary.ID) } } } diff --git a/builtin/providers/azure/resource_azure_sql_database_service_test.go b/builtin/providers/azure/resource_azure_sql_database_service_test.go index 31ea8990e..f368abf68 100644 --- a/builtin/providers/azure/resource_azure_sql_database_service_test.go +++ b/builtin/providers/azure/resource_azure_sql_database_service_test.go @@ -156,7 +156,7 @@ func testAccCheckAzureSqlDatabaseServiceDeleted(s *terraform.State) error { for _, srv := range dbs.ServiceResources { if srv.Name == resource.Primary.ID { - fmt.Errorf("SQL Service %s still exists.", resource.Primary.ID) + return fmt.Errorf("SQL Service %s still exists.", resource.Primary.ID) } } } diff --git a/builtin/providers/digitalocean/resource_digitalocean_floating_ip_test.go b/builtin/providers/digitalocean/resource_digitalocean_floating_ip_test.go index ae53e1a89..5b57aa8dc 100644 --- a/builtin/providers/digitalocean/resource_digitalocean_floating_ip_test.go +++ b/builtin/providers/digitalocean/resource_digitalocean_floating_ip_test.go @@ -61,7 +61,7 @@ func testAccCheckDigitalOceanFloatingIPDestroy(s *terraform.State) error { _, _, err := client.FloatingIPs.Get(rs.Primary.ID) if err == nil { - fmt.Errorf("Floating IP still exists") + return fmt.Errorf("Floating IP still exists") } } diff --git a/builtin/providers/digitalocean/resource_digitalocean_ssh_key_test.go b/builtin/providers/digitalocean/resource_digitalocean_ssh_key_test.go index 3aebe1821..e14833fe9 100644 --- a/builtin/providers/digitalocean/resource_digitalocean_ssh_key_test.go +++ b/builtin/providers/digitalocean/resource_digitalocean_ssh_key_test.go @@ -51,7 +51,7 @@ func testAccCheckDigitalOceanSSHKeyDestroy(s *terraform.State) error { _, _, err = client.Keys.GetByID(id) if err == nil { - fmt.Errorf("SSH key still exists") + return fmt.Errorf("SSH key still exists") } } diff --git a/builtin/providers/google/resource_pubsub_subscription_test.go b/builtin/providers/google/resource_pubsub_subscription_test.go index 9cc0a218b..ad35e8e2c 100644 --- a/builtin/providers/google/resource_pubsub_subscription_test.go +++ b/builtin/providers/google/resource_pubsub_subscription_test.go @@ -36,7 +36,7 @@ func testAccCheckPubsubSubscriptionDestroy(s *terraform.State) error { config := testAccProvider.Meta().(*Config) _, err := config.clientPubsub.Projects.Subscriptions.Get(rs.Primary.ID).Do() if err != nil { - fmt.Errorf("Subscription still present") + return fmt.Errorf("Subscription still present") } } @@ -56,7 +56,7 @@ func testAccPubsubSubscriptionExists(n string) resource.TestCheckFunc { config := testAccProvider.Meta().(*Config) _, err := config.clientPubsub.Projects.Subscriptions.Get(rs.Primary.ID).Do() if err != nil { - fmt.Errorf("Subscription still present") + return fmt.Errorf("Subscription still present") } return nil diff --git a/builtin/providers/google/resource_pubsub_topic_test.go b/builtin/providers/google/resource_pubsub_topic_test.go index f81b9c21d..4305a1827 100644 --- a/builtin/providers/google/resource_pubsub_topic_test.go +++ b/builtin/providers/google/resource_pubsub_topic_test.go @@ -36,7 +36,7 @@ func testAccCheckPubsubTopicDestroy(s *terraform.State) error { config := testAccProvider.Meta().(*Config) _, err := config.clientPubsub.Projects.Topics.Get(rs.Primary.ID).Do() if err != nil { - fmt.Errorf("Topic still present") + return fmt.Errorf("Topic still present") } } @@ -56,7 +56,7 @@ func testAccPubsubTopicExists(n string) resource.TestCheckFunc { config := testAccProvider.Meta().(*Config) _, err := config.clientPubsub.Projects.Topics.Get(rs.Primary.ID).Do() if err != nil { - fmt.Errorf("Topic still present") + return fmt.Errorf("Topic still present") } return nil diff --git a/builtin/providers/heroku/resource_heroku_domain.go b/builtin/providers/heroku/resource_heroku_domain.go index b3c1de79f..f45d6795a 100644 --- a/builtin/providers/heroku/resource_heroku_domain.go +++ b/builtin/providers/heroku/resource_heroku_domain.go @@ -43,7 +43,7 @@ func resourceHerokuDomainCreate(d *schema.ResourceData, meta interface{}) error log.Printf("[DEBUG] Domain create configuration: %#v, %#v", app, hostname) - do, err := client.DomainCreate(app, heroku.DomainCreateOpts{hostname}) + do, err := client.DomainCreate(app, heroku.DomainCreateOpts{Hostname: hostname}) if err != nil { return err } diff --git a/builtin/providers/heroku/resource_heroku_drain.go b/builtin/providers/heroku/resource_heroku_drain.go index 6bbab1e18..9abe3cd9a 100644 --- a/builtin/providers/heroku/resource_heroku_drain.go +++ b/builtin/providers/heroku/resource_heroku_drain.go @@ -50,7 +50,7 @@ func resourceHerokuDrainCreate(d *schema.ResourceData, meta interface{}) error { var dr *heroku.LogDrain err := resource.Retry(2*time.Minute, func() error { - d, err := client.LogDrainCreate(app, heroku.LogDrainCreateOpts{url}) + d, err := client.LogDrainCreate(app, heroku.LogDrainCreateOpts{URL: url}) if err != nil { if strings.Contains(err.Error(), retryableError) { return err diff --git a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go index 4c71ebf66..2115c0b99 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go @@ -374,8 +374,8 @@ func resourceComputeInstanceV2Create(d *schema.ResourceData, meta interface{}) e if keyName, ok := d.Get("key_pair").(string); ok && keyName != "" { createOpts = &keypairs.CreateOptsExt{ - createOpts, - keyName, + CreateOptsBuilder: createOpts, + KeyName: keyName, } } @@ -384,8 +384,8 @@ func resourceComputeInstanceV2Create(d *schema.ResourceData, meta interface{}) e blockDeviceRaw := v.(map[string]interface{}) blockDevice := resourceInstanceBlockDeviceV2(d, blockDeviceRaw) createOpts = &bootfromvolume.CreateOptsExt{ - createOpts, - blockDevice, + CreateOptsBuilder: createOpts, + BlockDevice: blockDevice, } log.Printf("[DEBUG] Create BFV Options: %+v", createOpts) } @@ -396,8 +396,8 @@ func resourceComputeInstanceV2Create(d *schema.ResourceData, meta interface{}) e log.Printf("[DEBUG] schedulerhints: %+v", schedulerHintsRaw) schedulerHints := resourceInstanceSchedulerHintsV2(d, schedulerHintsRaw[0].(map[string]interface{})) createOpts = &schedulerhints.CreateOptsExt{ - createOpts, - schedulerHints, + CreateOptsBuilder: createOpts, + SchedulerHints: schedulerHints, } } diff --git a/builtin/providers/statuscake/resource_statuscaketest_test.go b/builtin/providers/statuscake/resource_statuscaketest_test.go index f5b47e470..bbc6932a8 100644 --- a/builtin/providers/statuscake/resource_statuscaketest_test.go +++ b/builtin/providers/statuscake/resource_statuscaketest_test.go @@ -93,7 +93,6 @@ func testAccTestCheckDestroy(test *statuscake.Test) resource.TestCheckFunc { return nil } - return nil } const testAccTestConfig_basic = ` diff --git a/builtin/providers/tls/resource_cert_request.go b/builtin/providers/tls/resource_cert_request.go index 7dd1430c6..267f0db39 100644 --- a/builtin/providers/tls/resource_cert_request.go +++ b/builtin/providers/tls/resource_cert_request.go @@ -107,7 +107,7 @@ func CreateCertRequest(d *schema.ResourceData, meta interface{}) error { certReqBytes, err := x509.CreateCertificateRequest(rand.Reader, &certReq, key) if err != nil { - fmt.Errorf("Error creating certificate request: %s", err) + return fmt.Errorf("Error creating certificate request: %s", err) } certReqPem := string(pem.EncodeToMemory(&pem.Block{Type: pemCertReqType, Bytes: certReqBytes})) diff --git a/builtin/providers/tls/resource_certificate.go b/builtin/providers/tls/resource_certificate.go index bfdc6eea7..d30efa720 100644 --- a/builtin/providers/tls/resource_certificate.go +++ b/builtin/providers/tls/resource_certificate.go @@ -159,7 +159,7 @@ func createCertificate(d *schema.ResourceData, template, parent *x509.Certificat certBytes, err := x509.CreateCertificate(rand.Reader, template, parent, pub, priv) if err != nil { - fmt.Errorf("error creating certificate: %s", err) + return fmt.Errorf("error creating certificate: %s", err) } certPem := string(pem.EncodeToMemory(&pem.Block{Type: pemCertType, Bytes: certBytes})) diff --git a/builtin/providers/vsphere/resource_vsphere_virtual_machine.go b/builtin/providers/vsphere/resource_vsphere_virtual_machine.go index c89c0b8dd..2b0667427 100644 --- a/builtin/providers/vsphere/resource_vsphere_virtual_machine.go +++ b/builtin/providers/vsphere/resource_vsphere_virtual_machine.go @@ -648,8 +648,8 @@ func buildNetworkDevice(f *find.Finder, label, adapterType string) (*types.Virtu return &types.VirtualDeviceConfigSpec{ Operation: types.VirtualDeviceConfigSpecOperationAdd, Device: &types.VirtualVmxnet3{ - types.VirtualVmxnet{ - types.VirtualEthernetCard{ + VirtualVmxnet: types.VirtualVmxnet{ + VirtualEthernetCard: types.VirtualEthernetCard{ VirtualDevice: types.VirtualDevice{ Key: -1, Backing: backing, @@ -663,7 +663,7 @@ func buildNetworkDevice(f *find.Finder, label, adapterType string) (*types.Virtu return &types.VirtualDeviceConfigSpec{ Operation: types.VirtualDeviceConfigSpecOperationAdd, Device: &types.VirtualE1000{ - types.VirtualEthernetCard{ + VirtualEthernetCard: types.VirtualEthernetCard{ VirtualDevice: types.VirtualDevice{ Key: -1, Backing: backing, @@ -922,7 +922,7 @@ func (vm *virtualMachine) createVirtualMachine(c *govmomi.Client) error { if d.Type == "StoragePod" { sp := object.StoragePod{ - object.NewFolder(c.Client, d), + Folder: object.NewFolder(c.Client, d), } sps := buildStoragePlacementSpecCreate(dcFolders, resourcePool, sp, configSpec) datastore, err = findDatastore(c, sps) @@ -1054,7 +1054,7 @@ func (vm *virtualMachine) deployVirtualMachine(c *govmomi.Client) error { if d.Type == "StoragePod" { sp := object.StoragePod{ - object.NewFolder(c.Client, d), + Folder: object.NewFolder(c.Client, d), } sps := buildStoragePlacementSpecClone(c, dcFolders, template, resourcePool, sp) datastore, err = findDatastore(c, sps) diff --git a/state/remote/atlas.go b/state/remote/atlas.go index 82b57d6c5..e3988e02c 100644 --- a/state/remote/atlas.go +++ b/state/remote/atlas.go @@ -217,8 +217,6 @@ func (c *AtlasClient) Delete() error { "HTTP error: %d\n\nBody: %s", resp.StatusCode, c.readBody(resp.Body)) } - - return fmt.Errorf("Unexpected HTTP response code %d", resp.StatusCode) } func (c *AtlasClient) readBody(b io.Reader) string { diff --git a/state/remote/consul.go b/state/remote/consul.go index 6a3894b68..8ac686f24 100644 --- a/state/remote/consul.go +++ b/state/remote/consul.go @@ -33,7 +33,10 @@ func consulFactory(conf map[string]string) (Client, error) { } else { username = auth } - config.HttpAuth = &consulapi.HttpBasicAuth{username, password} + config.HttpAuth = &consulapi.HttpBasicAuth{ + Username: username, + Password: password, + } } client, err := consulapi.NewClient(config) diff --git a/terraform/state_test.go b/terraform/state_test.go index 181360138..8351dc6ae 100644 --- a/terraform/state_test.go +++ b/terraform/state_test.go @@ -795,9 +795,6 @@ func TestReadWriteState(t *testing.T) { }, } - // Checksum before the write - chksum := checksumStruct(t, state) - buf := new(bytes.Buffer) if err := WriteState(state, buf); err != nil { t.Fatalf("err: %s", err) @@ -808,12 +805,6 @@ func TestReadWriteState(t *testing.T) { t.Fatalf("bad version number: %d", state.Version) } - // Checksum after the write - chksumAfter := checksumStruct(t, state) - if chksumAfter != chksum { - t.Fatalf("structure changed during serialization!") - } - actual, err := ReadState(buf) if err != nil { t.Fatalf("err: %s", err) diff --git a/terraform/state_v1_test.go b/terraform/state_v1_test.go index 0e7bc3aa1..c959e6efe 100644 --- a/terraform/state_v1_test.go +++ b/terraform/state_v1_test.go @@ -8,6 +8,8 @@ import ( "reflect" "sync" "testing" + + "github.com/mitchellh/hashstructure" ) func TestReadWriteStateV1(t *testing.T) { @@ -25,7 +27,10 @@ func TestReadWriteStateV1(t *testing.T) { } // Checksum before the write - chksum := checksumStruct(t, state) + chksum, err := hashstructure.Hash(state, nil) + if err != nil { + t.Fatalf("hash: %s", err) + } buf := new(bytes.Buffer) if err := testWriteStateV1(state, buf); err != nil { @@ -33,7 +38,11 @@ func TestReadWriteStateV1(t *testing.T) { } // Checksum after the write - chksumAfter := checksumStruct(t, state) + chksumAfter, err := hashstructure.Hash(state, nil) + if err != nil { + t.Fatalf("hash: %s", err) + } + if chksumAfter != chksum { t.Fatalf("structure changed during serialization!") } diff --git a/terraform/terraform_test.go b/terraform/terraform_test.go index 0fc0b71fe..09fb2370c 100644 --- a/terraform/terraform_test.go +++ b/terraform/terraform_test.go @@ -1,10 +1,6 @@ package terraform import ( - "bytes" - "crypto/sha1" - "encoding/gob" - "encoding/hex" "fmt" "io/ioutil" "os" @@ -21,21 +17,6 @@ import ( // This is the directory where our test fixtures are. const fixtureDir = "./test-fixtures" -func checksumStruct(t *testing.T, i interface{}) string { - // TODO(mitchellh): write a library to do this because gob is not - // deterministic in order - return "foo" - - buf := new(bytes.Buffer) - enc := gob.NewEncoder(buf) - if err := enc.Encode(i); err != nil { - t.Fatalf("err: %s", err) - } - - sum := sha1.Sum(buf.Bytes()) - return hex.EncodeToString(sum[:]) -} - func tempDir(t *testing.T) string { dir, err := ioutil.TempDir("", "tf") if err != nil { diff --git a/vendor/github.com/mitchellh/hashstructure/LICENSE b/vendor/github.com/mitchellh/hashstructure/LICENSE new file mode 100644 index 000000000..a3866a291 --- /dev/null +++ b/vendor/github.com/mitchellh/hashstructure/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Mitchell Hashimoto + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/github.com/mitchellh/hashstructure/README.md b/vendor/github.com/mitchellh/hashstructure/README.md new file mode 100644 index 000000000..7d0de5bf5 --- /dev/null +++ b/vendor/github.com/mitchellh/hashstructure/README.md @@ -0,0 +1,61 @@ +# hashstructure + +hashstructure is a Go library for creating a unique hash value +for arbitrary values in Go. + +This can be used to key values in a hash (for use in a map, set, etc.) +that are complex. The most common use case is comparing two values without +sending data across the network, caching values locally (de-dup), and so on. + +## Features + + * Hash any arbitrary Go value, including complex types. + + * Tag a struct field to ignore it and not affect the hash value. + + * Tag a slice type struct field to treat it as a set where ordering + doesn't affect the hash code but the field itself is still taken into + account to create the hash value. + + * Optionally specify a custom hash function to optimize for speed, collision + avoidance for your data set, etc. + +## Installation + +Standard `go get`: + +``` +$ go get github.com/mitchellh/hashstructure +``` + +## Usage & Example + +For usage and examples see the [Godoc](http://godoc.org/github.com/mitchellh/hashstructure). + +A quick code example is shown below: + + + type ComplexStruct struct { + Name string + Age uint + Metadata map[string]interface{} + } + + v := ComplexStruct{ + Name: "mitchellh", + Age: 64, + Metadata: map[string]interface{}{ + "car": true, + "location": "California", + "siblings": []string{"Bob", "John"}, + }, + } + + hash, err := hashstructure.Hash(v, nil) + if err != nil { + panic(err) + } + + fmt.Printf("%d", hash) + // Output: + // 2307517237273902113 diff --git a/vendor/github.com/mitchellh/hashstructure/hashstructure.go b/vendor/github.com/mitchellh/hashstructure/hashstructure.go new file mode 100644 index 000000000..6f586fa77 --- /dev/null +++ b/vendor/github.com/mitchellh/hashstructure/hashstructure.go @@ -0,0 +1,323 @@ +package hashstructure + +import ( + "encoding/binary" + "fmt" + "hash" + "hash/fnv" + "reflect" +) + +// HashOptions are options that are available for hashing. +type HashOptions struct { + // Hasher is the hash function to use. If this isn't set, it will + // default to FNV. + Hasher hash.Hash64 + + // TagName is the struct tag to look at when hashing the structure. + // By default this is "hash". + TagName string +} + +// Hash returns the hash value of an arbitrary value. +// +// If opts is nil, then default options will be used. See HashOptions +// for the default values. +// +// Notes on the value: +// +// * Unexported fields on structs are ignored and do not affect the +// hash value. +// +// * Adding an exported field to a struct with the zero value will change +// the hash value. +// +// For structs, the hashing can be controlled using tags. For example: +// +// struct { +// Name string +// UUID string `hash:"ignore"` +// } +// +// The available tag values are: +// +// * "ignore" - The field will be ignored and not affect the hash code. +// +// * "set" - The field will be treated as a set, where ordering doesn't +// affect the hash code. This only works for slices. +// +func Hash(v interface{}, opts *HashOptions) (uint64, error) { + // Create default options + if opts == nil { + opts = &HashOptions{} + } + if opts.Hasher == nil { + opts.Hasher = fnv.New64() + } + if opts.TagName == "" { + opts.TagName = "hash" + } + + // Reset the hash + opts.Hasher.Reset() + + // Create our walker and walk the structure + w := &walker{ + h: opts.Hasher, + tag: opts.TagName, + } + return w.visit(reflect.ValueOf(v), nil) +} + +type walker struct { + h hash.Hash64 + tag string +} + +type visitOpts struct { + // Flags are a bitmask of flags to affect behavior of this visit + Flags visitFlag + + // Information about the struct containing this field + Struct interface{} + StructField string +} + +func (w *walker) visit(v reflect.Value, opts *visitOpts) (uint64, error) { + // Loop since these can be wrapped in multiple layers of pointers + // and interfaces. + for { + // If we have an interface, dereference it. We have to do this up + // here because it might be a nil in there and the check below must + // catch that. + if v.Kind() == reflect.Interface { + v = v.Elem() + continue + } + + if v.Kind() == reflect.Ptr { + v = reflect.Indirect(v) + continue + } + + break + } + + // If it is nil, treat it like a zero. + if !v.IsValid() { + var tmp int8 + v = reflect.ValueOf(tmp) + } + + // Binary writing can use raw ints, we have to convert to + // a sized-int, we'll choose the largest... + switch v.Kind() { + case reflect.Int: + v = reflect.ValueOf(int64(v.Int())) + case reflect.Uint: + v = reflect.ValueOf(uint64(v.Uint())) + case reflect.Bool: + var tmp int8 + if v.Bool() { + tmp = 1 + } + v = reflect.ValueOf(tmp) + } + + k := v.Kind() + + // We can shortcut numeric values by directly binary writing them + if k >= reflect.Int && k <= reflect.Complex64 { + // A direct hash calculation + w.h.Reset() + err := binary.Write(w.h, binary.LittleEndian, v.Interface()) + return w.h.Sum64(), err + } + + switch k { + case reflect.Array: + var h uint64 + l := v.Len() + for i := 0; i < l; i++ { + current, err := w.visit(v.Index(i), nil) + if err != nil { + return 0, err + } + + h = hashUpdateOrdered(w.h, h, current) + } + + return h, nil + + case reflect.Map: + var includeMap IncludableMap + if opts != nil && opts.Struct != nil { + if v, ok := opts.Struct.(IncludableMap); ok { + includeMap = v + } + } + + // Build the hash for the map. We do this by XOR-ing all the key + // and value hashes. This makes it deterministic despite ordering. + var h uint64 + for _, k := range v.MapKeys() { + v := v.MapIndex(k) + if includeMap != nil { + incl, err := includeMap.HashIncludeMap( + opts.StructField, k.Interface(), v.Interface()) + if err != nil { + return 0, err + } + if !incl { + continue + } + } + + kh, err := w.visit(k, nil) + if err != nil { + return 0, err + } + vh, err := w.visit(v, nil) + if err != nil { + return 0, err + } + + fieldHash := hashUpdateOrdered(w.h, kh, vh) + h = hashUpdateUnordered(h, fieldHash) + } + + return h, nil + + case reflect.Struct: + var include Includable + parent := v.Interface() + if impl, ok := parent.(Includable); ok { + include = impl + } + + t := v.Type() + h, err := w.visit(reflect.ValueOf(t.Name()), nil) + if err != nil { + return 0, err + } + + l := v.NumField() + for i := 0; i < l; i++ { + if v := v.Field(i); v.CanSet() || t.Field(i).Name != "_" { + var f visitFlag + fieldType := t.Field(i) + if fieldType.PkgPath != "" { + // Unexported + continue + } + + tag := fieldType.Tag.Get(w.tag) + if tag == "ignore" { + // Ignore this field + continue + } + + // Check if we implement includable and check it + if include != nil { + incl, err := include.HashInclude(fieldType.Name, v) + if err != nil { + return 0, err + } + if !incl { + continue + } + } + + switch tag { + case "set": + f |= visitFlagSet + } + + kh, err := w.visit(reflect.ValueOf(fieldType.Name), nil) + if err != nil { + return 0, err + } + + vh, err := w.visit(v, &visitOpts{ + Flags: f, + Struct: parent, + StructField: fieldType.Name, + }) + if err != nil { + return 0, err + } + + fieldHash := hashUpdateOrdered(w.h, kh, vh) + h = hashUpdateUnordered(h, fieldHash) + } + } + + return h, nil + + case reflect.Slice: + // We have two behaviors here. If it isn't a set, then we just + // visit all the elements. If it is a set, then we do a deterministic + // hash code. + var h uint64 + var set bool + if opts != nil { + set = (opts.Flags & visitFlagSet) != 0 + } + l := v.Len() + for i := 0; i < l; i++ { + current, err := w.visit(v.Index(i), nil) + if err != nil { + return 0, err + } + + if set { + h = hashUpdateUnordered(h, current) + } else { + h = hashUpdateOrdered(w.h, h, current) + } + } + + return h, nil + + case reflect.String: + // Directly hash + w.h.Reset() + _, err := w.h.Write([]byte(v.String())) + return w.h.Sum64(), err + + default: + return 0, fmt.Errorf("unknown kind to hash: %s", k) + } + + return 0, nil +} + +func hashUpdateOrdered(h hash.Hash64, a, b uint64) uint64 { + // For ordered updates, use a real hash function + h.Reset() + + // We just panic if the binary writes fail because we are writing + // an int64 which should never be fail-able. + e1 := binary.Write(h, binary.LittleEndian, a) + e2 := binary.Write(h, binary.LittleEndian, b) + if e1 != nil { + panic(e1) + } + if e2 != nil { + panic(e2) + } + + return h.Sum64() +} + +func hashUpdateUnordered(a, b uint64) uint64 { + return a ^ b +} + +// visitFlag is used as a bitmask for affecting visit behavior +type visitFlag uint + +const ( + visitFlagInvalid visitFlag = iota + visitFlagSet = iota << 1 +) diff --git a/vendor/github.com/mitchellh/hashstructure/include.go b/vendor/github.com/mitchellh/hashstructure/include.go new file mode 100644 index 000000000..b6289c0be --- /dev/null +++ b/vendor/github.com/mitchellh/hashstructure/include.go @@ -0,0 +1,15 @@ +package hashstructure + +// Includable is an interface that can optionally be implemented by +// a struct. It will be called for each field in the struct to check whether +// it should be included in the hash. +type Includable interface { + HashInclude(field string, v interface{}) (bool, error) +} + +// IncludableMap is an interface that can optionally be implemented by +// a struct. It will be called when a map-type field is found to ask the +// struct if the map item should be included in the hash. +type IncludableMap interface { + HashIncludeMap(field string, k, v interface{}) (bool, error) +}