Allow hash suffixing of arbitrary types

This commit is contained in:
Nick
2020-07-19 19:52:54 +01:00
parent 45eed23b26
commit 4fbe565b36
3 changed files with 56 additions and 7 deletions

View File

@@ -5,7 +5,6 @@ package kunstruct
import (
"encoding/json"
"fmt"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -21,7 +20,7 @@ func NewKustHash() *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) {
u := unstructured.Unstructured{
Object: m.Map(),
@@ -36,15 +35,12 @@ func (h *kustHash) Hash(m ifc.Kunstructured) (string, error) {
return configMapHash(cm)
case "Secret":
sec, err := unstructuredToSecret(u)
if err != nil {
return "", err
}
return secretHash(sec)
default:
return "", fmt.Errorf(
"type %s is not supported for hashing in %v",
kind, m.Map())
return unstructuredHash(&u)
}
}
@@ -76,6 +72,21 @@ func secretHash(sec *corev1.Secret) (string, error) {
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.

View File

@@ -9,6 +9,7 @@ import (
"testing"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
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) {
cases := []struct {
desc string

View File

@@ -255,7 +255,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"`.
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:
```yaml