Commit e869c36c authored by Matt Butcher's avatar Matt Butcher

feat(helm, tiller): implement list

parent 6950fe42
...@@ -2,31 +2,42 @@ package main ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 ...@@ -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 ( ...@@ -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)
......
package main package main
import ( import (
"fmt"
"strings" "strings"
"testing" "testing"
...@@ -12,6 +13,7 @@ import ( ...@@ -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) { ...@@ -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) { ...@@ -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
......
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