Skip to content

Commit aa397b5

Browse files
feat: add func to shorten strings, deprecate old hash funcs (#145)
* feat: add func to shorten strings, deprecate old hash funcs * deprecate K8sNameUUID * typo
1 parent a6ab513 commit aa397b5

File tree

3 files changed

+75
-5
lines changed

3 files changed

+75
-5
lines changed

pkg/controller/hash.go

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,13 @@ import (
1313
)
1414

1515
var (
16-
ErrInvalidNames = errors.New("list of names must not be empty and contain at least one non-empty string")
16+
ErrInvalidNames = errors.New("list of names must not be empty and contain at least one non-empty string")
17+
ErrMaxLenTooSmall = errors.New("maxLen must be greater than 10")
1718
)
1819

19-
// Version8UUID creates a new UUID (version 8) from a byte slice. Returns an error if the slice does not have a length of 16. The bytes are copied from the slice.
20+
// version8UUID creates a new UUID (version 8) from a byte slice. Returns an error if the slice does not have a length of 16. The bytes are copied from the slice.
2021
// The bits 48-51 and 64-65 are modified to make the output recognizable as a version 8 UUID, so only 122 out of 128 bits from the input data will be kept.
21-
func Version8UUID(data []byte) (uuid.UUID, error) {
22+
func version8UUID(data []byte) (uuid.UUID, error) {
2223
if len(data) != 16 {
2324
return uuid.Nil, fmt.Errorf("invalid data (got %d bytes)", len(data))
2425
}
@@ -40,14 +41,15 @@ func Version8UUID(data []byte) (uuid.UUID, error) {
4041
// K8sNameUUID takes any number of string arguments and computes a hash out of it, which is then formatted as a version 8 UUID.
4142
// The arguments are joined with '/' before being hashed.
4243
// Returns an error if the list of ids is empty or contains only empty strings.
44+
// Deprecated: Use NameHashSHAKE128Base32 instead.
4345
func K8sNameUUID(names ...string) (string, error) {
4446
if err := validateIDs(names); err != nil {
4547
return "", err
4648
}
4749

4850
name := strings.Join(names, "/")
4951
hash := sha3.SumSHAKE128([]byte(name), 16)
50-
u, err := Version8UUID(hash)
52+
u, err := version8UUID(hash)
5153

5254
return u.String(), err
5355
}
@@ -64,6 +66,7 @@ func validateIDs(names []string) error {
6466

6567
// K8sNameUUIDUnsafe works like K8sNameUUID, but panics instead of returning an error.
6668
// This should only be used in places where the input is guaranteed to be valid.
69+
// Deprecated: Use NameHashSHAKE128Base32 instead.
6770
func K8sNameUUIDUnsafe(names ...string) string {
6871
uuid, err := K8sNameUUID(names...)
6972
if err != nil {
@@ -74,6 +77,7 @@ func K8sNameUUIDUnsafe(names ...string) string {
7477

7578
// K8sObjectUUID takes a client object and computes a hash out of the namespace and name, which is then formatted as a version 8 UUID.
7679
// An empty namespace will be replaced by "default".
80+
// Deprecated: Use ObjectHashSHAKE128Base32 instead.
7781
func K8sObjectUUID(obj client.Object) (string, error) {
7882
name, namespace := obj.GetName(), obj.GetNamespace()
7983
if namespace == "" {
@@ -84,6 +88,7 @@ func K8sObjectUUID(obj client.Object) (string, error) {
8488

8589
// K8sObjectUUIDUnsafe works like K8sObjectUUID, but panics instead of returning an error.
8690
// This should only be used in places where the input is guaranteed to be valid.
91+
// Deprecated: Use ObjectHashSHAKE128Base32 instead.
8792
func K8sObjectUUIDUnsafe(obj client.Object) string {
8893
uuid, err := K8sObjectUUID(obj)
8994
if err != nil {
@@ -113,3 +118,21 @@ func ObjectHashSHAKE128Base32(obj client.Object) string {
113118
}
114119
return NameHashSHAKE128Base32(namespace, name)
115120
}
121+
122+
// ShortenToXCharacters shortens the input string if it exceeds maxLen.
123+
// It uses NameHashSHAKE128Base32 to generate a hash which will replace the last few characters.
124+
func ShortenToXCharacters(input string, maxLen int) (string, error) {
125+
if len(input) <= maxLen {
126+
return input, nil
127+
}
128+
129+
hash := NameHashSHAKE128Base32(input)
130+
suffix := fmt.Sprintf("--%s", hash)
131+
trimLength := maxLen - len(suffix)
132+
133+
if trimLength <= 0 {
134+
return "", ErrMaxLenTooSmall
135+
}
136+
137+
return input[:trimLength] + suffix, nil
138+
}

pkg/controller/hash_test.go

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ func Test_Version8UUID(t *testing.T) {
4444
}
4545
for _, tC := range testCases {
4646
t.Run(tC.desc, func(t *testing.T) {
47-
u, err := Version8UUID(tC.data)
47+
u, err := version8UUID(tC.data)
4848
if tC.expectedErr == nil {
4949
assert.NoError(t, err)
5050
assert.Equal(t, uuid.Version(8), u.Version(), "unexpected version")
@@ -189,3 +189,49 @@ func Test_ObjectHashSHAKE128Base32(t *testing.T) {
189189
})
190190
}
191191
}
192+
193+
func Test_ShortenToXCharacters(t *testing.T) {
194+
testCases := []struct {
195+
desc string
196+
input string
197+
maxLen int
198+
expected string
199+
expectedErr error
200+
}{
201+
{
202+
desc: "short string",
203+
input: "short",
204+
expected: "short",
205+
maxLen: 100,
206+
},
207+
{
208+
desc: "short string to trim",
209+
input: "short1234567",
210+
expected: "s--j5gore3p",
211+
maxLen: 11,
212+
},
213+
{
214+
desc: "maxLen too small",
215+
input: "shorter1234",
216+
maxLen: 10,
217+
expectedErr: ErrMaxLenTooSmall,
218+
},
219+
{
220+
desc: "long string",
221+
input: "this-is-a-very-a-very-a-very-long-string-that-is-over-63-characters",
222+
expected: "this-is-a-very-a-very-a-very-long-string-that-is-over--6reoyp5o",
223+
maxLen: 63,
224+
},
225+
}
226+
for _, tC := range testCases {
227+
t.Run(tC.desc, func(t *testing.T) {
228+
actual, err := ShortenToXCharacters(tC.input, tC.maxLen)
229+
if tC.expectedErr == nil {
230+
assert.Equal(t, tC.expected, actual)
231+
assert.LessOrEqual(t, len(actual), tC.maxLen)
232+
} else {
233+
assert.Equal(t, tC.expectedErr, err)
234+
}
235+
})
236+
}
237+
}

pkg/controller/utils.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const (
1515

1616
// K8sNameHash takes any number of string arguments and computes a hash out of it, which is then base32-encoded to be a valid DNS1123Subdomain k8s resource name
1717
// The arguments are joined with '/' before being hashed.
18+
// Deprecated: Use NameHashSHAKE128Base32 instead.
1819
func K8sNameHash(ids ...string) string {
1920
name := strings.Join(ids, "/")
2021
// since we are not worried about length-extension attacks (in fact we are not even using hashing for

0 commit comments

Comments
 (0)