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
31 changes: 27 additions & 4 deletions pkg/controller/hash.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ import (
)

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

// 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.
// 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.
// 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.
func Version8UUID(data []byte) (uuid.UUID, error) {
func version8UUID(data []byte) (uuid.UUID, error) {
if len(data) != 16 {
return uuid.Nil, fmt.Errorf("invalid data (got %d bytes)", len(data))
}
Expand All @@ -40,14 +41,15 @@ func Version8UUID(data []byte) (uuid.UUID, error) {
// K8sNameUUID takes any number of string arguments and computes a hash out of it, which is then formatted as a version 8 UUID.
// The arguments are joined with '/' before being hashed.
// Returns an error if the list of ids is empty or contains only empty strings.
// Deprecated: Use NameHashSHAKE128Base32 instead.
func K8sNameUUID(names ...string) (string, error) {
if err := validateIDs(names); err != nil {
return "", err
}

name := strings.Join(names, "/")
hash := sha3.SumSHAKE128([]byte(name), 16)
u, err := Version8UUID(hash)
u, err := version8UUID(hash)

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

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

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

// K8sObjectUUIDUnsafe works like K8sObjectUUID, but panics instead of returning an error.
// This should only be used in places where the input is guaranteed to be valid.
// Deprecated: Use ObjectHashSHAKE128Base32 instead.
func K8sObjectUUIDUnsafe(obj client.Object) string {
uuid, err := K8sObjectUUID(obj)
if err != nil {
Expand Down Expand Up @@ -113,3 +118,21 @@ func ObjectHashSHAKE128Base32(obj client.Object) string {
}
return NameHashSHAKE128Base32(namespace, name)
}

// ShortenToXCharacters shortens the input string if it exceeds maxLen.
// It uses NameHashSHAKE128Base32 to generate a hash which will replace the last few characters.
func ShortenToXCharacters(input string, maxLen int) (string, error) {
if len(input) <= maxLen {
return input, nil
}

hash := NameHashSHAKE128Base32(input)
suffix := fmt.Sprintf("--%s", hash)
trimLength := maxLen - len(suffix)

if trimLength <= 0 {
return "", ErrMaxLenTooSmall
}

return input[:trimLength] + suffix, nil
}
48 changes: 47 additions & 1 deletion pkg/controller/hash_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func Test_Version8UUID(t *testing.T) {
}
for _, tC := range testCases {
t.Run(tC.desc, func(t *testing.T) {
u, err := Version8UUID(tC.data)
u, err := version8UUID(tC.data)
if tC.expectedErr == nil {
assert.NoError(t, err)
assert.Equal(t, uuid.Version(8), u.Version(), "unexpected version")
Expand Down Expand Up @@ -189,3 +189,49 @@ func Test_ObjectHashSHAKE128Base32(t *testing.T) {
})
}
}

func Test_ShortenToXCharacters(t *testing.T) {
testCases := []struct {
desc string
input string
maxLen int
expected string
expectedErr error
}{
{
desc: "short string",
input: "short",
expected: "short",
maxLen: 100,
},
{
desc: "short string to trim",
input: "short1234567",
expected: "s--j5gore3p",
maxLen: 11,
},
{
desc: "maxLen too small",
input: "shorter1234",
maxLen: 10,
expectedErr: ErrMaxLenTooSmall,
},
{
desc: "long string",
input: "this-is-a-very-a-very-a-very-long-string-that-is-over-63-characters",
expected: "this-is-a-very-a-very-a-very-long-string-that-is-over--6reoyp5o",
maxLen: 63,
},
}
for _, tC := range testCases {
t.Run(tC.desc, func(t *testing.T) {
actual, err := ShortenToXCharacters(tC.input, tC.maxLen)
if tC.expectedErr == nil {
assert.Equal(t, tC.expected, actual)
assert.LessOrEqual(t, len(actual), tC.maxLen)
} else {
assert.Equal(t, tC.expectedErr, err)
}
})
}
}
1 change: 1 addition & 0 deletions pkg/controller/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const (

// 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
// The arguments are joined with '/' before being hashed.
// Deprecated: Use NameHashSHAKE128Base32 instead.
func K8sNameHash(ids ...string) string {
name := strings.Join(ids, "/")
// since we are not worried about length-extension attacks (in fact we are not even using hashing for
Expand Down