audit: add hashstructure
This commit is contained in:
parent
6c1b75659a
commit
2a6bb96276
|
@ -0,0 +1,181 @@
|
|||
package audit
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/copystructure"
|
||||
"github.com/mitchellh/reflectwalk"
|
||||
)
|
||||
|
||||
// HashStructures takes an interface and hashes all the values within
|
||||
// the structure. Only _values_ are hashed: keys of objects are not.
|
||||
func HashStructure(s interface{}, cb HashCallback) (interface{}, error) {
|
||||
s, err := copystructure.Copy(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
walker := &hashWalker{Callback: cb}
|
||||
if err := reflectwalk.Walk(s, walker); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// HashCallback is the callback called for HashStructure to hash
|
||||
// a value.
|
||||
type HashCallback func(string) (string, error)
|
||||
|
||||
// hashWalker implements interfaces for the reflectwalk package
|
||||
// (github.com/mitchellh/reflectwalk) that can be used to automatically
|
||||
// replace primitives with a hashed value.
|
||||
type hashWalker struct {
|
||||
// Callback is the function to call with the primitive that is
|
||||
// to be hashed. If there is an error, walking will be halted
|
||||
// immediately and the error returned.
|
||||
Callback HashCallback
|
||||
|
||||
key []string
|
||||
lastValue reflect.Value
|
||||
loc reflectwalk.Location
|
||||
cs []reflect.Value
|
||||
csKey []reflect.Value
|
||||
csData interface{}
|
||||
sliceIndex int
|
||||
unknownKeys []string
|
||||
}
|
||||
|
||||
func (w *hashWalker) Enter(loc reflectwalk.Location) error {
|
||||
w.loc = loc
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *hashWalker) Exit(loc reflectwalk.Location) error {
|
||||
w.loc = reflectwalk.None
|
||||
|
||||
switch loc {
|
||||
case reflectwalk.Map:
|
||||
w.cs = w.cs[:len(w.cs)-1]
|
||||
case reflectwalk.MapValue:
|
||||
w.key = w.key[:len(w.key)-1]
|
||||
w.csKey = w.csKey[:len(w.csKey)-1]
|
||||
case reflectwalk.Slice:
|
||||
w.cs = w.cs[:len(w.cs)-1]
|
||||
case reflectwalk.SliceElem:
|
||||
w.csKey = w.csKey[:len(w.csKey)-1]
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *hashWalker) Map(m reflect.Value) error {
|
||||
w.cs = append(w.cs, m)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *hashWalker) MapElem(m, k, v reflect.Value) error {
|
||||
w.csData = k
|
||||
w.csKey = append(w.csKey, k)
|
||||
w.key = append(w.key, k.String())
|
||||
w.lastValue = v
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *hashWalker) Slice(s reflect.Value) error {
|
||||
w.cs = append(w.cs, s)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *hashWalker) SliceElem(i int, elem reflect.Value) error {
|
||||
w.csKey = append(w.csKey, reflect.ValueOf(i))
|
||||
w.sliceIndex = i
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *hashWalker) Primitive(v reflect.Value) error {
|
||||
if w.Callback == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// We don't touch map keys
|
||||
if w.loc == reflectwalk.MapKey {
|
||||
return nil
|
||||
}
|
||||
|
||||
setV := v
|
||||
|
||||
// We only care about strings
|
||||
if v.Kind() == reflect.Interface {
|
||||
setV = v
|
||||
v = v.Elem()
|
||||
}
|
||||
if v.Kind() != reflect.String {
|
||||
return nil
|
||||
}
|
||||
|
||||
replaceVal, err := w.Callback(v.Interface().(string))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error hashing value: %s", err)
|
||||
}
|
||||
|
||||
resultVal := reflect.ValueOf(replaceVal)
|
||||
switch w.loc {
|
||||
case reflectwalk.MapKey:
|
||||
m := w.cs[len(w.cs)-1]
|
||||
|
||||
// Delete the old value
|
||||
var zero reflect.Value
|
||||
m.SetMapIndex(w.csData.(reflect.Value), zero)
|
||||
|
||||
// Set the new key with the existing value
|
||||
m.SetMapIndex(resultVal, w.lastValue)
|
||||
|
||||
// Set the key to be the new key
|
||||
w.csData = resultVal
|
||||
case reflectwalk.MapValue:
|
||||
// If we're in a map, then the only way to set a map value is
|
||||
// to set it directly.
|
||||
m := w.cs[len(w.cs)-1]
|
||||
mk := w.csData.(reflect.Value)
|
||||
m.SetMapIndex(mk, resultVal)
|
||||
default:
|
||||
// Otherwise, we should be addressable
|
||||
setV.Set(resultVal)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *hashWalker) removeCurrent() {
|
||||
// Append the key to the unknown keys
|
||||
w.unknownKeys = append(w.unknownKeys, strings.Join(w.key, "."))
|
||||
|
||||
for i := 1; i <= len(w.cs); i++ {
|
||||
c := w.cs[len(w.cs)-i]
|
||||
switch c.Kind() {
|
||||
case reflect.Map:
|
||||
// Zero value so that we delete the map key
|
||||
var val reflect.Value
|
||||
|
||||
// Get the key and delete it
|
||||
k := w.csData.(reflect.Value)
|
||||
c.SetMapIndex(k, val)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
panic("No container found for removeCurrent")
|
||||
}
|
||||
|
||||
func (w *hashWalker) replaceCurrent(v reflect.Value) {
|
||||
c := w.cs[len(w.cs)-2]
|
||||
switch c.Kind() {
|
||||
case reflect.Map:
|
||||
// Get the key and delete it
|
||||
k := w.csKey[len(w.csKey)-1]
|
||||
c.SetMapIndex(k, v)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package audit
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHashWalker(t *testing.T) {
|
||||
replaceText := "foo"
|
||||
|
||||
cases := []struct {
|
||||
Input interface{}
|
||||
Output interface{}
|
||||
}{
|
||||
{
|
||||
map[string]interface{}{
|
||||
"hello": "foo",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"hello": replaceText,
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
map[string]interface{}{
|
||||
"hello": []interface{}{"world"},
|
||||
},
|
||||
map[string]interface{}{
|
||||
"hello": []interface{}{replaceText},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
output, err := HashStructure(tc.Input, func(string) (string, error) {
|
||||
return replaceText, nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s\n\n%#v", err, tc.Input)
|
||||
}
|
||||
if !reflect.DeepEqual(output, tc.Output) {
|
||||
t.Fatalf("bad:\n\n%#v\n\n%#v", tc.Input, output)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue