diff --git a/coderd/apikey.go b/coderd/apikey.go index 3354124be3..303de98b3e 100644 --- a/coderd/apikey.go +++ b/coderd/apikey.go @@ -9,7 +9,6 @@ import ( "github.com/go-chi/chi/v5" "github.com/google/uuid" - "github.com/moby/moby/pkg/namesgenerator" "golang.org/x/xerrors" "cdr.dev/slog/v3" @@ -22,6 +21,7 @@ import ( "github.com/coder/coder/v2/coderd/rbac" "github.com/coder/coder/v2/coderd/rbac/policy" "github.com/coder/coder/v2/coderd/telemetry" + "github.com/coder/coder/v2/coderd/util/namesgenerator" "github.com/coder/coder/v2/codersdk" ) @@ -101,7 +101,7 @@ func (api *API) postToken(rw http.ResponseWriter, r *http.Request) { } } - tokenName := namesgenerator.GetRandomName(1) + tokenName := namesgenerator.NameDigitWith("_") if len(createToken.TokenName) != 0 { tokenName = createToken.TokenName diff --git a/coderd/coderdtest/authorize.go b/coderd/coderdtest/authorize.go index f53ef3fa3b..42146f9409 100644 --- a/coderd/coderdtest/authorize.go +++ b/coderd/coderdtest/authorize.go @@ -11,7 +11,6 @@ import ( "testing" "github.com/google/uuid" - "github.com/moby/moby/pkg/namesgenerator" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "golang.org/x/xerrors" @@ -22,6 +21,7 @@ import ( "github.com/coder/coder/v2/coderd/rbac" "github.com/coder/coder/v2/coderd/rbac/policy" "github.com/coder/coder/v2/coderd/rbac/regosql" + "github.com/coder/coder/v2/coderd/util/namesgenerator" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/cryptorand" ) @@ -439,10 +439,10 @@ func RandomRBACObject() rbac.Object { OrgID: uuid.NewString(), Type: randomRBACType(), ACLUserList: map[string][]policy.Action{ - namesgenerator.GetRandomName(1): {RandomRBACAction()}, + namesgenerator.UniqueName(): {RandomRBACAction()}, }, ACLGroupList: map[string][]policy.Action{ - namesgenerator.GetRandomName(1): {RandomRBACAction()}, + namesgenerator.UniqueName(): {RandomRBACAction()}, }, } } @@ -471,7 +471,7 @@ func RandomRBACSubject() rbac.Subject { return rbac.Subject{ ID: uuid.NewString(), Roles: rbac.RoleIdentifiers{rbac.RoleMember()}, - Groups: []string{namesgenerator.GetRandomName(1)}, + Groups: []string{namesgenerator.UniqueName()}, Scope: rbac.ScopeAll, } } diff --git a/coderd/coderdtest/coderdtest.go b/coderd/coderdtest/coderdtest.go index a82663dea8..a167230c19 100644 --- a/coderd/coderdtest/coderdtest.go +++ b/coderd/coderdtest/coderdtest.go @@ -30,17 +30,17 @@ import ( "sync/atomic" "testing" "time" - "unicode" "cloud.google.com/go/compute/metadata" "github.com/fullsailor/pkcs7" "github.com/go-chi/chi/v5" "github.com/golang-jwt/jwt/v4" "github.com/google/uuid" - "github.com/moby/moby/pkg/namesgenerator" "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "golang.org/x/text/cases" + "golang.org/x/text/language" "golang.org/x/xerrors" "google.golang.org/api/idtoken" "google.golang.org/api/option" @@ -80,6 +80,7 @@ import ( "github.com/coder/coder/v2/coderd/schedule" "github.com/coder/coder/v2/coderd/telemetry" "github.com/coder/coder/v2/coderd/updatecheck" + "github.com/coder/coder/v2/coderd/util/namesgenerator" "github.com/coder/coder/v2/coderd/util/ptr" "github.com/coder/coder/v2/coderd/webpush" "github.com/coder/coder/v2/coderd/workspaceapps" @@ -792,7 +793,7 @@ func AuthzUserSubject(user codersdk.User, orgID uuid.UUID) rbac.Subject { func createAnotherUserRetry(t testing.TB, client *codersdk.Client, organizationIDs []uuid.UUID, retries int, roles []rbac.RoleIdentifier, mutators ...func(r *codersdk.CreateUserRequestWithOrgs)) (*codersdk.Client, codersdk.User) { req := codersdk.CreateUserRequestWithOrgs{ - Email: namesgenerator.GetRandomName(10) + "@coder.com", + Email: namesgenerator.UniqueName() + "@coder.com", Username: RandomUsername(t), Name: RandomName(t), Password: "SomeSecurePassword!", @@ -1556,37 +1557,15 @@ func NewAzureInstanceIdentity(t testing.TB, instanceID string) (x509.VerifyOptio } } -func RandomUsername(t testing.TB) string { - suffix, err := cryptorand.String(3) - require.NoError(t, err) - suffix = "-" + suffix - n := strings.ReplaceAll(namesgenerator.GetRandomName(10), "_", "-") + suffix - if len(n) > 32 { - n = n[:32-len(suffix)] + suffix - } - return n +func RandomUsername(_ testing.TB) string { + return namesgenerator.UniqueNameWith("-") } -func RandomName(t testing.TB) string { - var sb strings.Builder - var err error - ss := strings.Split(namesgenerator.GetRandomName(10), "_") - for si, s := range ss { - for ri, r := range s { - if ri == 0 { - _, err = sb.WriteRune(unicode.ToTitle(r)) - require.NoError(t, err) - } else { - _, err = sb.WriteRune(r) - require.NoError(t, err) - } - } - if si < len(ss)-1 { - _, err = sb.WriteRune(' ') - require.NoError(t, err) - } - } - return sb.String() +// RandomName returns a random name in title case (e.g. "Happy Einstein"). +func RandomName(_ testing.TB) string { + return cases.Title(language.English).String( + namesgenerator.NameWith(" "), + ) } // Used to easily create an HTTP transport! diff --git a/coderd/coderdtest/coderdtest_test.go b/coderd/coderdtest/coderdtest_test.go index 8bd4898fe2..e245898de6 100644 --- a/coderd/coderdtest/coderdtest_test.go +++ b/coderd/coderdtest/coderdtest_test.go @@ -1,8 +1,11 @@ package coderdtest_test import ( + "strings" "testing" + "unicode" + "github.com/stretchr/testify/require" "go.uber.org/goleak" "github.com/coder/coder/v2/coderd/coderdtest" @@ -28,3 +31,22 @@ func TestNew(t *testing.T) { _, _ = coderdtest.NewGoogleInstanceIdentity(t, "example", false) _, _ = coderdtest.NewAWSInstanceIdentity(t, "an-instance") } + +func TestRandomName(t *testing.T) { + t.Parallel() + + for range 10 { + name := coderdtest.RandomName(t) + + require.NotEmpty(t, name, "name should not be empty") + require.NotContains(t, name, "_", "name should not contain underscores") + + // Should be title cased (e.g., "Happy Einstein"). + words := strings.Split(name, " ") + require.Len(t, words, 2, "name should have exactly two words") + for _, word := range words { + firstRune := []rune(word)[0] + require.True(t, unicode.IsUpper(firstRune), "word %q should start with uppercase letter", word) + } + } +} diff --git a/coderd/taskname/taskname.go b/coderd/taskname/taskname.go index 8888468a7e..2677dca672 100644 --- a/coderd/taskname/taskname.go +++ b/coderd/taskname/taskname.go @@ -12,11 +12,11 @@ import ( "github.com/anthropics/anthropic-sdk-go" anthropicoption "github.com/anthropics/anthropic-sdk-go/option" - "github.com/moby/moby/pkg/namesgenerator" "golang.org/x/xerrors" "cdr.dev/slog/v3" "github.com/coder/aisdk-go" + "github.com/coder/coder/v2/coderd/util/namesgenerator" strutil "github.com/coder/coder/v2/coderd/util/strings" "github.com/coder/coder/v2/codersdk" ) @@ -125,10 +125,7 @@ func generateFallback() TaskName { // We have a 32 character limit for the name. // We have a 5 character suffix `-ffff`. // This leaves us with 27 characters for the name. - // - // `namesgenerator.GetRandomName(0)` can generate names - // up to 27 characters, but we truncate defensively. - name := strings.ReplaceAll(namesgenerator.GetRandomName(0), "_", "-") + name := namesgenerator.NameWith("-") name = name[:min(len(name), 27)] name = strings.TrimSuffix(name, "-") diff --git a/coderd/templateversions.go b/coderd/templateversions.go index 6c97a4371b..f5fadeb605 100644 --- a/coderd/templateversions.go +++ b/coderd/templateversions.go @@ -16,7 +16,6 @@ import ( "github.com/go-chi/chi/v5" "github.com/google/uuid" - "github.com/moby/moby/pkg/namesgenerator" "github.com/sqlc-dev/pqtype" "github.com/zclconf/go-cty/cty" "golang.org/x/xerrors" @@ -37,6 +36,7 @@ import ( "github.com/coder/coder/v2/coderd/rbac" "github.com/coder/coder/v2/coderd/rbac/policy" "github.com/coder/coder/v2/coderd/tracing" + "github.com/coder/coder/v2/coderd/util/namesgenerator" "github.com/coder/coder/v2/coderd/util/ptr" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/examples" @@ -1699,7 +1699,7 @@ func (api *API) postTemplateVersionsByOrganization(rw http.ResponseWriter, r *ht } if req.Name == "" { - req.Name = namesgenerator.GetRandomName(1) + req.Name = namesgenerator.NameDigitWith("_") } err = tx.InsertTemplateVersion(ctx, database.InsertTemplateVersionParams{ diff --git a/coderd/userauth.go b/coderd/userauth.go index 440dcf99d8..0a189f991e 100644 --- a/coderd/userauth.go +++ b/coderd/userauth.go @@ -19,7 +19,6 @@ import ( "github.com/go-jose/go-jose/v4/jwt" "github.com/google/go-github/v43/github" "github.com/google/uuid" - "github.com/moby/moby/pkg/namesgenerator" "golang.org/x/oauth2" "golang.org/x/xerrors" @@ -41,6 +40,7 @@ import ( "github.com/coder/coder/v2/coderd/render" "github.com/coder/coder/v2/coderd/telemetry" "github.com/coder/coder/v2/coderd/userpassword" + "github.com/coder/coder/v2/coderd/util/namesgenerator" "github.com/coder/coder/v2/coderd/util/ptr" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/cryptorand" @@ -1723,7 +1723,7 @@ func (api *API) oauthLogin(r *http.Request, params *oauthLoginParams) ([]*http.C validUsername bool ) for i := 0; i < 10; i++ { - alternate := fmt.Sprintf("%s-%s", original, namesgenerator.GetRandomName(1)) + alternate := fmt.Sprintf("%s-%s", original, namesgenerator.NameDigitWith("_")) params.Username = codersdk.UsernameFrom(alternate) diff --git a/coderd/util/namesgenerator/namesgenerator.go b/coderd/util/namesgenerator/namesgenerator.go new file mode 100644 index 0000000000..23bd9e9a6e --- /dev/null +++ b/coderd/util/namesgenerator/namesgenerator.go @@ -0,0 +1,82 @@ +// Package namesgenerator generates random names. +// +// This package provides functions for generating random names in the format +// "adjective_surname" with various options for delimiters and uniqueness. +// +// For identifiers that must be unique within a process, use UniqueName or +// UniqueNameWith. For display purposes where uniqueness is not required, +// use NameWith. +package namesgenerator + +import ( + "math/rand/v2" + "strconv" + "strings" + "sync/atomic" + + "github.com/brianvoe/gofakeit/v7" +) + +// maxNameLen is the maximum length for names. Many places in Coder have a 32 +// character limit for names (e.g. usernames, workspace names). +const maxNameLen = 32 + +// counter provides unique suffixes for UniqueName functions. +var counter atomic.Int64 + +// NameWith returns a random name with a custom delimiter. +// Names are not guaranteed to be unique. +func NameWith(delim string) string { + const seed = 0 // gofakeit will use a random crypto seed. + faker := gofakeit.New(seed) + adjective := strings.ToLower(faker.AdjectiveDescriptive()) + last := strings.ToLower(faker.LastName()) + return adjective + delim + last +} + +// NameDigitWith returns a random name with a single random digit suffix (1-9), +// in the format "[adjective][delim][surname][digit]" e.g. "happy_smith9". +// Provides some collision resistance while keeping names short and clean. +// Not guaranteed to be unique. +func NameDigitWith(delim string) string { + const ( + minDigit = 1 + maxDigit = 9 + ) + //nolint:gosec // The random digit doesn't need to be cryptographically secure. + return NameWith(delim) + strconv.Itoa(rand.IntN(maxDigit-minDigit+1)) +} + +// UniqueName returns a random name with a monotonically increasing suffix, +// guaranteeing uniqueness within the process. The name is truncated to 32 +// characters if necessary, preserving the numeric suffix. +func UniqueName() string { + return UniqueNameWith("_") +} + +// UniqueNameWith returns a unique name with a custom delimiter. +// See UniqueName for details on uniqueness guarantees. +func UniqueNameWith(delim string) string { + name := NameWith(delim) + strconv.FormatInt(counter.Add(1), 10) + return truncate(name, maxNameLen) +} + +// truncate truncates a name to maxLen characters. It assumes the name ends with +// a numeric suffix and preserves it, truncating the base name portion instead. +func truncate(name string, maxLen int) string { + if len(name) <= maxLen { + return name + } + // Find where the numeric suffix starts. + suffixStart := len(name) + for suffixStart > 0 && name[suffixStart-1] >= '0' && name[suffixStart-1] <= '9' { + suffixStart-- + } + base := name[:suffixStart] + suffix := name[suffixStart:] + truncateAt := maxLen - len(suffix) + if truncateAt <= 0 { + return strconv.Itoa(maxLen) // Fallback, shouldn't happen in practice. + } + return base[:truncateAt] + suffix +} diff --git a/coderd/util/namesgenerator/namesgenerator_internal_test.go b/coderd/util/namesgenerator/namesgenerator_internal_test.go new file mode 100644 index 0000000000..a69b66bdaa --- /dev/null +++ b/coderd/util/namesgenerator/namesgenerator_internal_test.go @@ -0,0 +1,103 @@ +package namesgenerator + +import ( + "strings" + "testing" + "unicode" + + "github.com/stretchr/testify/assert" +) + +func TestTruncate(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + input string + maxLen int + want string + }{ + { + name: "no truncation needed", + input: "foo1", + maxLen: 10, + want: "foo1", + }, + { + name: "exact fit", + input: "foo1", + maxLen: 4, + want: "foo1", + }, + { + name: "truncate base", + input: "foobar42", + maxLen: 5, + want: "foo42", + }, + { + name: "truncate more", + input: "foobar3", + maxLen: 3, + want: "fo3", + }, + { + name: "long suffix", + input: "foo123456", + maxLen: 8, + want: "fo123456", + }, + { + name: "realistic name", + input: "condescending_proskuriakova999999", + maxLen: 32, + want: "condescending_proskuriakov999999", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got := truncate(tt.input, tt.maxLen) + assert.Equal(t, tt.want, got) + assert.LessOrEqual(t, len(got), tt.maxLen) + }) + } +} + +func TestUniqueNameLength(t *testing.T) { + t.Parallel() + + // Generate many names to exercise the truncation logic. + const iter = 10000 + for range iter { + name := UniqueName() + assert.LessOrEqual(t, len(name), maxNameLen) + assert.Contains(t, name, "_") + assert.Equal(t, name, strings.ToLower(name)) + verifyNoWhitespace(t, name) + } +} + +func TestUniqueNameWithLength(t *testing.T) { + t.Parallel() + + // Generate many names with hyphen delimiter. + const iter = 10000 + for range iter { + name := UniqueNameWith("-") + assert.LessOrEqual(t, len(name), maxNameLen) + assert.Contains(t, name, "-") + assert.Equal(t, name, strings.ToLower(name)) + verifyNoWhitespace(t, name) + } +} + +func verifyNoWhitespace(t *testing.T, s string) { + t.Helper() + for _, r := range s { + if unicode.IsSpace(r) { + t.Fatalf("found whitespace in string %q: %v", s, r) + } + } +} diff --git a/codersdk/name.go b/codersdk/name.go index 8942e08caf..b044fee7e8 100644 --- a/codersdk/name.go +++ b/codersdk/name.go @@ -5,8 +5,9 @@ import ( "regexp" "strings" - "github.com/moby/moby/pkg/namesgenerator" "golang.org/x/xerrors" + + "github.com/coder/coder/v2/coderd/util/namesgenerator" ) var ( @@ -35,7 +36,7 @@ func UsernameFrom(str string) string { if valid := NameValid(str); valid == nil { return str } - return strings.ReplaceAll(namesgenerator.GetRandomName(1), "_", "-") + return namesgenerator.NameDigitWith("-") } // NameValid returns whether the input string is a valid name. diff --git a/enterprise/coderd/coderd_test.go b/enterprise/coderd/coderd_test.go index a82882e390..3588199941 100644 --- a/enterprise/coderd/coderd_test.go +++ b/enterprise/coderd/coderd_test.go @@ -18,7 +18,6 @@ import ( "time" "github.com/google/uuid" - "github.com/moby/moby/pkg/namesgenerator" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/goleak" @@ -42,6 +41,7 @@ import ( agplprebuilds "github.com/coder/coder/v2/coderd/prebuilds" "github.com/coder/coder/v2/coderd/rbac" "github.com/coder/coder/v2/coderd/rbac/policy" + "github.com/coder/coder/v2/coderd/util/namesgenerator" "github.com/coder/coder/v2/coderd/util/ptr" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/codersdk/workspacesdk" @@ -1106,7 +1106,7 @@ func tcpEchoServer(t *testing.T) string { // nolint:revive // t takes precedence. func writeReadEcho(t *testing.T, ctx context.Context, conn net.Conn) { - msg := namesgenerator.GetRandomName(0) + msg := namesgenerator.UniqueName() deadline, ok := ctx.Deadline() if ok { diff --git a/enterprise/coderd/coderdenttest/coderdenttest.go b/enterprise/coderd/coderdenttest/coderdenttest.go index 0e5f92bc35..8ef44cc7cb 100644 --- a/enterprise/coderd/coderdenttest/coderdenttest.go +++ b/enterprise/coderd/coderdenttest/coderdenttest.go @@ -15,7 +15,6 @@ import ( "github.com/golang-jwt/jwt/v4" "github.com/google/uuid" - "github.com/moby/moby/pkg/namesgenerator" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -24,6 +23,7 @@ import ( "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/database/pubsub" "github.com/coder/coder/v2/coderd/prebuilds" + "github.com/coder/coder/v2/coderd/util/namesgenerator" "github.com/coder/coder/v2/coderd/util/ptr" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/codersdk/drpcsdk" @@ -328,9 +328,9 @@ type CreateOrganizationOptions struct { func CreateOrganization(t *testing.T, client *codersdk.Client, opts CreateOrganizationOptions, mutators ...func(*codersdk.CreateOrganizationRequest)) codersdk.Organization { ctx := testutil.Context(t, testutil.WaitMedium) req := codersdk.CreateOrganizationRequest{ - Name: strings.ReplaceAll(strings.ToLower(namesgenerator.GetRandomName(0)), "_", "-"), - DisplayName: namesgenerator.GetRandomName(1), - Description: namesgenerator.GetRandomName(1), + Name: strings.ToLower(namesgenerator.UniqueNameWith("-")), + DisplayName: namesgenerator.UniqueName(), + Description: namesgenerator.UniqueName(), Icon: "", } for _, mutator := range mutators { diff --git a/enterprise/coderd/coderdenttest/proxytest.go b/enterprise/coderd/coderdenttest/proxytest.go index ae765f5095..02dfab6676 100644 --- a/enterprise/coderd/coderdenttest/proxytest.go +++ b/enterprise/coderd/coderdenttest/proxytest.go @@ -12,12 +12,12 @@ import ( "sync" "testing" - "github.com/moby/moby/pkg/namesgenerator" "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "cdr.dev/slog/v3" + "github.com/coder/coder/v2/coderd/util/namesgenerator" "github.com/coder/coder/v2/coderd/workspaceapps/appurl" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/enterprise/coderd" @@ -124,7 +124,7 @@ func NewWorkspaceProxyReplica(t *testing.T, coderdAPI *coderd.API, owner *coders } if options.Name == "" { - options.Name = namesgenerator.GetRandomName(1) + options.Name = namesgenerator.UniqueName() } token := options.Token diff --git a/enterprise/coderd/provisionerdaemons.go b/enterprise/coderd/provisionerdaemons.go index 2fbac9efef..af52fc9b6e 100644 --- a/enterprise/coderd/provisionerdaemons.go +++ b/enterprise/coderd/provisionerdaemons.go @@ -11,7 +11,6 @@ import ( "github.com/google/uuid" "github.com/hashicorp/yamux" - "github.com/moby/moby/pkg/namesgenerator" "go.opentelemetry.io/otel/trace" "golang.org/x/exp/maps" "golang.org/x/xerrors" @@ -29,6 +28,7 @@ import ( "github.com/coder/coder/v2/coderd/rbac" "github.com/coder/coder/v2/coderd/rbac/policy" "github.com/coder/coder/v2/coderd/telemetry" + "github.com/coder/coder/v2/coderd/util/namesgenerator" "github.com/coder/coder/v2/coderd/util/ptr" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/codersdk/drpcsdk" @@ -192,7 +192,7 @@ func (api *API) provisionerDaemonServe(rw http.ResponseWriter, r *http.Request) } } - name := namesgenerator.GetRandomName(10) + name := namesgenerator.NameDigitWith("_") if vals, ok := r.URL.Query()["name"]; ok && len(vals) > 0 { name = vals[0] } else { diff --git a/go.mod b/go.mod index 15056effed..db63252256 100644 --- a/go.mod +++ b/go.mod @@ -154,7 +154,6 @@ require ( github.com/mattn/go-isatty v0.0.20 github.com/mitchellh/go-wordwrap v1.0.1 github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c - github.com/moby/moby v28.5.0+incompatible github.com/mocktools/go-smtp-mock/v2 v2.5.0 github.com/muesli/termenv v0.16.0 github.com/natefinch/atomic v1.0.1 diff --git a/go.sum b/go.sum index b3b866837b..ce7de01c3b 100644 --- a/go.sum +++ b/go.sum @@ -1622,8 +1622,6 @@ github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3N github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ= github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo= -github.com/moby/moby v28.5.0+incompatible h1:eN6ksRE7BojoGW18USJGfyqhx/FWJPLs0jqaTNlfSsM= -github.com/moby/moby v28.5.0+incompatible/go.mod h1:fDXVQ6+S340veQPv35CzDahGBmHsiclFwfEygB/TWMc= github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= diff --git a/testutil/names.go b/testutil/names.go index bb804ba2cf..eb5b0bb244 100644 --- a/testutil/names.go +++ b/testutil/names.go @@ -1,49 +1,21 @@ package testutil import ( - "strconv" - "strings" - "sync/atomic" "testing" - "github.com/moby/moby/pkg/namesgenerator" + "github.com/coder/coder/v2/coderd/util/namesgenerator" ) -var n atomic.Int64 - -const maxNameLen = 32 - -// GetRandomName returns a random name using moby/pkg/namesgenerator. -// namesgenerator.GetRandomName exposes a retry parameter that appends -// a pseudo-random number between 1 and 10 to its return value. -// While this reduces the probability of collisions, it does not negate them. -// This function calls namesgenerator.GetRandomName without the retry -// parameter and instead increments a monotonically increasing integer -// to the return value. +// GetRandomName returns a random name with a unique suffix, truncated to 32 +// characters to fit common name length limits in tests. func GetRandomName(t testing.TB) string { t.Helper() - name := namesgenerator.GetRandomName(0) - return incSuffix(name, n.Add(1), maxNameLen) + return namesgenerator.UniqueName() } -// GetRandomNameHyphenated is as GetRandomName but uses a hyphen "-" instead of -// an underscore. +// GetRandomNameHyphenated is like GetRandomName but uses hyphens instead of +// underscores. func GetRandomNameHyphenated(t testing.TB) string { t.Helper() - name := GetRandomName(t) - return strings.ReplaceAll(name, "_", "-") -} - -func incSuffix(s string, num int64, maxLen int) string { - suffix := strconv.FormatInt(num, 10) - if len(s)+len(suffix) <= maxLen { - return s + suffix - } - stripLen := (len(s) + len(suffix)) - maxLen - stripIdx := len(s) - stripLen - if stripIdx < 0 { - return "" - } - s = s[:stripIdx] - return s + suffix + return namesgenerator.UniqueNameWith("-") } diff --git a/testutil/names_internal_test.go b/testutil/names_internal_test.go deleted file mode 100644 index 7fabc0a8c3..0000000000 --- a/testutil/names_internal_test.go +++ /dev/null @@ -1,52 +0,0 @@ -package testutil - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestIncSuffix(t *testing.T) { - t.Parallel() - - for _, tt := range []struct { - s string - num int64 - maxLen int - want string - }{ - { - s: "foo", - num: 1, - maxLen: 4, - want: "foo1", - }, - { - s: "foo", - num: 42, - maxLen: 3, - want: "f42", - }, - { - s: "foo", - num: 3, - maxLen: 2, - want: "f3", - }, - { - s: "foo", - num: 4, - maxLen: 1, - want: "4", - }, - { - s: "foo", - num: 0, - maxLen: 0, - want: "", - }, - } { - actual := incSuffix(tt.s, tt.num, tt.maxLen) - assert.Equal(t, tt.want, actual) - } -}