diff --git a/cmd/helm/helm_test.go b/cmd/helm/helm_test.go index 5f9d80a3a..8c6c492f8 100644 --- a/cmd/helm/helm_test.go +++ b/cmd/helm/helm_test.go @@ -143,6 +143,9 @@ func executeActionCommandC(store *storage.Storage, cmd string) (*cobra.Command, root.SetOutput(buf) root.SetArgs(args) + if mem, ok := store.Driver.(*driver.Memory); ok { + mem.SetNamespace(settings.Namespace()) + } c, err := root.ExecuteC() return c, buf.String(), err diff --git a/cmd/helm/list_test.go b/cmd/helm/list_test.go index 127a8a980..fe773a803 100644 --- a/cmd/helm/list_test.go +++ b/cmd/helm/list_test.go @@ -131,6 +131,16 @@ func TestListCmd(t *testing.T) { }, Chart: chartInfo, }, + { + Name: "starlord", + Version: 2, + Namespace: "milano", + Info: &release.Info{ + LastDeployed: timestamp1, + Status: release.StatusDeployed, + }, + Chart: chartInfo, + }, } tests := []cmdTestCase{{ @@ -203,6 +213,11 @@ func TestListCmd(t *testing.T) { cmd: "list --uninstalling", golden: "output/list-uninstalling.txt", rels: releaseFixture, + }, { + name: "list releases in another namespace", + cmd: "list -n milano", + golden: "output/list-namespace.txt", + rels: releaseFixture, }} runTestCmd(t, tests) } diff --git a/cmd/helm/testdata/output/list-namespace.txt b/cmd/helm/testdata/output/list-namespace.txt new file mode 100644 index 000000000..9382327d6 --- /dev/null +++ b/cmd/helm/testdata/output/list-namespace.txt @@ -0,0 +1,2 @@ +NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION +starlord milano 2 2016-01-16 00:00:01 +0000 UTC deployed chickadee-1.0.0 0.0.1 diff --git a/pkg/action/action.go b/pkg/action/action.go index 9405cc401..a97533696 100644 --- a/pkg/action/action.go +++ b/pkg/action/action.go @@ -241,6 +241,7 @@ func (c *Configuration) Init(getter genericclioptions.RESTClientGetter, namespac store = storage.Init(d) case "memory": d := driver.NewMemory() + d.SetNamespace(namespace) store = storage.Init(d) default: // Not sure what to do here. diff --git a/pkg/storage/driver/memory.go b/pkg/storage/driver/memory.go index ca8756c0c..a99b36ef0 100644 --- a/pkg/storage/driver/memory.go +++ b/pkg/storage/driver/memory.go @@ -26,18 +26,33 @@ import ( var _ Driver = (*Memory)(nil) -// MemoryDriverName is the string name of this driver. -const MemoryDriverName = "Memory" +const ( + // MemoryDriverName is the string name of this driver. + MemoryDriverName = "Memory" + + defaultNamespace = "default" +) + +// A map of release names to list of release records +type memReleases map[string]records // Memory is the in-memory storage driver implementation. type Memory struct { sync.RWMutex - cache map[string]records + namespace string + // A map of namespaces to releases + cache map[string]memReleases } // NewMemory initializes a new memory driver. func NewMemory() *Memory { - return &Memory{cache: map[string]records{}} + return &Memory{cache: map[string]memReleases{}, namespace: "default"} +} + +// SetNamespace sets a specific namespace in which releases will be accessed. +// An empty string indicates all namespaces (for the list operation) +func (mem *Memory) SetNamespace(ns string) { + mem.namespace = ns } // Name returns the name of the driver. @@ -56,7 +71,7 @@ func (mem *Memory) Get(key string) (*rspb.Release, error) { if _, err := strconv.Atoi(ver); err != nil { return nil, ErrInvalidKey } - if recs, ok := mem.cache[name]; ok { + if recs, ok := mem.cache[mem.namespace][name]; ok { if r := recs.Get(key); r != nil { return r.rls, nil } @@ -72,13 +87,23 @@ func (mem *Memory) List(filter func(*rspb.Release) bool) ([]*rspb.Release, error defer unlock(mem.rlock()) var ls []*rspb.Release - for _, recs := range mem.cache { - recs.Iter(func(_ int, rec *record) bool { - if filter(rec.rls) { - ls = append(ls, rec.rls) - } - return true - }) + for namespace := range mem.cache { + if mem.namespace != "" { + // Should only list releases of this namespace + namespace = mem.namespace + } + for _, recs := range mem.cache[namespace] { + recs.Iter(func(_ int, rec *record) bool { + if filter(rec.rls) { + ls = append(ls, rec.rls) + } + return true + }) + } + if mem.namespace != "" { + // Should only list releases of this namespace + break + } } return ls, nil } @@ -93,18 +118,28 @@ func (mem *Memory) Query(keyvals map[string]string) ([]*rspb.Release, error) { lbs.fromMap(keyvals) var ls []*rspb.Release - for _, recs := range mem.cache { - recs.Iter(func(_ int, rec *record) bool { - // A query for a release name that doesn't exist (has been deleted) - // can cause rec to be nil. - if rec == nil { - return false - } - if rec.lbs.match(lbs) { - ls = append(ls, rec.rls) - } - return true - }) + for namespace := range mem.cache { + if mem.namespace != "" { + // Should only query releases of this namespace + namespace = mem.namespace + } + for _, recs := range mem.cache[namespace] { + recs.Iter(func(_ int, rec *record) bool { + // A query for a release name that doesn't exist (has been deleted) + // can cause rec to be nil. + if rec == nil { + return false + } + if rec.lbs.match(lbs) { + ls = append(ls, rec.rls) + } + return true + }) + } + if mem.namespace != "" { + // Should only query releases of this namespace + break + } } return ls, nil } @@ -113,14 +148,25 @@ func (mem *Memory) Query(keyvals map[string]string) ([]*rspb.Release, error) { func (mem *Memory) Create(key string, rls *rspb.Release) error { defer unlock(mem.wlock()) - if recs, ok := mem.cache[rls.Name]; ok { + // For backwards compatibility, we protect against an unset namespace + namespace := rls.Namespace + if namespace == "" { + namespace = defaultNamespace + } + mem.SetNamespace(namespace) + + if _, ok := mem.cache[namespace]; !ok { + mem.cache[namespace] = memReleases{} + } + + if recs, ok := mem.cache[namespace][rls.Name]; ok { if err := recs.Add(newRecord(key, rls)); err != nil { return err } - mem.cache[rls.Name] = recs + mem.cache[namespace][rls.Name] = recs return nil } - mem.cache[rls.Name] = records{newRecord(key, rls)} + mem.cache[namespace][rls.Name] = records{newRecord(key, rls)} return nil } @@ -128,9 +174,18 @@ func (mem *Memory) Create(key string, rls *rspb.Release) error { func (mem *Memory) Update(key string, rls *rspb.Release) error { defer unlock(mem.wlock()) - if rs, ok := mem.cache[rls.Name]; ok && rs.Exists(key) { - rs.Replace(key, newRecord(key, rls)) - return nil + // For backwards compatibility, we protect against an unset namespace + namespace := rls.Namespace + if namespace == "" { + namespace = defaultNamespace + } + mem.SetNamespace(namespace) + + if _, ok := mem.cache[namespace]; ok { + if rs, ok := mem.cache[namespace][rls.Name]; ok && rs.Exists(key) { + rs.Replace(key, newRecord(key, rls)) + return nil + } } return ErrReleaseNotFound } @@ -150,11 +205,13 @@ func (mem *Memory) Delete(key string) (*rspb.Release, error) { if _, err := strconv.Atoi(ver); err != nil { return nil, ErrInvalidKey } - if recs, ok := mem.cache[name]; ok { - if r := recs.Remove(key); r != nil { - // recs.Remove changes the slice reference, so we have to re-assign it. - mem.cache[name] = recs - return r.rls, nil + if _, ok := mem.cache[mem.namespace]; ok { + if recs, ok := mem.cache[mem.namespace][name]; ok { + if r := recs.Remove(key); r != nil { + // recs.Remove changes the slice reference, so we have to re-assign it. + mem.cache[mem.namespace][name] = recs + return r.rls, nil + } } } return nil, ErrReleaseNotFound diff --git a/pkg/storage/driver/memory_test.go b/pkg/storage/driver/memory_test.go index e9d709c1f..e86a798f3 100644 --- a/pkg/storage/driver/memory_test.go +++ b/pkg/storage/driver/memory_test.go @@ -37,7 +37,7 @@ func TestMemoryCreate(t *testing.T) { err bool }{ { - "create should success", + "create should succeed", releaseStub("rls-c", 1, "default", rspb.StatusDeployed), false, }, @@ -46,6 +46,16 @@ func TestMemoryCreate(t *testing.T) { releaseStub("rls-a", 1, "default", rspb.StatusDeployed), true, }, + { + "create in namespace should succeed", + releaseStub("rls-a", 1, "mynamespace", rspb.StatusDeployed), + false, + }, + { + "create in other namespace should fail (release already exists)", + releaseStub("rls-c", 1, "mynamespace", rspb.StatusDeployed), + true, + }, } ts := tsFixtureMemory(t) @@ -57,26 +67,34 @@ func TestMemoryCreate(t *testing.T) { if !tt.err { t.Fatalf("failed to create %q: %s", tt.desc, err) } + } else if tt.err { + t.Fatalf("Did not get expected error for %q\n", tt.desc) } } } func TestMemoryGet(t *testing.T) { var tests = []struct { - desc string - key string - err bool + desc string + key string + namespace string + err bool }{ - {"release key should exist", "rls-a.v1", false}, - {"release key should not exist", "rls-a.v5", true}, + {"release key should exist", "rls-a.v1", "default", false}, + {"release key should not exist", "rls-a.v5", "default", true}, + {"release key in namespace should exist", "rls-c.v1", "mynamespace", false}, + {"release key in namespace should not exist", "rls-a.v1", "mynamespace", true}, } ts := tsFixtureMemory(t) for _, tt := range tests { + ts.SetNamespace(tt.namespace) if _, err := ts.Get(tt.key); err != nil { if !tt.err { t.Fatalf("Failed %q to get '%s': %q\n", tt.desc, tt.key, err) } + } else if tt.err { + t.Fatalf("Did not get expected error for %q '%s'\n", tt.desc, tt.key) } } } @@ -123,19 +141,28 @@ func TestMemoryList(t *testing.T) { func TestMemoryQuery(t *testing.T) { var tests = []struct { - desc string - xlen int - lbs map[string]string + desc string + xlen int + namespace string + lbs map[string]string }{ { "should be 2 query results", 2, + "default", + map[string]string{"status": "deployed"}, + }, + { + "should be 1 query result", + 1, + "mynamespace", map[string]string{"status": "deployed"}, }, } ts := tsFixtureMemory(t) for _, tt := range tests { + ts.SetNamespace(tt.namespace) l, err := ts.Query(tt.lbs) if err != nil { t.Fatalf("Failed to query: %s\n", err) @@ -162,8 +189,20 @@ func TestMemoryUpdate(t *testing.T) { }, { "update release does not exist", - "rls-z.v1", - releaseStub("rls-z", 1, "default", rspb.StatusUninstalled), + "rls-c.v1", + releaseStub("rls-c", 1, "default", rspb.StatusUninstalled), + true, + }, + { + "update release status in namespace", + "rls-c.v4", + releaseStub("rls-c", 4, "mynamespace", rspb.StatusSuperseded), + false, + }, + { + "update release in namespace does not exist", + "rls-a.v1", + releaseStub("rls-a", 1, "mynamespace", rspb.StatusUninstalled), true, }, } @@ -175,8 +214,11 @@ func TestMemoryUpdate(t *testing.T) { t.Fatalf("Failed %q: %s\n", tt.desc, err) } continue + } else if tt.err { + t.Fatalf("Did not get expected error for %q '%s'\n", tt.desc, tt.key) } + ts.SetNamespace(tt.rls.Namespace) r, err := ts.Get(tt.key) if err != nil { t.Fatalf("Failed to get: %s\n", err) @@ -190,26 +232,35 @@ func TestMemoryUpdate(t *testing.T) { func TestMemoryDelete(t *testing.T) { var tests = []struct { - desc string - key string - err bool + desc string + key string + namespace string + err bool }{ - {"release key should exist", "rls-a.v1", false}, - {"release key should not exist", "rls-a.v5", true}, + {"release key should exist", "rls-a.v4", "default", false}, + {"release key should not exist", "rls-a.v5", "default", true}, + {"release key from other namespace should not exist", "rls-c.v4", "default", true}, + {"release key from namespace should exist", "rls-c.v4", "mynamespace", false}, + {"release key from namespace should not exist", "rls-c.v5", "mynamespace", true}, + {"release key from namespace2 should not exist", "rls-a.v4", "mynamespace", true}, } ts := tsFixtureMemory(t) - start, err := ts.Query(map[string]string{"name": "rls-a"}) + ts.SetNamespace("") + start, err := ts.Query(map[string]string{"status": "deployed"}) if err != nil { t.Errorf("Query failed: %s", err) } startLen := len(start) for _, tt := range tests { + ts.SetNamespace(tt.namespace) if rel, err := ts.Delete(tt.key); err != nil { if !tt.err { t.Fatalf("Failed %q to get '%s': %q\n", tt.desc, tt.key, err) } continue + } else if tt.err { + t.Fatalf("Did not get expected error for %q '%s'\n", tt.desc, tt.key) } else if fmt.Sprintf("%s.v%d", rel.Name, rel.Version) != tt.key { t.Fatalf("Asked for delete on %s, but deleted %d", tt.key, rel.Version) } @@ -220,14 +271,15 @@ func TestMemoryDelete(t *testing.T) { } // Make sure that the deleted records are gone. - end, err := ts.Query(map[string]string{"name": "rls-a"}) + ts.SetNamespace("") + end, err := ts.Query(map[string]string{"status": "deployed"}) if err != nil { t.Errorf("Query failed: %s", err) } endLen := len(end) - if startLen <= endLen { - t.Errorf("expected start %d to be less than end %d", startLen, endLen) + if startLen-2 != endLen { + t.Errorf("expected end to be %d instead of %d", startLen-2, endLen) for _, ee := range end { t.Logf("Name: %s, Version: %d", ee.Name, ee.Version) } diff --git a/pkg/storage/driver/mock_test.go b/pkg/storage/driver/mock_test.go index 4c9b4ef9c..3cb3773c2 100644 --- a/pkg/storage/driver/mock_test.go +++ b/pkg/storage/driver/mock_test.go @@ -53,6 +53,11 @@ func tsFixtureMemory(t *testing.T) *Memory { releaseStub("rls-b", 1, "default", rspb.StatusSuperseded), releaseStub("rls-b", 3, "default", rspb.StatusSuperseded), releaseStub("rls-b", 2, "default", rspb.StatusSuperseded), + // rls-c in other namespace + releaseStub("rls-c", 4, "mynamespace", rspb.StatusDeployed), + releaseStub("rls-c", 1, "mynamespace", rspb.StatusSuperseded), + releaseStub("rls-c", 3, "mynamespace", rspb.StatusSuperseded), + releaseStub("rls-c", 2, "mynamespace", rspb.StatusSuperseded), } mem := NewMemory()