2019-03-08 13:49:16 -06:00
|
|
|
package remotecache
|
2019-02-23 09:12:37 -06:00
|
|
|
|
|
|
|
import (
|
2021-09-20 15:21:59 -05:00
|
|
|
"context"
|
2019-08-13 05:51:13 -05:00
|
|
|
"crypto/tls"
|
2019-06-10 08:27:08 -05:00
|
|
|
"fmt"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
2019-02-23 09:12:37 -06:00
|
|
|
"time"
|
|
|
|
|
2021-09-20 15:21:59 -05:00
|
|
|
"github.com/go-redis/redis/v8"
|
2023-01-30 02:32:25 -06:00
|
|
|
|
2019-03-03 14:48:00 -06:00
|
|
|
"github.com/grafana/grafana/pkg/setting"
|
2019-02-23 09:12:37 -06:00
|
|
|
)
|
|
|
|
|
2019-03-14 03:27:41 -05:00
|
|
|
const redisCacheType = "redis"
|
|
|
|
|
2019-02-23 09:12:37 -06:00
|
|
|
type redisStorage struct {
|
2023-03-10 06:57:29 -06:00
|
|
|
c *redis.Client
|
2019-02-23 09:12:37 -06:00
|
|
|
}
|
|
|
|
|
2019-06-10 08:27:08 -05:00
|
|
|
// parseRedisConnStr parses k=v pairs in csv and builds a redis Options object
|
|
|
|
func parseRedisConnStr(connStr string) (*redis.Options, error) {
|
|
|
|
keyValueCSV := strings.Split(connStr, ",")
|
|
|
|
options := &redis.Options{Network: "tcp"}
|
2019-08-13 05:51:13 -05:00
|
|
|
setTLSIsTrue := false
|
2019-06-10 08:27:08 -05:00
|
|
|
for _, rawKeyValue := range keyValueCSV {
|
2019-07-23 08:45:04 -05:00
|
|
|
keyValueTuple := strings.SplitN(rawKeyValue, "=", 2)
|
2019-06-10 08:27:08 -05:00
|
|
|
if len(keyValueTuple) != 2 {
|
2019-07-23 08:45:04 -05:00
|
|
|
if strings.HasPrefix(rawKeyValue, "password") {
|
|
|
|
// don't log the password
|
2021-05-10 10:03:10 -05:00
|
|
|
rawKeyValue = "password" + setting.RedactedPassword
|
2019-07-23 08:45:04 -05:00
|
|
|
}
|
2019-06-10 08:27:08 -05:00
|
|
|
return nil, fmt.Errorf("incorrect redis connection string format detected for '%v', format is key=value,key=value", rawKeyValue)
|
|
|
|
}
|
|
|
|
connKey := keyValueTuple[0]
|
|
|
|
connVal := keyValueTuple[1]
|
|
|
|
switch connKey {
|
|
|
|
case "addr":
|
|
|
|
options.Addr = connVal
|
|
|
|
case "password":
|
|
|
|
options.Password = connVal
|
|
|
|
case "db":
|
2019-08-13 05:51:13 -05:00
|
|
|
i, err := strconv.Atoi(connVal)
|
2019-06-10 08:27:08 -05:00
|
|
|
if err != nil {
|
2022-06-03 02:24:24 -05:00
|
|
|
return nil, fmt.Errorf("%v: %w", "value for db in redis connection string must be a number", err)
|
2019-06-10 08:27:08 -05:00
|
|
|
}
|
|
|
|
options.DB = i
|
|
|
|
case "pool_size":
|
|
|
|
i, err := strconv.Atoi(connVal)
|
|
|
|
if err != nil {
|
2022-06-03 02:24:24 -05:00
|
|
|
return nil, fmt.Errorf("%v: %w", "value for pool_size in redis connection string must be a number", err)
|
2019-06-10 08:27:08 -05:00
|
|
|
}
|
|
|
|
options.PoolSize = i
|
2019-08-13 05:51:13 -05:00
|
|
|
case "ssl":
|
|
|
|
if connVal != "true" && connVal != "false" && connVal != "insecure" {
|
|
|
|
return nil, fmt.Errorf("ssl must be set to 'true', 'false', or 'insecure' when present")
|
|
|
|
}
|
|
|
|
if connVal == "true" {
|
|
|
|
setTLSIsTrue = true // Needs addr already parsed, so set later
|
|
|
|
}
|
|
|
|
if connVal == "insecure" {
|
|
|
|
options.TLSConfig = &tls.Config{InsecureSkipVerify: true}
|
|
|
|
}
|
2019-06-10 08:27:08 -05:00
|
|
|
default:
|
2019-08-13 05:51:13 -05:00
|
|
|
return nil, fmt.Errorf("unrecognized option '%v' in redis connection string", connKey)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if setTLSIsTrue {
|
|
|
|
// Get hostname from the Addr property and set it on the configuration for TLS
|
|
|
|
sp := strings.Split(options.Addr, ":")
|
|
|
|
if len(sp) < 1 {
|
|
|
|
return nil, fmt.Errorf("unable to get hostname from the addr field, expected host:port, got '%v'", options.Addr)
|
2019-06-10 08:27:08 -05:00
|
|
|
}
|
2019-08-13 05:51:13 -05:00
|
|
|
options.TLSConfig = &tls.Config{ServerName: sp[0]}
|
2019-03-03 14:48:00 -06:00
|
|
|
}
|
2019-06-10 08:27:08 -05:00
|
|
|
return options, nil
|
|
|
|
}
|
|
|
|
|
2023-03-10 06:57:29 -06:00
|
|
|
func newRedisStorage(opts *setting.RemoteCacheOptions) (*redisStorage, error) {
|
2019-06-10 08:27:08 -05:00
|
|
|
opt, err := parseRedisConnStr(opts.ConnStr)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-03-10 06:57:29 -06:00
|
|
|
return &redisStorage{c: redis.NewClient(opt)}, nil
|
2023-02-06 06:08:03 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// Set sets value to a given key
|
2023-03-10 06:57:29 -06:00
|
|
|
func (s *redisStorage) Set(ctx context.Context, key string, data []byte, expires time.Duration) error {
|
2023-02-06 06:08:03 -06:00
|
|
|
status := s.c.Set(ctx, key, data, expires)
|
2019-02-23 09:12:37 -06:00
|
|
|
return status.Err()
|
|
|
|
}
|
|
|
|
|
2023-02-06 06:08:03 -06:00
|
|
|
// GetByteArray returns the value as byte array
|
2023-03-10 06:57:29 -06:00
|
|
|
func (s *redisStorage) Get(ctx context.Context, key string) ([]byte, error) {
|
2023-02-06 06:08:03 -06:00
|
|
|
return s.c.Get(ctx, key).Bytes()
|
|
|
|
}
|
|
|
|
|
2019-02-23 09:12:37 -06:00
|
|
|
// Delete delete a key from session.
|
2021-12-22 04:02:42 -06:00
|
|
|
func (s *redisStorage) Delete(ctx context.Context, key string) error {
|
|
|
|
cmd := s.c.Del(ctx, key)
|
2019-02-23 09:12:37 -06:00
|
|
|
return cmd.Err()
|
|
|
|
}
|
2023-02-21 09:21:18 -06:00
|
|
|
|
|
|
|
func (s *redisStorage) Count(ctx context.Context, prefix string) (int64, error) {
|
|
|
|
cmd := s.c.Keys(ctx, prefix+"*")
|
|
|
|
if cmd.Err() != nil {
|
|
|
|
return 0, cmd.Err()
|
|
|
|
}
|
|
|
|
|
|
|
|
return int64(len(cmd.Val())), nil
|
|
|
|
}
|