Skip to content

Commit

Permalink
add redis store (#1008)
Browse files Browse the repository at this point in the history
* add redis store

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
  • Loading branch information
mcalhoun and autofix-ci[bot] authored Feb 3, 2025
1 parent 1e3211b commit 2725f99
Show file tree
Hide file tree
Showing 7 changed files with 497 additions and 1 deletion.
2 changes: 2 additions & 0 deletions go.mod

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

150 changes: 150 additions & 0 deletions pkg/store/redis_store.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package store

import (
"context"
"encoding/json"
"fmt"
"os"
"strings"
"time"

"github.com/redis/go-redis/v9"
)

type RedisStore struct {
prefix string
redisClient RedisClient
stackDelimiter *string
}

type RedisStoreOptions struct {
Prefix *string `mapstructure:"prefix"`
StackDelimiter *string `mapstructure:"stack_delimiter"`
URL *string `mapstructure:"url"`
}

// RedisClient interface allows us to mock the Redis Client in test with only the methods we are using in the
// RedisStore.
type RedisClient interface {
Get(ctx context.Context, key string) *redis.StringCmd
Set(ctx context.Context, key string, value interface{}, expiration time.Duration) *redis.StatusCmd
}

// Ensure RedisStore implements the store.Store interface.
var _ Store = (*RedisStore)(nil)

func getRedisOptions(options *RedisStoreOptions) (*redis.Options, error) {
if options.URL != nil {
opts, err := redis.ParseURL(*options.URL)
if err != nil {
return &redis.Options{}, fmt.Errorf("failed to parse redis url: %v", err)
}

return opts, nil
}

if os.Getenv("ATMOS_REDIS_URL") != "" {
return redis.ParseURL(os.Getenv("ATMOS_REDIS_URL"))
}

return &redis.Options{}, fmt.Errorf("either url must be set in options or REDIS_URL environment variable must be set")
}

func NewRedisStore(options RedisStoreOptions) (Store, error) {
prefix := ""
if options.Prefix != nil {
prefix = *options.Prefix
}

stackDelimiter := "/"
if options.StackDelimiter != nil {
stackDelimiter = *options.StackDelimiter
}

opts, err := getRedisOptions(&options)
if err != nil {
return nil, fmt.Errorf("failed to parse redis url: %v", err)
}

redisClient := redis.NewClient(opts)

return &RedisStore{
prefix: prefix,
redisClient: redisClient,
stackDelimiter: &stackDelimiter,
}, nil
}

func (s *RedisStore) getKey(stack string, component string, key string) (string, error) {
if s.stackDelimiter == nil {
return "", fmt.Errorf("stack delimiter is not set")
}

prefixParts := []string{s.prefix}
prefix := strings.Join(prefixParts, "/")

return getKey(prefix, *s.stackDelimiter, stack, component, key, "/")
}

func (s *RedisStore) Get(stack string, component string, key string) (interface{}, error) {
if stack == "" {
return nil, fmt.Errorf("stack cannot be empty")
}

if component == "" {
return nil, fmt.Errorf("component cannot be empty")
}

if key == "" {
return nil, fmt.Errorf("key cannot be empty")
}

paramName, err := s.getKey(stack, component, key)
if err != nil {
return nil, fmt.Errorf("failed to get key: %v", err)
}

ctx := context.Background()
jsonData, err := s.redisClient.Get(ctx, paramName).Result()
if err != nil {
return nil, fmt.Errorf("failed to get key: %v", err)
}

var result interface{}
err = json.Unmarshal([]byte(jsonData), &result)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal file: %v", err)
}

return result, nil
}

func (s *RedisStore) Set(stack string, component string, key string, value interface{}) error {
if stack == "" {
return fmt.Errorf("stack cannot be empty")
}

if component == "" {
return fmt.Errorf("component cannot be empty")
}

if key == "" {
return fmt.Errorf("key cannot be empty")
}

// Construct the full parameter name using getKey
paramName, err := s.getKey(stack, component, key)
if err != nil {
return fmt.Errorf("failed to get key: %v", err)
}

jsonData, err := json.Marshal(value)
if err != nil {
return fmt.Errorf("failed to marshal value: %v", err)
}

ctx := context.Background()
err = s.redisClient.Set(ctx, paramName, jsonData, 0).Err()

return err
}
Loading

0 comments on commit 2725f99

Please sign in to comment.