mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-12 01:14:22 +00:00
Merge pull request #2737 from tinselspoon/hash-arbitrary-objects
Allow hash suffixing of arbitrary types
This commit is contained in:
@@ -5,7 +5,6 @@ package kunstruct
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
@@ -21,7 +20,7 @@ func NewKustHash() *kustHash {
|
|||||||
return &kustHash{}
|
return &kustHash{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hash returns a hash of either a ConfigMap or a Secret
|
// Hash returns a hash of the given object
|
||||||
func (h *kustHash) Hash(m ifc.Kunstructured) (string, error) {
|
func (h *kustHash) Hash(m ifc.Kunstructured) (string, error) {
|
||||||
u := unstructured.Unstructured{
|
u := unstructured.Unstructured{
|
||||||
Object: m.Map(),
|
Object: m.Map(),
|
||||||
@@ -36,15 +35,12 @@ func (h *kustHash) Hash(m ifc.Kunstructured) (string, error) {
|
|||||||
return configMapHash(cm)
|
return configMapHash(cm)
|
||||||
case "Secret":
|
case "Secret":
|
||||||
sec, err := unstructuredToSecret(u)
|
sec, err := unstructuredToSecret(u)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return secretHash(sec)
|
return secretHash(sec)
|
||||||
default:
|
default:
|
||||||
return "", fmt.Errorf(
|
return unstructuredHash(&u)
|
||||||
"type %s is not supported for hashing in %v",
|
|
||||||
kind, m.Map())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,6 +72,21 @@ func secretHash(sec *corev1.Secret) (string, error) {
|
|||||||
return h, nil
|
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.
|
// encodeConfigMap encodes a ConfigMap.
|
||||||
// Data, Kind, and Name are taken into account.
|
// Data, Kind, and Name are taken into account.
|
||||||
// BinaryData is included if it's not empty to avoid useless key in output.
|
// BinaryData is included if it's not empty to avoid useless key in output.
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestConfigMapHash(t *testing.T) {
|
func TestConfigMapHash(t *testing.T) {
|
||||||
@@ -75,6 +76,39 @@ func TestSecretHash(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
func TestEncodeConfigMap(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
desc string
|
desc string
|
||||||
|
|||||||
@@ -253,7 +253,11 @@ A generator exec plugin can adjust the generator options for the resources it em
|
|||||||
|
|
||||||
Resources can be marked as needing to be processed by the internal hash transformer by including the `needs-hash` annotation. When set valid values for the annotation are `"true"` and `"false"` which respectively enable or disable hash suffixing for the resource. Omitting the annotation is equivalent to setting the value `"false"`.
|
Resources can be marked as needing to be processed by the internal hash transformer by including the `needs-hash` annotation. When set valid values for the annotation are `"true"` and `"false"` which respectively enable or disable hash suffixing for the resource. Omitting the annotation is equivalent to setting the value `"false"`.
|
||||||
|
|
||||||
If this annotation is set on a resource not supported by the hash transformer the build will fail.
|
Hashes are determined as follows:
|
||||||
|
|
||||||
|
* For `ConfigMap` resources, hashes are based on the values of the `name`, `data`, and `binaryData` fields.
|
||||||
|
* For `Secret` resources, hashes are based on the values of the `name`, `type`, `data`, and `stringData` fields.
|
||||||
|
* For any other object type, hashes are based on the entire object content (i.e. all fields).
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user