From 0b04f823724bb9bc1e9fd3ecff1d13c86a2e613b Mon Sep 17 00:00:00 2001 From: Nik Voss Date: Tue, 4 Aug 2020 17:39:11 +0200 Subject: [PATCH] Storage interface with beginning of implementation for git --- pkg/storage/git/git.go | 127 +++++++++++++++++++++++++++++++++ pkg/storage/git/git_test.go | 30 ++++++++ pkg/storage/git/suite_test.go | 106 +++++++++++++++++++++++++++ pkg/storage/storage.go | 30 ++++++++ pkg/testutil/gitserver_test.go | 2 +- 5 files changed, 294 insertions(+), 1 deletion(-) create mode 100644 pkg/storage/git/git.go create mode 100644 pkg/storage/git/git_test.go create mode 100644 pkg/storage/git/suite_test.go create mode 100644 pkg/storage/storage.go diff --git a/pkg/storage/git/git.go b/pkg/storage/git/git.go new file mode 100644 index 0000000..2cdc1d8 --- /dev/null +++ b/pkg/storage/git/git.go @@ -0,0 +1,127 @@ +/* +Copyright 2020 Smorgasbord Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package git + +import ( + "encoding/json" + "io/ioutil" + "os" + + "github.com/kubism/smorgasbord/pkg/storage" + + "github.com/go-git/go-billy/v5" + "github.com/go-git/go-billy/v5/memfs" + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing/transport" + gitstorage "github.com/go-git/go-git/v5/storage" + "github.com/go-git/go-git/v5/storage/memory" +) + +const stateName = "smorgasbord.json" + +type state = map[string][]storage.Entry + +type gitStorage struct { + auth transport.AuthMethod + fs billy.Filesystem + storer gitstorage.Storer + repo *git.Repository +} + +func NewStorage(repositoryURL string, auth transport.AuthMethod) (storage.Storage, error) { + s := &gitStorage{ + auth: auth, + fs: memfs.New(), + storer: memory.NewStorage(), + } + return s, s.clone(repositoryURL) +} + +func (s *gitStorage) Add(id, publicKey string) error { + return nil +} + +func (s *gitStorage) Delete(id, publicKey string) error { + return nil +} + +func (s *gitStorage) List(id string) ([]storage.Entry, error) { + st, err := s.load() + if err != nil { + return nil, err + } + entries, ok := st[id] + if !ok || entries == nil { + return []storage.Entry{}, nil + } + return entries, nil +} + +func (s *gitStorage) Save() error { + return nil +} + +func (s *gitStorage) Close() error { + return nil +} + +func (s *gitStorage) clone(url string) error { + var err error + s.repo, err = git.Clone(s.storer, s.fs, &git.CloneOptions{ + URL: url, + Auth: s.auth, + Depth: 5, + }) + return err +} + +func (s *gitStorage) load() (state, error) { + if err := s.pull(); err != nil { + return nil, err + } + f, err := s.fs.Open(stateName) + if os.IsNotExist(err) { + return state{}, nil + } else if err != nil { + return nil, err + } + defer func() { + _ = f.Close() + }() + data, err := ioutil.ReadAll(f) + if err != nil { + return nil, err + } + st := state{} + err = json.Unmarshal(data, &st) + if err != nil { + return nil, err + } + return st, nil +} + +func (s *gitStorage) pull() error { + w, err := s.repo.Worktree() + if err != nil { + return err + } + err = w.Pull(&git.PullOptions{RemoteName: "origin"}) + if err == git.NoErrAlreadyUpToDate { + return nil + } + return err +} diff --git a/pkg/storage/git/git_test.go b/pkg/storage/git/git_test.go new file mode 100644 index 0000000..9fc26e0 --- /dev/null +++ b/pkg/storage/git/git_test.go @@ -0,0 +1,30 @@ +/* +Copyright 2020 Smorgasbord Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package git + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("GitStorage", func() { + It("contains initial element", func() { + entries, err := gitS.List(testID) + Expect(err).ToNot(HaveOccurred()) + Expect(len(entries)).To(BeNumerically(">", 0)) + }) +}) diff --git a/pkg/storage/git/suite_test.go b/pkg/storage/git/suite_test.go new file mode 100644 index 0000000..5a17458 --- /dev/null +++ b/pkg/storage/git/suite_test.go @@ -0,0 +1,106 @@ +/* +Copyright 2020 Smorgasbord Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package git + +import ( + "fmt" + "io" + "io/ioutil" + "os" + "testing" + "time" + + _ "github.com/kubism/smorgasbord/internal/flags" + "github.com/kubism/smorgasbord/pkg/storage" + "github.com/kubism/smorgasbord/pkg/testutil" + + "github.com/go-git/go-billy/v5/memfs" + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/config" + "github.com/go-git/go-git/v5/plumbing/object" + "github.com/go-git/go-git/v5/storage/memory" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +const testID = "test@test.com" + +var ( + gitServer *testutil.GitServer + gitS storage.Storage + tmpDir string +) + +func TestGitStorage(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "pkg/storage/git") +} + +var _ = BeforeSuite(func(done Done) { + var err error + tmpDir, err = ioutil.TempDir("", "smorgasbord") + Expect(err).ToNot(HaveOccurred()) + gitServer, err = testutil.NewGitServer(tmpDir) + Expect(err).ToNot(HaveOccurred()) + // Let's setup the repository for first use + fs := memfs.New() + r, err := git.Init(memory.NewStorage(), fs) + Expect(err).ToNot(HaveOccurred()) + _, err = r.CreateRemote(&config.RemoteConfig{ + Name: "origin", + URLs: []string{getRemoteURL()}, + }) + Expect(err).ToNot(HaveOccurred()) + f, err := fs.Create(stateName) + Expect(err).ToNot(HaveOccurred()) + _, err = io.WriteString(f, `{ "test@test.com": [{ "publicKey": "...", "allowedIP": "0.0.0.0/0" }] }`) + Expect(err).ToNot(HaveOccurred()) + Expect(f.Close()).To(Succeed()) + w, err := r.Worktree() + Expect(err).ToNot(HaveOccurred()) + _, err = w.Add(stateName) + Expect(err).ToNot(HaveOccurred()) + _, err = w.Commit("first commit", &git.CommitOptions{ + Author: &object.Signature{ + Name: "John Doe", + Email: "john@doe.org", + When: time.Now(), + }, + }) + Expect(err).ToNot(HaveOccurred()) + Expect(r.Push(&git.PushOptions{ + RemoteName: "origin", + })).To(Succeed()) + // Lastly setup gitStorage + gitS, err = NewStorage(getRemoteURL(), nil) + Expect(err).ToNot(HaveOccurred()) + close(done) +}, 240) + +var _ = AfterSuite(func() { + if gitServer != nil { + _ = gitServer.Close() + } + if tmpDir != "" { + _ = os.RemoveAll(tmpDir) + } +}) + +func getRemoteURL() string { + return fmt.Sprintf("http://%s/test.git", gitServer.GetAddr()) +} diff --git a/pkg/storage/storage.go b/pkg/storage/storage.go new file mode 100644 index 0000000..cbfdafc --- /dev/null +++ b/pkg/storage/storage.go @@ -0,0 +1,30 @@ +/* +Copyright 2020 Smorgasbord Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package storage + +type Entry struct { + PublicKey string `json:"publicKey"` + AllowedIP string `json:"allowedIP"` +} + +type Storage interface { + Add(id, publicKey string) error + Delete(id, publicKey string) error + List(id string) ([]Entry, error) + Save() error + Close() error +} diff --git a/pkg/testutil/gitserver_test.go b/pkg/testutil/gitserver_test.go index 4d6df56..6701356 100644 --- a/pkg/testutil/gitserver_test.go +++ b/pkg/testutil/gitserver_test.go @@ -34,7 +34,7 @@ import ( ) var _ = Describe("GitServer", func() { - It("is created successfully and repo is functional", func() { + It("list contains entry", func() { dir, err := ioutil.TempDir("", "smorgasbord") Expect(err).ToNot(HaveOccurred()) defer os.RemoveAll(dir)