2022-10-30 19:11:36 +00:00
|
|
|
package storage
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"context"
|
|
|
|
"encoding/gob"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"time"
|
|
|
|
|
2024-11-11 18:50:17 +00:00
|
|
|
"github.com/rs/zerolog/log"
|
2022-10-30 19:11:36 +00:00
|
|
|
"github.com/spf13/viper"
|
|
|
|
clientv3 "go.etcd.io/etcd/client/v3"
|
|
|
|
"go.etcd.io/etcd/client/v3/namespace"
|
|
|
|
)
|
|
|
|
|
|
|
|
type EtcdSerializer interface {
|
|
|
|
Deserialize(d []byte, m *any) error
|
|
|
|
Serialize(m any) ([]byte, error)
|
|
|
|
}
|
|
|
|
|
|
|
|
type JSONEtcdSerializer struct{}
|
|
|
|
|
|
|
|
// Serialize to JSON. Will err if there are unmarshalable key values
|
|
|
|
func (s JSONEtcdSerializer) Serialize(m any) ([]byte, error) {
|
|
|
|
return json.Marshal(m)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Deserialize back to map[string]interface{}
|
|
|
|
func (s JSONEtcdSerializer) Deserialize(d []byte, m *any) (err error) {
|
|
|
|
err = json.Unmarshal(d, &m)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("JSONSerializer.deserialize() Error: %v", err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// GobEtcdSerializer uses gob package to encode the session map
|
|
|
|
type GobEtcdSerializer struct{}
|
|
|
|
|
|
|
|
// Serialize using gob
|
|
|
|
func (s GobEtcdSerializer) Serialize(m any) ([]byte, error) {
|
|
|
|
buf := new(bytes.Buffer)
|
|
|
|
enc := gob.NewEncoder(buf)
|
|
|
|
err := enc.Encode(m)
|
|
|
|
if err == nil {
|
|
|
|
return buf.Bytes(), nil
|
|
|
|
}
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Deserialize back to map[interface{}]interface{}
|
|
|
|
func (s GobEtcdSerializer) Deserialize(d []byte, m any) error {
|
|
|
|
dec := gob.NewDecoder(bytes.NewBuffer(d))
|
|
|
|
return dec.Decode(&m)
|
|
|
|
}
|
|
|
|
|
|
|
|
type EtcdHandler struct {
|
|
|
|
*clientv3.Client
|
|
|
|
serializer EtcdSerializer
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewEtcdHandler(cfg *viper.Viper) (*EtcdHandler, error) {
|
|
|
|
var (
|
|
|
|
endpoints = cfg.GetStringSlice("storage.kv.etcd.endpoints")
|
|
|
|
prefix = cfg.GetString("storage.kv.etcd.prefix")
|
2022-11-06 17:44:29 +00:00
|
|
|
username = cfg.GetString("storage.kv.etcd.username")
|
|
|
|
password = cfg.GetString("storage.kv.etcd.password")
|
2022-10-30 19:11:36 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
cli, err := clientv3.New(clientv3.Config{
|
|
|
|
Endpoints: endpoints,
|
2022-11-06 17:44:29 +00:00
|
|
|
Username: username,
|
|
|
|
Password: password,
|
2022-10-30 19:11:36 +00:00
|
|
|
DialTimeout: 5 * time.Second,
|
|
|
|
})
|
|
|
|
if err != nil {
|
2024-11-11 18:50:17 +00:00
|
|
|
log.Error().Err(err).Msg("")
|
2022-10-30 19:11:36 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
cli.KV = namespace.NewKV(cli.KV, prefix)
|
|
|
|
cli.Watcher = namespace.NewWatcher(cli.Watcher, prefix)
|
|
|
|
cli.Lease = namespace.NewLease(cli.Lease, prefix)
|
|
|
|
|
|
|
|
return &EtcdHandler{
|
|
|
|
Client: cli,
|
|
|
|
serializer: JSONEtcdSerializer{},
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *EtcdHandler) Put(k string, v any) error {
|
|
|
|
data, err := s.serializer.Serialize(v)
|
|
|
|
if err != nil {
|
2024-11-11 18:50:17 +00:00
|
|
|
log.Error().Err(err).Msg("")
|
2022-10-30 19:11:36 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
// _, err = s.Client.KV.Put(context.TODO(), k, data.String())
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
|
|
_, err = s.Client.KV.Put(ctx, k, string(data))
|
|
|
|
cancel()
|
|
|
|
if err != nil {
|
2024-11-11 18:50:17 +00:00
|
|
|
log.Error().Err(err).Msg("")
|
2022-10-30 19:11:36 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *EtcdHandler) PutWithTTL(k string, v any, duration time.Duration) error {
|
|
|
|
lease, err := s.Client.Lease.Grant(context.TODO(), int64(duration.Seconds()))
|
|
|
|
if err != nil {
|
2024-11-11 18:50:17 +00:00
|
|
|
log.Error().Err(err).Msg("")
|
2022-10-30 19:11:36 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
data, err := s.serializer.Serialize(v)
|
|
|
|
if err != nil {
|
2024-11-11 18:50:17 +00:00
|
|
|
log.Error().Err(err).Msg("")
|
2022-10-30 19:11:36 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
// _, err = s.Client.KV.Put(context.TODO(), k, data.String(), clientv3.WithLease(lease.ID))
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
|
|
_, err = s.Client.KV.Put(ctx, k, string(data), clientv3.WithLease(lease.ID))
|
|
|
|
cancel()
|
2022-11-07 00:24:16 +00:00
|
|
|
|
2022-10-30 19:11:36 +00:00
|
|
|
if err != nil {
|
2024-11-11 18:50:17 +00:00
|
|
|
log.Error().Err(err).Msg("")
|
2022-10-30 19:11:36 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *EtcdHandler) Get(k string) (any, error) {
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
|
|
resp, err := s.Client.KV.Get(ctx, k)
|
|
|
|
cancel()
|
|
|
|
if err != nil {
|
2024-11-11 18:50:17 +00:00
|
|
|
log.Error().Err(err).Msg("")
|
2022-10-30 19:11:36 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
for _, v := range resp.Kvs {
|
|
|
|
var data any
|
|
|
|
err := s.serializer.Deserialize([]byte(v.Value), &data)
|
|
|
|
if err != nil {
|
2024-11-11 18:50:17 +00:00
|
|
|
log.Error().Err(err).Msg("")
|
2022-10-30 19:11:36 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
// We return directly as we want to last revision of value
|
|
|
|
return data, nil
|
|
|
|
}
|
|
|
|
return nil, fmt.Errorf("no value %v", k)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *EtcdHandler) Delete(k string) error {
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
|
|
_, err := s.Client.KV.Delete(ctx, k)
|
|
|
|
cancel()
|
|
|
|
if err != nil {
|
2024-11-11 18:50:17 +00:00
|
|
|
log.Error().Err(err).Msg("")
|
2022-10-30 19:11:36 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|