Merge pull request #1922 from larryrensing/feat/list-namespaces
feat(*): add --namespace flag to 'helm list'
This commit is contained in:
commit
5618afe3d4
|
@ -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.
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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{} }
|
||||
|
|
|
@ -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}...)
|
||||
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue