From 7d2afa34a018989621ca44de62882b838f46bc86 Mon Sep 17 00:00:00 2001 From: Jake Sanders Date: Wed, 16 Oct 2019 12:58:14 +0100 Subject: [PATCH] Implementation and tests for certmagic.Storage interface --- api/server/acme/certmagic/certmagic_test.go | 140 +++++++++++++++++++- api/server/acme/certmagic/storage.go | 26 ++-- 2 files changed, 149 insertions(+), 17 deletions(-) diff --git a/api/server/acme/certmagic/certmagic_test.go b/api/server/acme/certmagic/certmagic_test.go index 52ede19c..367f6360 100644 --- a/api/server/acme/certmagic/certmagic_test.go +++ b/api/server/acme/certmagic/certmagic_test.go @@ -2,11 +2,16 @@ package certmagic import ( "os" + "reflect" + "sort" "testing" + "time" "github.com/go-acme/lego/v3/providers/dns/cloudflare" "github.com/mholt/certmagic" "github.com/micro/go-micro/api/server/acme" + "github.com/micro/go-micro/config/options" + cloudflarestorage "github.com/micro/go-micro/store/cloudflare" "github.com/micro/go-micro/sync/lock/memory" ) @@ -43,13 +48,140 @@ func TestCertMagic(t *testing.T) { } func TestStorageImplementation(t *testing.T) { - var s certmagic.Storage - s = &storage{ - lock: memory.NewLock(), + apiToken, accountID := os.Getenv("CF_API_TOKEN"), os.Getenv("CF_ACCOUNT_ID") + kvID := os.Getenv("KV_NAMESPACE_ID") + if len(apiToken) == 0 || len(accountID) == 0 || len(kvID) == 0 { + t.Skip("No Cloudflare API keys available, skipping test") } + + var s certmagic.Storage + st, err := cloudflarestorage.New( + options.WithValue("CF_API_TOKEN", apiToken), + options.WithValue("CF_ACCOUNT_ID", accountID), + options.WithValue("KV_NAMESPACE_ID", kvID), + ) + if err != nil { + t.Fatalf("Couldn't initialise cloudflare storage: %s\n", err.Error()) + } + s = &storage{ + lock: memory.NewLock(), + store: st, + } + + // Test Lock if err := s.Lock("test"); err != nil { t.Error(err) } - s.Unlock("test") + + // Test Unlock + if err := s.Unlock("test"); err != nil { + t.Error(err) + } + + // Test data + testdata := []struct { + key string + value []byte + }{ + {key: "/foo/a", value: []byte("lorem")}, + {key: "/foo/b", value: []byte("ipsum")}, + {key: "/foo/c", value: []byte("dolor")}, + {key: "/foo/d", value: []byte("sit")}, + {key: "/bar/a", value: []byte("amet")}, + {key: "/bar/b", value: []byte("consectetur")}, + {key: "/bar/c", value: []byte("adipiscing")}, + {key: "/bar/d", value: []byte("elit")}, + {key: "/foo/bar/a", value: []byte("sed")}, + {key: "/foo/bar/b", value: []byte("do")}, + {key: "/foo/bar/c", value: []byte("eiusmod")}, + {key: "/foo/bar/d", value: []byte("tempor")}, + {key: "/foo/bar/baz/a", value: []byte("incididunt")}, + {key: "/foo/bar/baz/b", value: []byte("ut")}, + {key: "/foo/bar/baz/c", value: []byte("labore")}, + {key: "/foo/bar/baz/d", value: []byte("et")}, + // a duplicate just in case there's any edge cases + {key: "/foo/a", value: []byte("lorem")}, + } + + // Test Store + for _, d := range testdata { + if err := s.Store(d.key, d.value); err != nil { + t.Error(err.Error()) + } + } + + // Test Load + for _, d := range testdata { + if value, err := s.Load(d.key); err != nil { + t.Error(err.Error()) + } else { + if !reflect.DeepEqual(value, d.value) { + t.Errorf("Load %s: expected %v, got %v", d.key, d.value, value) + } + } + } + + // Test Exists + for _, d := range testdata { + if !s.Exists(d.key) { + t.Errorf("%s should exist, but doesn't\n", d.key) + } + } + + // Test List + if list, err := s.List("/", true); err != nil { + t.Error(err.Error()) + } else { + var expected []string + for i, d := range testdata { + if i != len(testdata)-1 { + // Don't store the intentionally duplicated key + expected = append(expected, d.key) + } + } + sort.Strings(expected) + sort.Strings(list) + if !reflect.DeepEqual(expected, list) { + t.Errorf("List: Expected %v, got %v\n", expected, list) + } + } + if list, err := s.List("/foo", false); err != nil { + t.Error(err.Error()) + } else { + sort.Strings(list) + expected := []string{"/foo/a", "/foo/b", "/foo/bar", "/foo/c", "/foo/d"} + if !reflect.DeepEqual(expected, list) { + t.Errorf("List: expected %s, got %s\n", expected, list) + } + } + + // Test Stat + for _, d := range testdata { + info, err := s.Stat(d.key) + if err != nil { + t.Error(err.Error()) + } else { + if info.Key != d.key { + t.Errorf("Stat().Key: expected %s, got %s\n", d.key, info.Key) + } + if info.Size != int64(len(d.value)) { + t.Errorf("Stat().Size: expected %d, got %d\n", len(d.value), info.Size) + } + if time.Since(info.Modified) > time.Minute { + t.Errorf("Stat().Modified: expected time since last modified to be < 1 minute, got %v\n", time.Since(info.Modified)) + } + } + + } + + // Test Delete + for _, d := range testdata { + if err := s.Delete(d.key); err != nil { + t.Error(err.Error()) + } + } + + // New interface doesn't return an error, so call it in case any log.Fatal + // happens New(acme.Cache(s)) } diff --git a/api/server/acme/certmagic/storage.go b/api/server/acme/certmagic/storage.go index b1bb92b4..d0dd3ab9 100644 --- a/api/server/acme/certmagic/storage.go +++ b/api/server/acme/certmagic/storage.go @@ -13,12 +13,12 @@ import ( "github.com/micro/go-micro/sync/lock" ) -// file represents a "file" that will be stored in store.Store - the contents and last modified time -type file struct { +// 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 + LastModified time.Time + // Contents + Contents []byte } // storage is an implementation of certmagic.Storage using micro's sync.Map and store.Store interfaces. @@ -38,9 +38,9 @@ func (s *storage) Unlock(key string) error { } func (s *storage) Store(key string, value []byte) error { - f := file{ - lastModified: time.Now(), - contents: value, + f := File{ + LastModified: time.Now(), + Contents: value, } buf := &bytes.Buffer{} e := gob.NewEncoder(buf) @@ -64,12 +64,12 @@ func (s *storage) Load(key string) ([]byte, error) { } b := bytes.NewBuffer(records[0].Value) d := gob.NewDecoder(b) - var f file + var f File err = d.Decode(&f) if err != nil { return nil, err } - return f.contents, nil + return f.Contents, nil } func (s *storage) Delete(key string) error { @@ -120,15 +120,15 @@ func (s *storage) Stat(key string) (certmagic.KeyInfo, error) { } b := bytes.NewBuffer(records[0].Value) d := gob.NewDecoder(b) - var f file + 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)), + Modified: f.LastModified, + Size: int64(len(f.Contents)), IsTerminal: false, }, nil }