isle/go/secrets/store_fs.go

81 lines
1.6 KiB
Go
Raw Normal View History

package secrets
import (
"context"
"encoding/json"
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"
)
type fsStore struct {
dirPath string
}
type fsStorePayload[Body any] struct {
Version int
Body Body
}
// NewFSStore returns a Store which will store secrets to the given directory.
// The directory must already exist.
func NewFSStore(dirPath string) Store {
return &fsStore{dirPath}
}
func (s *fsStore) path(id ID) string {
return filepath.Join(s.dirPath, string(id))
}
func (s *fsStore) Set(_ context.Context, id ID, payload any) error {
path := s.path(id)
f, err := os.Create(path)
if err != nil {
return fmt.Errorf("creating file %q: %w", path, err)
}
defer f.Close()
if err := json.NewEncoder(f).Encode(fsStorePayload[any]{
Version: 1,
Body: payload,
}); err != nil {
return fmt.Errorf("writing JSON encoded payload to %q: %w", path, err)
}
return nil
}
func (s *fsStore) Get(_ context.Context, into any, id ID) error {
path := s.path(id)
f, err := os.Open(path)
if errors.Is(err, fs.ErrNotExist) {
return ErrNotFound
} else if err != nil {
return fmt.Errorf("creating file %q: %w", path, err)
}
defer f.Close()
var fullPayload fsStorePayload[json.RawMessage]
if err := json.NewDecoder(f).Decode(&fullPayload); err != nil {
return fmt.Errorf("decoding JSON payload from %q: %w", path, err)
}
if fullPayload.Version != 1 {
return fmt.Errorf(
"unexpected JSON payload version %d", fullPayload.Version,
)
}
if err := json.Unmarshal(fullPayload.Body, into); err != nil {
return fmt.Errorf(
"decoding JSON payload body from %q into %T: %w", path, into, err,
)
}
return nil
}