Files
storage/internal/vault/vault.go
Dmitry Fedotov 3757a43318 feat: objects in vault are stored as a single secret
Co-authored-by: Dmitry Fedotov <dmitry@uint32.ru>
Co-committed-by: Dmitry Fedotov <dmitry@uint32.ru>
2025-08-10 13:02:38 +03:00

138 lines
2.5 KiB
Go

package vault
import (
"context"
"encoding/base64"
"errors"
"fmt"
"path"
"strings"
"github.com/hashicorp/vault/api"
"code.uint32.ru/tiny/storage/internal/errinternal"
)
var (
ErrNotFound = errinternal.ErrNotFound
)
type Storage struct {
key string
kv *api.KVv1
// TODO: kv2: *api.KVv2
}
// New returns Storage writing to the specified vault path.
// Objects will be base64 encoded and written to path as a single
// secret.
func New(c *api.Client, secretpath string) *Storage {
p, k := path.Split(secretpath)
p = strings.Trim(p, "/")
k = strings.Trim(k, "/")
return &Storage{kv: c.KVv1(p), key: k}
}
func (s *Storage) Save(key string, data []byte) error {
datamap, err := s.getData()
if err != nil {
return err
}
str := base64.StdEncoding.EncodeToString(data)
datamap[key] = str
if err := s.putData(datamap); err != nil {
return err
}
return nil
}
func (s *Storage) Load(key string) ([]byte, error) {
data, err := s.getData()
if err != nil {
return nil, err
}
payload, ok := data[key]
if !ok {
return nil, errors.Join(ErrNotFound, errors.New("key not found in stored secret"))
}
str, ok := payload.(string)
if !ok {
// returning ErrNotFound because this will fail again on retry
return nil, errors.Join(ErrNotFound, errors.New("could not convert payload to string"))
}
b := []byte{}
b, err = base64.StdEncoding.AppendDecode(b, []byte(str))
if err != nil {
return nil, errors.Join(ErrNotFound, fmt.Errorf("could not base64 decode value: %w", err))
}
return b, nil
}
func (s *Storage) putData(data map[string]any) error {
m := map[string]any{
"data": data,
}
if err := s.kv.Put(context.Background(), s.key, m); err != nil {
return err
}
return nil
}
func (s *Storage) getData() (map[string]any, error) {
m, err := s.kv.Get(context.Background(), s.key)
if err != nil && errors.Is(err, api.ErrSecretNotFound) {
// will create secret with no payload
m := make(map[string]any)
if err := s.putData(m); err != nil {
return nil, err
}
return m, nil
} else if err != nil {
return nil, err
}
data, ok := m.Data["data"] // map[string]any
if !ok {
return nil, errors.New("no data found")
}
datamap, ok := data.(map[string]any)
if !ok {
return nil, errors.New("no payload map")
}
return datamap, nil
}
func (s *Storage) Delete(key string) error {
data, err := s.getData()
if err != nil {
return err
}
if _, ok := data[key]; !ok {
return nil
}
delete(data, key)
if err := s.putData(data); err != nil {
return err
}
return nil
}