mirror of
https://github.com/sablierapp/sablier.git
synced 2025-12-21 13:23:03 +01:00
refactor(storage): add store.Store interface
There is a first implementation with ValKey that will allow to use redis APIs as a backend for Sablier with Hight Availability
This commit is contained in:
1
.testcontainers.properties
Normal file
1
.testcontainers.properties
Normal file
@@ -0,0 +1 @@
|
||||
ryuk.disabled=true
|
||||
3
Makefile
3
Makefile
@@ -20,6 +20,9 @@ $(PLATFORMS):
|
||||
run:
|
||||
go run main.go start
|
||||
|
||||
generate:
|
||||
go generate ./..
|
||||
|
||||
build:
|
||||
go build -v .
|
||||
|
||||
|
||||
@@ -2,8 +2,9 @@ package discovery
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/sablierapp/sablier/app/providers"
|
||||
"github.com/sablierapp/sablier/pkg/arrays"
|
||||
"github.com/sablierapp/sablier/pkg/store"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
@@ -12,7 +13,7 @@ import (
|
||||
// as running instances by Sablier.
|
||||
// By default, Sablier does not stop all already running instances. Meaning that you need to make an
|
||||
// initial request in order to trigger the scaling to zero.
|
||||
func StopAllUnregisteredInstances(ctx context.Context, provider providers.Provider, registered []string) error {
|
||||
func StopAllUnregisteredInstances(ctx context.Context, provider providers.Provider, s store.Store) error {
|
||||
log.Info("Stopping all unregistered running instances")
|
||||
|
||||
log.Tracef("Retrieving all instances with label [%v=true]", LabelEnable)
|
||||
@@ -25,12 +26,14 @@ func StopAllUnregisteredInstances(ctx context.Context, provider providers.Provid
|
||||
}
|
||||
|
||||
log.Tracef("Found %v instances with label [%v=true]", len(instances), LabelEnable)
|
||||
names := make([]string, 0, len(instances))
|
||||
unregistered := make([]string, 0)
|
||||
for _, instance := range instances {
|
||||
names = append(names, instance.Name)
|
||||
_, err = s.Get(ctx, instance.Name)
|
||||
if errors.Is(err, store.ErrKeyNotFound) {
|
||||
unregistered = append(unregistered, instance.Name)
|
||||
}
|
||||
}
|
||||
|
||||
unregistered := arrays.RemoveElements(names, registered)
|
||||
log.Tracef("Found %v unregistered instances ", len(instances))
|
||||
|
||||
waitGroup := errgroup.Group{}
|
||||
|
||||
@@ -4,10 +4,14 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/sablierapp/sablier/app/discovery"
|
||||
"github.com/sablierapp/sablier/app/instance"
|
||||
"github.com/sablierapp/sablier/app/providers"
|
||||
"github.com/sablierapp/sablier/app/providers/mock"
|
||||
"github.com/sablierapp/sablier/app/types"
|
||||
"github.com/sablierapp/sablier/pkg/store/inmemory"
|
||||
"gotest.tools/v3/assert"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestStopAllUnregisteredInstances(t *testing.T) {
|
||||
@@ -20,7 +24,9 @@ func TestStopAllUnregisteredInstances(t *testing.T) {
|
||||
{Name: "instance2"},
|
||||
{Name: "instance3"},
|
||||
}
|
||||
registered := []string{"instance1"}
|
||||
store := inmemory.NewInMemory()
|
||||
err := store.Put(ctx, instance.State{Name: "instance1"}, time.Minute)
|
||||
assert.NilError(t, err)
|
||||
|
||||
// Set up expectations for InstanceList
|
||||
mockProvider.On("InstanceList", ctx, providers.InstanceListOptions{
|
||||
@@ -33,10 +39,8 @@ func TestStopAllUnregisteredInstances(t *testing.T) {
|
||||
mockProvider.On("Stop", ctx, "instance3").Return(nil)
|
||||
|
||||
// Call the function under test
|
||||
err := discovery.StopAllUnregisteredInstances(ctx, mockProvider, registered)
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, but got %v", err)
|
||||
}
|
||||
err = discovery.StopAllUnregisteredInstances(ctx, mockProvider, store)
|
||||
assert.NilError(t, err)
|
||||
|
||||
// Check expectations
|
||||
mockProvider.AssertExpectations(t)
|
||||
@@ -52,7 +56,9 @@ func TestStopAllUnregisteredInstances_WithError(t *testing.T) {
|
||||
{Name: "instance2"},
|
||||
{Name: "instance3"},
|
||||
}
|
||||
registered := []string{"instance1"}
|
||||
store := inmemory.NewInMemory()
|
||||
err := store.Put(ctx, instance.State{Name: "instance1"}, time.Minute)
|
||||
assert.NilError(t, err)
|
||||
|
||||
// Set up expectations for InstanceList
|
||||
mockProvider.On("InstanceList", ctx, providers.InstanceListOptions{
|
||||
@@ -65,10 +71,8 @@ func TestStopAllUnregisteredInstances_WithError(t *testing.T) {
|
||||
mockProvider.On("Stop", ctx, "instance3").Return(nil)
|
||||
|
||||
// Call the function under test
|
||||
err := discovery.StopAllUnregisteredInstances(ctx, mockProvider, registered)
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error, but got nil")
|
||||
}
|
||||
err = discovery.StopAllUnregisteredInstances(ctx, mockProvider, store)
|
||||
assert.Error(t, err, "stop error")
|
||||
|
||||
// Check expectations
|
||||
mockProvider.AssertExpectations(t)
|
||||
|
||||
@@ -8,23 +8,27 @@ import (
|
||||
"github.com/sablierapp/sablier/app/providers/docker"
|
||||
"github.com/sablierapp/sablier/app/providers/dockerswarm"
|
||||
"github.com/sablierapp/sablier/app/providers/kubernetes"
|
||||
"github.com/sablierapp/sablier/pkg/store/inmemory"
|
||||
"log/slog"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/sablierapp/sablier/app/instance"
|
||||
"github.com/sablierapp/sablier/app/providers"
|
||||
"github.com/sablierapp/sablier/app/sessions"
|
||||
"github.com/sablierapp/sablier/app/storage"
|
||||
"github.com/sablierapp/sablier/app/theme"
|
||||
"github.com/sablierapp/sablier/config"
|
||||
"github.com/sablierapp/sablier/internal/server"
|
||||
"github.com/sablierapp/sablier/pkg/tinykv"
|
||||
"github.com/sablierapp/sablier/version"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func Start(ctx context.Context, conf config.Config) error {
|
||||
|
||||
// Create context that listens for the interrupt signal from the OS.
|
||||
ctx, stop := signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM)
|
||||
defer stop()
|
||||
logLevel, err := log.ParseLevel(conf.Logging.Level)
|
||||
|
||||
if err != nil {
|
||||
@@ -45,7 +49,11 @@ func Start(ctx context.Context, conf config.Config) error {
|
||||
|
||||
log.Infof("using provider \"%s\"", conf.Provider.Name)
|
||||
|
||||
store := tinykv.New(conf.Sessions.ExpirationInterval, onSessionExpires(provider))
|
||||
store := inmemory.NewInMemory()
|
||||
err = store.OnExpire(ctx, onSessionExpires(provider))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
storage, err := storage.NewFileStorage(conf.Storage)
|
||||
if err != nil {
|
||||
@@ -55,13 +63,39 @@ func Start(ctx context.Context, conf config.Config) error {
|
||||
sessionsManager := sessions.NewSessionsManager(store, provider)
|
||||
defer sessionsManager.Stop()
|
||||
|
||||
groups, err := provider.GetGroups(ctx)
|
||||
if err != nil {
|
||||
log.Warn("could not get groups", err)
|
||||
} else {
|
||||
sessionsManager.SetGroups(groups)
|
||||
}
|
||||
|
||||
updateGroups := make(chan map[string][]string)
|
||||
go WatchGroups(ctx, provider, 2*time.Second, updateGroups)
|
||||
go func() {
|
||||
for groups := range updateGroups {
|
||||
sessionsManager.SetGroups(groups)
|
||||
}
|
||||
}()
|
||||
|
||||
instanceStopped := make(chan string)
|
||||
go provider.NotifyInstanceStopped(ctx, instanceStopped)
|
||||
go func() {
|
||||
for stopped := range instanceStopped {
|
||||
err := sessionsManager.RemoveInstance(stopped)
|
||||
if err != nil {
|
||||
logger.Warn("could not remove instance", slog.Any("error", err))
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if storage.Enabled() {
|
||||
defer saveSessions(storage, sessionsManager)
|
||||
loadSessions(storage, sessionsManager)
|
||||
}
|
||||
|
||||
if conf.Provider.AutoStopOnStartup {
|
||||
err := discovery.StopAllUnregisteredInstances(context.Background(), provider, store.Keys())
|
||||
err := discovery.StopAllUnregisteredInstances(context.Background(), provider, store)
|
||||
if err != nil {
|
||||
log.Warnf("Stopping unregistered instances had an error: %v", err)
|
||||
}
|
||||
@@ -91,14 +125,28 @@ func Start(ctx context.Context, conf config.Config) error {
|
||||
SessionsConfig: conf.Sessions,
|
||||
}
|
||||
|
||||
server.Start(ctx, logger, conf.Server, strategy)
|
||||
go server.Start(ctx, logger, conf.Server, strategy)
|
||||
|
||||
// Listen for the interrupt signal.
|
||||
<-ctx.Done()
|
||||
|
||||
// Restore default behavior on the interrupt signal and notify user of shutdown.
|
||||
stop()
|
||||
log.Println("shutting down gracefully, press Ctrl+C again to force")
|
||||
|
||||
// The context is used to inform the server it has 5 seconds to finish
|
||||
// the request it is currently handling
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
log.Println("Server exiting")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func onSessionExpires(provider providers.Provider) func(key string, instance instance.State) {
|
||||
return func(_key string, _instance instance.State) {
|
||||
go func(key string, instance instance.State) {
|
||||
func onSessionExpires(provider providers.Provider) func(key string) {
|
||||
return func(_key string) {
|
||||
go func(key string) {
|
||||
log.Debugf("stopping %s...", key)
|
||||
err := provider.Stop(context.Background(), key)
|
||||
|
||||
@@ -107,11 +155,12 @@ func onSessionExpires(provider providers.Provider) func(key string, instance ins
|
||||
} else {
|
||||
log.Debugf("stopped %s", key)
|
||||
}
|
||||
}(_key, _instance)
|
||||
}(_key)
|
||||
}
|
||||
}
|
||||
|
||||
func loadSessions(storage storage.Storage, sessions sessions.Manager) {
|
||||
slog.Info("loading sessions from storage")
|
||||
reader, err := storage.Reader()
|
||||
if err != nil {
|
||||
log.Error("error loading sessions", err)
|
||||
@@ -123,6 +172,7 @@ func loadSessions(storage storage.Storage, sessions sessions.Manager) {
|
||||
}
|
||||
|
||||
func saveSessions(storage storage.Storage, sessions sessions.Manager) {
|
||||
slog.Info("writing sessions to storage")
|
||||
writer, err := storage.Writer()
|
||||
if err != nil {
|
||||
log.Error("error saving sessions", err)
|
||||
@@ -149,3 +199,20 @@ func NewProvider(config config.Provider) (providers.Provider, error) {
|
||||
}
|
||||
return nil, fmt.Errorf("unimplemented provider %s", config.Name)
|
||||
}
|
||||
|
||||
func WatchGroups(ctx context.Context, provider providers.Provider, frequency time.Duration, send chan<- map[string][]string) {
|
||||
ticker := time.NewTicker(frequency)
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
groups, err := provider.GetGroups(ctx)
|
||||
if err != nil {
|
||||
log.Warn("could not get groups", err)
|
||||
} else if groups != nil {
|
||||
send <- groups
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
package sessions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/sablierapp/sablier/app/providers"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// watchGroups watches indefinitely for new groups
|
||||
func watchGroups(ctx context.Context, provider providers.Provider, frequency time.Duration, send chan<- map[string][]string) {
|
||||
ticker := time.NewTicker(frequency)
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
groups, err := provider.GetGroups(ctx)
|
||||
if err != nil {
|
||||
log.Warn("could not get groups", err)
|
||||
} else {
|
||||
send <- groups
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,9 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/sablierapp/sablier/pkg/store"
|
||||
"io"
|
||||
"log/slog"
|
||||
"maps"
|
||||
"slices"
|
||||
"sync"
|
||||
@@ -13,12 +15,9 @@ import (
|
||||
|
||||
"github.com/sablierapp/sablier/app/instance"
|
||||
"github.com/sablierapp/sablier/app/providers"
|
||||
"github.com/sablierapp/sablier/pkg/tinykv"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const defaultRefreshFrequency = 2 * time.Second
|
||||
|
||||
//go:generate mockgen -package sessionstest -source=sessions_manager.go -destination=sessionstest/mocks_sessions_manager.go *
|
||||
|
||||
type Manager interface {
|
||||
@@ -30,6 +29,9 @@ type Manager interface {
|
||||
LoadSessions(io.ReadCloser) error
|
||||
SaveSessions(io.WriteCloser) error
|
||||
|
||||
RemoveInstance(name string) error
|
||||
SetGroups(groups map[string][]string)
|
||||
|
||||
Stop()
|
||||
}
|
||||
|
||||
@@ -37,71 +39,57 @@ type SessionsManager struct {
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
|
||||
store tinykv.KV[instance.State]
|
||||
store store.Store
|
||||
provider providers.Provider
|
||||
groups map[string][]string
|
||||
}
|
||||
|
||||
func NewSessionsManager(store tinykv.KV[instance.State], provider providers.Provider) Manager {
|
||||
func NewSessionsManager(store store.Store, provider providers.Provider) Manager {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
groups, err := provider.GetGroups(ctx)
|
||||
if err != nil {
|
||||
groups = make(map[string][]string)
|
||||
log.Warn("could not get groups", err)
|
||||
}
|
||||
|
||||
sm := &SessionsManager{
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
store: store,
|
||||
provider: provider,
|
||||
groups: groups,
|
||||
groups: map[string][]string{},
|
||||
}
|
||||
|
||||
sm.initWatchers()
|
||||
|
||||
return sm
|
||||
}
|
||||
|
||||
func (sm *SessionsManager) initWatchers() {
|
||||
updateGroups := make(chan map[string][]string)
|
||||
go watchGroups(sm.ctx, sm.provider, defaultRefreshFrequency, updateGroups)
|
||||
go sm.consumeGroups(updateGroups)
|
||||
|
||||
instanceStopped := make(chan string)
|
||||
go sm.provider.NotifyInstanceStopped(sm.ctx, instanceStopped)
|
||||
go sm.consumeInstanceStopped(instanceStopped)
|
||||
func (sm *SessionsManager) SetGroups(groups map[string][]string) {
|
||||
if groups == nil {
|
||||
groups = map[string][]string{}
|
||||
}
|
||||
slog.Info("set groups", slog.Any("old", sm.groups), slog.Any("new", groups))
|
||||
sm.groups = groups
|
||||
}
|
||||
|
||||
func (sm *SessionsManager) consumeGroups(receive chan map[string][]string) {
|
||||
for groups := range receive {
|
||||
sm.groups = groups
|
||||
}
|
||||
}
|
||||
|
||||
func (sm *SessionsManager) consumeInstanceStopped(instanceStopped chan string) {
|
||||
for instance := range instanceStopped {
|
||||
// Will delete from the store containers that have been stop either by external sources
|
||||
// or by the internal expiration loop, if the deleted entry does not exist, it doesn't matter
|
||||
log.Debugf("received event instance %s is stopped, removing from store", instance)
|
||||
sm.store.Delete(instance)
|
||||
}
|
||||
func (sm *SessionsManager) RemoveInstance(name string) error {
|
||||
return sm.store.Delete(context.Background(), name)
|
||||
}
|
||||
|
||||
func (sm *SessionsManager) LoadSessions(reader io.ReadCloser) error {
|
||||
unmarshaler, ok := sm.store.(json.Unmarshaler)
|
||||
defer reader.Close()
|
||||
return json.NewDecoder(reader).Decode(sm.store)
|
||||
if ok {
|
||||
return json.NewDecoder(reader).Decode(unmarshaler)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sm *SessionsManager) SaveSessions(writer io.WriteCloser) error {
|
||||
marshaler, ok := sm.store.(json.Marshaler)
|
||||
defer writer.Close()
|
||||
if ok {
|
||||
encoder := json.NewEncoder(writer)
|
||||
encoder.SetEscapeHTML(false)
|
||||
encoder.SetIndent("", " ")
|
||||
|
||||
encoder := json.NewEncoder(writer)
|
||||
encoder.SetEscapeHTML(false)
|
||||
encoder.SetIndent("", " ")
|
||||
|
||||
return encoder.Encode(sm.store)
|
||||
return encoder.Encode(marshaler)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type InstanceState struct {
|
||||
@@ -110,26 +98,21 @@ type InstanceState struct {
|
||||
}
|
||||
|
||||
type SessionState struct {
|
||||
Instances *sync.Map
|
||||
Instances map[string]InstanceState `json:"instances"`
|
||||
}
|
||||
|
||||
func (s *SessionState) IsReady() bool {
|
||||
ready := true
|
||||
|
||||
if s.Instances == nil {
|
||||
s.Instances = &sync.Map{}
|
||||
s.Instances = map[string]InstanceState{}
|
||||
}
|
||||
|
||||
s.Instances.Range(func(key, value interface{}) bool {
|
||||
state := value.(InstanceState)
|
||||
if state.Error != nil || state.Instance.Status != instance.Ready {
|
||||
ready = false
|
||||
for _, v := range s.Instances {
|
||||
if v.Error != nil || v.Instance.Status != instance.Ready {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
return ready
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *SessionState) Status() string {
|
||||
@@ -148,7 +131,7 @@ func (s *SessionsManager) RequestSession(names []string, duration time.Duration)
|
||||
var wg sync.WaitGroup
|
||||
|
||||
sessionState = &SessionState{
|
||||
Instances: &sync.Map{},
|
||||
Instances: map[string]InstanceState{},
|
||||
}
|
||||
|
||||
wg.Add(len(names))
|
||||
@@ -158,10 +141,10 @@ func (s *SessionsManager) RequestSession(names []string, duration time.Duration)
|
||||
defer wg.Done()
|
||||
state, err := s.requestSessionInstance(name, duration)
|
||||
|
||||
sessionState.Instances.Store(name, InstanceState{
|
||||
sessionState.Instances[name] = InstanceState{
|
||||
Instance: state,
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
}(names[i])
|
||||
}
|
||||
|
||||
@@ -195,9 +178,8 @@ func (s *SessionsManager) requestSessionInstance(name string, duration time.Dura
|
||||
return nil, errors.New("instance name cannot be empty")
|
||||
}
|
||||
|
||||
requestState, exists := s.store.Get(name)
|
||||
|
||||
if !exists {
|
||||
requestState, err := s.store.Get(context.TODO(), name)
|
||||
if errors.Is(err, store.ErrKeyNotFound) {
|
||||
log.Debugf("starting [%s]...", name)
|
||||
|
||||
err := s.provider.Start(s.ctx, name)
|
||||
@@ -217,6 +199,8 @@ func (s *SessionsManager) requestSessionInstance(name string, duration time.Dura
|
||||
requestState.Message = state.Message
|
||||
|
||||
log.Debugf("status for [%s]=[%s]", name, requestState.Status)
|
||||
} else if err != nil {
|
||||
return nil, fmt.Errorf("cannot retrieve instance from store: %w", err)
|
||||
} else if requestState.Status != instance.Ready {
|
||||
log.Debugf("checking [%s]...", name)
|
||||
state, err := s.provider.GetState(s.ctx, name)
|
||||
@@ -250,6 +234,7 @@ func (s *SessionsManager) RequestReadySession(ctx context.Context, names []strin
|
||||
|
||||
ticker := time.NewTicker(5 * time.Second)
|
||||
readiness := make(chan *SessionState)
|
||||
errch := make(chan error)
|
||||
quit := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
@@ -258,6 +243,7 @@ func (s *SessionsManager) RequestReadySession(ctx context.Context, names []strin
|
||||
case <-ticker.C:
|
||||
session, err := s.RequestSession(names, duration)
|
||||
if err != nil {
|
||||
errch <- err
|
||||
return
|
||||
}
|
||||
if session.IsReady() {
|
||||
@@ -274,10 +260,16 @@ func (s *SessionsManager) RequestReadySession(ctx context.Context, names []strin
|
||||
case <-ctx.Done():
|
||||
log.Debug("request cancelled by user, stopping timeout")
|
||||
close(quit)
|
||||
if ctx.Err() != nil {
|
||||
return nil, fmt.Errorf("request cancelled by user: %w", ctx.Err())
|
||||
}
|
||||
return nil, fmt.Errorf("request cancelled by user")
|
||||
case status := <-readiness:
|
||||
close(quit)
|
||||
return status, nil
|
||||
case err := <-errch:
|
||||
close(quit)
|
||||
return nil, err
|
||||
case <-time.After(timeout):
|
||||
close(quit)
|
||||
return nil, fmt.Errorf("session was not ready after %s", timeout.String())
|
||||
@@ -306,25 +298,19 @@ func (s *SessionsManager) RequestReadySessionGroup(ctx context.Context, group st
|
||||
}
|
||||
|
||||
func (s *SessionsManager) ExpiresAfter(instance *instance.State, duration time.Duration) {
|
||||
s.store.Put(instance.Name, *instance, duration)
|
||||
err := s.store.Put(context.TODO(), *instance, duration)
|
||||
if err != nil {
|
||||
slog.Default().Warn("could not put instance to store, will not expire", slog.Any("error", err), slog.String("instance", instance.Name))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SessionsManager) Stop() {
|
||||
// Stop event listeners
|
||||
s.cancel()
|
||||
|
||||
// Stop the store
|
||||
s.store.Stop()
|
||||
}
|
||||
|
||||
func (s *SessionState) MarshalJSON() ([]byte, error) {
|
||||
instances := []InstanceState{}
|
||||
|
||||
s.Instances.Range(func(key, value interface{}) bool {
|
||||
state := value.(InstanceState)
|
||||
instances = append(instances, state)
|
||||
return true
|
||||
})
|
||||
instances := maps.Values(s.Instances)
|
||||
|
||||
return json.Marshal(map[string]any{
|
||||
"instances": instances,
|
||||
|
||||
@@ -2,7 +2,8 @@ package sessions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"github.com/sablierapp/sablier/pkg/store/storetest"
|
||||
"go.uber.org/mock/gomock"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -14,7 +15,7 @@ import (
|
||||
|
||||
func TestSessionState_IsReady(t *testing.T) {
|
||||
type fields struct {
|
||||
Instances *sync.Map
|
||||
Instances map[string]InstanceState
|
||||
Error error
|
||||
}
|
||||
tests := []struct {
|
||||
@@ -72,102 +73,73 @@ func TestSessionState_IsReady(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func createMap(instances []*instance.State) (store *sync.Map) {
|
||||
store = &sync.Map{}
|
||||
func createMap(instances []*instance.State) map[string]InstanceState {
|
||||
states := make(map[string]InstanceState)
|
||||
|
||||
for _, v := range instances {
|
||||
store.Store(v.Name, InstanceState{
|
||||
states[v.Name] = InstanceState{
|
||||
Instance: v,
|
||||
Error: nil,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
return states
|
||||
}
|
||||
|
||||
func TestNewSessionsManagerEvents(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
stoppedInstances []string
|
||||
}{
|
||||
{
|
||||
name: "when nginx is stopped it is removed from the store",
|
||||
stoppedInstances: []string{"nginx"},
|
||||
},
|
||||
{
|
||||
name: "when nginx, apache and whoami is stopped it is removed from the store",
|
||||
stoppedInstances: []string{"nginx", "apache", "whoami"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
provider := mocks.NewProviderMockWithStoppedInstancesEvents(tt.stoppedInstances)
|
||||
provider.Add(1)
|
||||
func setupSessionManager(t *testing.T) (Manager, *storetest.MockStore, *mocks.ProviderMock) {
|
||||
t.Helper()
|
||||
ctrl := gomock.NewController(t)
|
||||
|
||||
kv := mocks.NewKVMock()
|
||||
kv.Add(len(tt.stoppedInstances))
|
||||
kv.Mock.On("Delete", mock.AnythingOfType("string")).Return()
|
||||
p := mocks.NewProviderMock()
|
||||
s := storetest.NewMockStore(ctrl)
|
||||
|
||||
NewSessionsManager(kv, provider)
|
||||
m := NewSessionsManager(s, p)
|
||||
return m, s, p
|
||||
}
|
||||
|
||||
// The provider watches notifications from a Goroutine, must wait
|
||||
provider.Wait()
|
||||
// The key is deleted inside a Goroutine by the session manager, must wait
|
||||
kv.Wait()
|
||||
|
||||
for _, instance := range tt.stoppedInstances {
|
||||
kv.AssertCalled(t, "Delete", instance)
|
||||
}
|
||||
})
|
||||
}
|
||||
func TestSessionsManager(t *testing.T) {
|
||||
t.Run("RemoveInstance", func(t *testing.T) {
|
||||
manager, store, _ := setupSessionManager(t)
|
||||
store.EXPECT().Delete(gomock.Any(), "test")
|
||||
err := manager.RemoveInstance("test")
|
||||
assert.NilError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSessionsManager_RequestReadySessionCancelledByUser(t *testing.T) {
|
||||
|
||||
t.Run("request ready session is cancelled by user", func(t *testing.T) {
|
||||
kvmock := mocks.NewKVMock()
|
||||
kvmock.On("Get", mock.Anything).Return(instance.State{Name: "apache", Status: instance.NotReady}, true)
|
||||
|
||||
providermock := mocks.NewProviderMock()
|
||||
providermock.On("GetState", mock.Anything).Return(instance.State{Name: "apache", Status: instance.NotReady}, nil)
|
||||
|
||||
s := &SessionsManager{
|
||||
store: kvmock,
|
||||
provider: providermock,
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
manager, store, provider := setupSessionManager(t)
|
||||
store.EXPECT().Get(gomock.Any(), gomock.Any()).Return(instance.State{Name: "apache", Status: instance.NotReady}, nil).AnyTimes()
|
||||
store.EXPECT().Put(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
|
||||
|
||||
provider.On("GetState", mock.Anything).Return(instance.State{Name: "apache", Status: instance.NotReady}, nil)
|
||||
|
||||
errchan := make(chan error)
|
||||
go func() {
|
||||
_, err := s.RequestReadySession(ctx, []string{"nginx", "whoami"}, time.Minute, time.Minute)
|
||||
_, err := manager.RequestReadySession(ctx, []string{"apache"}, time.Minute, time.Minute)
|
||||
errchan <- err
|
||||
}()
|
||||
|
||||
// Cancel the call
|
||||
cancel()
|
||||
|
||||
assert.Error(t, <-errchan, "request cancelled by user")
|
||||
assert.Error(t, <-errchan, "request cancelled by user: context canceled")
|
||||
})
|
||||
}
|
||||
|
||||
func TestSessionsManager_RequestReadySessionCancelledByTimeout(t *testing.T) {
|
||||
|
||||
t.Run("request ready session is cancelled by timeout", func(t *testing.T) {
|
||||
kvmock := mocks.NewKVMock()
|
||||
kvmock.On("Get", mock.Anything).Return(instance.State{Name: "apache", Status: instance.NotReady}, true)
|
||||
manager, store, provider := setupSessionManager(t)
|
||||
store.EXPECT().Get(gomock.Any(), gomock.Any()).Return(instance.State{Name: "apache", Status: instance.NotReady}, nil).AnyTimes()
|
||||
store.EXPECT().Put(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
|
||||
|
||||
providermock := mocks.NewProviderMock()
|
||||
providermock.On("GetState", mock.Anything).Return(instance.State{Name: "apache", Status: instance.NotReady}, nil)
|
||||
|
||||
s := &SessionsManager{
|
||||
store: kvmock,
|
||||
provider: providermock,
|
||||
}
|
||||
provider.On("GetState", mock.Anything).Return(instance.State{Name: "apache", Status: instance.NotReady}, nil)
|
||||
|
||||
errchan := make(chan error)
|
||||
go func() {
|
||||
_, err := s.RequestReadySession(context.Background(), []string{"nginx", "whoami"}, time.Minute, time.Second)
|
||||
_, err := manager.RequestReadySession(context.Background(), []string{"apache"}, time.Minute, time.Second)
|
||||
errchan <- err
|
||||
}()
|
||||
|
||||
@@ -178,19 +150,13 @@ func TestSessionsManager_RequestReadySessionCancelledByTimeout(t *testing.T) {
|
||||
func TestSessionsManager_RequestReadySession(t *testing.T) {
|
||||
|
||||
t.Run("request ready session is ready", func(t *testing.T) {
|
||||
kvmock := mocks.NewKVMock()
|
||||
kvmock.On("Get", mock.Anything).Return(instance.State{Name: "apache", Status: instance.Ready}, true)
|
||||
|
||||
providermock := mocks.NewProviderMock()
|
||||
|
||||
s := &SessionsManager{
|
||||
store: kvmock,
|
||||
provider: providermock,
|
||||
}
|
||||
manager, store, _ := setupSessionManager(t)
|
||||
store.EXPECT().Get(gomock.Any(), gomock.Any()).Return(instance.State{Name: "apache", Status: instance.Ready}, nil).AnyTimes()
|
||||
store.EXPECT().Put(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
|
||||
|
||||
errchan := make(chan error)
|
||||
go func() {
|
||||
_, err := s.RequestReadySession(context.Background(), []string{"nginx", "whoami"}, time.Minute, time.Second)
|
||||
_, err := manager.RequestReadySession(context.Background(), []string{"apache"}, time.Minute, time.Second)
|
||||
errchan <- err
|
||||
}()
|
||||
|
||||
|
||||
@@ -57,6 +57,20 @@ func (mr *MockManagerMockRecorder) LoadSessions(arg0 any) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadSessions", reflect.TypeOf((*MockManager)(nil).LoadSessions), arg0)
|
||||
}
|
||||
|
||||
// RemoveInstance mocks base method.
|
||||
func (m *MockManager) RemoveInstance(name string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "RemoveInstance", name)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// RemoveInstance indicates an expected call of RemoveInstance.
|
||||
func (mr *MockManagerMockRecorder) RemoveInstance(name any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveInstance", reflect.TypeOf((*MockManager)(nil).RemoveInstance), name)
|
||||
}
|
||||
|
||||
// RequestReadySession mocks base method.
|
||||
func (m *MockManager) RequestReadySession(ctx context.Context, names []string, duration, timeout time.Duration) (*sessions.SessionState, error) {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -131,6 +145,18 @@ func (mr *MockManagerMockRecorder) SaveSessions(arg0 any) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveSessions", reflect.TypeOf((*MockManager)(nil).SaveSessions), arg0)
|
||||
}
|
||||
|
||||
// SetGroups mocks base method.
|
||||
func (m *MockManager) SetGroups(groups map[string][]string) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "SetGroups", groups)
|
||||
}
|
||||
|
||||
// SetGroups indicates an expected call of SetGroups.
|
||||
func (mr *MockManagerMockRecorder) SetGroups(groups any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetGroups", reflect.TypeOf((*MockManager)(nil).SetGroups), groups)
|
||||
}
|
||||
|
||||
// Stop mocks base method.
|
||||
func (m *MockManager) Stop() {
|
||||
m.ctrl.T.Helper()
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
package types
|
||||
38
go.mod
38
go.mod
@@ -18,6 +18,7 @@ require (
|
||||
github.com/spf13/viper v1.19.0
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/tniswong/go.rfcx v0.0.0-20181019234604-07783c52761f
|
||||
github.com/valkey-io/valkey-go v1.0.53
|
||||
go.uber.org/mock v0.5.0
|
||||
golang.org/x/sync v0.10.0
|
||||
gotest.tools/v3 v3.5.1
|
||||
@@ -27,17 +28,22 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||
dario.cat/mergo v1.0.0 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/ajg/form v1.5.1 // indirect
|
||||
github.com/andybalholm/brotli v1.0.4 // indirect
|
||||
github.com/bytedance/sonic v1.11.9 // indirect
|
||||
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/containerd/platforms v0.2.1 // indirect
|
||||
github.com/cpuguy83/dockercfg v0.3.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/distribution/reference v0.5.0 // indirect
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/docker/go-connections v0.5.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||
github.com/fatih/color v1.14.1 // indirect
|
||||
@@ -49,6 +55,7 @@ require (
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||
github.com/go-openapi/swag v0.22.4 // indirect
|
||||
@@ -71,9 +78,10 @@ require (
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.17.2 // indirect
|
||||
github.com/klauspost/compress v1.17.9 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
@@ -81,24 +89,35 @@ require (
|
||||
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/moby/patternmatcher v0.6.0 // indirect
|
||||
github.com/moby/sys/sequential v0.5.0 // indirect
|
||||
github.com/moby/sys/user v0.1.0 // indirect
|
||||
github.com/moby/sys/userns v0.1.0 // indirect
|
||||
github.com/moby/term v0.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.2 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/sanity-io/litter v1.5.5 // indirect
|
||||
github.com/sergi/go-diff v1.0.0 // indirect
|
||||
github.com/shirou/gopsutil/v3 v3.24.5 // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.11.0 // indirect
|
||||
github.com/spf13/cast v1.6.0 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/testcontainers/testcontainers-go v0.35.0 // indirect
|
||||
github.com/testcontainers/testcontainers-go/modules/valkey v0.35.0 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.14 // indirect
|
||||
github.com/tklauser/numcpus v0.8.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
@@ -110,7 +129,8 @@ require (
|
||||
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 // indirect
|
||||
github.com/yudai/gojsondiff v1.0.0 // indirect
|
||||
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect
|
||||
go.opentelemetry.io/otel v1.29.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.29.0 // indirect
|
||||
@@ -120,15 +140,15 @@ require (
|
||||
go.uber.org/multierr v1.9.0 // indirect
|
||||
golang.org/x/arch v0.8.0 // indirect
|
||||
golang.org/x/crypto v0.31.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
||||
golang.org/x/mod v0.18.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
|
||||
golang.org/x/mod v0.19.0 // indirect
|
||||
golang.org/x/net v0.33.0 // indirect
|
||||
golang.org/x/oauth2 v0.21.0 // indirect
|
||||
golang.org/x/sys v0.28.0 // indirect
|
||||
golang.org/x/term v0.27.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.org/x/tools v0.22.0 // indirect
|
||||
golang.org/x/tools v0.23.0 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
gopkg.in/fsnotify.v1 v1.4.7 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
|
||||
71
go.sum
71
go.sum
@@ -1,7 +1,13 @@
|
||||
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
||||
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/acouvreur/httpexpect/v2 v2.16.0 h1:FGXaR9jt6IQMXxpqbM8YpX7EEvyERU0Lps3ooEc/gk8=
|
||||
github.com/acouvreur/httpexpect/v2 v2.16.0/go.mod h1:7myOP3A3VyS4+qnA4cm8DAad8zMN+7zxDB80W9f8yIc=
|
||||
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
||||
@@ -20,6 +26,10 @@ github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||
github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=
|
||||
github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
|
||||
github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA=
|
||||
github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -29,10 +39,14 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0=
|
||||
github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/docker/docker v27.4.1+incompatible h1:ZJvcY7gfwHn1JF48PfbyXg7Jyt9ZCWDW+GGXOIxEwp4=
|
||||
github.com/docker/docker v27.4.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
|
||||
@@ -60,6 +74,9 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
|
||||
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
|
||||
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
|
||||
@@ -121,6 +138,8 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
|
||||
github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4=
|
||||
github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
@@ -134,6 +153,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae h1:dIZY4ULFcto4tAFlj1FYZl8ztUZ13bdq+PLY+NOfbyI=
|
||||
github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
|
||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
@@ -149,6 +170,14 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
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.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc=
|
||||
github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo=
|
||||
github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg=
|
||||
github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU=
|
||||
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
|
||||
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
|
||||
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
||||
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
@@ -164,12 +193,14 @@ github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo=
|
||||
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA=
|
||||
github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=
|
||||
github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
|
||||
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
|
||||
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
|
||||
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM=
|
||||
github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
||||
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7/go.mod h1:zO8QMzTeZd5cpnIkz/Gn6iK0jDfGicM1nynOkkPIl28=
|
||||
@@ -179,6 +210,8 @@ github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
@@ -192,6 +225,10 @@ github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQ
|
||||
github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U=
|
||||
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
|
||||
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
|
||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||
@@ -225,12 +262,22 @@ github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8=
|
||||
github.com/testcontainers/testcontainers-go v0.35.0 h1:uADsZpTKFAtp8SLK+hMwSaa+X+JiERHtd4sQAFmXeMo=
|
||||
github.com/testcontainers/testcontainers-go v0.35.0/go.mod h1:oEVBj5zrfJTrgjwONs1SsRbnBtH9OKl+IGl3UMcr2B4=
|
||||
github.com/testcontainers/testcontainers-go/modules/valkey v0.35.0 h1:0cX9txu8oW4NVXzaGMLBEOX/BBmWmQtd1X55JILNb6E=
|
||||
github.com/testcontainers/testcontainers-go/modules/valkey v0.35.0/go.mod h1:Bro7Md5b9MoFzM1bs/NWEwazdePpYBy96thih94pYxs=
|
||||
github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
|
||||
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
|
||||
github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY=
|
||||
github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE=
|
||||
github.com/tniswong/go.rfcx v0.0.0-20181019234604-07783c52761f h1:C43EMGXFtvYf/zunHR6ivZV7Z6ytg73t0GXwYyicXMQ=
|
||||
github.com/tniswong/go.rfcx v0.0.0-20181019234604-07783c52761f/go.mod h1:N+sR0vLSCTtI6o06PMWsjMB4TVqqDttKNq4iC9wvxVY=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/valkey-io/valkey-go v1.0.53 h1:bntDqQVPzkLdE/4ypXBrHalXJB+BOTMk+JwXNRCGudg=
|
||||
github.com/valkey-io/valkey-go v1.0.53/go.mod h1:BXlVAPIL9rFQinSFM+N32JfWzfCaUAqBpZkc4vPY6fM=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.34.0 h1:d3AAQJ2DRcxJYHm7OXNXtXt2as1vMDfxeIcFvhmGGm4=
|
||||
@@ -255,8 +302,12 @@ github.com/yudai/pp v2.0.1+incompatible h1:Q4//iY4pNF6yPLZIigmvcl7k/bPgrcTPIFIcm
|
||||
github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg=
|
||||
go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw=
|
||||
go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0 h1:R9DE4kQ4k+YtfLI2ULwX82VtNQ2J8yZmA7ZIF/D+7Mc=
|
||||
@@ -286,13 +337,13 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
|
||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
|
||||
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
|
||||
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
@@ -310,14 +361,18 @@ golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
@@ -339,8 +394,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
|
||||
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
|
||||
golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
|
||||
golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
||||
20
go.work.sum
20
go.work.sum
@@ -72,6 +72,8 @@ github.com/clbanning/mxj v1.8.4 h1:HuhwZtbyvyOw+3Z1AowPkU87JkJUSv751ELWaiTpj8I=
|
||||
github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng=
|
||||
github.com/cncf/xds/go v0.0.0-20240318125728-8a4994d93e50 h1:DBmgJDC9dTfkVyGgipamEh2BpGYxScCH1TOF1LL1cXc=
|
||||
github.com/cncf/xds/go v0.0.0-20240318125728-8a4994d93e50/go.mod h1:5e1+Vvlzido69INQaVO6d87Qn543Xr6nooe9Kz7oBFM=
|
||||
github.com/containerd/containerd v1.7.19 h1:/xQ4XRJ0tamDkdzrrBAUy/LE5nCcxFKdBm4EcPrSMEE=
|
||||
github.com/containerd/containerd v1.7.19/go.mod h1:h4FtNYUUMB4Phr6v+xG89RYKj9XccvbNSCKjdufCrkc=
|
||||
github.com/coreos/etcd v3.3.10+incompatible h1:jFneRYjIvLMLhDLCzuTuU4rSJUjRplcJQ7pD7MnhC04=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible h1:bXhRBIXoTm9BYHS3gE0TtQuyNZyeEMux2sDi4oo5YOo=
|
||||
github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
|
||||
@@ -125,6 +127,7 @@ github.com/golang-jwt/jwt/v4 v4.0.0 h1:RAqyYixv1p7uEnocuy8P1nru5wprCh/MH2BIlW5z5
|
||||
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
|
||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
@@ -288,6 +291,8 @@ github.com/sagikazarmark/crypt v0.19.0/go.mod h1:c6vimRziqqERhtSe0MhIvzE1w54FrCH
|
||||
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
|
||||
github.com/savsgio/gotils v0.0.0-20210617111740-97865ed5a873 h1:N3Af8f13ooDKcIhsmFT7Z05CStZWu4C7Md0uDEy4q6o=
|
||||
github.com/savsgio/gotils v0.0.0-20210617111740-97865ed5a873/go.mod h1:dmPawKuiAeG/aFYVs2i+Dyosoo7FNcm+Pi8iK6ZUrX8=
|
||||
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
||||
@@ -355,16 +360,15 @@ golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
||||
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
|
||||
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
|
||||
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
|
||||
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
||||
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
||||
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
|
||||
@@ -373,10 +377,13 @@ golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
|
||||
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
|
||||
golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||
@@ -395,18 +402,17 @@ golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
|
||||
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
|
||||
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
|
||||
golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
|
||||
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
|
||||
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
|
||||
golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
|
||||
golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
|
||||
golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE=
|
||||
golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg=
|
||||
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
|
||||
@@ -422,6 +428,8 @@ google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJ
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240509183442-62759503f434/go.mod h1:I7Y+G38R2bu5j1aLzfFmQfTcU/WnFuqDwLZAbvKTKpM=
|
||||
google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA=
|
||||
google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
|
||||
@@ -96,15 +96,10 @@ func sessionStateToRenderOptionsInstanceState(sessionState *sessions.SessionStat
|
||||
log.Warnf("sessionStateToRenderOptionsInstanceState: sessionState is nil")
|
||||
return
|
||||
}
|
||||
sessionState.Instances.Range(func(key, value any) bool {
|
||||
if value != nil {
|
||||
instances = append(instances, instanceStateToRenderOptionsRequestState(value.(sessions.InstanceState).Instance))
|
||||
} else {
|
||||
log.Warnf("sessionStateToRenderOptionsInstanceState: sessionState instance is nil, key: %v", key)
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
for _, v := range sessionState.Instances {
|
||||
instances = append(instances, instanceStateToRenderOptionsRequestState(v.Instance))
|
||||
}
|
||||
|
||||
sort.SliceStable(instances, func(i, j int) bool {
|
||||
return strings.Compare(instances[i].Name, instances[j].Name) == -1
|
||||
|
||||
@@ -2,6 +2,7 @@ package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/sablierapp/sablier/app/instance"
|
||||
"github.com/sablierapp/sablier/app/sessions"
|
||||
"github.com/tniswong/go.rfcx/rfc7807"
|
||||
"go.uber.org/mock/gomock"
|
||||
@@ -10,6 +11,23 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func session() *sessions.SessionState {
|
||||
state := instance.ReadyInstanceState("test", 1)
|
||||
state2 := instance.ReadyInstanceState("test2", 1)
|
||||
return &sessions.SessionState{
|
||||
Instances: map[string]sessions.InstanceState{
|
||||
"test": {
|
||||
Instance: &state,
|
||||
Error: nil,
|
||||
},
|
||||
"test2": {
|
||||
Instance: &state2,
|
||||
Error: nil,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestStartDynamic(t *testing.T) {
|
||||
t.Run("StartDynamicInvalidBind", func(t *testing.T) {
|
||||
app, router, strategy, _ := NewApiTest(t)
|
||||
@@ -35,7 +53,7 @@ func TestStartDynamic(t *testing.T) {
|
||||
t.Run("StartDynamicThemeNotFound", func(t *testing.T) {
|
||||
app, router, strategy, m := NewApiTest(t)
|
||||
StartDynamic(router, strategy)
|
||||
m.EXPECT().RequestSessionGroup("test", gomock.Any()).Return(&sessions.SessionState{}, nil)
|
||||
m.EXPECT().RequestSessionGroup("test", gomock.Any()).Return(session(), nil)
|
||||
r := PerformRequest(app, "GET", "/api/strategies/dynamic?group=test&theme=invalid")
|
||||
assert.Equal(t, http.StatusNotFound, r.Code)
|
||||
assert.Equal(t, rfc7807.JSONMediaType, r.Header().Get("Content-Type"))
|
||||
@@ -43,7 +61,7 @@ func TestStartDynamic(t *testing.T) {
|
||||
t.Run("StartDynamicByNames", func(t *testing.T) {
|
||||
app, router, strategy, m := NewApiTest(t)
|
||||
StartDynamic(router, strategy)
|
||||
m.EXPECT().RequestSession([]string{"test"}, gomock.Any()).Return(&sessions.SessionState{}, nil)
|
||||
m.EXPECT().RequestSession([]string{"test"}, gomock.Any()).Return(session(), nil)
|
||||
r := PerformRequest(app, "GET", "/api/strategies/dynamic?names=test")
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
assert.Equal(t, SablierStatusReady, r.Header().Get(SablierStatusHeader))
|
||||
@@ -51,7 +69,7 @@ func TestStartDynamic(t *testing.T) {
|
||||
t.Run("StartDynamicByGroup", func(t *testing.T) {
|
||||
app, router, strategy, m := NewApiTest(t)
|
||||
StartDynamic(router, strategy)
|
||||
m.EXPECT().RequestSessionGroup("test", gomock.Any()).Return(&sessions.SessionState{}, nil)
|
||||
m.EXPECT().RequestSessionGroup("test", gomock.Any()).Return(session(), nil)
|
||||
r := PerformRequest(app, "GET", "/api/strategies/dynamic?group=test")
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
assert.Equal(t, SablierStatusReady, r.Header().Get(SablierStatusHeader))
|
||||
|
||||
56
pkg/store/inmemory/inmemory.go
Normal file
56
pkg/store/inmemory/inmemory.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package inmemory
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"github.com/sablierapp/sablier/app/instance"
|
||||
"github.com/sablierapp/sablier/pkg/store"
|
||||
"github.com/sablierapp/sablier/pkg/tinykv"
|
||||
"time"
|
||||
)
|
||||
|
||||
var _ store.Store = (*InMemory)(nil)
|
||||
var _ json.Marshaler = (*InMemory)(nil)
|
||||
var _ json.Unmarshaler = (*InMemory)(nil)
|
||||
|
||||
func NewInMemory() store.Store {
|
||||
return &InMemory{
|
||||
kv: tinykv.New[instance.State](1*time.Second, nil),
|
||||
}
|
||||
}
|
||||
|
||||
type InMemory struct {
|
||||
kv tinykv.KV[instance.State]
|
||||
}
|
||||
|
||||
func (i InMemory) UnmarshalJSON(bytes []byte) error {
|
||||
return i.kv.UnmarshalJSON(bytes)
|
||||
}
|
||||
|
||||
func (i InMemory) MarshalJSON() ([]byte, error) {
|
||||
return i.kv.MarshalJSON()
|
||||
}
|
||||
|
||||
func (i InMemory) Get(_ context.Context, s string) (instance.State, error) {
|
||||
val, ok := i.kv.Get(s)
|
||||
if !ok {
|
||||
return instance.State{}, store.ErrKeyNotFound
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
func (i InMemory) Put(_ context.Context, state instance.State, duration time.Duration) error {
|
||||
return i.kv.Put(state.Name, state, duration)
|
||||
}
|
||||
|
||||
func (i InMemory) Delete(_ context.Context, s string) error {
|
||||
i.kv.Delete(s)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i InMemory) OnExpire(_ context.Context, f func(string)) error {
|
||||
i.kv.SetOnExpire(func(k string, _ instance.State) {
|
||||
f(k)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
74
pkg/store/inmemory/inmemory_test.go
Normal file
74
pkg/store/inmemory/inmemory_test.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package inmemory
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/sablierapp/sablier/app/instance"
|
||||
"github.com/sablierapp/sablier/pkg/store"
|
||||
"gotest.tools/v3/assert"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestInMemory(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("InMemoryErrNotFound", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
vk := NewInMemory()
|
||||
|
||||
_, err := vk.Get(ctx, "test")
|
||||
assert.ErrorIs(t, err, store.ErrKeyNotFound)
|
||||
})
|
||||
t.Run("InMemoryPut", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
vk := NewInMemory()
|
||||
|
||||
err := vk.Put(ctx, instance.State{Name: "test"}, 1*time.Second)
|
||||
assert.NilError(t, err)
|
||||
|
||||
i, err := vk.Get(ctx, "test")
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, i.Name, "test")
|
||||
|
||||
<-time.After(2 * time.Second)
|
||||
_, err = vk.Get(ctx, "test")
|
||||
assert.ErrorIs(t, err, store.ErrKeyNotFound)
|
||||
})
|
||||
t.Run("InMemoryDelete", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
vk := NewInMemory()
|
||||
|
||||
err := vk.Put(ctx, instance.State{Name: "test"}, 30*time.Second)
|
||||
assert.NilError(t, err)
|
||||
|
||||
i, err := vk.Get(ctx, "test")
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, i.Name, "test")
|
||||
|
||||
err = vk.Delete(ctx, "test")
|
||||
assert.NilError(t, err)
|
||||
|
||||
_, err = vk.Get(ctx, "test")
|
||||
assert.ErrorIs(t, err, store.ErrKeyNotFound)
|
||||
})
|
||||
t.Run("InMemoryOnExpire", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
vk := NewInMemory()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
expirations := make(chan string)
|
||||
err := vk.OnExpire(ctx, func(key string) {
|
||||
expirations <- key
|
||||
})
|
||||
assert.NilError(t, err)
|
||||
|
||||
err = vk.Put(ctx, instance.State{Name: "test"}, 1*time.Second)
|
||||
assert.NilError(t, err)
|
||||
expired := <-expirations
|
||||
assert.Equal(t, expired, "test")
|
||||
})
|
||||
}
|
||||
19
pkg/store/store.go
Normal file
19
pkg/store/store.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/sablierapp/sablier/app/instance"
|
||||
"time"
|
||||
)
|
||||
|
||||
var ErrKeyNotFound = errors.New("key not found")
|
||||
|
||||
//go:generate mockgen -package storetest -source=store.go -destination=storetest/mocks_store.go *
|
||||
|
||||
type Store interface {
|
||||
Get(context.Context, string) (instance.State, error)
|
||||
Put(context.Context, instance.State, time.Duration) error
|
||||
Delete(context.Context, string) error
|
||||
OnExpire(context.Context, func(string)) error
|
||||
}
|
||||
100
pkg/store/storetest/mocks_store.go
Normal file
100
pkg/store/storetest/mocks_store.go
Normal file
@@ -0,0 +1,100 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: store.go
|
||||
//
|
||||
// Generated by this command:
|
||||
//
|
||||
// mockgen -package storetest -source=store.go -destination=storetest/mocks_store.go *
|
||||
//
|
||||
|
||||
// Package storetest is a generated GoMock package.
|
||||
package storetest
|
||||
|
||||
import (
|
||||
context "context"
|
||||
reflect "reflect"
|
||||
time "time"
|
||||
|
||||
instance "github.com/sablierapp/sablier/app/instance"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockStore is a mock of Store interface.
|
||||
type MockStore struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockStoreMockRecorder
|
||||
isgomock struct{}
|
||||
}
|
||||
|
||||
// MockStoreMockRecorder is the mock recorder for MockStore.
|
||||
type MockStoreMockRecorder struct {
|
||||
mock *MockStore
|
||||
}
|
||||
|
||||
// NewMockStore creates a new mock instance.
|
||||
func NewMockStore(ctrl *gomock.Controller) *MockStore {
|
||||
mock := &MockStore{ctrl: ctrl}
|
||||
mock.recorder = &MockStoreMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockStore) EXPECT() *MockStoreMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Delete mocks base method.
|
||||
func (m *MockStore) Delete(arg0 context.Context, arg1 string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Delete", arg0, arg1)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Delete indicates an expected call of Delete.
|
||||
func (mr *MockStoreMockRecorder) Delete(arg0, arg1 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockStore)(nil).Delete), arg0, arg1)
|
||||
}
|
||||
|
||||
// Get mocks base method.
|
||||
func (m *MockStore) Get(arg0 context.Context, arg1 string) (instance.State, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Get", arg0, arg1)
|
||||
ret0, _ := ret[0].(instance.State)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Get indicates an expected call of Get.
|
||||
func (mr *MockStoreMockRecorder) Get(arg0, arg1 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockStore)(nil).Get), arg0, arg1)
|
||||
}
|
||||
|
||||
// OnExpire mocks base method.
|
||||
func (m *MockStore) OnExpire(arg0 context.Context, arg1 func(string)) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "OnExpire", arg0, arg1)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// OnExpire indicates an expected call of OnExpire.
|
||||
func (mr *MockStoreMockRecorder) OnExpire(arg0, arg1 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnExpire", reflect.TypeOf((*MockStore)(nil).OnExpire), arg0, arg1)
|
||||
}
|
||||
|
||||
// Put mocks base method.
|
||||
func (m *MockStore) Put(arg0 context.Context, arg1 instance.State, arg2 time.Duration) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Put", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Put indicates an expected call of Put.
|
||||
func (mr *MockStoreMockRecorder) Put(arg0, arg1, arg2 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Put", reflect.TypeOf((*MockStore)(nil).Put), arg0, arg1, arg2)
|
||||
}
|
||||
81
pkg/store/valkey/valkey.go
Normal file
81
pkg/store/valkey/valkey.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package valkey
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"github.com/sablierapp/sablier/app/instance"
|
||||
"github.com/sablierapp/sablier/pkg/store"
|
||||
"github.com/valkey-io/valkey-go"
|
||||
"log/slog"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var _ store.Store = (*ValKey)(nil)
|
||||
|
||||
type ValKey struct {
|
||||
Client valkey.Client
|
||||
}
|
||||
|
||||
func New(ctx context.Context, client valkey.Client) (store.Store, error) {
|
||||
err := client.Do(ctx, client.B().Ping().Build()).Error()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = client.Do(ctx, client.B().ConfigSet().ParameterValue().
|
||||
ParameterValue("notify-keyspace-events", "KEx").
|
||||
Build()).Error()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ValKey{Client: client}, nil
|
||||
}
|
||||
|
||||
func (v *ValKey) Get(ctx context.Context, s string) (instance.State, error) {
|
||||
b, err := v.Client.Do(ctx, v.Client.B().Get().Key(s).Build()).AsBytes()
|
||||
if valkey.IsValkeyNil(err) {
|
||||
return instance.State{}, store.ErrKeyNotFound
|
||||
}
|
||||
if err != nil {
|
||||
return instance.State{}, err
|
||||
}
|
||||
|
||||
var i instance.State
|
||||
err = json.Unmarshal(b, &i)
|
||||
if err != nil {
|
||||
return instance.State{}, err
|
||||
}
|
||||
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func (v *ValKey) Put(ctx context.Context, state instance.State, duration time.Duration) error {
|
||||
value, err := json.Marshal(state)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return v.Client.Do(ctx, v.Client.B().Set().Key(state.Name).Value(string(value)).Ex(duration).Build()).Error()
|
||||
}
|
||||
|
||||
func (v *ValKey) Delete(ctx context.Context, s string) error {
|
||||
return v.Client.Do(ctx, v.Client.B().Del().Key(s).Build()).Error()
|
||||
}
|
||||
|
||||
func (v *ValKey) OnExpire(ctx context.Context, f func(string)) error {
|
||||
go func() {
|
||||
err := v.Client.Receive(ctx, v.Client.B().Psubscribe().Pattern("__key*__:*").Build(), func(msg valkey.PubSubMessage) {
|
||||
if msg.Message == "expired" {
|
||||
split := strings.Split(msg.Channel, ":")
|
||||
key := split[len(split)-1]
|
||||
f(key)
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
slog.Error("error subscribing", slog.Any("error", err))
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
108
pkg/store/valkey/valkey_test.go
Normal file
108
pkg/store/valkey/valkey_test.go
Normal file
@@ -0,0 +1,108 @@
|
||||
package valkey
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/sablierapp/sablier/app/instance"
|
||||
"github.com/sablierapp/sablier/pkg/store"
|
||||
"github.com/testcontainers/testcontainers-go"
|
||||
tcvalkey "github.com/testcontainers/testcontainers-go/modules/valkey"
|
||||
"github.com/testcontainers/testcontainers-go/wait"
|
||||
"github.com/valkey-io/valkey-go"
|
||||
"gotest.tools/v3/assert"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func setupValKeyContainer(t *testing.T) valkey.Client {
|
||||
t.Helper()
|
||||
ctx := context.Background()
|
||||
c, err := tcvalkey.Run(ctx, "valkey/valkey:7.2.5",
|
||||
tcvalkey.WithLogLevel(tcvalkey.LogLevelDebug),
|
||||
testcontainers.WithWaitStrategy(wait.ForListeningPort("6379/tcp")),
|
||||
)
|
||||
testcontainers.CleanupContainer(t, c)
|
||||
assert.NilError(t, err)
|
||||
|
||||
uri, err := c.ConnectionString(ctx)
|
||||
assert.NilError(t, err)
|
||||
|
||||
options, err := valkey.ParseURL(uri)
|
||||
assert.NilError(t, err)
|
||||
|
||||
client, err := valkey.NewClient(options)
|
||||
assert.NilError(t, err)
|
||||
|
||||
return client
|
||||
}
|
||||
|
||||
func setupValKey(t *testing.T) *ValKey {
|
||||
t.Helper()
|
||||
client := setupValKeyContainer(t)
|
||||
vk, err := New(context.Background(), client)
|
||||
assert.NilError(t, err)
|
||||
return vk.(*ValKey)
|
||||
}
|
||||
|
||||
func TestValKey(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("ValKeyErrNotFound", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
vk := setupValKey(t)
|
||||
|
||||
_, err := vk.Get(ctx, "test")
|
||||
assert.ErrorIs(t, err, store.ErrKeyNotFound)
|
||||
})
|
||||
t.Run("ValKeyPut", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
vk := setupValKey(t)
|
||||
|
||||
err := vk.Put(ctx, instance.State{Name: "test"}, 1*time.Second)
|
||||
assert.NilError(t, err)
|
||||
|
||||
i, err := vk.Get(ctx, "test")
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, i.Name, "test")
|
||||
|
||||
<-time.After(2 * time.Second)
|
||||
_, err = vk.Get(ctx, "test")
|
||||
assert.ErrorIs(t, err, store.ErrKeyNotFound)
|
||||
})
|
||||
t.Run("ValKeyDelete", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
vk := setupValKey(t)
|
||||
|
||||
err := vk.Put(ctx, instance.State{Name: "test"}, 30*time.Second)
|
||||
assert.NilError(t, err)
|
||||
|
||||
i, err := vk.Get(ctx, "test")
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, i.Name, "test")
|
||||
|
||||
err = vk.Delete(ctx, "test")
|
||||
assert.NilError(t, err)
|
||||
|
||||
_, err = vk.Get(ctx, "test")
|
||||
assert.ErrorIs(t, err, store.ErrKeyNotFound)
|
||||
})
|
||||
t.Run("ValKeyOnExpire", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
vk := setupValKey(t)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
expirations := make(chan string)
|
||||
err := vk.OnExpire(ctx, func(key string) {
|
||||
expirations <- key
|
||||
})
|
||||
assert.NilError(t, err)
|
||||
|
||||
err = vk.Put(ctx, instance.State{Name: "test"}, 1*time.Second)
|
||||
assert.NilError(t, err)
|
||||
expired := <-expirations
|
||||
assert.Equal(t, expired, "test")
|
||||
})
|
||||
}
|
||||
@@ -64,6 +64,7 @@ type KV[T any] interface {
|
||||
Entries() (entries map[string]entry[T])
|
||||
Put(k string, v T, expiresAfter time.Duration) error
|
||||
Stop()
|
||||
SetOnExpire(onExpire func(k string, v T))
|
||||
MarshalJSON() ([]byte, error)
|
||||
UnmarshalJSON(b []byte) error
|
||||
}
|
||||
@@ -83,23 +84,26 @@ type store[T any] struct {
|
||||
}
|
||||
|
||||
// New creates a new *store, onExpire is for notification (must be fast).
|
||||
func New[T any](expirationInterval time.Duration, onExpire ...func(k string, v T)) KV[T] {
|
||||
func New[T any](expirationInterval time.Duration, onExpire func(k string, v T)) KV[T] {
|
||||
if expirationInterval <= 0 {
|
||||
expirationInterval = time.Second * 20
|
||||
}
|
||||
res := &store[T]{
|
||||
onExpire: onExpire,
|
||||
stop: make(chan struct{}),
|
||||
kv: make(map[string]*entry[T]),
|
||||
expirationInterval: expirationInterval,
|
||||
heap: th{},
|
||||
}
|
||||
if len(onExpire) > 0 && onExpire[0] != nil {
|
||||
res.onExpire = onExpire[0]
|
||||
}
|
||||
|
||||
go res.expireLoop()
|
||||
return res
|
||||
}
|
||||
|
||||
func (kv *store[T]) SetOnExpire(onExpire func(k string, v T)) {
|
||||
kv.onExpire = onExpire
|
||||
}
|
||||
|
||||
// Stop stops the goroutine
|
||||
func (kv *store[T]) Stop() {
|
||||
kv.stopOnce.Do(func() { close(kv.stop) })
|
||||
|
||||
@@ -41,7 +41,7 @@ var _ KV[int] = &store[int]{}
|
||||
|
||||
func TestGetPut(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
rg := New[int](0)
|
||||
rg := New[int](0, nil)
|
||||
defer rg.Stop()
|
||||
|
||||
rg.Put("1", 1, time.Minute*50)
|
||||
@@ -62,7 +62,7 @@ func TestGetPut(t *testing.T) {
|
||||
|
||||
func TestKeys(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
rg := New[int](0)
|
||||
rg := New[int](0, nil)
|
||||
defer rg.Stop()
|
||||
|
||||
rg.Put("1", 1, time.Minute*50)
|
||||
@@ -76,7 +76,7 @@ func TestKeys(t *testing.T) {
|
||||
|
||||
func TestValues(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
rg := New[int](0)
|
||||
rg := New[int](0, nil)
|
||||
defer rg.Stop()
|
||||
|
||||
rg.Put("1", 1, time.Minute*50)
|
||||
@@ -90,7 +90,7 @@ func TestValues(t *testing.T) {
|
||||
|
||||
func TestEntries(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
rg := New[int](0)
|
||||
rg := New[int](0, nil)
|
||||
defer rg.Stop()
|
||||
|
||||
rg.Put("1", 1, time.Minute*50)
|
||||
@@ -107,7 +107,7 @@ func TestEntries(t *testing.T) {
|
||||
func TestMarshalJSON(t *testing.T) {
|
||||
os.Setenv("TZ", "")
|
||||
assert := assert.New(t)
|
||||
rg := New[int](0)
|
||||
rg := New[int](0, nil)
|
||||
defer rg.Stop()
|
||||
|
||||
rg.Put("3", 3, time.Minute*50)
|
||||
@@ -125,7 +125,7 @@ func TestUnmarshalJSON(t *testing.T) {
|
||||
assert.Nil(err)
|
||||
jsons := `{"1":{"value":1},"2":{"value":2},"3":{"value":3,"expiresAt":` + string(in5MinutesJson) + `}}`
|
||||
|
||||
rg := New[int](0)
|
||||
rg := New[int](0, nil)
|
||||
defer rg.Stop()
|
||||
|
||||
err = json.Unmarshal([]byte(jsons), &rg)
|
||||
@@ -141,7 +141,7 @@ func TestUnmarshalJSONExpired(t *testing.T) {
|
||||
assert.Nil(err)
|
||||
jsons := `{"1":{"value":1},"2":{"value":2},"3":{"value":3,"expiresAt":` + string(since5MinutesJson) + `}}`
|
||||
|
||||
rg := New[int](0)
|
||||
rg := New[int](0, nil)
|
||||
defer rg.Stop()
|
||||
|
||||
err = json.Unmarshal([]byte(jsons), &rg)
|
||||
@@ -371,14 +371,14 @@ func TestOrdering(t *testing.T) {
|
||||
}
|
||||
|
||||
func BenchmarkGetNoValue(b *testing.B) {
|
||||
rg := New[interface{}](-1)
|
||||
rg := New[interface{}](-1, nil)
|
||||
for n := 0; n < b.N; n++ {
|
||||
rg.Get("1")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGetValue(b *testing.B) {
|
||||
rg := New[interface{}](-1)
|
||||
rg := New[interface{}](-1, nil)
|
||||
rg.Put("1", 1, time.Minute*50)
|
||||
for n := 0; n < b.N; n++ {
|
||||
rg.Get("1")
|
||||
@@ -386,7 +386,7 @@ func BenchmarkGetValue(b *testing.B) {
|
||||
}
|
||||
|
||||
func BenchmarkGetSlidingTimeout(b *testing.B) {
|
||||
rg := New[interface{}](-1)
|
||||
rg := New[interface{}](-1, nil)
|
||||
rg.Put("1", 1, time.Second*10)
|
||||
for n := 0; n < b.N; n++ {
|
||||
rg.Get("1")
|
||||
@@ -394,7 +394,7 @@ func BenchmarkGetSlidingTimeout(b *testing.B) {
|
||||
}
|
||||
|
||||
func BenchmarkPutExpire(b *testing.B) {
|
||||
rg := New[interface{}](-1)
|
||||
rg := New[interface{}](-1, nil)
|
||||
for n := 0; n < b.N; n++ {
|
||||
rg.Put("1", 1, time.Second*10)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user