From c9ff6a9ab98e315ceb1f8c2911ce2accdd4c79d8 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Wed, 24 Jan 2024 11:27:46 -0800 Subject: [PATCH] Chore: Generate shorter UIDs (#79843) --- pkg/util/shortid_generator.go | 36 +++++++++++++++++++++--------- pkg/util/shortid_generator_test.go | 24 +++++++++++++++----- 2 files changed, 45 insertions(+), 15 deletions(-) diff --git a/pkg/util/shortid_generator.go b/pkg/util/shortid_generator.go index eb841b7bba0..db216e622a6 100644 --- a/pkg/util/shortid_generator.go +++ b/pkg/util/shortid_generator.go @@ -8,6 +8,7 @@ import ( "sync" "time" + "github.com/bwmarrin/snowflake" "github.com/google/uuid" ) @@ -42,24 +43,39 @@ func IsShortUIDTooLong(uid string) bool { return len(uid) > MaxUIDLength } +var node *snowflake.Node + // GenerateShortUID will generate a UUID that can also be a k8s name // it is guaranteed to have a character as the first letter // This UID will be a valid k8s name func GenerateShortUID() string { mtx.Lock() defer mtx.Unlock() - uid, err := uuid.NewRandom() - if err != nil { - // This should never happen... but this seems better than a panic - for i := range uid { - uid[i] = byte(uidrand.Intn(255)) + + if node == nil { + // ignoring the error happens when input outside 0-1023 + node, _ = snowflake.NewNode(rand.Int63n(1024)) + } + + // Use UUIDs if snowflake failed (should be never) + if node == nil { + uid, err := uuid.NewRandom() + if err != nil { + // This should never happen... but this seems better than a panic + for i := range uid { + uid[i] = byte(uidrand.Intn(255)) + } } + uuid := uid.String() + if rune(uuid[0]) < rune('a') { + uuid = string(hexLetters[uidrand.Intn(len(hexLetters))]) + uuid[1:] + } + return uuid } - uuid := uid.String() - if rune(uuid[0]) < rune('a') { - return string(hexLetters[uidrand.Intn(len(hexLetters))]) + uuid[1:] - } - return uuid + + return string(hexLetters[uidrand.Intn(len(hexLetters))]) + // start with a letter + node.Generate().Base36() + + string(hexLetters[uidrand.Intn(len(hexLetters))]) // a bit more entropy } // ValidateUID checks the format and length of the string and returns error if it does not pass the condition diff --git a/pkg/util/shortid_generator_test.go b/pkg/util/shortid_generator_test.go index 010219244ae..6bc4bb05b5c 100644 --- a/pkg/util/shortid_generator_test.go +++ b/pkg/util/shortid_generator_test.go @@ -1,12 +1,13 @@ package util import ( + "fmt" "sync" "testing" "cuelang.org/go/pkg/strings" - "github.com/google/uuid" "github.com/stretchr/testify/require" + "github.com/teris-io/shortid" "k8s.io/apimachinery/pkg/util/validation" ) @@ -48,14 +49,27 @@ func TestRandomUIDs(t *testing.T) { t.Fatalf("created invalid name: %v", validation) } - _, err := uuid.Parse(v) - require.NoError(t, err) - - //fmt.Println(v) + // fmt.Println(v) } // t.FailNow() } +func TestCaseInsensitiveCollisionsUIDs(t *testing.T) { + history := make(map[string]bool, 0) + for i := 0; i < 100000; i++ { + v := GenerateShortUID() + if false { + v, _ = shortid.Generate() // collides in less then 500 iterations + } + + lower := strings.ToLower(v) + _, exists := history[lower] + require.False(t, exists, fmt.Sprintf("already found: %s (index:%d)", v, i)) + + history[lower] = true + } +} + func TestIsShortUIDTooLong(t *testing.T) { var tests = []struct { name string