diff --git a/api/hasher/hasher.go b/api/hasher/hasher.go index fe54a1025..36d930af6 100644 --- a/api/hasher/hasher.go +++ b/api/hasher/hasher.go @@ -8,6 +8,8 @@ import ( "encoding/json" "fmt" "sort" + + "sigs.k8s.io/kustomize/kyaml/yaml" ) // SortArrayAndComputeHash sorts a string array and @@ -50,3 +52,105 @@ 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 + encoded := "" + switch kind { + case "ConfigMap": + encoded, err = encodeConfigMap(node) + case "Secret": + encoded, err = encodeSecret(node) + default: + var encodedBytes []byte + encodedBytes, err = json.Marshal(node.YNode()) + encoded = string(encodedBytes) + } + if err != nil { + return "", err + } + return Encode(Hash(encoded)) +} + +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 +} diff --git a/api/hasher/hasher_test.go b/api/hasher/hasher_test.go index 4cd8e2f57..f7e0c512e 100644 --- a/api/hasher/hasher_test.go +++ b/api/hasher/hasher_test.go @@ -1,12 +1,13 @@ // Copyright 2019 The Kubernetes Authors. // SPDX-License-Identifier: Apache-2.0 -package hasher_test +package hasher import ( + "strings" "testing" - . "sigs.k8s.io/kustomize/api/hasher" + "sigs.k8s.io/kustomize/kyaml/yaml" ) func TestSortArrayAndComputeHash(t *testing.T) { @@ -39,3 +40,314 @@ func TestHash(t *testing.T) { t.Errorf("expected hash %q but got %q", expect, sum) } } + +func TestConfigMapHash(t *testing.T) { + cases := []struct { + desc string + cmYaml string + hash string + err string + }{ + // empty map + {"empty data", ` +apiVersion: v1 +kind: ConfigMap`, "6ct58987ht", ""}, + // one key + {"one key", ` +apiVersion: v1 +kind: ConfigMap +data: + one: ""`, "9g67k2htb6", ""}, + // three keys (tests sorting order) + {"three keys", ` +apiVersion: v1 +kind: ConfigMap +data: + two: 2 + one: "" + three: 3`, "7757f9kkct", ""}, + // empty binary data map + {"empty binary data", ` +apiVersion: v1 +kind: ConfigMap`, "6ct58987ht", ""}, + // one key with binary data + {"one key with binary data", ` +apiVersion: v1 +kind: ConfigMap +binaryData: + one: ""`, "6mtk2m274t", ""}, + // three keys with binary data (tests sorting order) + {"three keys with binary data", ` +apiVersion: v1 +kind: ConfigMap +binaryData: + two: 2 + one: "" + three: 3`, "9th7kc28dg", ""}, + // two keys, one with string and another with binary data + {"two keys with one each", ` +apiVersion: v1 +kind: ConfigMap +data: + one: "" +binaryData: + two: ""`, "698h7c7t9m", ""}, + } + + for _, c := range cases { + node, err := yaml.Parse(c.cmYaml) + if err != nil { + t.Fatal(err) + } + h, err := HashRNode(node) + if SkipRest(t, c.desc, err, c.err) { + continue + } + if c.hash != h { + t.Errorf("case %q, expect hash %q but got %q", c.desc, c.hash, h) + } + } +} + +func TestSecretHash(t *testing.T) { + cases := []struct { + desc string + secretYaml string + hash string + err string + }{ + // empty map + {"empty data", ` +apiVersion: v1 +kind: Secret +type: my-type`, "5gmgkf8578", ""}, + // one key + {"one key", ` +apiVersion: v1 +kind: Secret +type: my-type +data: + one: ""`, "74bd68bm66", ""}, + // three keys (tests sorting order) + {"three keys", ` +apiVersion: v1 +kind: Secret +type: my-type +data: + two: 2 + one: "" + three: 3`, "4gf75c7476", ""}, + // with stringdata + {"stringdata", ` +apiVersion: v1 +kind: Secret +type: my-type +data: + one: "" +stringData: + two: 2`, "c4h4264gdb", ""}, + // empty stringdata + {"empty stringdata", ` +apiVersion: v1 +kind: Secret +type: my-type +data: + one: ""`, "74bd68bm66", ""}, + } + + for _, c := range cases { + node, err := yaml.Parse(c.secretYaml) + if err != nil { + t.Fatal(err) + } + h, err := HashRNode(node) + if SkipRest(t, c.desc, err, c.err) { + continue + } + if c.hash != h { + t.Errorf("case %q, expect hash %q but got %q", c.desc, c.hash, h) + } + } +} + +func TestUnstructuredHash(t *testing.T) { + cases := []struct { + desc string + unstructured string + hash string + err string + }{ + {"minimal", ` +apiVersion: test/v1 +kind: TestResource +metadata: + name: my-resource`, "244782mkb7", ""}, + {"with spec", ` +apiVersion: test/v1 +kind: TestResource +metadata: + name: my-resource +spec: + foo: 1 + bar: abc`, "59m2mdccg4", ""}, + } + + for _, c := range cases { + node, err := yaml.Parse(c.unstructured) + if err != nil { + t.Fatal(err) + } + h, err := HashRNode(node) + if SkipRest(t, c.desc, err, c.err) { + continue + } + if c.hash != h { + t.Errorf("case %q, expect hash %q but got %q", c.desc, c.hash, h) + } + } +} + +func TestEncodeConfigMap(t *testing.T) { + cases := []struct { + desc string + cmYaml string + expect string + err string + }{ + // empty map + {"empty data", ` +apiVersion: v1 +kind: ConfigMap`, `{"data":"","kind":"ConfigMap","name":""}`, ""}, + // one key + {"one key", ` +apiVersion: v1 +kind: ConfigMap +data: + one: ""`, `{"data":{"one":""},"kind":"ConfigMap","name":""}`, ""}, + // three keys (tests sorting order) + {"three keys", ` +apiVersion: v1 +kind: ConfigMap +data: + two: 2 + one: "" + three: 3`, `{"data":{"one":"","three":3,"two":2},"kind":"ConfigMap","name":""}`, ""}, + // empty binary map + {"empty data", ` +apiVersion: v1 +kind: ConfigMap`, `{"data":"","kind":"ConfigMap","name":""}`, ""}, + // one key with binary data + {"one key", ` +apiVersion: v1 +kind: ConfigMap +binaryData: + one: ""`, `{"binaryData":{"one":""},"data":"","kind":"ConfigMap","name":""}`, ""}, + // three keys with binary data (tests sorting order) + {"three keys", ` +apiVersion: v1 +kind: ConfigMap +binaryData: + two: 2 + one: "" + three: 3`, `{"binaryData":{"one":"","three":3,"two":2},"data":"","kind":"ConfigMap","name":""}`, ""}, + // two keys, one string and one binary values + {"two keys with one each", ` +apiVersion: v1 +kind: ConfigMap +data: + one: "" +binaryData: + two: ""`, `{"binaryData":{"two":""},"data":{"one":""},"kind":"ConfigMap","name":""}`, ""}, + } + for _, c := range cases { + node, err := yaml.Parse(c.cmYaml) + if err != nil { + t.Fatal(err) + } + s, err := encodeConfigMap(node) + if SkipRest(t, c.desc, err, c.err) { + continue + } + if s != c.expect { + t.Errorf("case %q, expect %q but got %q from encode %#v", c.desc, c.expect, s, c.cmYaml) + } + } +} + +func TestEncodeSecret(t *testing.T) { + cases := []struct { + desc string + secretYaml string + expect string + err string + }{ + // empty map + {"empty data", ` +apiVersion: v1 +kind: Secret +type: my-type`, `{"data":"","kind":"Secret","name":"","type":"my-type"}`, ""}, + // one key + {"one key", ` +apiVersion: v1 +kind: Secret +type: my-type +data: + one: ""`, `{"data":{"one":""},"kind":"Secret","name":"","type":"my-type"}`, ""}, + // three keys (tests sorting order) - note json.Marshal base64 encodes the values because they come in as []byte + {"three keys", ` +apiVersion: v1 +kind: Secret +type: my-type +data: + two: 2 + one: "" + three: 3`, `{"data":{"one":"","three":3,"two":2},"kind":"Secret","name":"","type":"my-type"}`, ""}, + // with stringdata + {"stringdata", ` +apiVersion: v1 +kind: Secret +type: my-type +data: + one: "" +stringData: + two: 2`, `{"data":{"one":""},"kind":"Secret","name":"","stringData":{"two":2},"type":"my-type"}`, ""}, + // empty stringdata + {"empty stringdata", ` +apiVersion: v1 +kind: Secret +type: my-type +data: + one: ""`, `{"data":{"one":""},"kind":"Secret","name":"","type":"my-type"}`, ""}, + } + for _, c := range cases { + node, err := yaml.Parse(c.secretYaml) + if err != nil { + t.Fatal(err) + } + s, err := encodeSecret(node) + if SkipRest(t, c.desc, err, c.err) { + continue + } + if s != c.expect { + t.Errorf("case %q, expect %q but got %q from encode %#v", c.desc, c.expect, s, c.secretYaml) + } + } +} + +// SkipRest returns true if there was a non-nil error or if we expected an error that didn't happen, +// and logs the appropriate error on the test object. +// The return value indicates whether we should skip the rest of the test case due to the error result. +func SkipRest(t *testing.T, desc string, err error, contains string) bool { + if err != nil { + if len(contains) == 0 { + t.Errorf("case %q, expect nil error but got %q", desc, err.Error()) + } else if !strings.Contains(err.Error(), contains) { + t.Errorf("case %q, expect error to contain %q but got %q", desc, contains, err.Error()) + } + return true + } else if len(contains) > 0 { + t.Errorf("case %q, expect error to contain %q but got nil error", desc, contains) + return true + } + return false +} diff --git a/api/internal/target/kusttarget_test.go b/api/internal/target/kusttarget_test.go index 4b3e6ac32..7bb3d0856 100644 --- a/api/internal/target/kusttarget_test.go +++ b/api/internal/target/kusttarget_test.go @@ -201,7 +201,7 @@ metadata: "apiVersion": "v1", "kind": "ConfigMap", "metadata": map[string]interface{}{ - "name": "foo-literalConfigMap-bar-8d2dkb8k24", + "name": "foo-literalConfigMap-bar-g5f6t456f5", "namespace": "ns1", "labels": map[string]interface{}{ "app": "nginx", @@ -220,7 +220,7 @@ metadata: "apiVersion": "v1", "kind": "Secret", "metadata": map[string]interface{}{ - "name": "foo-secret-bar-9btc7bt4kb", + "name": "foo-secret-bar-82c2g5f8f6", "namespace": "ns1", "labels": map[string]interface{}{ "app": "nginx", diff --git a/api/k8sdeps/kunstruct/hasher.go b/api/k8sdeps/kunstruct/hasher.go index 1b263fede..41f0ddbb9 100644 --- a/api/k8sdeps/kunstruct/hasher.go +++ b/api/k8sdeps/kunstruct/hasher.go @@ -4,12 +4,9 @@ package kunstruct import ( - "encoding/json" - - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "sigs.k8s.io/kustomize/api/hasher" "sigs.k8s.io/kustomize/api/ifc" + "sigs.k8s.io/kustomize/kyaml/filtersutil" ) // kustHash computes a hash of an unstructured object. @@ -22,119 +19,9 @@ func NewKustHash() *kustHash { // Hash returns a hash of the given object func (h *kustHash) Hash(m ifc.Kunstructured) (string, error) { - u := unstructured.Unstructured{ - Object: m.Map(), - } - kind := u.GetKind() - switch kind { - case "ConfigMap": - cm, err := unstructuredToConfigmap(u) - if err != nil { - return "", err - } - return configMapHash(cm) - case "Secret": - sec, err := unstructuredToSecret(u) - if err != nil { - return "", err - } - return secretHash(sec) - default: - return unstructuredHash(&u) - } -} - -// configMapHash returns a hash of the ConfigMap. -// The Data, Kind, and Name are taken into account. -func configMapHash(cm *corev1.ConfigMap) (string, error) { - encoded, err := encodeConfigMap(cm) + node, err := filtersutil.GetRNode(m) if err != nil { return "", err } - h, err := hasher.Encode(hasher.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(sec *corev1.Secret) (string, error) { - encoded, err := encodeSecret(sec) - if err != nil { - return "", err - } - h, err := hasher.Encode(hasher.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 unstructuredHash(u *unstructured.Unstructured) (string, error) { - encoded, err := json.Marshal(u.Object) - if err != nil { - return "", err - } - h, err := hasher.Encode(hasher.Hash(string(encoded))) - if err != nil { - return "", err - } - return h, 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(cm *corev1.ConfigMap) (string, error) { - // json.Marshal sorts the keys in a stable order in the encoding - m := map[string]interface{}{"kind": "ConfigMap", "name": cm.Name, "data": cm.Data} - if len(cm.BinaryData) > 0 { - m["binaryData"] = cm.BinaryData - } - 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(sec *corev1.Secret) (string, error) { - // json.Marshal sorts the keys in a stable order in the encoding - m := map[string]interface{}{"kind": "Secret", "type": sec.Type, "name": sec.Name, "data": sec.Data} - if len(sec.StringData) > 0 { - m["stringData"] = sec.StringData - } - data, err := json.Marshal(m) - if err != nil { - return "", err - } - return string(data), nil -} - -func unstructuredToConfigmap(u unstructured.Unstructured) (*corev1.ConfigMap, error) { - marshaled, err := json.Marshal(u.Object) - if err != nil { - return nil, err - } - var out corev1.ConfigMap - err = json.Unmarshal(marshaled, &out) - return &out, err -} - -func unstructuredToSecret(u unstructured.Unstructured) (*corev1.Secret, error) { - marshaled, err := json.Marshal(u.Object) - if err != nil { - return nil, err - } - var out corev1.Secret - err = json.Unmarshal(marshaled, &out) - return &out, err + return hasher.HashRNode(node) } diff --git a/api/k8sdeps/kunstruct/hasher_test.go b/api/k8sdeps/kunstruct/hasher_test.go index be84ad382..02953f970 100644 --- a/api/k8sdeps/kunstruct/hasher_test.go +++ b/api/k8sdeps/kunstruct/hasher_test.go @@ -1,224 +1,34 @@ -// Copyright 2019 The Kubernetes Authors. -// SPDX-License-Identifier: Apache-2.0 - package kunstruct import ( - "reflect" - "strings" "testing" - - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) -func TestConfigMapHash(t *testing.T) { - cases := []struct { - desc string - cm *corev1.ConfigMap - hash string - err string - }{ - // empty map - {"empty data", &corev1.ConfigMap{Data: map[string]string{}, BinaryData: map[string][]byte{}}, "42745tchd9", ""}, - // one key - {"one key", &corev1.ConfigMap{Data: map[string]string{"one": ""}}, "9g67k2htb6", ""}, - // three keys (tests sorting order) - {"three keys", &corev1.ConfigMap{Data: map[string]string{"two": "2", "one": "", "three": "3"}}, "f5h7t85m9b", ""}, - // empty binary data map - {"empty binary data", &corev1.ConfigMap{BinaryData: map[string][]byte{}}, "dk855m5d49", ""}, - // one key with binary data - {"one key with binary data", &corev1.ConfigMap{BinaryData: map[string][]byte{"one": []byte("")}}, "mk79584b8c", ""}, - // three keys with binary data (tests sorting order) - {"three keys with binary data", &corev1.ConfigMap{BinaryData: map[string][]byte{"two": []byte("2"), "one": []byte(""), "three": []byte("3")}}, "t458mc6db2", ""}, - // two keys, one with string and another with binary data - {"two keys with one each", &corev1.ConfigMap{Data: map[string]string{"one": ""}, BinaryData: map[string][]byte{"two": []byte("")}}, "698h7c7t9m", ""}, - } - - for _, c := range cases { - h, err := configMapHash(c.cm) - if SkipRest(t, c.desc, err, c.err) { - continue - } - if c.hash != h { - t.Errorf("case %q, expect hash %q but got %q", c.desc, c.hash, h) - } - } -} - -func TestSecretHash(t *testing.T) { - cases := []struct { - desc string - secret *corev1.Secret - hash string - err string - }{ - // empty map - {"empty data", &corev1.Secret{Type: "my-type", Data: map[string][]byte{}}, "t75bgf6ctb", ""}, - // one key - {"one key", &corev1.Secret{Type: "my-type", Data: map[string][]byte{"one": []byte("")}}, "74bd68bm66", ""}, - // three keys (tests sorting order) - {"three keys", &corev1.Secret{Type: "my-type", Data: map[string][]byte{"two": []byte("2"), "one": []byte(""), "three": []byte("3")}}, "dgcb6h9tmk", ""}, - // with stringdata - {"stringdata", &corev1.Secret{Type: "my-type", Data: map[string][]byte{"one": []byte("")}, StringData: map[string]string{"two": "2"}}, "ckm7f798g2", ""}, - // empty stringdata - {"empty stringdata", &corev1.Secret{Type: "my-type", Data: map[string][]byte{"one": []byte("")}, StringData: map[string]string{}}, "74bd68bm66", ""}, - } - - for _, c := range cases { - h, err := secretHash(c.secret) - if SkipRest(t, c.desc, err, c.err) { - continue - } - if c.hash != h { - t.Errorf("case %q, expect hash %q but got %q", c.desc, c.hash, h) - } - } -} - -func TestUnstructuredHash(t *testing.T) { - cases := []struct { - desc string - unstructured *unstructured.Unstructured - hash string - err string - }{ - {"minimal", &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": "test/v1", - "kind": "TestResource", - "metadata": map[string]string{"name": "my-resource"}}, - }, "2tt46d7f79", ""}, - {"with spec", &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": "test/v1", - "kind": "TestResource", - "metadata": map[string]string{"name": "my-resource"}, - "spec": map[string]interface{}{"foo": 1, "bar": "abc"}}, - }, "6gc62g4m6k", ""}, - } - - for _, c := range cases { - h, err := unstructuredHash(c.unstructured) - if SkipRest(t, c.desc, err, c.err) { - continue - } - if c.hash != h { - t.Errorf("case %q, expect hash %q but got %q", c.desc, c.hash, h) - } - } -} - -func TestEncodeConfigMap(t *testing.T) { - cases := []struct { - desc string - cm *corev1.ConfigMap - expect string - err string - }{ - // empty map - {"empty data", &corev1.ConfigMap{Data: map[string]string{}}, `{"data":{},"kind":"ConfigMap","name":""}`, ""}, - // one key - {"one key", &corev1.ConfigMap{Data: map[string]string{"one": ""}}, `{"data":{"one":""},"kind":"ConfigMap","name":""}`, ""}, - // three keys (tests sorting order) - {"three keys", &corev1.ConfigMap{Data: map[string]string{"two": "2", "one": "", "three": "3"}}, - `{"data":{"one":"","three":"3","two":"2"},"kind":"ConfigMap","name":""}`, ""}, - // empty binary map - {"empty data", &corev1.ConfigMap{BinaryData: map[string][]byte{}}, `{"data":null,"kind":"ConfigMap","name":""}`, ""}, - // one key with binary data - {"one key", &corev1.ConfigMap{BinaryData: map[string][]byte{"one": []byte("")}}, - `{"binaryData":{"one":""},"data":null,"kind":"ConfigMap","name":""}`, ""}, - // three keys with binary data (tests sorting order) - {"three keys", &corev1.ConfigMap{BinaryData: map[string][]byte{"two": []byte("2"), "one": []byte(""), "three": []byte("3")}}, - `{"binaryData":{"one":"","three":"Mw==","two":"Mg=="},"data":null,"kind":"ConfigMap","name":""}`, ""}, - // two keys, one string and one binary values - {"two keys with one each", &corev1.ConfigMap{Data: map[string]string{"one": ""}, BinaryData: map[string][]byte{"two": []byte("")}}, - `{"binaryData":{"two":""},"data":{"one":""},"kind":"ConfigMap","name":""}`, ""}, - } - for _, c := range cases { - s, err := encodeConfigMap(c.cm) - if SkipRest(t, c.desc, err, c.err) { - continue - } - if s != c.expect { - t.Errorf("case %q, expect %q but got %q from encode %#v", c.desc, c.expect, s, c.cm) - } - } -} - -func TestEncodeSecret(t *testing.T) { - cases := []struct { - desc string - secret *corev1.Secret - expect string - err string - }{ - // empty map - {"empty data", &corev1.Secret{Type: "my-type", Data: map[string][]byte{}}, `{"data":{},"kind":"Secret","name":"","type":"my-type"}`, ""}, - // one key - {"one key", &corev1.Secret{Type: "my-type", Data: map[string][]byte{"one": []byte("")}}, `{"data":{"one":""},"kind":"Secret","name":"","type":"my-type"}`, ""}, - // three keys (tests sorting order) - note json.Marshal base64 encodes the values because they come in as []byte - {"three keys", &corev1.Secret{ - Type: "my-type", - Data: map[string][]byte{"two": []byte("2"), "one": []byte(""), "three": []byte("3")}, - }, - `{"data":{"one":"","three":"Mw==","two":"Mg=="},"kind":"Secret","name":"","type":"my-type"}`, ""}, - // with stringdata - {"stringdata", &corev1.Secret{Type: "my-type", Data: map[string][]byte{"one": []byte("")}, StringData: map[string]string{"two": "2"}}, - `{"data":{"one":""},"kind":"Secret","name":"","stringData":{"two":"2"},"type":"my-type"}`, ""}, - // empty stringdata - {"empty stringdata", &corev1.Secret{Type: "my-type", Data: map[string][]byte{"one": []byte("")}, StringData: map[string]string{}}, - `{"data":{"one":""},"kind":"Secret","name":"","type":"my-type"}`, ""}, - } - for _, c := range cases { - s, err := encodeSecret(c.secret) - if SkipRest(t, c.desc, err, c.err) { - continue - } - if s != c.expect { - t.Errorf("case %q, expect %q but got %q from encode %#v", c.desc, c.expect, s, c.secret) - } - } -} - -// warn devs who change types that they might have to update a hash function -// not perfect, as it only checks the number of top-level fields -func TestTypeStability(t *testing.T) { - errfmt := `case %q, expected %d fields but got %d -Depending on the field(s) you added, you may need to modify the hash function for this type. -To guide you: the hash function targets fields that comprise the contents of objects, -not their metadata (e.g. the Data of a ConfigMap, but nothing in ObjectMeta). +func TestHasher(t *testing.T) { + input := ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: foo +data: + one: "" +binaryData: + two: "" ` - cases := []struct { - typeName string - obj interface{} - expect int - }{ - {"ConfigMap", corev1.ConfigMap{}, 4}, - {"Secret", corev1.Secret{}, 5}, - } - for _, c := range cases { - val := reflect.ValueOf(c.obj) - if num := val.NumField(); c.expect != num { - t.Errorf(errfmt, c.typeName, c.expect, num) - } - } -} + expect := "698h7c7t9m" -// SkipRest returns true if there was a non-nil error or if we expected an error that didn't happen, -// and logs the appropriate error on the test object. -// The return value indicates whether we should skip the rest of the test case due to the error result. -func SkipRest(t *testing.T, desc string, err error, contains string) bool { + factory := NewKunstructuredFactoryImpl() + k, err := factory.SliceFromBytes([]byte(input)) if err != nil { - if len(contains) == 0 { - t.Errorf("case %q, expect nil error but got %q", desc, err.Error()) - } else if !strings.Contains(err.Error(), contains) { - t.Errorf("case %q, expect error to contain %q but got %q", desc, contains, err.Error()) - } - return true - } else if len(contains) > 0 { - t.Errorf("case %q, expect error to contain %q but got nil error", desc, contains) - return true + t.Fatal(err) + } + + hasher := NewKustHash() + result, err := hasher.Hash(k[0]) + if err != nil { + t.Fatal(err) + } + if result != expect { + t.Fatalf("expect %s but got %s", expect, result) } - return false } diff --git a/api/krusty/baseandoverlaymedium_test.go b/api/krusty/baseandoverlaymedium_test.go index 19bb6fe7a..89eb108e9 100644 --- a/api/krusty/baseandoverlaymedium_test.go +++ b/api/krusty/baseandoverlaymedium_test.go @@ -231,7 +231,7 @@ spec: valueFrom: configMapKeyRef: key: somekey - name: test-infra-app-env-ffmd9b969m + name: test-infra-app-env-8h5mh7f7ch image: nginx:1.8.0 name: nginx ports: @@ -240,7 +240,7 @@ spec: - configMapRef: name: someConfigMap - configMapRef: - name: test-infra-app-env-ffmd9b969m + name: test-infra-app-env-8h5mh7f7ch image: busybox name: busybox volumeMounts: @@ -248,7 +248,7 @@ spec: name: app-env volumes: - configMap: - name: test-infra-app-env-ffmd9b969m + name: test-infra-app-env-8h5mh7f7ch name: app-env --- apiVersion: v1 @@ -288,7 +288,7 @@ metadata: app: mungebot org: kubernetes repo: test-infra - name: test-infra-app-env-ffmd9b969m + name: test-infra-app-env-8h5mh7f7ch --- apiVersion: v1 data: @@ -301,6 +301,6 @@ metadata: app: mungebot org: kubernetes repo: test-infra - name: test-infra-app-config-f462h769f9 + name: test-infra-app-config-49d6f5h7b5 `) } diff --git a/api/krusty/component_test.go b/api/krusty/component_test.go index bad059172..778e42289 100644 --- a/api/krusty/component_test.go +++ b/api/krusty/component_test.go @@ -132,7 +132,7 @@ kind: ConfigMap metadata: annotations: {} labels: {} - name: comp-my-configmap-ct5bgtbccd + name: comp-my-configmap-kc6k2kmkh9 --- apiVersion: v1 kind: Deployment @@ -186,7 +186,7 @@ kind: ConfigMap metadata: annotations: {} labels: {} - name: comp-my-configmap-dgf97tmg6h + name: comp-my-configmap-55249mf5kb --- apiVersion: v1 kind: Deployment @@ -241,7 +241,7 @@ kind: ConfigMap metadata: annotations: {} labels: {} - name: comp-my-configmap-dgf97tmg6h + name: comp-my-configmap-55249mf5kb --- apiVersion: v1 kind: Deployment @@ -283,7 +283,7 @@ data: testValue: "1" kind: ConfigMap metadata: - name: my-configmap-7k9t4h74f8 + name: my-configmap-2g9c94mhb8 --- apiVersion: v1 kind: Deployment @@ -301,7 +301,7 @@ kind: ConfigMap metadata: annotations: {} labels: {} - name: comp-my-configmap-ct5bgtbccd + name: comp-my-configmap-kc6k2kmkh9 --- apiVersion: v1 kind: Deployment @@ -349,7 +349,7 @@ kind: ConfigMap metadata: annotations: {} labels: {} - name: my-configmap-96dt22k28h + name: my-configmap-kc6k2kmkh9 `, }, "missing-optional-component-api-version": { @@ -380,7 +380,7 @@ kind: ConfigMap metadata: annotations: {} labels: {} - name: my-configmap-72cfg2mg5d + name: my-configmap-5g7gh5mgt5 --- apiVersion: v1 kind: Deployment @@ -427,7 +427,7 @@ data: testValue: "1" kind: ConfigMap metadata: - name: my-configmap-a-b-tfb7c5t69m + name: my-configmap-a-b-2g9c94mhb8 --- apiVersion: v1 kind: Deployment @@ -442,7 +442,7 @@ data: testValue: "1" kind: ConfigMap metadata: - name: my-configmap-b-8h7b8862bb + name: my-configmap-b-2g9c94mhb8 `, }, diff --git a/api/krusty/configmaps_test.go b/api/krusty/configmaps_test.go index 4563ac59d..a174ce3ec 100644 --- a/api/krusty/configmaps_test.go +++ b/api/krusty/configmaps_test.go @@ -81,7 +81,7 @@ data: vegetable: broccoli kind: ConfigMap metadata: - name: blah-bob-k772g5db55 + name: blah-bob-d87t8m8tgm --- apiVersion: v1 data: @@ -89,7 +89,7 @@ data: v2: '[{"path": "var/druid/segment-cache"}]' kind: ConfigMap metadata: - name: blah-json-9gtcc2fgb4 + name: blah-json-5298bc8g99 --- apiVersion: v1 data: @@ -101,7 +101,7 @@ data: vegetable: YnJvY2NvbGk= kind: Secret metadata: - name: blah-bob-gmc2824f4b + name: blah-bob-ftht6hfgmb type: Opaque `) } @@ -155,7 +155,7 @@ data: vegetable: broccoli kind: ConfigMap metadata: - name: blah-bob-gfkcbk5ckf + name: blah-bob-db529cg5bk `) } @@ -217,7 +217,7 @@ kind: ConfigMap metadata: annotations: {} labels: {} - name: p1-com1-dhbbm922gd + name: p1-com1-8tc62428t2 --- apiVersion: v1 data: @@ -226,7 +226,7 @@ kind: ConfigMap metadata: annotations: {} labels: {} - name: p2-com2-c4b8md75k9 + name: p2-com2-87mcggf7d7 `) } @@ -274,7 +274,7 @@ kind: ConfigMap metadata: annotations: {} labels: {} - name: o1-cm-28g596k77k + name: o1-cm-ft9mmdc8c6 --- apiVersion: v1 data: @@ -284,6 +284,6 @@ kind: ConfigMap metadata: annotations: {} labels: {} - name: cm-o2-gfcc59fg5m + name: cm-o2-5k95kd76ft `) } diff --git a/api/krusty/disablenamesuffix_test.go b/api/krusty/disablenamesuffix_test.go index de1753e0d..9580a8914 100644 --- a/api/krusty/disablenamesuffix_test.go +++ b/api/krusty/disablenamesuffix_test.go @@ -83,7 +83,7 @@ metadata: if secret == nil { t.Errorf("Expected to find a Secret") } - if secret.GetName() != "foo-secret-bar-9btc7bt4kb" { + if secret.GetName() != "foo-secret-bar-82c2g5f8f6" { t.Errorf("unexpected secret resource name: %s", secret.GetName()) } @@ -140,7 +140,7 @@ secretGenerator: if secret == nil { t.Errorf("Expected to find a Secret") } - if secret.GetName() != "yeshash-mcgcmdcm69" { + if secret.GetName() != "yeshash-82c2g5f8f6" { t.Errorf("unexpected secret resource name: %s", secret.GetName()) } } diff --git a/api/krusty/generatormergeandreplace_test.go b/api/krusty/generatormergeandreplace_test.go index fa0ac9631..6a8d4a82a 100644 --- a/api/krusty/generatormergeandreplace_test.go +++ b/api/krusty/generatormergeandreplace_test.go @@ -251,7 +251,7 @@ spec: - emptyDir: {} name: nginx-persistent-storage - configMap: - name: team-foo-configmap-in-base-bbdmdh7m8t + name: team-foo-configmap-in-base-798k5k7g9f name: configmap-in-base --- apiVersion: v1 @@ -283,7 +283,7 @@ metadata: app: mynginx org: example.com team: foo - name: team-foo-configmap-in-base-bbdmdh7m8t + name: team-foo-configmap-in-base-798k5k7g9f --- apiVersion: v1 data: @@ -297,7 +297,7 @@ metadata: app: mynginx org: example.com team: foo - name: team-foo-secret-in-base-tkm7hhtf8d + name: team-foo-secret-in-base-bgd6bkgdm2 type: Opaque `) } @@ -386,10 +386,10 @@ spec: pdName: nginx-persistent-storage name: nginx-persistent-storage - configMap: - name: staging-team-foo-configmap-in-base-gh9d7t85gb + name: staging-team-foo-configmap-in-base-hc6g9dk6g9 name: configmap-in-base - configMap: - name: staging-configmap-in-overlay-k7cbc75tg8 + name: staging-configmap-in-overlay-dc6fm46dhm name: configmap-in-overlay --- apiVersion: v1 @@ -424,7 +424,7 @@ metadata: env: staging org: example.com team: override-foo - name: staging-team-foo-configmap-in-base-gh9d7t85gb + name: staging-team-foo-configmap-in-base-hc6g9dk6g9 --- apiVersion: v1 data: @@ -440,7 +440,7 @@ metadata: env: staging org: example.com team: override-foo - name: staging-team-foo-secret-in-base-c8db7gk2m2 + name: staging-team-foo-secret-in-base-k2k4692t9g type: Opaque --- apiVersion: v1 @@ -451,7 +451,7 @@ metadata: labels: env: staging team: override-foo - name: staging-configmap-in-overlay-k7cbc75tg8 + name: staging-configmap-in-overlay-dc6fm46dhm `) } @@ -486,7 +486,7 @@ data: key: value kind: ConfigMap metadata: - name: test-t5t4md8fdm + name: test-t757gk2bmf namespace: default --- apiVersion: v1 @@ -494,7 +494,7 @@ data: key: value kind: ConfigMap metadata: - name: test-t5t4md8fdm + name: test-t757gk2bmf namespace: kube-system --- apiVersion: v1 @@ -503,7 +503,7 @@ data: username: YWRtaW4= kind: Secret metadata: - name: test-h65t9hg6kc + name: test-bgd6bkgdm2 namespace: default type: Opaque --- @@ -513,7 +513,7 @@ data: username: YWRtaW4= kind: Secret metadata: - name: test-h65t9hg6kc + name: test-bgd6bkgdm2 namespace: kube-system type: Opaque `) diff --git a/api/krusty/generatoroptions_test.go b/api/krusty/generatoroptions_test.go index aa2566347..5592b7a84 100644 --- a/api/krusty/generatoroptions_test.go +++ b/api/krusty/generatoroptions_test.go @@ -40,7 +40,7 @@ data: passphrase: ZGF0IHBocmFzZQ== kind: Secret metadata: - name: bob-kf5c9fccbt + name: bob-bh645k7tmg type: Opaque `) } @@ -91,6 +91,6 @@ kind: ConfigMap metadata: labels: fruit: apple - name: shouldHaveHash-2k9hc848ff + name: shouldHaveHash-c9867f8446 `) } diff --git a/api/krusty/multiplepatch_test.go b/api/krusty/multiplepatch_test.go index 8f80fcd19..e130b28b3 100644 --- a/api/krusty/multiplepatch_test.go +++ b/api/krusty/multiplepatch_test.go @@ -147,10 +147,10 @@ spec: pdName: nginx-persistent-storage name: nginx-persistent-storage - configMap: - name: a-b-configmap-in-base-fm96mhk4dt + name: a-b-configmap-in-base-798k5k7g9f name: configmap-in-base - configMap: - name: a-configmap-in-overlay-ffm9hf78mc + name: a-configmap-in-overlay-dc6fm46dhm name: configmap-in-overlay --- apiVersion: v1 @@ -175,7 +175,7 @@ metadata: labels: env: staging team: foo - name: a-b-configmap-in-base-fm96mhk4dt + name: a-b-configmap-in-base-798k5k7g9f --- apiVersion: v1 data: @@ -184,7 +184,7 @@ kind: ConfigMap metadata: labels: env: staging - name: a-configmap-in-overlay-ffm9hf78mc + name: a-configmap-in-overlay-dc6fm46dhm `) } @@ -352,10 +352,10 @@ spec: pdName: nginx-persistent-storage name: nginx-persistent-storage - configMap: - name: staging-team-foo-configmap-in-base-g7k6gt2889 + name: staging-team-foo-configmap-in-base-798k5k7g9f name: configmap-in-base - configMap: - name: staging-configmap-in-overlay-k7cbc75tg8 + name: staging-configmap-in-overlay-dc6fm46dhm name: configmap-in-overlay --- apiVersion: v1 @@ -390,7 +390,7 @@ metadata: env: staging org: example.com team: foo - name: staging-team-foo-configmap-in-base-g7k6gt2889 + name: staging-team-foo-configmap-in-base-798k5k7g9f --- apiVersion: v1 data: @@ -399,7 +399,7 @@ kind: ConfigMap metadata: labels: env: staging - name: staging-configmap-in-overlay-k7cbc75tg8 + name: staging-configmap-in-overlay-dc6fm46dhm `) } @@ -542,7 +542,7 @@ spec: volumes: - name: nginx-persistent-storage - configMap: - name: staging-team-foo-configmap-in-base-g7k6gt2889 + name: staging-team-foo-configmap-in-base-798k5k7g9f name: configmap-in-base --- apiVersion: v1 @@ -577,7 +577,7 @@ metadata: env: staging org: example.com team: foo - name: staging-team-foo-configmap-in-base-g7k6gt2889 + name: staging-team-foo-configmap-in-base-798k5k7g9f --- apiVersion: v1 data: @@ -586,7 +586,7 @@ kind: ConfigMap metadata: labels: env: staging - name: staging-configmap-in-overlay-k7cbc75tg8 + name: staging-configmap-in-overlay-dc6fm46dhm `) }) } diff --git a/api/krusty/namespacedgenerators_test.go b/api/krusty/namespacedgenerators_test.go index b6349fa1e..3c1d408fd 100644 --- a/api/krusty/namespacedgenerators_test.go +++ b/api/krusty/namespacedgenerators_test.go @@ -42,7 +42,7 @@ data: enableRisky: "false" kind: ConfigMap metadata: - name: the-non-default-namespace-map-b6h49k7mt8 + name: the-non-default-namespace-map-64b2md8tth namespace: non-default --- apiVersion: v1 @@ -51,14 +51,14 @@ data: enableRisky: "false" kind: ConfigMap metadata: - name: the-map-4959m5tm6c + name: the-map-tg7t5hk8bk --- apiVersion: v1 data: password.txt: dmVyeVNlY3JldA== kind: Secret metadata: - name: the-non-default-namespace-secret-h8d9hkgtb9 + name: the-non-default-namespace-secret-8tc9gdd76t namespace: non-default type: Opaque --- @@ -67,7 +67,7 @@ data: password.txt: YW5vdGhlclNlY3JldA== kind: Secret metadata: - name: the-secret-fgb45h45bh + name: the-secret-6557m7fcg8 type: Opaque `) } @@ -104,7 +104,7 @@ kind: ConfigMap metadata: annotations: {} labels: {} - name: testCase-4g75kbk6gm + name: testCase-bcbmmg48hd namespace: overlay `) } diff --git a/api/krusty/transformerplugin_test.go b/api/krusty/transformerplugin_test.go index 279900f81..110baaabd 100644 --- a/api/krusty/transformerplugin_test.go +++ b/api/krusty/transformerplugin_test.go @@ -96,7 +96,7 @@ data: FOO: foo kind: ConfigMap metadata: - name: test-k4bkhftttd + name: test-6bc28fff49 `) } diff --git a/api/krusty/variableref_test.go b/api/krusty/variableref_test.go index 663ac9bc3..ea246106f 100644 --- a/api/krusty/variableref_test.go +++ b/api/krusty/variableref_test.go @@ -895,7 +895,7 @@ spec: - command: - echo - dev-base-cockroachdb - - dev-base-test-config-map-b2g2dmd64b + - dev-base-test-config-map-6b85g79g7g env: - name: CDB_PUBLIC_SVC value: dev-base-cockroachdb-public @@ -921,7 +921,7 @@ data: foo: bar kind: ConfigMap metadata: - name: dev-base-test-config-map-b2g2dmd64b + name: dev-base-test-config-map-6b85g79g7g `) } diff --git a/examples/configGeneration.md b/examples/configGeneration.md index 8ba53ce17..ab73afbfb 100644 --- a/examples/configGeneration.md +++ b/examples/configGeneration.md @@ -159,11 +159,11 @@ The configMap name is suffixed by _-v1_, per the The suffix to the configMap name is generated from a hash of the maps content - in this case the name suffix -is _k25m8k5k5m_: +is _5276h4th55_: - + ``` -kustomize build $OVERLAYS/staging | grep k25m8k5k5m +kustomize build $OVERLAYS/staging | grep 5276h4th55 ``` Now modify the map patch, to change the greeting @@ -190,20 +190,20 @@ kustomize build $OVERLAYS/staging |\ ``` Confirm that the change in configMap content resulted -in three new names ending in _cd7kdh48fd_ - one in the +in three new names ending in _c2g8fcbf88_ - one in the configMap name itself, and two in the deployment that uses the map: - + ``` test 3 == \ - $(kustomize build $OVERLAYS/staging | grep cd7kdh48fd | wc -l); \ + $(kustomize build $OVERLAYS/staging | grep c2g8fcbf88 | wc -l); \ echo $? ``` Applying these resources to the cluster will result in a rolling update of the deployments pods, retargetting -them from the _k25m8k5k5m_ maps to the _cd7kdh48fd_ +them from the _5276h4th55_ maps to the _c2g8fcbf88_ maps. The system will later garbage collect the unused maps. diff --git a/examples/zh/configGeneration.md b/examples/zh/configGeneration.md index 4a02b15fc..ce2169c64 100644 --- a/examples/zh/configGeneration.md +++ b/examples/zh/configGeneration.md @@ -139,11 +139,11 @@ kustomize build $OVERLAYS/staging |\ 根据 `$OVERLAYS/staging/kustomization.yaml` 中的 `nameSuffix` 字段,configMap 名称以 _-v1_ 为后缀。 -configMap 名称的后缀是由 map 内容的哈希生成的 - 在这种情况下,名称后缀是 _k25m8k5k5m_ : +configMap 名称的后缀是由 map 内容的哈希生成的 - 在这种情况下,名称后缀是 _5276h4th55_ : - + ``` -kustomize build $OVERLAYS/staging | grep k25m8k5k5m +kustomize build $OVERLAYS/staging | grep 5276h4th55 ``` 现在修改 map patch ,更改该服务将使用的问候消息: @@ -168,16 +168,16 @@ kustomize build $OVERLAYS/staging |\ grep -B 8 -A 1 staging-the-map ``` -确认 configMap 内容的更改将会生成以 _cd7kdh48fd_ 结尾的三个新名称 - 一个在 configMap 的名称中,另两个在使用 ConfigMap 的 deployment 中: +确认 configMap 内容的更改将会生成以 _c2g8fcbf88_ 结尾的三个新名称 - 一个在 configMap 的名称中,另两个在使用 ConfigMap 的 deployment 中: - + ``` test 3 == \ - $(kustomize build $OVERLAYS/staging | grep cd7kdh48fd | wc -l); \ + $(kustomize build $OVERLAYS/staging | grep c2g8fcbf88 | wc -l); \ echo $? ``` -将这些资源应用于群集将导致 deployment pod 的滚动更新,将它们从 _k25m8k5k5m_ map 重新定位到 _cd7kdh48fd_ map 。系统稍后将垃圾收集未使用的 map。 +将这些资源应用于群集将导致 deployment pod 的滚动更新,将它们从 _5276h4th55_ map 重新定位到 _c2g8fcbf88_ map 。系统稍后将垃圾收集未使用的 map。 ## 回滚