From fa6a33c349ac7ec30d629ff0ad54f64386b871f5 Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Wed, 13 Apr 2016 12:54:57 -0600 Subject: [PATCH 1/2] feat(storage): add basic implementation of storage This is an in-memory storage layer for storing releases. This will be superseded by the Kubernetes ConfigMap implementtion. --- cmd/tiller/environment/environment.go | 12 ++++ cmd/tiller/environment/environment_test.go | 8 +++ pkg/engine/engine.go | 3 + pkg/storage/doc.go | 7 +++ pkg/storage/memory.go | 53 ++++++++++++++++ pkg/storage/memory_test.go | 70 ++++++++++++++++++++++ 6 files changed, 153 insertions(+) create mode 100644 pkg/storage/doc.go create mode 100644 pkg/storage/memory.go create mode 100644 pkg/storage/memory_test.go diff --git a/cmd/tiller/environment/environment.go b/cmd/tiller/environment/environment.go index 61305e6f7..1845d83c7 100644 --- a/cmd/tiller/environment/environment.go +++ b/cmd/tiller/environment/environment.go @@ -12,6 +12,10 @@ var DefaultEngine = GoTplEngine // EngineYard maps engine names to engine implementations. type EngineYard map[string]Engine +// Get retrieves a template engine by name. +// +// If no matching template engine is found, the second return value will +// be false. func (y EngineYard) Get(k string) (Engine, bool) { e, ok := y[k] return e, ok @@ -49,8 +53,16 @@ type Engine interface { // // Release storage must be concurrency safe. type ReleaseStorage interface { + // Get takes a name and returns the accompanying release. Get(key string) (*hapi.Release, error) + // Set saves the release with the given name. Set(key string, val *hapi.Release) error + // List lists all active (non-deleted, non-superseded) releases. + // + // To get deleted or superseded releases, use Query. + List() ([]*hapi.Release, error) + // Query takes a map of labels and returns any releases that match. + Query(map[string]string) ([]*hapi.Release, error) } // KubeClient represents a client capable of communicating with the Kubernetes API. diff --git a/cmd/tiller/environment/environment_test.go b/cmd/tiller/environment/environment_test.go index e13f4def1..c1d037423 100644 --- a/cmd/tiller/environment/environment_test.go +++ b/cmd/tiller/environment/environment_test.go @@ -27,6 +27,14 @@ func (r *mockReleaseStorage) Set(k string, v *hapi.Release) error { return nil } +func (r *mockReleaseStorage) List() ([]*hapi.Release, error) { + return []*hapi.Release{}, nil +} + +func (r *mockReleaseStorage) Query(labels map[string]string) ([]*hapi.Release, error) { + return []*hapi.Release{}, nil +} + type mockKubeClient struct { } diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index 067d6b98a..59a42f96f 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -9,7 +9,10 @@ import ( "github.com/deis/tiller/pkg/hapi" ) +// Engine is an implementation of 'cmd/tiller/environment'.Engine that uses Go templates. type Engine struct { + // FuncMap contains the template functions that will be passed to each + // render call. This may only be modified before the first call to Render. FuncMap template.FuncMap } diff --git a/pkg/storage/doc.go b/pkg/storage/doc.go new file mode 100644 index 000000000..21e11ade4 --- /dev/null +++ b/pkg/storage/doc.go @@ -0,0 +1,7 @@ +/*Package storage implements storage for Tiller objects. + +Tiller stores releases (see 'cmd/tiller/environment'.Environment). The backend +storage mechanism may be implemented with different backends. This package +and its subpackages provide storage layers for Tiller objects. +*/ +package storage diff --git a/pkg/storage/memory.go b/pkg/storage/memory.go new file mode 100644 index 000000000..a3670426a --- /dev/null +++ b/pkg/storage/memory.go @@ -0,0 +1,53 @@ +package storage + +import ( + "errors" + + "github.com/deis/tiller/pkg/hapi" +) + +// Memory is an in-memory ReleaseStorage implementation. +type Memory struct { + releases map[string]*hapi.Release +} + +func NewMemory() *Memory { + return &Memory{ + releases: map[string]*hapi.Release{}, + } +} + +var ErrNotFound = errors.New("release not found") + +// Get returns the named Release. +// +// If the release is not found, an ErrNotFound error is returned. +func (m *Memory) Get(k string) (*hapi.Release, error) { + v, ok := m.releases[k] + if !ok { + return v, ErrNotFound + } + return v, nil +} + +// Set sets a release. +// +// TODO: Is there any reason why Set doesn't just use the release name? +func (m *Memory) Set(k string, rel *hapi.Release) error { + m.releases[k] = rel + return nil +} + +// List returns all releases +func (m *Memory) List() ([]*hapi.Release, error) { + buf := make([]*hapi.Release, len(m.releases)) + i := 0 + for _, v := range m.releases { + buf[i] = v + i++ + } + return buf, nil +} +func (m *Memory) Query(labels map[string]string) ([]*hapi.Release, error) { + return []*hapi.Release{}, errors.New("Cannot implement until hapi.Release is defined.") +} diff --git a/pkg/storage/memory_test.go b/pkg/storage/memory_test.go new file mode 100644 index 000000000..7b619b5b0 --- /dev/null +++ b/pkg/storage/memory_test.go @@ -0,0 +1,70 @@ +package storage + +import ( + "testing" + + "github.com/deis/tiller/pkg/hapi" +) + +func TestSet(t *testing.T) { + k := "test-1" + r := &hapi.Release{Name: k} + + ms := NewMemory() + if err := ms.Set(k, r); err != nil { + t.Fatalf("Failed set: %s", err) + } + + if ms.releases[k].Name != k { + t.Errorf("Unexpected release name: %s", ms.releases[k].Name) + } +} + +func TestGet(t *testing.T) { + k := "test-1" + r := &hapi.Release{Name: k} + + ms := NewMemory() + ms.Set(k, r) + + if out, err := ms.Get(k); err != nil { + t.Errorf("Could not get %s: %s", k, err) + } else if out.Name != k { + t.Errorf("Expected %s, got %s", k, out.Name) + } +} + +func TestList(t *testing.T) { + ms := NewMemory() + rels := []string{"a", "b", "c"} + + for _, k := range rels { + ms.Set(k, &hapi.Release{Name: k}) + } + + l, err := ms.List() + if err != nil { + t.Error(err) + } + + if len(l) != 3 { + t.Errorf("Expected 3, got %d", len(l)) + } + + for _, n := range rels { + foundN := false + for _, rr := range l { + if rr.Name == n { + foundN = true + break + } + } + if !foundN { + t.Errorf("Did not find %s in list.", n) + } + } +} + +func TestQuery(t *testing.T) { + t.Skip("Not Implemented") +} From 44dc0de71fbb6d414f810d51a1642b86960c461f Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Wed, 13 Apr 2016 17:12:45 -0600 Subject: [PATCH 2/2] fix(server): add tests for in-memory storage This adds a higher level set of tests for storage. --- cmd/tiller/environment/environment.go | 12 +++++++++++- cmd/tiller/server_test.go | 2 ++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/cmd/tiller/environment/environment.go b/cmd/tiller/environment/environment.go index 1845d83c7..a0e2c2990 100644 --- a/cmd/tiller/environment/environment.go +++ b/cmd/tiller/environment/environment.go @@ -3,10 +3,15 @@ package environment import ( "github.com/deis/tiller/pkg/engine" "github.com/deis/tiller/pkg/hapi" + "github.com/deis/tiller/pkg/storage" ) +// GoTplEngine is the name of the Go template engine, as registered in the EngineYard. const GoTplEngine = "gotpl" +// DefaultEngine points to the engine that the EngineYard should treat as the +// default. A chart that does not specify an engine may be run through the +// default engine. var DefaultEngine = GoTplEngine // EngineYard maps engine names to engine implementations. @@ -93,8 +98,13 @@ type Environment struct { // New returns an environment initialized with the defaults. func New() *Environment { e := engine.New() - var ey EngineYard = map[string]Engine{GoTplEngine: e} + var ey EngineYard = map[string]Engine{ + // Currently, the only template engine we support is the GoTpl one. But + // we can easily add some here. + GoTplEngine: e, + } return &Environment{ EngineYard: ey, + Releases: storage.NewMemory(), } } diff --git a/cmd/tiller/server_test.go b/cmd/tiller/server_test.go index e018530ad..82efbfe6a 100644 --- a/cmd/tiller/server_test.go +++ b/cmd/tiller/server_test.go @@ -5,11 +5,13 @@ import ( "github.com/deis/tiller/cmd/tiller/environment" "github.com/deis/tiller/pkg/engine" + "github.com/deis/tiller/pkg/storage" ) // These are canary tests to make sure that the default server actually // fulfills its requirements. var _ environment.Engine = &engine.Engine{} +var _ environment.ReleaseStorage = storage.NewMemory() func TestNewServer(t *testing.T) { defer func() {