bouncer/internal/store/redis/proxy_repository.go

212 lines
4.6 KiB
Go
Raw Normal View History

2023-04-24 20:52:12 +02:00
package redis
import (
"context"
"net/url"
"time"
"forge.cadoles.com/cadoles/bouncer/internal/store"
"github.com/pkg/errors"
"github.com/redis/go-redis/v9"
)
const (
keyProxyName = "name"
keyProxyFrom = "from"
keyProxyTo = "to"
keyProxyUpdatedAt = "updated_at"
keyProxyCreatedAt = "created_at"
keyProxyWeight = "weight"
keyPrefixProxy = "proxy:"
)
type ProxyRepository struct {
client redis.UniversalClient
}
// GetProxy implements store.ProxyRepository
func (r *ProxyRepository) GetProxy(ctx context.Context, name store.ProxyName) (*store.Proxy, error) {
var proxy store.Proxy
key := proxyKey(name)
cmd := r.client.HMGet(ctx, key, keyProxyFrom, keyProxyTo, keyProxyWeight, keyProxyCreatedAt, keyProxyUpdatedAt)
values, err := cmd.Result()
if err != nil {
return nil, errors.WithStack(err)
}
if allNilValues(values) {
return nil, errors.WithStack(store.ErrNotFound)
}
proxy.Name = name
from, err := unwrap[[]string](values[0])
if err != nil {
return nil, errors.WithStack(err)
}
proxy.From = from
rawTo, ok := values[1].(string)
if !ok {
return nil, errors.Errorf("unexpected 'to' value of type '%T'", values[1])
}
to, err := url.Parse(rawTo)
if err != nil {
return nil, errors.WithStack(err)
}
proxy.To = to
weight, err := unwrap[int](values[2])
if err != nil {
return nil, errors.WithStack(err)
}
proxy.Weight = weight
createdAt, err := unwrap[time.Time](values[3])
if err != nil {
return nil, errors.WithStack(err)
}
proxy.CreatedAt = createdAt
updatedAt, err := unwrap[time.Time](values[4])
if err != nil {
return nil, errors.WithStack(err)
}
proxy.UpdatedAt = updatedAt
return &proxy, nil
}
// CreateProxy implements store.ProxyRepository
func (r *ProxyRepository) CreateProxy(ctx context.Context, name store.ProxyName, to *url.URL, from ...string) (*store.Proxy, error) {
now := time.Now().UTC()
key := proxyKey(name)
txf := func(tx *redis.Tx) error {
exists, err := tx.Exists(ctx, key).Uint64()
if err != nil {
return errors.WithStack(err)
}
if exists > 0 {
return errors.WithStack(store.ErrAlreadyExist)
}
_, err = tx.TxPipelined(ctx, func(p redis.Pipeliner) error {
p.HMSet(ctx, key, keyProxyName, string(name))
p.HMSet(ctx, key, keyProxyFrom, wrap(from))
p.HMSet(ctx, key, keyProxyTo, to.String())
p.HMSet(ctx, key, keyProxyWeight, wrap(0))
p.HMSet(ctx, key, keyProxyCreatedAt, wrap(now))
p.HMSet(ctx, key, keyProxyUpdatedAt, wrap(now))
return nil
})
return err
}
err := r.client.Watch(ctx, txf, key)
if err != nil {
return nil, errors.WithStack(err)
}
return &store.Proxy{
ProxyHeader: store.ProxyHeader{
Name: name,
CreatedAt: now,
UpdatedAt: now,
},
To: to,
From: from,
}, nil
}
// DeleteProxy implements store.ProxyRepository
func (r *ProxyRepository) DeleteProxy(ctx context.Context, name store.ProxyName) error {
key := proxyKey(name)
if cmd := r.client.Del(ctx, key); cmd.Err() != nil {
return errors.WithStack(cmd.Err())
}
return nil
}
// QueryProxy implements store.ProxyRepository
func (r *ProxyRepository) QueryProxy(ctx context.Context, funcs ...store.QueryProxyOptionFunc) ([]*store.ProxyHeader, error) {
iter := r.client.Scan(ctx, 0, keyPrefixProxy+"*", 0).Iterator()
headers := make([]*store.ProxyHeader, 0)
for iter.Next(ctx) {
key := iter.Val()
cmd := r.client.HMGet(ctx, key, keyProxyName, keyProxyCreatedAt, keyProxyUpdatedAt)
values, err := cmd.Result()
if err != nil {
return nil, errors.WithStack(err)
}
if allNilValues(values) {
continue
}
name, ok := values[0].(string)
if !ok {
return nil, errors.Errorf("unexpected 'name' field value for key '%s': '%s'", key, values[0])
}
createdAt, err := unwrap[time.Time](values[1])
if err != nil {
return nil, errors.WithStack(err)
}
updatedAt, err := unwrap[time.Time](values[2])
if err != nil {
return nil, errors.WithStack(err)
}
h := &store.ProxyHeader{
Name: store.ProxyName(name),
CreatedAt: createdAt,
UpdatedAt: updatedAt,
}
headers = append(headers, h)
}
if err := iter.Err(); err != nil {
return nil, errors.WithStack(err)
}
return headers, nil
}
// UpdateProxy implements store.ProxyRepository
func (*ProxyRepository) UpdateProxy(ctx context.Context, name store.ProxyName, funcs ...store.UpdateProxyOptionFunc) (*store.Proxy, error) {
panic("unimplemented")
}
func NewProxyRepository(client redis.UniversalClient) *ProxyRepository {
return &ProxyRepository{
client: client,
}
}
var _ store.ProxyRepository = &ProxyRepository{}
func proxyKey(name store.ProxyName) string {
return key(keyPrefixProxy, string(name))
}