Commit 1be28d6f authored by Matt Butcher's avatar Matt Butcher

feat(*): add 'helm list --all' and related flags

This adds support for the following 'helm list' flags:

- all: show all release types
- deleted: show deleted releases
- deployed: show deployed releases
- failed: show failed releases

These flags can be toggled. Only '--deployed' is turned on by default.

On the server side, Tiller's list function can now filter based on a
slice of release.Status_Code filters. While the client only supports a
subset, the server supports all known release.Status_Code types.

Closes #973
parent 926d7931
......@@ -20,6 +20,7 @@ import "hapi/chart/chart.proto";
import "hapi/chart/config.proto";
import "hapi/release/release.proto";
import "hapi/release/info.proto";
import "hapi/release/status.proto";
option go_package = "services";
......@@ -90,7 +91,10 @@ message ListReleasesRequest {
// Anything that matches the regexp will be included in the results.
string filter = 4;
// SortOrder is the ordering directive used for sorting.
ListSort.SortOrder sort_order = 5;
repeated hapi.release.Status.Code status_codes = 6;
}
// ListSort defines sorting fields on a release list.
......
......@@ -49,6 +49,7 @@ type releaseOptions struct {
name string
version int32
chart *chart.Chart
statusCode release.Status_Code
}
func releaseMock(opts *releaseOptions) *release.Release {
......@@ -77,12 +78,17 @@ func releaseMock(opts *releaseOptions) *release.Release {
}
}
scode := release.Status_DEPLOYED
if opts.statusCode > 0 {
scode = opts.statusCode
}
return &release.Release{
Name: name,
Info: &release.Info{
FirstDeployed: &date,
LastDeployed: &date,
Status: &release.Status{Code: release.Status_DEPLOYED},
Status: &release.Status{Code: scode},
},
Chart: ch,
Config: &chart.Config{Raw: `name: "value"`},
......
......@@ -31,7 +31,10 @@ import (
)
var listHelp = `
This command lists all of the currently deployed releases.
This command lists all of the releases.
By default, it lists only releases that are deployed. Flags like '--delete' and
'--all' will alter this behavior. Such flags can be combined: '--deleted --failed'.
By default, items are sorted alphabetically. Use the '-d' flag to sort by
release date.
......@@ -61,6 +64,11 @@ type listCmd struct {
byDate bool
sortDesc bool
out io.Writer
all bool
deleted bool
deployed bool
failed bool
superseded bool
client helm.Interface
}
......@@ -91,6 +99,12 @@ func newListCmd(client helm.Interface, out io.Writer) *cobra.Command {
f.BoolVarP(&list.sortDesc, "reverse", "r", false, "reverse the sort order")
f.IntVarP(&list.limit, "max", "m", 256, "maximum number of releases to fetch")
f.StringVarP(&list.offset, "offset", "o", "", "the next release name in the list, used to offset from start value")
f.BoolVar(&list.all, "all", false, "show all releases, not just the ones marked DEPLOYED")
f.BoolVar(&list.deleted, "deleted", false, "show deleted releases")
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")
// TODO: Do we want this as a feature of 'helm list'?
//f.BoolVar(&list.superseded, "history", true, "show historical releases")
return cmd
}
......@@ -105,12 +119,15 @@ func (l *listCmd) run() error {
sortOrder = services.ListSort_DESC
}
stats := l.statusCodes()
res, err := l.client.ListReleases(
helm.ReleaseListLimit(l.limit),
helm.ReleaseListOffset(l.offset),
helm.ReleaseListFilter(l.filter),
helm.ReleaseListSort(int32(sortBy)),
helm.ReleaseListOrder(int32(sortOrder)),
helm.ReleaseListStatuses(stats),
)
if err != nil {
......@@ -138,6 +155,40 @@ func (l *listCmd) run() error {
return nil
}
// statusCodes gets the list of status codes that are to be included in the results.
func (l *listCmd) statusCodes() []release.Status_Code {
if l.all {
return []release.Status_Code{
release.Status_UNKNOWN,
release.Status_DEPLOYED,
release.Status_DELETED,
// TODO: Should we return superseded records? These are records
// that were replaced by an upgrade.
//release.Status_SUPERSEDED,
release.Status_FAILED,
}
}
status := []release.Status_Code{}
if l.deployed {
status = append(status, release.Status_DEPLOYED)
}
if l.deleted {
status = append(status, release.Status_DELETED)
}
if l.failed {
status = append(status, release.Status_FAILED)
}
if l.superseded {
status = append(status, release.Status_SUPERSEDED)
}
// Default case.
if len(status) == 0 {
status = append(status, release.Status_DEPLOYED)
}
return status
}
func formatList(rels []*release.Release) string {
table := uitable.New()
table.MaxColWidth = 30
......
......@@ -41,13 +41,33 @@ func TestListCmd(t *testing.T) {
},
{
name: "list --long",
//flags: map[string]string{"long": "1"},
args: []string{"--long"},
resp: []*release.Release{
releaseMock(&releaseOptions{name: "atlas"}),
},
expected: "NAME \tVERSION\tUPDATED \tSTATUS \tCHART \natlas\t1 \t(.*)\tDEPLOYED\tfoo-0.1.0-beta.1\n",
},
{
name: "with a release, multiple flags",
args: []string{"--deleted", "--deployed", "--failed"},
resp: []*release.Release{
releaseMock(&releaseOptions{name: "thomas-guide", statusCode: release.Status_DELETED}),
releaseMock(&releaseOptions{name: "atlas-guide", statusCode: release.Status_DEPLOYED}),
},
// Note: We're really only testing that the flags parsed correctly. Which results are returned
// depends on the backend. And until pkg/helm is done, we can't mock this.
expected: "thomas-guide\natlas-guide",
},
{
name: "with a release, multiple flags",
args: []string{"--all"},
resp: []*release.Release{
releaseMock(&releaseOptions{name: "thomas-guide", statusCode: release.Status_DELETED}),
releaseMock(&releaseOptions{name: "atlas-guide", statusCode: release.Status_DEPLOYED}),
},
// See note on previous test.
expected: "thomas-guide\natlas-guide",
},
}
var buf bytes.Buffer
......
......@@ -68,7 +68,20 @@ type releaseServer struct {
}
func (s *releaseServer) ListReleases(req *services.ListReleasesRequest, stream services.ReleaseService_ListReleasesServer) error {
rels, err := s.env.Releases.ListDeployed()
if len(req.StatusCodes) == 0 {
req.StatusCodes = []release.Status_Code{release.Status_DEPLOYED}
}
//rels, err := s.env.Releases.ListDeployed()
rels, err := s.env.Releases.ListFilterAll(func(r *release.Release) bool {
for _, sc := range req.StatusCodes {
if sc == r.Info.Status.Code {
return true
}
}
return false
})
if err != nil {
return err
}
......
......@@ -82,13 +82,17 @@ func chartStub() *chart.Chart {
// releaseStub creates a release stub, complete with the chartStub as its chart.
func releaseStub() *release.Release {
return namedReleaseStub("angry-panda", release.Status_DEPLOYED)
}
func namedReleaseStub(name string, status release.Status_Code) *release.Release {
date := timestamp.Timestamp{Seconds: 242085845, Nanos: 0}
return &release.Release{
Name: "angry-panda",
Name: name,
Info: &release.Info{
FirstDeployed: &date,
LastDeployed: &date,
Status: &release.Status{Code: release.Status_DEPLOYED},
Status: &release.Status{Code: status},
},
Chart: chartStub(),
Config: &chart.Config{Raw: `name = "value"`},
......@@ -581,6 +585,71 @@ func TestListReleases(t *testing.T) {
}
}
func TestListReleasesByStatus(t *testing.T) {
rs := rsFixture()
stubs := []*release.Release{
namedReleaseStub("kamal", release.Status_DEPLOYED),
namedReleaseStub("astrolabe", release.Status_DELETED),
namedReleaseStub("octant", release.Status_FAILED),
namedReleaseStub("sextant", release.Status_UNKNOWN),
}
for _, stub := range stubs {
if err := rs.env.Releases.Create(stub); err != nil {
t.Fatalf("Could not create stub: %s", err)
}
}
tests := []struct {
statusCodes []release.Status_Code
names []string
}{
{
names: []string{"kamal"},
statusCodes: []release.Status_Code{release.Status_DEPLOYED},
},
{
names: []string{"astrolabe"},
statusCodes: []release.Status_Code{release.Status_DELETED},
},
{
names: []string{"kamal", "octant"},
statusCodes: []release.Status_Code{release.Status_DEPLOYED, release.Status_FAILED},
},
{
names: []string{"kamal", "astrolabe", "octant", "sextant"},
statusCodes: []release.Status_Code{
release.Status_DEPLOYED,
release.Status_DELETED,
release.Status_FAILED,
release.Status_UNKNOWN,
},
},
}
for i, tt := range tests {
mrs := &mockListServer{}
if err := rs.ListReleases(&services.ListReleasesRequest{StatusCodes: tt.statusCodes, Offset: "", Limit: 64}, mrs); err != nil {
t.Fatalf("Failed listing %d: %s", i, err)
}
if len(tt.names) != len(mrs.val.Releases) {
t.Fatalf("Expected %d releases, got %d", len(tt.names), len(mrs.val.Releases))
}
for _, name := range tt.names {
found := false
for _, rel := range mrs.val.Releases {
if rel.Name == name {
found = true
}
}
if !found {
t.Errorf("%d: Did not find name %q", i, name)
}
}
}
}
func TestListReleasesSort(t *testing.T) {
rs := rsFixture()
......
......@@ -19,6 +19,7 @@ package helm
import (
"golang.org/x/net/context"
cpb "k8s.io/helm/pkg/proto/hapi/chart"
"k8s.io/helm/pkg/proto/hapi/release"
rls "k8s.io/helm/pkg/proto/hapi/services"
)
......@@ -105,6 +106,16 @@ func ReleaseListSort(sort int32) ReleaseListOption {
}
}
// ReleaseListStatuses specifies which status codes should be returned.
func ReleaseListStatuses(statuses []release.Status_Code) ReleaseListOption {
return func(opts *options) {
if len(statuses) == 0 {
statuses = []release.Status_Code{release.Status_DEPLOYED}
}
opts.listReq.StatusCodes = statuses
}
}
// InstallOption allows specifying various settings
// configurable by the helm client user for overriding
// the defaults used when running the `helm install` command.
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment