Merge pull request #1922 from larryrensing/feat/list-namespaces

feat(*): add --namespace flag to 'helm list'
This commit is contained in:
Matt Butcher 2017-02-13 11:14:50 -07:00 committed by GitHub
commit 5618afe3d4
13 changed files with 138 additions and 38 deletions

View File

@ -112,6 +112,8 @@ message ListReleasesRequest {
ListSort.SortOrder sort_order = 5;
repeated hapi.release.Status.Code status_codes = 6;
// Namespace is the filter to select releases only from a specific namespace.
string namespace = 7;
}
// ListSort defines sorting fields on a release list.

View File

@ -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",

View File

@ -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()
}

View File

@ -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

View File

@ -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

View File

@ -136,6 +136,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.

View File

@ -129,6 +129,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_release3.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{} }

View File

@ -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}...)

View File

@ -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,
},
}

View File

@ -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()

View File

@ -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 {

View File

@ -113,6 +113,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 {
@ -179,6 +186,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 {

View File

@ -1435,6 +1435,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 TestRunReleaseTest(t *testing.T) {
rs := rsFixture()
rel := namedReleaseStub("nemo", release.Status_DEPLOYED)