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 }