2024-07-13 12:34:06 +00:00
|
|
|
package secrets
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2024-07-14 09:58:39 +00:00
|
|
|
"encoding/json"
|
2024-07-13 12:34:06 +00:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
)
|
|
|
|
|
|
|
|
// ErrNotFound is returned when an ID could not be found.
|
|
|
|
var ErrNotFound = errors.New("not found")
|
|
|
|
|
|
|
|
// Store is used to persist and retrieve secrets. If a Store serializes a
|
|
|
|
// payload it will do so using JSON.
|
|
|
|
type Store interface {
|
|
|
|
// Set stores the secret payload of the given ID.
|
|
|
|
Set(context.Context, ID, any) error
|
|
|
|
|
|
|
|
// Get retrieves the secret of the given ID, setting it into the given
|
|
|
|
// pointer value, or returns ErrNotFound.
|
|
|
|
Get(context.Context, any, ID) error
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetSetFunctions returns a Get/Set function pair for the given ID and payload
|
|
|
|
// type.
|
|
|
|
func GetSetFunctions[T any](
|
|
|
|
id ID,
|
|
|
|
) (
|
|
|
|
func(context.Context, Store) (T, error), // Get
|
|
|
|
func(context.Context, Store, T) error, // Set
|
|
|
|
) {
|
|
|
|
var (
|
|
|
|
get = func(ctx context.Context, store Store) (T, error) {
|
|
|
|
var v T
|
|
|
|
err := store.Get(ctx, &v, id)
|
|
|
|
return v, err
|
|
|
|
}
|
|
|
|
set = func(ctx context.Context, store Store, v T) error {
|
|
|
|
return store.Set(ctx, id, v)
|
|
|
|
}
|
|
|
|
)
|
|
|
|
return get, set
|
|
|
|
}
|
|
|
|
|
|
|
|
// MultiSet will call Set on the given Store for every key-value pair in the
|
|
|
|
// given map.
|
|
|
|
func MultiSet(ctx context.Context, s Store, m map[ID]any) error {
|
|
|
|
var errs []error
|
|
|
|
for id, payload := range m {
|
|
|
|
if err := s.Set(ctx, id, payload); err != nil {
|
|
|
|
errs = append(errs, fmt.Errorf("setting payload for %q: %w", id, err))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return errors.Join(errs...)
|
|
|
|
}
|
|
|
|
|
|
|
|
// MultiGet will call Get on the given Store for every key-value pair in the
|
|
|
|
// given map. Each value in the map must be a pointer receiver.
|
|
|
|
func MultiGet(ctx context.Context, s Store, m map[ID]any) error {
|
|
|
|
var errs []error
|
|
|
|
for id, into := range m {
|
|
|
|
if err := s.Get(ctx, into, id); err != nil {
|
|
|
|
errs = append(errs, fmt.Errorf("getting payload for %q: %w", id, err))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return errors.Join(errs...)
|
|
|
|
}
|
2024-07-14 09:58:39 +00:00
|
|
|
|
|
|
|
// Export returns a map of ID to raw payload for each ID given. An error is
|
|
|
|
// returned for _each_ ID which could not be exported, wrapped using
|
|
|
|
// `errors.Join`, alongside whatever keys could be exported.
|
|
|
|
func Export(
|
|
|
|
ctx context.Context, s Store, ids []ID,
|
|
|
|
) (
|
|
|
|
map[ID]json.RawMessage, error,
|
|
|
|
) {
|
|
|
|
var (
|
|
|
|
m = map[ID]json.RawMessage{}
|
|
|
|
errs []error
|
|
|
|
)
|
|
|
|
|
|
|
|
for _, id := range ids {
|
|
|
|
var into json.RawMessage
|
|
|
|
if err := s.Get(ctx, &into, id); err != nil {
|
|
|
|
errs = append(errs, fmt.Errorf("exporting %q: %w", id, err))
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
m[id] = into
|
|
|
|
}
|
|
|
|
|
|
|
|
return m, errors.Join(errs...)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Import sets all given ID/payload pairs into the Store.
|
|
|
|
func Import(
|
|
|
|
ctx context.Context, s Store, m map[ID]json.RawMessage,
|
|
|
|
) error {
|
|
|
|
var errs []error
|
|
|
|
for id, payload := range m {
|
|
|
|
if err := s.Set(ctx, id, payload); err != nil {
|
|
|
|
errs = append(errs, fmt.Errorf("importing %q: %w", id, err))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return errors.Join(errs...)
|
|
|
|
}
|