diff --git a/go.mod b/go.mod index e20281eaa..db6549f82 100644 --- a/go.mod +++ b/go.mod @@ -23,9 +23,9 @@ require ( github.com/mattn/go-isatty v0.0.20 github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d github.com/muesli/cancelreader v0.2.2 + github.com/oklog/ulid/v2 v2.1.0 github.com/rogpeppe/go-internal v1.11.0 github.com/rs/cors v1.10.1 - github.com/rs/xid v1.5.0 github.com/rwtodd/Go.Sed v0.0.0-20230610052213-ba3e9c186f0a github.com/vektah/gqlparser/v2 v2.5.10 github.com/yuin/goldmark v1.5.6 diff --git a/go.sum b/go.sum index 44bf88953..955c3e444 100644 --- a/go.sum +++ b/go.sum @@ -153,8 +153,11 @@ github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= +github.com/oklog/ulid/v2 v2.1.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU= +github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= +github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= @@ -175,8 +178,6 @@ github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDN github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/rs/cors v1.10.1 h1:L0uuZVXIKlI1SShY2nhFfo44TYvDPQ1w4oFkUJNfhyo= github.com/rs/cors v1.10.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= -github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= -github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/rwtodd/Go.Sed v0.0.0-20230610052213-ba3e9c186f0a h1:URwYffGNuBQkfwkcn+1CZhb8IE/mKSXxPXp/zzQsn80= github.com/rwtodd/Go.Sed v0.0.0-20230610052213-ba3e9c186f0a/go.mod h1:c6qgHcSUeSISur4+Kcf3WYTvpL07S8eAsoP40hDiQ1I= diff --git a/internal/runner/command.go b/internal/runner/command.go index 765c84727..a2bf44761 100644 --- a/internal/runner/command.go +++ b/internal/runner/command.go @@ -15,7 +15,7 @@ import ( "github.com/creack/pty" "github.com/pkg/errors" - "github.com/rs/xid" + "github.com/stateful/runme/internal/utils" "go.uber.org/multierr" "go.uber.org/zap" ) @@ -198,8 +198,8 @@ func newCommand(cfg *commandConfig) (*command, error) { extraArgs = append(extraArgs, "-c", script) case CommandModeTempFile: for { - id := xid.New() - tempScriptFile = filepath.Join(cfg.Directory, fmt.Sprintf(".runme-script-%s", id.String())) + id := utils.GenerateID() + tempScriptFile = filepath.Join(cfg.Directory, fmt.Sprintf(".runme-script-%s", id)) if fileExtension != "" { tempScriptFile += "." + fileExtension diff --git a/internal/runner/service.go b/internal/runner/service.go index 16ddcd3f1..8be36fecf 100644 --- a/internal/runner/service.go +++ b/internal/runner/service.go @@ -9,10 +9,10 @@ import ( "github.com/creack/pty" "github.com/pkg/errors" - "github.com/rs/xid" "github.com/stateful/runme/internal/env" runnerv1 "github.com/stateful/runme/internal/gen/proto/go/runme/runner/v1" "github.com/stateful/runme/internal/rbuffer" + "github.com/stateful/runme/internal/utils" "github.com/stateful/runme/pkg/project" "go.uber.org/zap" "golang.org/x/sync/errgroup" @@ -155,7 +155,7 @@ func ConvertRunnerProject(runnerProj *runnerv1.Project) (project.Project, error) } func (r *runnerService) Execute(srv runnerv1.RunnerService_ExecuteServer) error { - logger := r.logger.With(zap.String("_id", xid.New().String())) + logger := r.logger.With(zap.String("_id", utils.GenerateID())) logger.Info("running Execute in runnerService") diff --git a/internal/runner/session.go b/internal/runner/session.go index d553bf621..0fa44766c 100644 --- a/internal/runner/session.go +++ b/internal/runner/session.go @@ -5,7 +5,7 @@ import ( "sync" lru "github.com/hashicorp/golang-lru/v2" - "github.com/rs/xid" + "github.com/stateful/runme/internal/utils" "github.com/stateful/runme/pkg/project" "go.uber.org/zap" ) @@ -36,7 +36,7 @@ func NewSession(envs []string, proj project.Project, logger *zap.Logger) (*Sessi } s := &Session{ - ID: xid.New().String(), + ID: utils.GenerateID(), envStore: newEnvStore(sessionEnvs...), logger: logger, diff --git a/internal/runner/shell_session.go b/internal/runner/shell_session.go index 6956a374a..646993ec6 100644 --- a/internal/runner/shell_session.go +++ b/internal/runner/shell_session.go @@ -8,8 +8,8 @@ import ( "github.com/creack/pty" "github.com/pkg/errors" - "github.com/rs/xid" xpty "github.com/stateful/runme/internal/pty" + "github.com/stateful/runme/internal/utils" "golang.org/x/term" ) @@ -25,7 +25,7 @@ type ShellSession struct { } func NewShellSession(command string) (*ShellSession, error) { - id := xid.New().String() + id := utils.GenerateID() cmd := exec.Command(command) cmd.Env = append(os.Environ(), "RUNMESHELL="+id) diff --git a/internal/utils/id.go b/internal/utils/id.go new file mode 100644 index 000000000..e20e08a6b --- /dev/null +++ b/internal/utils/id.go @@ -0,0 +1,42 @@ +// Package utils contains utility functions for the application +package utils + +import ( + "math/rand" + "regexp" + "time" + + "github.com/oklog/ulid/v2" +) + +// IsULID checks if the given string is a valid ULID +// ULID pattern: +// +// 01AN4Z07BY 79KA1307SR9X4MV3 +// |----------| |----------------| +// Timestamp Randomness +// +// 10 characters 16 characters +// Crockford's Base32 is used (excludes I, L, O, and U to avoid confusion and abuse) +func isULID(s string) bool { + + ulidRegex := `^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26}$` + matched, _ := regexp.MatchString(ulidRegex, s) + return matched +} + +// ValidID checks if the given id is valid +func ValidID(id string) bool { + _, err := ulid.Parse(id) + + return err == nil && isULID(id) +} + +// GenerateID generates a new universal ID +func GenerateID() string { + ts := ulid.Timestamp(time.Now()) + source := rand.NewSource(time.Now().UnixNano()) + entropy := rand.New(source) + + return ulid.MustNew(ts, entropy).String() +} diff --git a/internal/utils/id_test.go b/internal/utils/id_test.go new file mode 100644 index 000000000..6093156c7 --- /dev/null +++ b/internal/utils/id_test.go @@ -0,0 +1,46 @@ +package utils + +import ( + "strings" + "testing" +) + +// TestValidID tests the ValidID function with both valid and invalid inputs. +func TestValidID(t *testing.T) { + // Generate a valid ULID for testing. + validULID := GenerateID() + + tests := []struct { + id string + expected bool + }{ + {validULID, true}, + {"0", false}, + {"invalidulid", false}, + {"invalidulid", false}, + {"01B4E6BXY0PRJ5G420D25MWQY!", false}, + } + + for _, tt := range tests { + t.Run(tt.id, func(t *testing.T) { + if got := ValidID(tt.id); got != tt.expected { + t.Errorf("ValidID(%s) = %v; want %v", tt.id, got, tt.expected) + } + }) + } +} + +func TestGenerateID(t *testing.T) { + id := GenerateID() + if !ValidID(id) { + t.Errorf("Generated ID is not a valid ULID: %s", id) + } + + if len(id) != 26 { + t.Errorf("Generated ID does not have the correct length: got %v want %v", len(id), 26) + } + + if strings.ContainsAny(id, "ilou") { + t.Errorf("Generated ID contains invalid characters: %s", id) + } +}