diff --git a/cmd/helm/helm_test.go b/cmd/helm/helm_test.go index fc4c86b68..1ce04d0fc 100644 --- a/cmd/helm/helm_test.go +++ b/cmd/helm/helm_test.go @@ -56,6 +56,7 @@ type releaseOptions struct { version int32 chart *chart.Chart statusCode release.Status_Code + namespace string } func releaseMock(opts *releaseOptions) *release.Release { @@ -71,6 +72,11 @@ func releaseMock(opts *releaseOptions) *release.Release { version = opts.version } + namespace := opts.namespace + if namespace == "" { + namespace = "default" + } + ch := opts.chart if opts.chart == nil { ch = &chart.Chart{ @@ -97,9 +103,10 @@ func releaseMock(opts *releaseOptions) *release.Release { Status: &release.Status{Code: scode}, Description: "Release mock", }, - Chart: ch, - Config: &chart.Config{Raw: `name: "value"`}, - Version: version, + Chart: ch, + Config: &chart.Config{Raw: `name: "value"`}, + Version: version, + Namespace: namespace, Hooks: []*release.Hook{ { Name: "pre-install-hook", diff --git a/cmd/helm/list.go b/cmd/helm/list.go index c86343cd9..3764f365e 100644 --- a/cmd/helm/list.go +++ b/cmd/helm/list.go @@ -70,6 +70,7 @@ type listCmd struct { deleting bool deployed bool failed bool + namespace string superseded bool client helm.Interface } @@ -108,6 +109,8 @@ func newListCmd(client helm.Interface, out io.Writer) *cobra.Command { f.BoolVar(&list.deleting, "deleting", false, "show releases that are currently being deleted") f.BoolVar(&list.deployed, "deployed", false, "show deployed releases. If no other is specified, this will be automatically enabled") f.BoolVar(&list.failed, "failed", false, "show failed releases") + f.StringVar(&list.namespace, "namespace", "", "show releases within a specific namespace") + // TODO: Do we want this as a feature of 'helm list'? //f.BoolVar(&list.superseded, "history", true, "show historical releases") @@ -134,6 +137,7 @@ func (l *listCmd) run() error { helm.ReleaseListSort(int32(sortBy)), helm.ReleaseListOrder(int32(sortOrder)), helm.ReleaseListStatuses(stats), + helm.ReleaseListNamespace(l.namespace), ) if err != nil { @@ -198,13 +202,14 @@ func (l *listCmd) statusCodes() []release.Status_Code { func formatList(rels []*release.Release) string { table := uitable.New() table.MaxColWidth = 60 - table.AddRow("NAME", "REVISION", "UPDATED", "STATUS", "CHART") + table.AddRow("NAME", "REVISION", "UPDATED", "STATUS", "CHART", "NAMESPACE") for _, r := range rels { c := fmt.Sprintf("%s-%s", r.Chart.Metadata.Name, r.Chart.Metadata.Version) t := timeconv.String(r.Info.LastDeployed) s := r.Info.Status.Code.String() v := r.Version - table.AddRow(r.Name, v, t, s, c) + n := r.Namespace + table.AddRow(r.Name, v, t, s, c, n) } return table.String() } diff --git a/cmd/helm/list_test.go b/cmd/helm/list_test.go index 4f9592f31..611f47973 100644 --- a/cmd/helm/list_test.go +++ b/cmd/helm/list_test.go @@ -45,7 +45,7 @@ func TestListCmd(t *testing.T) { resp: []*release.Release{ releaseMock(&releaseOptions{name: "atlas"}), }, - expected: "NAME \tREVISION\tUPDATED \tSTATUS \tCHART \natlas\t1 \t(.*)\tDEPLOYED\tfoo-0.1.0-beta.1\n", + expected: "NAME \tREVISION\tUPDATED \tSTATUS \tCHART \tNAMESPACE\natlas\t1 \t(.*)\tDEPLOYED\tfoo-0.1.0-beta.1\tdefault \n", }, { name: "list, one deployed, one failed", @@ -87,6 +87,16 @@ func TestListCmd(t *testing.T) { // See note on previous test. expected: "thomas-guide\natlas-guide", }, + { + name: "namespace defined, multiple flags", + args: []string{"--all", "-q", "--namespace test123"}, + resp: []*release.Release{ + releaseMock(&releaseOptions{name: "thomas-guide", namespace: "test123"}), + releaseMock(&releaseOptions{name: "atlas-guide", namespace: "test321"}), + }, + // See note on previous test. + expected: "thomas-guide", + }, } var buf bytes.Buffer diff --git a/pkg/helm/helm_test.go b/pkg/helm/helm_test.go index a6289f2c8..00db8b4e8 100644 --- a/pkg/helm/helm_test.go +++ b/pkg/helm/helm_test.go @@ -51,6 +51,7 @@ func TestListReleases_VerifyOptions(t *testing.T) { rls.Status_DEPLOYED, rls.Status_SUPERSEDED, } + var namespace = "namespace" // Expected ListReleasesRequest message exp := &tpb.ListReleasesRequest{ @@ -60,6 +61,7 @@ func TestListReleases_VerifyOptions(t *testing.T) { SortBy: tpb.ListSort_SortBy(sortBy), SortOrder: tpb.ListSort_SortOrder(sortOrd), StatusCodes: codes, + Namespace: namespace, } // Options used in ListReleases @@ -70,6 +72,7 @@ func TestListReleases_VerifyOptions(t *testing.T) { ReleaseListOffset(offset), ReleaseListFilter(filter), ReleaseListStatuses(codes), + ReleaseListNamespace(namespace), } // BeforeCall option to intercept helm client ListReleasesRequest diff --git a/pkg/helm/option.go b/pkg/helm/option.go index 42df562cf..6ffb3e12f 100644 --- a/pkg/helm/option.go +++ b/pkg/helm/option.go @@ -134,6 +134,13 @@ func ReleaseListStatuses(statuses []release.Status_Code) ReleaseListOption { } } +// ReleaseListNamespace specifies the namespace to list releases from +func ReleaseListNamespace(namespace string) ReleaseListOption { + return func(opts *options) { + opts.listReq.Namespace = namespace + } +} + // InstallOption allows specifying various settings // configurable by the helm client user for overriding // the defaults used when running the `helm install` command. diff --git a/pkg/proto/hapi/services/tiller.pb.go b/pkg/proto/hapi/services/tiller.pb.go index 9faca5f8b..f6601f216 100644 --- a/pkg/proto/hapi/services/tiller.pb.go +++ b/pkg/proto/hapi/services/tiller.pb.go @@ -127,6 +127,8 @@ type ListReleasesRequest struct { // SortOrder is the ordering directive used for sorting. SortOrder ListSort_SortOrder `protobuf:"varint,5,opt,name=sort_order,json=sortOrder,enum=hapi.services.tiller.ListSort_SortOrder" json:"sort_order,omitempty"` StatusCodes []hapi_release1.Status_Code `protobuf:"varint,6,rep,packed,name=status_codes,json=statusCodes,enum=hapi.release.Status_Code" json:"status_codes,omitempty"` + // Namespace is the filter to select releases only from a specific namespace. + Namespace string `protobuf:"bytes,7,opt,name=namespace" json:"namespace,omitempty"` } func (m *ListReleasesRequest) Reset() { *m = ListReleasesRequest{} } diff --git a/pkg/storage/driver/cfgmaps_test.go b/pkg/storage/driver/cfgmaps_test.go index 13cc09b01..84a933811 100644 --- a/pkg/storage/driver/cfgmaps_test.go +++ b/pkg/storage/driver/cfgmaps_test.go @@ -34,8 +34,9 @@ func TestConfigMapName(t *testing.T) { func TestConfigMapGet(t *testing.T) { vers := int32(1) name := "smug-pigeon" + namespace := "default" key := testKey(name, vers) - rel := releaseStub(name, vers, rspb.Status_DEPLOYED) + rel := releaseStub(name, vers, namespace, rspb.Status_DEPLOYED) cfgmaps := newTestFixtureCfgMaps(t, []*rspb.Release{rel}...) @@ -53,8 +54,9 @@ func TestConfigMapGet(t *testing.T) { func TestUNcompressedConfigMapGet(t *testing.T) { vers := int32(1) name := "smug-pigeon" + namespace := "default" key := testKey(name, vers) - rel := releaseStub(name, vers, rspb.Status_DEPLOYED) + rel := releaseStub(name, vers, namespace, rspb.Status_DEPLOYED) // Create a test fixture which contains an uncompressed release cfgmap, err := newConfigMapsObject(key, rel, nil) @@ -83,12 +85,12 @@ func TestUNcompressedConfigMapGet(t *testing.T) { func TestConfigMapList(t *testing.T) { cfgmaps := newTestFixtureCfgMaps(t, []*rspb.Release{ - releaseStub("key-1", 1, rspb.Status_DELETED), - releaseStub("key-2", 1, rspb.Status_DELETED), - releaseStub("key-3", 1, rspb.Status_DEPLOYED), - releaseStub("key-4", 1, rspb.Status_DEPLOYED), - releaseStub("key-5", 1, rspb.Status_SUPERSEDED), - releaseStub("key-6", 1, rspb.Status_SUPERSEDED), + releaseStub("key-1", 1, "default", rspb.Status_DELETED), + releaseStub("key-2", 1, "default", rspb.Status_DELETED), + releaseStub("key-3", 1, "default", rspb.Status_DEPLOYED), + releaseStub("key-4", 1, "default", rspb.Status_DEPLOYED), + releaseStub("key-5", 1, "default", rspb.Status_SUPERSEDED), + releaseStub("key-6", 1, "default", rspb.Status_SUPERSEDED), }...) // list all deleted releases @@ -133,8 +135,9 @@ func TestConfigMapCreate(t *testing.T) { vers := int32(1) name := "smug-pigeon" + namespace := "default" key := testKey(name, vers) - rel := releaseStub(name, vers, rspb.Status_DEPLOYED) + rel := releaseStub(name, vers, namespace, rspb.Status_DEPLOYED) // store the release in a configmap if err := cfgmaps.Create(key, rel); err != nil { @@ -156,8 +159,9 @@ func TestConfigMapCreate(t *testing.T) { func TestConfigMapUpdate(t *testing.T) { vers := int32(1) name := "smug-pigeon" + namespace := "default" key := testKey(name, vers) - rel := releaseStub(name, vers, rspb.Status_DEPLOYED) + rel := releaseStub(name, vers, namespace, rspb.Status_DEPLOYED) cfgmaps := newTestFixtureCfgMaps(t, []*rspb.Release{rel}...) diff --git a/pkg/storage/driver/memory_test.go b/pkg/storage/driver/memory_test.go index 8407e588c..51f942992 100644 --- a/pkg/storage/driver/memory_test.go +++ b/pkg/storage/driver/memory_test.go @@ -37,12 +37,12 @@ func TestMemoryCreate(t *testing.T) { }{ { "create should success", - releaseStub("rls-c", 1, rspb.Status_DEPLOYED), + releaseStub("rls-c", 1, "default", rspb.Status_DEPLOYED), false, }, { "create should fail (release already exists)", - releaseStub("rls-a", 1, rspb.Status_DEPLOYED), + releaseStub("rls-a", 1, "default", rspb.Status_DEPLOYED), true, }, } @@ -116,13 +116,13 @@ func TestMemoryUpdate(t *testing.T) { { "update release status", "rls-a.v4", - releaseStub("rls-a", 4, rspb.Status_SUPERSEDED), + releaseStub("rls-a", 4, "default", rspb.Status_SUPERSEDED), false, }, { "update release does not exist", "rls-z.v1", - releaseStub("rls-z", 1, rspb.Status_DELETED), + releaseStub("rls-z", 1, "default", rspb.Status_DELETED), true, }, } diff --git a/pkg/storage/driver/mock_test.go b/pkg/storage/driver/mock_test.go index 3c546a45b..f66d4beec 100644 --- a/pkg/storage/driver/mock_test.go +++ b/pkg/storage/driver/mock_test.go @@ -27,11 +27,12 @@ import ( rspb "k8s.io/helm/pkg/proto/hapi/release" ) -func releaseStub(name string, vers int32, code rspb.Status_Code) *rspb.Release { +func releaseStub(name string, vers int32, namespace string, code rspb.Status_Code) *rspb.Release { return &rspb.Release{ - Name: name, - Version: vers, - Info: &rspb.Info{Status: &rspb.Status{Code: code}}, + Name: name, + Version: vers, + Namespace: namespace, + Info: &rspb.Info{Status: &rspb.Status{Code: code}}, } } @@ -42,15 +43,15 @@ func testKey(name string, vers int32) string { func tsFixtureMemory(t *testing.T) *Memory { hs := []*rspb.Release{ // rls-a - releaseStub("rls-a", 4, rspb.Status_DEPLOYED), - releaseStub("rls-a", 1, rspb.Status_SUPERSEDED), - releaseStub("rls-a", 3, rspb.Status_SUPERSEDED), - releaseStub("rls-a", 2, rspb.Status_SUPERSEDED), + releaseStub("rls-a", 4, "default", rspb.Status_DEPLOYED), + releaseStub("rls-a", 1, "default", rspb.Status_SUPERSEDED), + releaseStub("rls-a", 3, "default", rspb.Status_SUPERSEDED), + releaseStub("rls-a", 2, "default", rspb.Status_SUPERSEDED), // rls-b - releaseStub("rls-b", 4, rspb.Status_DEPLOYED), - releaseStub("rls-b", 1, rspb.Status_SUPERSEDED), - releaseStub("rls-b", 3, rspb.Status_SUPERSEDED), - releaseStub("rls-b", 2, rspb.Status_SUPERSEDED), + releaseStub("rls-b", 4, "default", rspb.Status_DEPLOYED), + releaseStub("rls-b", 1, "default", rspb.Status_SUPERSEDED), + releaseStub("rls-b", 3, "default", rspb.Status_SUPERSEDED), + releaseStub("rls-b", 2, "default", rspb.Status_SUPERSEDED), } mem := NewMemory() diff --git a/pkg/storage/driver/records_test.go b/pkg/storage/driver/records_test.go index 99f8b2d0d..380131d9a 100644 --- a/pkg/storage/driver/records_test.go +++ b/pkg/storage/driver/records_test.go @@ -24,8 +24,8 @@ import ( func TestRecordsAdd(t *testing.T) { rs := records([]*record{ - newRecord("rls-a.v1", releaseStub("rls-a", 1, rspb.Status_SUPERSEDED)), - newRecord("rls-a.v2", releaseStub("rls-a", 2, rspb.Status_DEPLOYED)), + newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", rspb.Status_SUPERSEDED)), + newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", rspb.Status_DEPLOYED)), }) var tests = []struct { @@ -38,13 +38,13 @@ func TestRecordsAdd(t *testing.T) { "add valid key", "rls-a.v3", false, - newRecord("rls-a.v3", releaseStub("rls-a", 3, rspb.Status_SUPERSEDED)), + newRecord("rls-a.v3", releaseStub("rls-a", 3, "default", rspb.Status_SUPERSEDED)), }, { "add already existing key", "rls-a.v1", true, - newRecord("rls-a.v1", releaseStub("rls-a", 1, rspb.Status_DEPLOYED)), + newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", rspb.Status_DEPLOYED)), }, } @@ -69,8 +69,8 @@ func TestRecordsRemove(t *testing.T) { } rs := records([]*record{ - newRecord("rls-a.v1", releaseStub("rls-a", 1, rspb.Status_SUPERSEDED)), - newRecord("rls-a.v2", releaseStub("rls-a", 2, rspb.Status_DEPLOYED)), + newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", rspb.Status_SUPERSEDED)), + newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", rspb.Status_DEPLOYED)), }) for _, tt := range tests { diff --git a/pkg/tiller/release_server.go b/pkg/tiller/release_server.go index 9ef14c915..b1ae7b182 100644 --- a/pkg/tiller/release_server.go +++ b/pkg/tiller/release_server.go @@ -112,6 +112,13 @@ func (s *ReleaseServer) ListReleases(req *services.ListReleasesRequest, stream s return err } + if req.Namespace != "" { + rels, err = filterByNamespace(req.Namespace, rels) + if err != nil { + return err + } + } + if len(req.Filter) != 0 { rels, err = filterReleases(req.Filter, rels) if err != nil { @@ -178,6 +185,16 @@ func (s *ReleaseServer) ListReleases(req *services.ListReleasesRequest, stream s return stream.Send(res) } +func filterByNamespace(namespace string, rels []*release.Release) ([]*release.Release, error) { + matches := []*release.Release{} + for _, r := range rels { + if namespace == r.Namespace { + matches = append(matches, r) + } + } + return matches, nil +} + func filterReleases(filter string, rels []*release.Release) ([]*release.Release, error) { preg, err := regexp.Compile(filter) if err != nil { diff --git a/pkg/tiller/release_server_test.go b/pkg/tiller/release_server_test.go index 1b84e4999..942e1e0b9 100644 --- a/pkg/tiller/release_server_test.go +++ b/pkg/tiller/release_server_test.go @@ -1411,6 +1411,48 @@ func TestListReleasesFilter(t *testing.T) { } } +func TestReleasesNamespace(t *testing.T) { + rs := rsFixture() + + names := []string{ + "axon", + "dendrite", + "neuron", + "ribosome", + } + + namespaces := []string{ + "default", + "test123", + "test123", + "cerebellum", + } + num := 4 + for i := 0; i < num; i++ { + rel := releaseStub() + rel.Name = names[i] + rel.Namespace = namespaces[i] + if err := rs.env.Releases.Create(rel); err != nil { + t.Fatalf("Could not store mock release: %s", err) + } + } + + mrs := &mockListServer{} + req := &services.ListReleasesRequest{ + Offset: "", + Limit: 64, + Namespace: "test123", + } + + if err := rs.ListReleases(req, mrs); err != nil { + t.Fatalf("Failed listing: %s", err) + } + + if len(mrs.val.Releases) != 2 { + t.Errorf("Expected 2 releases, got %d", len(mrs.val.Releases)) + } +} + func mockEnvironment() *environment.Environment { e := environment.New() e.Releases = storage.Init(driver.NewMemory())