148 lines
3.4 KiB
Go
148 lines
3.4 KiB
Go
package certmagic
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/gob"
|
|
"errors"
|
|
"fmt"
|
|
"path"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/caddyserver/certmagic"
|
|
"github.com/unistack-org/micro/v3/store"
|
|
"github.com/unistack-org/micro/v3/sync"
|
|
)
|
|
|
|
// File represents a "File" that will be stored in store.Store - the contents and last modified time
|
|
type File struct {
|
|
// last modified time
|
|
LastModified time.Time
|
|
// Contents
|
|
Contents []byte
|
|
}
|
|
|
|
// storage is an implementation of certmagic.Storage using micro's sync.Map and store.Store interfaces.
|
|
// As certmagic storage expects a filesystem (with stat() abilities) we have to implement
|
|
// the bare minimum of metadata.
|
|
type storage struct {
|
|
lock sync.Sync
|
|
store store.Store
|
|
}
|
|
|
|
func (s *storage) Lock(key string) error {
|
|
return s.lock.Lock(key, sync.LockTTL(10*time.Minute))
|
|
}
|
|
|
|
func (s *storage) Unlock(key string) error {
|
|
return s.lock.Unlock(key)
|
|
}
|
|
|
|
func (s *storage) Store(key string, value []byte) error {
|
|
f := File{
|
|
LastModified: time.Now(),
|
|
Contents: value,
|
|
}
|
|
buf := &bytes.Buffer{}
|
|
e := gob.NewEncoder(buf)
|
|
if err := e.Encode(f); err != nil {
|
|
return err
|
|
}
|
|
r := &store.Record{
|
|
Key: key,
|
|
Value: buf.Bytes(),
|
|
}
|
|
return s.store.Write(s.store.Options().Context, r)
|
|
}
|
|
|
|
func (s *storage) Load(key string) ([]byte, error) {
|
|
if !s.Exists(key) {
|
|
return nil, certmagic.ErrNotExist(errors.New(key + " doesn't exist"))
|
|
}
|
|
records, err := s.store.Read(s.store.Options().Context, key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(records) != 1 {
|
|
return nil, fmt.Errorf("ACME Storage: multiple records matched key %s", key)
|
|
}
|
|
b := bytes.NewBuffer(records[0].Value)
|
|
d := gob.NewDecoder(b)
|
|
var f File
|
|
err = d.Decode(&f)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return f.Contents, nil
|
|
}
|
|
|
|
func (s *storage) Delete(key string) error {
|
|
return s.store.Delete(s.store.Options().Context, key)
|
|
}
|
|
|
|
func (s *storage) Exists(key string) bool {
|
|
if _, err := s.store.Read(s.store.Options().Context, key); err != nil {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (s *storage) List(prefix string, recursive bool) ([]string, error) {
|
|
keys, err := s.store.List(s.store.Options().Context)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
//nolint:prealloc
|
|
var results []string
|
|
for _, k := range keys {
|
|
if strings.HasPrefix(k, prefix) {
|
|
results = append(results, k)
|
|
}
|
|
}
|
|
if recursive {
|
|
return results, nil
|
|
}
|
|
keysMap := make(map[string]bool)
|
|
for _, key := range results {
|
|
dir := strings.Split(strings.TrimPrefix(key, prefix+"/"), "/")
|
|
keysMap[dir[0]] = true
|
|
}
|
|
results = make([]string, 0)
|
|
for k := range keysMap {
|
|
results = append(results, path.Join(prefix, k))
|
|
}
|
|
return results, nil
|
|
}
|
|
|
|
func (s *storage) Stat(key string) (certmagic.KeyInfo, error) {
|
|
records, err := s.store.Read(s.store.Options().Context, key)
|
|
if err != nil {
|
|
return certmagic.KeyInfo{}, err
|
|
}
|
|
if len(records) != 1 {
|
|
return certmagic.KeyInfo{}, fmt.Errorf("ACME Storage: multiple records matched key %s", key)
|
|
}
|
|
b := bytes.NewBuffer(records[0].Value)
|
|
d := gob.NewDecoder(b)
|
|
var f File
|
|
err = d.Decode(&f)
|
|
if err != nil {
|
|
return certmagic.KeyInfo{}, err
|
|
}
|
|
return certmagic.KeyInfo{
|
|
Key: key,
|
|
Modified: f.LastModified,
|
|
Size: int64(len(f.Contents)),
|
|
IsTerminal: false,
|
|
}, nil
|
|
}
|
|
|
|
// NewStorage returns a certmagic.Storage backed by a micro/lock and micro/store
|
|
func NewStorage(lock sync.Sync, store store.Store) certmagic.Storage {
|
|
return &storage{
|
|
lock: lock,
|
|
store: store,
|
|
}
|
|
}
|