mirror of
https://github.com/letic/terraform-provider-google.git
synced 2024-10-14 23:17:15 +00:00
961c878e0d
Switch to using Go modules. This migrates our vendor.json to use Go 1.11's modules system, and replaces the vendor folder with the output of go mod vendor. The vendored code should remain basically the same; I believe some tree shaking of packages and support scripts/licenses/READMEs/etc. happened. This also fixes Travis and our Makefile to no longer use govendor.
359 lines
7.8 KiB
Go
359 lines
7.8 KiB
Go
package hashstructure
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
"hash"
|
|
"hash/fnv"
|
|
"reflect"
|
|
)
|
|
|
|
// ErrNotStringer is returned when there's an error with hash:"string"
|
|
type ErrNotStringer struct {
|
|
Field string
|
|
}
|
|
|
|
// Error implements error for ErrNotStringer
|
|
func (ens *ErrNotStringer) Error() string {
|
|
return fmt.Sprintf("hashstructure: %s has hash:\"string\" set, but does not implement fmt.Stringer", ens.Field)
|
|
}
|
|
|
|
// 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
|
|
|
|
// ZeroNil is flag determining if nil pointer should be treated equal
|
|
// to a zero value of pointed type. By default this is false.
|
|
ZeroNil bool
|
|
}
|
|
|
|
// 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. The same *HashOptions value cannot be used
|
|
// concurrently. None of the values within a *HashOptions struct are
|
|
// safe to read/write while hashing is being done.
|
|
//
|
|
// 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" or "-" - 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.
|
|
//
|
|
// * "string" - The field will be hashed as a string, only works when the
|
|
// field implements fmt.Stringer
|
|
//
|
|
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,
|
|
zeronil: opts.ZeroNil,
|
|
}
|
|
return w.visit(reflect.ValueOf(v), nil)
|
|
}
|
|
|
|
type walker struct {
|
|
h hash.Hash64
|
|
tag string
|
|
zeronil bool
|
|
}
|
|
|
|
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) {
|
|
t := reflect.TypeOf(0)
|
|
|
|
// 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 {
|
|
if w.zeronil {
|
|
t = v.Type().Elem()
|
|
}
|
|
v = reflect.Indirect(v)
|
|
continue
|
|
}
|
|
|
|
break
|
|
}
|
|
|
|
// If it is nil, treat it like a zero.
|
|
if !v.IsValid() {
|
|
v = reflect.Zero(t)
|
|
}
|
|
|
|
// 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:
|
|
parent := v.Interface()
|
|
var include Includable
|
|
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 innerV := 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" || tag == "-" {
|
|
// Ignore this field
|
|
continue
|
|
}
|
|
|
|
// if string is set, use the string value
|
|
if tag == "string" {
|
|
if impl, ok := innerV.Interface().(fmt.Stringer); ok {
|
|
innerV = reflect.ValueOf(impl.String())
|
|
} else {
|
|
return 0, &ErrNotStringer{
|
|
Field: v.Type().Field(i).Name,
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check if we implement includable and check it
|
|
if include != nil {
|
|
incl, err := include.HashInclude(fieldType.Name, innerV)
|
|
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(innerV, &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)
|
|
}
|
|
|
|
}
|
|
|
|
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
|
|
)
|