refactor hasher

This commit is contained in:
Donny Xia
2020-07-28 15:03:05 -07:00
parent f205641498
commit b61553e584
4 changed files with 457 additions and 342 deletions

View File

@@ -8,6 +8,8 @@ import (
"encoding/json"
"fmt"
"sort"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
// SortArrayAndComputeHash sorts a string array and
@@ -50,3 +52,141 @@ func Encode(hex string) (string, error) {
func Hash(data string) string {
return fmt.Sprintf("%x", sha256.Sum256([]byte(data)))
}
// HashRNode returns the hash value of input RNode
func HashRNode(node *yaml.RNode) (string, error) {
// get node kind
kindNode, err := node.Pipe(yaml.FieldMatcher{Name: "kind"})
if err != nil {
return "", err
}
kind := kindNode.YNode().Value
// calculate hash for different kinds
switch kind {
case "ConfigMap":
return configMapHash(node)
case "Secret":
return secretHash(node)
default:
return defaultHash(node)
}
}
// configMapHash returns a hash of the ConfigMap.
// The Data, Kind, and Name are taken into account.
func configMapHash(node *yaml.RNode) (string, error) {
encoded, err := encodeConfigMap(node)
if err != nil {
return "", err
}
h, err := Encode(Hash(encoded))
if err != nil {
return "", err
}
return h, nil
}
// SecretHash returns a hash of the Secret.
// The Data, Kind, Name, and Type are taken into account.
func secretHash(node *yaml.RNode) (string, error) {
encoded, err := encodeSecret(node)
if err != nil {
return "", err
}
h, err := Encode(Hash(encoded))
if err != nil {
return "", err
}
return h, nil
}
// unstructuredHash creates a hash for an arbitrary type.
// All fields of the object are taken into account when generating the hash.
// This is a fallback for when a specalised hash for the type is unavailable.
func defaultHash(node *yaml.RNode) (string, error) {
encoded, err := json.Marshal(node.YNode())
if err != nil {
return "", err
}
h, err := Encode(Hash(string(encoded)))
if err != nil {
return "", err
}
return h, nil
}
func getNodeValues(node *yaml.RNode, paths []string) (map[string]interface{}, error) {
values := make(map[string]interface{})
for _, p := range paths {
vn, err := node.Pipe(yaml.Lookup(p))
if err != nil {
return map[string]interface{}{}, err
}
if vn == nil {
values[p] = ""
continue
}
if vn.YNode().Kind != yaml.ScalarNode {
vs, err := vn.MarshalJSON()
if err != nil {
return map[string]interface{}{}, err
}
// data, binaryData and stringData are all maps
var v map[string]interface{}
json.Unmarshal(vs, &v)
values[p] = v
} else {
values[p] = vn.YNode().Value
}
}
return values, nil
}
// encodeConfigMap encodes a ConfigMap.
// Data, Kind, and Name are taken into account.
// BinaryData is included if it's not empty to avoid useless key in output.
func encodeConfigMap(node *yaml.RNode) (string, error) {
// get fields
paths := []string{"metadata/name", "data", "binaryData"}
values, err := getNodeValues(node, paths)
if err != nil {
return "", err
}
m := map[string]interface{}{"kind": "ConfigMap", "name": values["metadata/name"],
"data": values["data"]}
if _, ok := values["binaryData"].(map[string]interface{}); ok {
m["binaryData"] = values["binaryData"]
}
// json.Marshal sorts the keys in a stable order in the encoding
data, err := json.Marshal(m)
if err != nil {
return "", err
}
return string(data), nil
}
// encodeSecret encodes a Secret.
// Data, Kind, Name, and Type are taken into account.
// StringData is included if it's not empty to avoid useless key in output.
func encodeSecret(node *yaml.RNode) (string, error) {
// get fields
paths := []string{"type", "metadata/name", "data", "stringData"}
values, err := getNodeValues(node, paths)
if err != nil {
return "", err
}
m := map[string]interface{}{"kind": "Secret", "type": values["type"],
"name": values["metadata/name"], "data": values["data"]}
if _, ok := values["stringData"].(map[string]interface{}); ok {
m["stringData"] = values["stringData"]
}
// json.Marshal sorts the keys in a stable order in the encoding
data, err := json.Marshal(m)
if err != nil {
return "", err
}
return string(data), nil
}