Skip to content

Commit 1e5fff5

Browse files
feat: new, shorter hash function for k8s resource names (#141)
* new, shorter hash function for k8s resource names * improve comment * typo
1 parent 8211e24 commit 1e5fff5

File tree

2 files changed

+80
-0
lines changed

2 files changed

+80
-0
lines changed

pkg/controller/hash.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package controller
22

33
import (
44
"crypto/sha3"
5+
"encoding/base32"
56
"errors"
67
"fmt"
78
"strings"
@@ -90,3 +91,25 @@ func K8sObjectUUIDUnsafe(obj client.Object) string {
9091
}
9192
return uuid
9293
}
94+
95+
// NameHashSHAKE128Base32 takes any number of string arguments and computes a hash out of it. The output string will be 8 characters long.
96+
// The arguments are joined with '/' before being hashed.
97+
func NameHashSHAKE128Base32(names ...string) string {
98+
name := strings.Join(names, "/")
99+
100+
// Desired output length = 8 chars
101+
// 8 chars * 5 bits (base32) / 8 bits per byte = 5 bytes
102+
hash := sha3.SumSHAKE128([]byte(name), 5)
103+
104+
return base32.NewEncoding(Base32EncodeStdLowerCase).WithPadding(base32.NoPadding).EncodeToString(hash)
105+
}
106+
107+
// ObjectHashSHAKE128Base32 takes a client object and computes a hash out of the namespace and name. The output string will be 8 characters long.
108+
// An empty namespace will be replaced by "default".
109+
func ObjectHashSHAKE128Base32(obj client.Object) string {
110+
name, namespace := obj.GetName(), obj.GetNamespace()
111+
if namespace == "" {
112+
namespace = corev1.NamespaceDefault
113+
}
114+
return NameHashSHAKE128Base32(namespace, name)
115+
}

pkg/controller/hash_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package controller
22

33
import (
44
"crypto/sha3"
5+
"strings"
56
"testing"
67

78
"github.com/google/uuid"
@@ -132,3 +133,59 @@ func Test_K8sObjectUUID(t *testing.T) {
132133
})
133134
}
134135
}
136+
137+
func Test_NameHashSHAKE128Base32(t *testing.T) {
138+
testCases := []struct {
139+
input []string
140+
expected string
141+
}{
142+
{
143+
input: []string{"example"},
144+
expected: "epgccknc",
145+
},
146+
{
147+
input: []string{corev1.NamespaceDefault, "example"},
148+
expected: "fphxsdub",
149+
},
150+
}
151+
for _, tC := range testCases {
152+
t.Run(strings.Join(tC.input, " "), func(t *testing.T) {
153+
actual := NameHashSHAKE128Base32(tC.input...)
154+
assert.Equal(t, tC.expected, actual)
155+
})
156+
}
157+
}
158+
159+
func Test_ObjectHashSHAKE128Base32(t *testing.T) {
160+
testCases := []struct {
161+
desc string
162+
obj client.Object
163+
expected string
164+
}{
165+
{
166+
desc: "should work with config map",
167+
obj: &corev1.ConfigMap{
168+
ObjectMeta: metav1.ObjectMeta{
169+
Name: "example",
170+
Namespace: "default",
171+
},
172+
},
173+
expected: "fphxsdub", // same as in Test_K8sNameUUID
174+
},
175+
{
176+
desc: "should work with config map and empty namespace",
177+
obj: &corev1.ConfigMap{
178+
ObjectMeta: metav1.ObjectMeta{
179+
Name: "example",
180+
},
181+
},
182+
expected: "fphxsdub", // same as above
183+
},
184+
}
185+
for _, tC := range testCases {
186+
t.Run(tC.desc, func(t *testing.T) {
187+
actual := ObjectHashSHAKE128Base32(tC.obj)
188+
assert.Equal(t, tC.expected, actual)
189+
})
190+
}
191+
}

0 commit comments

Comments
 (0)