Files
zot/pkg/api/config/redis/redis_test.go
T
Luca Muscariello 2402296e9a fix: migrate to Go module v2 for proper semantic versioning (#3462)
* fix: migrate to Go module v2 for proper semantic versioning

This change updates the module path from 'zotregistry.dev/zot' to
'zotregistry.dev/zot/v2' to comply with Go's semantic versioning rules.

According to Go's module versioning requirements, major version v2+
must include the major version in the module path. The current
module path 'zotregistry.dev/zot' only supports v0.x.x and v1.x.x
versions, making existing v2.x.x tags (like v2.1.8) unusable.

Changes:
- Updated go.mod module path to zotregistry.dev/zot/v2
- Updated all internal import paths across 280+ Go source files
- Updated configuration files (golangcilint.yaml, gqlgen.yml)
- Updated README.md Go reference badge

This fix enables proper use of existing v2.x.x Git tags and allows
external packages to import zot v2+ versions without compatibility
errors.

Resolves: Go module import compatibility for v2+ versions
Fixes: #3071
Signed-off-by: Luca Muscariello <muscariello@ieee.org>

* fix: regenerate GraphQL files with updated v2 import paths

The gqlgen tool needs to regenerate the GraphQL schema files after
the module path change to use the new v2 imports.

Signed-off-by: Luca Muscariello <muscariello@ieee.org>

---------

Signed-off-by: Luca Muscariello <muscariello@ieee.org>
2025-10-16 22:43:47 -07:00

361 lines
12 KiB
Go

package rediscfg_test
import (
"context"
"io"
"os"
"path"
"testing"
"time"
"github.com/redis/go-redis/v9"
. "github.com/smartystreets/goconvey/convey"
"zotregistry.dev/zot/v2/pkg/api/config"
rediscfg "zotregistry.dev/zot/v2/pkg/api/config/redis"
"zotregistry.dev/zot/v2/pkg/cli/server"
"zotregistry.dev/zot/v2/pkg/log"
)
func TestRedisLogger(t *testing.T) {
Convey("Print using Redis logger", t, func() {
logFile, err := os.CreateTemp(t.TempDir(), "zot-log*.txt")
So(err, ShouldBeNil)
writers := io.MultiWriter(os.Stdout, logFile)
logger := log.NewLoggerWithWriter("debug", writers)
rlog := rediscfg.RedisLogger{logger}
rlog.Printf(context.Background(), "this is a rest string")
content, err := os.ReadFile(logFile.Name())
So(err, ShouldBeNil)
So(string(content), ShouldContainSubstring, "this is a rest string")
})
}
func TestRedisOptions(t *testing.T) {
Convey("Test redis initialization", t, func() {
log := log.NewTestLogger()
So(log, ShouldNotBeNil)
Convey("Test redis url parsing", func() {
// Errors
config := map[string]interface{}{"url": false}
clientIntf, err := rediscfg.GetRedisClient(config, log)
So(err, ShouldNotBeNil)
So(clientIntf, ShouldBeNil)
config = map[string]interface{}{"url": ""}
clientIntf, err = rediscfg.GetRedisClient(config, log)
So(err, ShouldNotBeNil)
So(clientIntf, ShouldBeNil)
config = map[string]interface{}{"url": "qwerty@localhost:6379/1?dial_timeout=5s"}
clientIntf, err = rediscfg.GetRedisClient(config, log)
So(err, ShouldNotBeNil)
So(clientIntf, ShouldBeNil)
config = map[string]interface{}{"url": "http://:qwerty@localhost:6379/1?dial_timeout=5s"}
clientIntf, err = rediscfg.GetRedisClient(config, log)
So(err, ShouldNotBeNil)
So(clientIntf, ShouldBeNil)
config = map[string]interface{}{"url": "http://localhost:6379/1?addr=host2:6379&addr=host1:6379"}
clientIntf, err = rediscfg.GetRedisClient(config, log)
So(err, ShouldNotBeNil)
So(clientIntf, ShouldBeNil)
// Success
config = map[string]interface{}{"url": "redis://user:password@localhost:6379/1?dial_timeout=5s"}
clientIntf, err = rediscfg.GetRedisClient(config, log)
So(err, ShouldBeNil)
So(clientIntf, ShouldNotBeNil)
_, ok := clientIntf.(*redis.Client)
So(ok, ShouldBeTrue)
config = map[string]interface{}{"url": "redis://user:password@host1:6379?addr=host2:6379&addr=host1:6379"}
clientIntf, err = rediscfg.GetRedisClient(config, log)
So(err, ShouldBeNil)
So(clientIntf, ShouldNotBeNil)
_, ok = clientIntf.(*redis.ClusterClient)
So(ok, ShouldBeTrue)
})
Convey("Test empty redis options from struct successfully", func() {
config := map[string]interface{}{}
// All attributes will have zero values
options := rediscfg.ParseRedisUniversalOptions(config, log)
So(options, ShouldNotBeNil)
So(options.Addrs, ShouldEqual, []string(nil))
So(options.DB, ShouldEqual, 0)
So(options.MasterName, ShouldEqual, "")
So(options.ClientName, ShouldEqual, "")
So(options.Protocol, ShouldEqual, 0)
So(options.Username, ShouldEqual, "")
So(options.Password, ShouldEqual, "")
So(options.SentinelUsername, ShouldEqual, "")
So(options.SentinelPassword, ShouldEqual, "")
So(options.DialTimeout, ShouldEqual, 0)
So(options.MaxRetries, ShouldEqual, 0)
So(options.MinRetryBackoff, ShouldEqual, 0)
So(options.MaxRetryBackoff, ShouldEqual, 0)
So(options.ReadTimeout, ShouldEqual, 0)
So(options.WriteTimeout, ShouldEqual, 0)
So(options.ContextTimeoutEnabled, ShouldEqual, false)
So(options.PoolFIFO, ShouldEqual, false)
So(options.PoolSize, ShouldEqual, 0)
So(options.PoolTimeout, ShouldEqual, 0)
So(options.MinIdleConns, ShouldEqual, 0)
So(options.MaxIdleConns, ShouldEqual, 0)
So(options.MaxActiveConns, ShouldEqual, 0)
So(options.ConnMaxIdleTime, ShouldEqual, 0)
So(options.ConnMaxLifetime, ShouldEqual, 0)
So(options.MaxRedirects, ShouldEqual, 0)
So(options.ReadOnly, ShouldEqual, false)
So(options.RouteByLatency, ShouldEqual, false)
So(options.RouteRandomly, ShouldEqual, false)
So(options.DisableIdentity, ShouldEqual, false)
So(options.IdentitySuffix, ShouldEqual, "")
So(options.UnstableResp3, ShouldEqual, false)
clientIntf, err := rediscfg.GetRedisClient(config, log)
So(err, ShouldBeNil)
So(clientIntf, ShouldNotBeNil)
_, ok := clientIntf.(*redis.Client)
So(ok, ShouldBeTrue)
})
Convey("Test redis options from struct successfully", func() {
config := map[string]interface{}{
"addr": []string{
"a.repo:26379",
"b.repo:26379",
"c.repo:26379",
},
"db": 1,
"master_name": "zotmeta",
"client_name": "client",
"protocol": 3,
"username": "redis",
"password": "**secret**",
"sentinel_username": "sentinel",
"sentinel_password": "**secret**",
"dial_timeout": 5 * time.Second,
"max_retries": 5,
"min_retry_backoff": 1 * time.Second,
"max_retry_backoff": 3 * time.Second,
"read_timeout": 1 * time.Second,
"write_timeout": 1 * time.Second,
"context_timeout_enabled": true,
"pool_fifo": false,
"pool_size": 2,
"pool_timeout": 10 * time.Second,
"min_idle_conns": 1,
"max_idle_conns": 2,
"max_active_conns": 3,
"conn_max_idle_time": 20 * time.Second,
"conn_max_lifetime": 50 * time.Second,
"max_redirects": 3,
"read_only": true,
"route_by_latency": false,
"route_randomly": true,
"disable_identity": false,
"identity_suffix": "test",
"unstable_resp3": true,
}
// All attribute values are taken from config
options := rediscfg.ParseRedisUniversalOptions(config, log)
So(options, ShouldNotBeNil)
So(options.Addrs, ShouldEqual, []string{"a.repo:26379", "b.repo:26379", "c.repo:26379"})
So(options.DB, ShouldEqual, 1)
So(options.MasterName, ShouldEqual, "zotmeta")
So(options.ClientName, ShouldEqual, "client")
So(options.Protocol, ShouldEqual, 3)
So(options.Username, ShouldEqual, "redis")
So(options.Password, ShouldEqual, "**secret**")
So(options.SentinelUsername, ShouldEqual, "sentinel")
So(options.SentinelPassword, ShouldEqual, "**secret**")
So(options.DialTimeout, ShouldEqual, 5*time.Second)
So(options.MaxRetries, ShouldEqual, 5)
So(options.MinRetryBackoff, ShouldEqual, 1*time.Second)
So(options.MaxRetryBackoff, ShouldEqual, 3*time.Second)
So(options.ReadTimeout, ShouldEqual, 1*time.Second)
So(options.WriteTimeout, ShouldEqual, 1*time.Second)
So(options.ContextTimeoutEnabled, ShouldEqual, true)
So(options.PoolFIFO, ShouldEqual, false)
So(options.PoolSize, ShouldEqual, 2)
So(options.PoolTimeout, ShouldEqual, 10*time.Second)
So(options.MinIdleConns, ShouldEqual, 1)
So(options.MaxIdleConns, ShouldEqual, 2)
So(options.MaxActiveConns, ShouldEqual, 3)
So(options.ConnMaxIdleTime, ShouldEqual, 20*time.Second)
So(options.ConnMaxLifetime, ShouldEqual, 50*time.Second)
So(options.MaxRedirects, ShouldEqual, 3)
So(options.ReadOnly, ShouldEqual, true)
So(options.RouteByLatency, ShouldEqual, false)
So(options.RouteRandomly, ShouldEqual, true)
So(options.DisableIdentity, ShouldEqual, false)
So(options.IdentitySuffix, ShouldEqual, "test")
So(options.UnstableResp3, ShouldEqual, true)
clientIntf, err := rediscfg.GetRedisClient(config, log)
So(err, ShouldBeNil)
So(clientIntf, ShouldNotBeNil)
_, ok := clientIntf.(*redis.ClusterClient)
So(ok, ShouldBeTrue)
})
Convey("Test redis options from struct with warnings", func() {
config := map[string]interface{}{
"addr": map[string]int{},
"db": "somestring",
"master_name": map[string]int{},
"client_name": map[string]int{},
"protocol": "somestring",
"username": map[string]int{},
"password": map[string]int{},
"sentinel_username": map[string]int{},
"sentinel_password": map[string]int{},
"dial_timeout": "somestring",
"max_retries": "somestring",
"min_retry_backoff": "somestring",
"max_retry_backoff": "somestring",
"read_timeout": false,
"write_timeout": true,
"context_timeout_enabled": "somestring",
"pool_fifo": "somestring",
"pool_size": "somestring",
"pool_timeout": "somestring",
"min_idle_conns": map[string]int{},
"max_idle_conns": map[string]int{},
"max_active_conns": "somestring",
"conn_max_idle_time": "somestring",
"conn_max_lifetime": "somestring",
"max_redirects": map[string]int{},
"read_only": map[string]int{},
"route_by_latency": "somestring",
"route_randomly": map[string]int{},
"disable_identity": "somestring",
"identity_suffix": map[string]int{},
"unstable_resp3": "somestring",
}
// All attributes remain with default values
options := rediscfg.ParseRedisUniversalOptions(config, log)
So(options, ShouldNotBeNil)
So(options.Addrs, ShouldEqual, []string(nil))
So(options.DB, ShouldEqual, 0)
So(options.MasterName, ShouldEqual, "")
So(options.ClientName, ShouldEqual, "")
So(options.Protocol, ShouldEqual, 0)
So(options.Username, ShouldEqual, "")
So(options.Password, ShouldEqual, "")
So(options.SentinelUsername, ShouldEqual, "")
So(options.SentinelPassword, ShouldEqual, "")
So(options.DialTimeout, ShouldEqual, 0)
So(options.MaxRetries, ShouldEqual, 0)
So(options.MinRetryBackoff, ShouldEqual, 0)
So(options.MaxRetryBackoff, ShouldEqual, 0)
So(options.ReadTimeout, ShouldEqual, 0)
So(options.WriteTimeout, ShouldEqual, 0)
So(options.ContextTimeoutEnabled, ShouldEqual, false)
So(options.PoolFIFO, ShouldEqual, false)
So(options.PoolSize, ShouldEqual, 0)
So(options.PoolTimeout, ShouldEqual, 0)
So(options.MinIdleConns, ShouldEqual, 0)
So(options.MaxIdleConns, ShouldEqual, 0)
So(options.MaxActiveConns, ShouldEqual, 0)
So(options.ConnMaxIdleTime, ShouldEqual, 0)
So(options.ConnMaxLifetime, ShouldEqual, 0)
So(options.MaxRedirects, ShouldEqual, 0)
So(options.ReadOnly, ShouldEqual, false)
So(options.RouteByLatency, ShouldEqual, false)
So(options.RouteRandomly, ShouldEqual, false)
So(options.DisableIdentity, ShouldEqual, false)
So(options.IdentitySuffix, ShouldEqual, "")
So(options.UnstableResp3, ShouldEqual, false)
clientIntf, err := rediscfg.GetRedisClient(config, log)
So(err, ShouldBeNil)
So(clientIntf, ShouldNotBeNil)
_, ok := clientIntf.(*redis.Client)
So(ok, ShouldBeTrue)
})
Convey("Test redis options from json", func(c C) {
fileContent := []byte(`{
"distSpecVersion": "1.1.1",
"storage": {
"remoteCache": true,
"cacheDriver": {
"name": "redis",
"addr": [
"a.repo:26379",
"b.repo:26379",
"c.repo:26379"
],
"db": 1,
"master_name": "zotmeta",
"username": "redis",
"password": "**secret**",
"dial_timeout": "5s"
},
"commit": false,
"dedupe": false,
"gc": true,
"rootDirectory": "/data/zot-cache/dev"
},
"http": {
"address": "127.0.0.1",
"port": "8080"
},
"log": {
"level": "debug"
}
}`)
dir := t.TempDir()
configPath := path.Join(dir, "test-config.json")
err := os.WriteFile(configPath, fileContent, 0o600)
So(err, ShouldBeNil)
conf := config.New()
err = server.LoadConfiguration(conf, configPath)
So(err, ShouldBeNil)
options := rediscfg.ParseRedisUniversalOptions(conf.Storage.CacheDriver, log)
So(options, ShouldNotBeNil)
So(options.Addrs, ShouldEqual, []string{"a.repo:26379", "b.repo:26379", "c.repo:26379"})
So(options.DB, ShouldEqual, 1)
So(options.MasterName, ShouldEqual, "zotmeta")
So(options.Username, ShouldEqual, "redis")
So(options.Password, ShouldEqual, "**secret**")
So(options.DialTimeout, ShouldEqual, 5*time.Second)
clientIntf, err := rediscfg.GetRedisClient(conf.Storage.CacheDriver, log)
So(err, ShouldBeNil)
So(clientIntf, ShouldNotBeNil)
_, ok := clientIntf.(*redis.Client)
So(ok, ShouldBeTrue)
})
})
}