feat(helm, tiller): implement list

pull/632/head
Matt Butcher 9 years ago
parent 6950fe42dd
commit e869c36cd2

@ -2,31 +2,42 @@ package main
import ( import (
"fmt" "fmt"
"sort"
"time"
"github.com/gosuri/uitable"
"github.com/kubernetes/helm/pkg/helm" "github.com/kubernetes/helm/pkg/helm"
"github.com/kubernetes/helm/pkg/proto/hapi/release" "github.com/kubernetes/helm/pkg/proto/hapi/release"
"github.com/kubernetes/helm/pkg/timeconv"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
var listHelp = ` var listHelp = `
This command lists all of the currently deployed releases. This command lists all of the currently deployed releases.
By default, items are sorted alphabetically. By default, items are sorted alphabetically. Sorting is done client-side, so if
the number of releases is less than the setting in '--max', some values will
be omitted, and in no particular lexicographic order.
` `
var listCommand = &cobra.Command{ var listCommand = &cobra.Command{
Use: "list [flags] [FILTER]", Use: "list [flags]",
Short: "List releases", Short: "List releases",
Long: listHelp, Long: listHelp,
RunE: listCmd, RunE: listCmd,
Aliases: []string{"ls"},
} }
var listLong bool var listLong bool
var listMax int var listMax int
var listOffset int
var listByDate bool
func init() { func init() {
listCommand.LocalFlags().BoolVar(&listLong, "l", false, "output long listing format") listCommand.Flags().BoolVarP(&listLong, "long", "l", false, "output long listing format")
listCommand.LocalFlags().IntVar(&listMax, "m", 256, "maximum number of releases to fetch") listCommand.Flags().BoolVarP(&listByDate, "date", "d", false, "sort by release date")
listCommand.Flags().IntVarP(&listMax, "max", "m", 256, "maximum number of releases to fetch")
listCommand.Flags().IntVarP(&listOffset, "offset", "o", 0, "offset from start value (zero-indexed)")
RootCommand.AddCommand(listCommand) RootCommand.AddCommand(listCommand)
} }
@ -35,7 +46,7 @@ func listCmd(cmd *cobra.Command, args []string) error {
fmt.Println("TODO: Implement filter.") fmt.Println("TODO: Implement filter.")
} }
res, err := helm.ListReleases(listMax, 0) res, err := helm.ListReleases(listMax, listOffset)
if err != nil { if err != nil {
return err return err
} }
@ -45,7 +56,11 @@ func listCmd(cmd *cobra.Command, args []string) error {
fmt.Println("Not all values were fetched.") fmt.Println("Not all values were fetched.")
} }
// TODO: Add sort here. if listByDate {
sort.Sort(byDate(rels))
} else {
sort.Sort(byName(rels))
}
// Purty output, ya'll // Purty output, ya'll
if listLong { if listLong {
@ -59,10 +74,38 @@ func listCmd(cmd *cobra.Command, args []string) error {
} }
func formatList(rels []*release.Release) error { func formatList(rels []*release.Release) error {
// TODO: Pretty it up table := uitable.New()
table.MaxColWidth = 30
table.AddRow("NAME", "UPDATED", "CHART")
for _, r := range rels { for _, r := range rels {
fmt.Println(r.Name) c := fmt.Sprintf("%s-%s", r.Chart.Metadata.Name, r.Chart.Metadata.Version)
t := timeconv.Format(r.Info.LastDeployed, time.ANSIC)
table.AddRow(r.Name, t, c)
} }
fmt.Println(table)
return nil return nil
} }
// byName implements the sort.Interface for []*release.Release.
type byName []*release.Release
func (r byName) Len() int {
return len(r)
}
func (r byName) Swap(p, q int) {
r[p], r[q] = r[q], r[p]
}
func (r byName) Less(i, j int) bool {
return r[i].Name < r[j].Name
}
type byDate []*release.Release
func (r byDate) Len() int { return len(r) }
func (r byDate) Swap(p, q int) {
r[p], r[q] = r[q], r[p]
}
func (r byDate) Less(p, q int) bool {
return r[p].Info.LastDeployed.Seconds < r[q].Info.LastDeployed.Seconds
}

@ -3,6 +3,7 @@ package main
import ( import (
"bytes" "bytes"
"errors" "errors"
"fmt"
"log" "log"
"github.com/kubernetes/helm/cmd/tiller/environment" "github.com/kubernetes/helm/cmd/tiller/environment"
@ -33,18 +34,39 @@ var (
errMissingRelease = errors.New("no release provided") errMissingRelease = errors.New("no release provided")
) )
// ListDefaultLimit is the default limit for number of items returned in a list.
var ListDefaultLimit int64 = 512
func (s *releaseServer) ListReleases(req *services.ListReleasesRequest, stream services.ReleaseService_ListReleasesServer) error { func (s *releaseServer) ListReleases(req *services.ListReleasesRequest, stream services.ReleaseService_ListReleasesServer) error {
rels, err := s.env.Releases.List() rels, err := s.env.Releases.List()
if err != nil { if err != nil {
return err return err
} }
total := int64(len(rels))
l := int64(len(rels)) l := int64(len(rels))
if req.Offset > 0 {
if req.Offset >= l {
return fmt.Errorf("offset %d is outside of range %d", req.Offset, l)
}
rels = rels[req.Offset:]
l = int64(len(rels))
}
if req.Limit == 0 {
req.Limit = ListDefaultLimit
}
if l > req.Limit {
rels = rels[0:req.Limit]
l = int64(len(rels))
}
res := &services.ListReleasesResponse{ res := &services.ListReleasesResponse{
Offset: 0, Offset: 0,
Count: l, Count: l,
Total: l, Total: total,
Releases: rels, Releases: rels,
} }
stream.Send(res) stream.Send(res)

@ -1,6 +1,7 @@
package main package main
import ( import (
"fmt"
"strings" "strings"
"testing" "testing"
@ -12,6 +13,7 @@ import (
"github.com/kubernetes/helm/pkg/storage" "github.com/kubernetes/helm/pkg/storage"
"github.com/kubernetes/helm/pkg/timeconv" "github.com/kubernetes/helm/pkg/timeconv"
"golang.org/x/net/context" "golang.org/x/net/context"
"google.golang.org/grpc/metadata"
) )
func rsFixture() *releaseServer { func rsFixture() *releaseServer {
@ -193,8 +195,44 @@ func TestGetReleaseStatus(t *testing.T) {
} }
} }
func TestListReleases(t *testing.T) {
rs := rsFixture()
num := 7
for i := 0; i < num; i++ {
rel := releaseMock()
rel.Name = fmt.Sprintf("rel-%d", i)
if err := rs.env.Releases.Create(rel); err != nil {
t.Fatalf("Could not store mock release: %s", err)
}
}
mrs := &mockListServer{}
if err := rs.ListReleases(&services.ListReleasesRequest{Offset: 0, Limit: 64}, mrs); err != nil {
t.Fatalf("Failed listing: %s", err)
}
if len(mrs.val.Releases) != num {
t.Errorf("Expected %d releases, got %d", num, len(mrs.val.Releases))
}
}
func mockEnvironment() *environment.Environment { func mockEnvironment() *environment.Environment {
e := environment.New() e := environment.New()
e.Releases = storage.NewMemory() e.Releases = storage.NewMemory()
return e return e
} }
type mockListServer struct {
val *services.ListReleasesResponse
}
func (l *mockListServer) Send(res *services.ListReleasesResponse) error {
l.val = res
return nil
}
func (l *mockListServer) Context() context.Context { return context.TODO() }
func (l *mockListServer) SendMsg(v interface{}) error { return nil }
func (l *mockListServer) RecvMsg(v interface{}) error { return nil }
func (l *mockListServer) SendHeader(m metadata.MD) error { return nil }
func (l *mockListServer) SetTrailer(m metadata.MD) {}

@ -20,7 +20,10 @@ func ListReleases(limit, offset int) (*services.ListReleasesResponse, error) {
} }
defer c.Close() defer c.Close()
req := &services.ListReleasesRequest{} req := &services.ListReleasesRequest{
Limit: int64(limit),
Offset: int64(offset),
}
cli, err := c.impl.ListReleases(context.TODO(), req, c.cfg.CallOpts()...) cli, err := c.impl.ListReleases(context.TODO(), req, c.cfg.CallOpts()...)
if err != nil { if err != nil {
return nil, err return nil, err

Loading…
Cancel
Save