Skip to content

Commit

Permalink
feat: support redis as external cache (#29)
Browse files Browse the repository at this point in the history
* feat: support redis as external state cache

* feat: support redis as external state cache

* feat: support redis as external state cache
  • Loading branch information
clambin authored Jun 19, 2024
1 parent c3b7e6c commit d0e5d5d
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 4 deletions.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/go-jose/go-jose/v3 v3.0.3 // indirect
github.com/go-jose/go-jose/v4 v4.0.1 // indirect
github.com/golang-jwt/jwt/v5 v5.2.0 // indirect
Expand All @@ -29,6 +30,7 @@ require (
github.com/prometheus/client_model v0.6.0 // indirect
github.com/prometheus/common v0.52.3 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
github.com/redis/go-redis/v9 v9.5.3 // indirect
golang.org/x/crypto v0.22.0 // indirect
golang.org/x/sys v0.19.0 // indirect
google.golang.org/protobuf v1.33.0 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k=
github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U=
Expand All @@ -41,6 +43,8 @@ github.com/prometheus/common v0.52.3 h1:5f8uj6ZwHSscOGNdIQg6OiZv/ybiK2CO2q2drVZA
github.com/prometheus/common v0.52.3/go.mod h1:BrxBKv3FWBIGXw89Mg1AeBq7FSyRzXWI3l3e7W3RN5U=
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
github.com/redis/go-redis/v9 v9.5.3 h1:fOAp1/uJG+ZtcITgZOfYFmTKPE7n4Vclj1wZFgRciUU=
github.com/redis/go-redis/v9 v9.5.3/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
Expand Down
14 changes: 13 additions & 1 deletion internal/cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/clambin/traefik-simple-auth/pkg/state"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/redis/go-redis/v9"
"io"
"log/slog"
"net/http"
Expand Down Expand Up @@ -80,7 +81,18 @@ func makeStateStore(cfg configuration.CacheConfiguration) state.States[string] {
backend = state.NewLocalCache[string]()
case "memcached":
backend = state.MemcachedCache[string]{
Client: memcache.New(cfg.MemcachedConfiguration.Addr),
Namespace: "github.com/clambin/traefik-simple-auth",
Client: memcache.New(cfg.MemcachedConfiguration.Addr),
}
case "redis":
backend = state.RedisCache[string]{
Namespace: "github.com/clambin/traefik-simple-auth",
Client: redis.NewClient(&redis.Options{
Addr: cfg.RedisConfiguration.Addr,
DB: cfg.RedisConfiguration.Database,
Username: cfg.RedisConfiguration.Username,
Password: cfg.RedisConfiguration.Password,
}),
}
default:
panic("unknown backend: " + cfg.Backend)
Expand Down
10 changes: 10 additions & 0 deletions internal/server/configuration/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ var (
secret = flag.String("secret", "", "Secret to use for authentication (base64 encoded)")
cacheBackend = flag.String("cache", "memory", "The backend to use for caching")
cacheMemcachedAddr = flag.String("cache-memcached-addr", "", "memcached address to use (only used when cache backend is memcached)")
cacheRedisAddr = flag.String("cache-redis-addr", "", "redis address to use (only used when cache backend is redis)")
cacheRedisDatabase = flag.Int("cache-redis-database", 0, "redis database to use (only used when cache backend is redis)")
cacheRedisUsername = flag.String("cache-redis-username", "", "Redis username (only used when cache backend is redis)")
cacheRedisPassword = flag.String("cache-redis-password", "", "Redis password (only used when cache backend is redis)")
)

type Configuration struct {
Expand Down Expand Up @@ -81,6 +85,12 @@ func GetConfiguration() (Configuration, error) {
MemcachedConfiguration: MemcachedConfiguration{
Addr: *cacheMemcachedAddr,
},
RedisConfiguration: RedisConfiguration{
Addr: *cacheRedisAddr,
Database: *cacheRedisDatabase,
Username: *cacheRedisUsername,
Password: *cacheRedisPassword,
},
},
}
var err error
Expand Down
43 changes: 41 additions & 2 deletions pkg/state/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"errors"
"github.com/bradfitz/gomemcache/memcache"
"github.com/clambin/go-common/cache"
"github.com/redis/go-redis/v9"
"time"
)

Expand Down Expand Up @@ -80,7 +81,8 @@ func (l LocalCache[T]) len(_ context.Context) (int, error) {
}

type MemcachedCache[T any] struct {
Client MemcachedClient
Namespace string
Client MemcachedClient
}

type MemcachedClient interface {
Expand All @@ -93,7 +95,7 @@ func (m MemcachedCache[T]) add(_ context.Context, key string, value T, ttl time.
val, err := encode[T](value)
if err == nil {
err = m.Client.Set(&memcache.Item{
Key: key,
Key: m.Namespace + "|" + key,
Value: val,
Expiration: int32(ttl.Seconds()),
})
Expand All @@ -102,6 +104,7 @@ func (m MemcachedCache[T]) add(_ context.Context, key string, value T, ttl time.
}

func (m MemcachedCache[T]) get(_ context.Context, key string) (T, error) {

Check failure on line 106 in pkg/state/state.go

View workflow job for this annotation

GitHub Actions / test / go-lint

func `MemcachedCache[T].get` is unused (unused)
key = m.Namespace + "|" + key
item, err := m.Client.Get(key)
if err == nil {
err = m.Client.Delete(key)
Expand Down Expand Up @@ -140,3 +143,39 @@ func decode[T any](value []byte) (v T, err error) {
}
return v, err
}

var _ Backend[string] = RedisCache[string]{}

type RedisCache[T any] struct {
Namespace string
Client RedisClient
}

type RedisClient interface {
Set(context.Context, string, any, time.Duration) *redis.StatusCmd
GetDel(context.Context, string) *redis.StringCmd
}

func (r RedisCache[T]) add(ctx context.Context, key string, value T, ttl time.Duration) error {

Check failure on line 159 in pkg/state/state.go

View workflow job for this annotation

GitHub Actions / test / go-lint

func `RedisCache[T].add` is unused (unused)
val, err := encode[T](value)
if err == nil {
err = r.Client.Set(ctx, r.Namespace+"|"+key, val, ttl).Err()
}
return err
}

func (r RedisCache[T]) get(ctx context.Context, key string) (T, error) {

Check failure on line 167 in pkg/state/state.go

View workflow job for this annotation

GitHub Actions / test / go-lint

func `RedisCache[T].get` is unused (unused)
val, err := r.Client.GetDel(ctx, r.Namespace+"|"+key).Result()
if err != nil {
if errors.Is(err, redis.Nil) {
err = ErrNotFound
}
var value T
return value, err
}
return decode[T]([]byte(val))
}

func (r RedisCache[T]) len(_ context.Context) (int, error) {

Check failure on line 179 in pkg/state/state.go

View workflow job for this annotation

GitHub Actions / test / go-lint

func `RedisCache[T].len` is unused (unused)
return 0, nil
}
35 changes: 34 additions & 1 deletion pkg/state/state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"github.com/bradfitz/gomemcache/memcache"
"github.com/clambin/go-common/cache"
"github.com/redis/go-redis/v9"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"testing"
Expand All @@ -25,7 +26,15 @@ func TestStates(t *testing.T) {
{
name: "memcachedCache",
backend: MemcachedCache[string]{
Client: &fakeMemcachedClient{c: LocalCache[string]{values: cache.New[string, string](0, 0)}},
Namespace: "github.com/clambin/traefik-simple-auth",
Client: &fakeMemcachedClient{c: LocalCache[string]{values: cache.New[string, string](0, 0)}},
},
},
{
name: "redisCache",
backend: RedisCache[string]{
Namespace: "github.com/clambin/traefik-simple-auth",
Client: &fakeRedisClient{c: LocalCache[string]{values: cache.New[string, string](0, 0)}},
},
},
}
Expand Down Expand Up @@ -101,3 +110,27 @@ func (f *fakeMemcachedClient) Delete(key string) error {
f.c.values.Remove(key)
return nil
}

var _ RedisClient = fakeRedisClient{}

type fakeRedisClient struct {
c LocalCache[string]
}

func (f fakeRedisClient) Set(ctx context.Context, key string, value any, ttl time.Duration) *redis.StatusCmd {
err := f.c.add(ctx, key, string(value.([]byte)), ttl)
cmd := redis.NewStatusCmd(ctx)
cmd.SetErr(err)
return cmd
}

func (f fakeRedisClient) GetDel(ctx context.Context, key string) *redis.StringCmd {
val, err := f.c.get(ctx, key)
if errors.Is(err, ErrNotFound) {
err = redis.Nil
}
cmd := redis.NewStringCmd(ctx)
cmd.SetErr(err)
cmd.SetVal(val)
return cmd
}

0 comments on commit d0e5d5d

Please # to comment.