Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions pkg/controller/hash.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package controller

import (
"crypto/sha3"
"encoding/base32"
"errors"
"fmt"
"strings"
Expand Down Expand Up @@ -90,3 +91,25 @@ func K8sObjectUUIDUnsafe(obj client.Object) string {
}
return uuid
}

// NameHashSHAKE128Base32 takes any number of string arguments and computes a hash out of it. The output string will be 8 characters long.
// The arguments are joined with '/' before being hashed.
func NameHashSHAKE128Base32(names ...string) string {
name := strings.Join(names, "/")

// Desired output length = 8 chars
// 8 chars * 5 bits (base32) / 8 bits per byte = 5 bytes
hash := sha3.SumSHAKE128([]byte(name), 5)

return base32.NewEncoding(Base32EncodeStdLowerCase).WithPadding(base32.NoPadding).EncodeToString(hash)
}

// ObjectHashSHAKE128Base32 takes a client object and computes a hash out of the namespace and name. The output string will be 8 characters long.
// An empty namespace will be replaced by "default".
func ObjectHashSHAKE128Base32(obj client.Object) string {
name, namespace := obj.GetName(), obj.GetNamespace()
if namespace == "" {
namespace = corev1.NamespaceDefault
}
return NameHashSHAKE128Base32(namespace, name)
}
57 changes: 57 additions & 0 deletions pkg/controller/hash_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package controller

import (
"crypto/sha3"
"strings"
"testing"

"github.com/google/uuid"
Expand Down Expand Up @@ -132,3 +133,59 @@ func Test_K8sObjectUUID(t *testing.T) {
})
}
}

func Test_NameHashSHAKE128Base32(t *testing.T) {
testCases := []struct {
input []string
expected string
}{
{
input: []string{"example"},
expected: "epgccknc",
},
{
input: []string{corev1.NamespaceDefault, "example"},
expected: "fphxsdub",
},
}
for _, tC := range testCases {
t.Run(strings.Join(tC.input, " "), func(t *testing.T) {
actual := NameHashSHAKE128Base32(tC.input...)
assert.Equal(t, tC.expected, actual)
})
}
}

func Test_ObjectHashSHAKE128Base32(t *testing.T) {
testCases := []struct {
desc string
obj client.Object
expected string
}{
{
desc: "should work with config map",
obj: &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
},
},
expected: "fphxsdub", // same as in Test_K8sNameUUID
},
{
desc: "should work with config map and empty namespace",
obj: &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
},
},
expected: "fphxsdub", // same as above
},
}
for _, tC := range testCases {
t.Run(tC.desc, func(t *testing.T) {
actual := ObjectHashSHAKE128Base32(tC.obj)
assert.Equal(t, tC.expected, actual)
})
}
}