package redis
import (
"crypto/tls"
"fmt"
"net/url"
"slices"
"strconv"
"strings"
"github.com/go-redis/redis/v8"
)
func parseSentinelURLs(urls []*url.URL) (*redis.UniversalOptions, error) {
uopts := redis.UniversalOptions{}
var schemes []string
var usernames []string
var passwords []string
for _, u := range urls {
if !slices.Contains(schemes, u.Scheme) {
schemes = append(schemes, u.Scheme)
}
if !slices.Contains(usernames, u.User.Username()) {
usernames = append(usernames, u.User.Username())
}
password, _ := u.User.Password()
if !slices.Contains(passwords, password) {
passwords = append(passwords, password)
}
if u.Scheme != "redis+sentinel" && u.Scheme != "rediss+sentinel" {
return nil, fmt.Errorf("invalid connection URL scheme: %s", u.Scheme)
}
uopts.Addrs = append(uopts.Addrs, u.Host)
}
// For global options, force uniformity
if len(schemes) > 1 {
return nil, fmt.Errorf("connection URLs must have uniform scheme")
}
if len(usernames) > 1 {
return nil, fmt.Errorf("connection URLs must have uniform password")
}
if len(passwords) > 1 {
return nil, fmt.Errorf("connection URLs must have uniform password")
}
u := urls[0]
path := strings.Split(u.Path[1:], "/")
uopts.MasterName = path[0]
if len(path) == 2 {
db, err := strconv.Atoi(path[1])
if err != nil {
return nil, err
}
uopts.DB = db
} else if len(path) > 2 {
return nil, fmt.Errorf("invalid connection URL path: %s", u.Path)
}
uopts.Username = u.User.Username()
uopts.SentinelUsername = u.User.Username()
uopts.Password, _ = u.User.Password()
uopts.SentinelPassword, _ = u.User.Password()
if u.Scheme == "rediss+sentinel" {
uopts.TLSConfig = &tls.Config{ServerName: u.Hostname()}
}
return &uopts, nil
}
func NewUniversalClient(raw string) (redis.UniversalClient, error) {
// Support multiple URLs for sentinel connections
var schemes []string
var urls []*url.URL
for _, r := range strings.Split(raw, ",") {
u, err := url.Parse(r)
if err != nil {
return nil, err
}
urls = append(urls, u)
if !slices.Contains(schemes, u.Scheme) {
schemes = append(schemes, u.Scheme)
}
}
if len(urls) == 1 {
if urls[0].Scheme == "redis" || urls[0].Scheme == "rediss" {
opts, err := redis.ParseURL(raw)
if err != nil {
return nil, err
}
return redis.NewClient(opts), nil
}
if urls[0].Scheme != "redis+sentinel" && urls[0].Scheme != "rediss+sentinel" {
return nil, fmt.Errorf("invalid connection URL scheme: %s", urls[0].Scheme)
}
// a single sentinel URL, fall through to parsing that
}
opts, err := parseSentinelURLs(urls)
if err != nil {
return nil, err
}
return redis.NewUniversalClient(opts), nil
}