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 }