Commit eba4c59a authored by Matt Butcher's avatar Matt Butcher

Merge pull request #632 from technosophos/feat/helm-list

feat(helm, tiller): add release listings
parents 1d0f43b0 e869c36c
...@@ -54,12 +54,13 @@ func runInstall(cmd *cobra.Command, args []string) error { ...@@ -54,12 +54,13 @@ func runInstall(cmd *cobra.Command, args []string) error {
// Might be friendly to wrap our proto model with pretty-printers. // Might be friendly to wrap our proto model with pretty-printers.
// //
func printRelease(rel *release.Release) { func printRelease(rel *release.Release) {
if rel == nil {
return
}
fmt.Printf("release.name: %s\n", rel.Name)
if verbose { if verbose {
if rel != nil { fmt.Printf("release.info: %s\n", rel.GetInfo())
fmt.Printf("release.name: %s\n", rel.Name) fmt.Printf("release.chart: %s\n", rel.GetChart())
fmt.Printf("release.info: %s\n", rel.GetInfo())
fmt.Printf("release.chart: %s\n", rel.GetChart())
}
} }
} }
......
package main
import (
"fmt"
"sort"
"time"
"github.com/gosuri/uitable"
"github.com/kubernetes/helm/pkg/helm"
"github.com/kubernetes/helm/pkg/proto/hapi/release"
"github.com/kubernetes/helm/pkg/timeconv"
"github.com/spf13/cobra"
)
var listHelp = `
This command lists all of the currently deployed releases.
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{
Use: "list [flags]",
Short: "List releases",
Long: listHelp,
RunE: listCmd,
Aliases: []string{"ls"},
}
var listLong bool
var listMax int
var listOffset int
var listByDate bool
func init() {
listCommand.Flags().BoolVarP(&listLong, "long", "l", false, "output long listing format")
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)
}
func listCmd(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
fmt.Println("TODO: Implement filter.")
}
res, err := helm.ListReleases(listMax, listOffset)
if err != nil {
return err
}
rels := res.Releases
if res.Count+res.Offset < res.Total {
fmt.Println("Not all values were fetched.")
}
if listByDate {
sort.Sort(byDate(rels))
} else {
sort.Sort(byName(rels))
}
// Purty output, ya'll
if listLong {
return formatList(rels)
}
for _, r := range rels {
fmt.Println(r.Name)
}
return nil
}
func formatList(rels []*release.Release) error {
table := uitable.New()
table.MaxColWidth = 30
table.AddRow("NAME", "UPDATED", "CHART")
for _, r := range rels {
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
}
// 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,8 +34,43 @@ var ( ...@@ -33,8 +34,43 @@ 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 {
return errNotImplemented rels, err := s.env.Releases.List()
if err != nil {
return err
}
total := 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{
Offset: 0,
Count: l,
Total: total,
Releases: rels,
}
stream.Send(res)
return nil
} }
func (s *releaseServer) GetReleaseStatus(c ctx.Context, req *services.GetReleaseStatusRequest) (*services.GetReleaseStatusResponse, error) { func (s *releaseServer) GetReleaseStatus(c ctx.Context, req *services.GetReleaseStatusRequest) (*services.GetReleaseStatusResponse, error) {
......
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) {}
name: alpine name: alpine
description: Deploy a basic Alpine Linux pod description: Deploy a basic Alpine Linux pod
version: 0.1.0 version: 0.1.0
home: "https://github.com/kubernetes/helm" home: https://github.com/kubernetes/helm
...@@ -13,8 +13,23 @@ var Config = &config{ ...@@ -13,8 +13,23 @@ var Config = &config{
} }
// ListReleases lists the current releases. // ListReleases lists the current releases.
func ListReleases(limit, offset int) (<-chan *services.ListReleasesResponse, error) { func ListReleases(limit, offset int) (*services.ListReleasesResponse, error) {
return nil, ErrNotImplemented c := Config.client()
if err := c.dial(); err != nil {
return nil, err
}
defer c.Close()
req := &services.ListReleasesRequest{
Limit: int64(limit),
Offset: int64(offset),
}
cli, err := c.impl.ListReleases(context.TODO(), req, c.cfg.CallOpts()...)
if err != nil {
return nil, err
}
return cli.Recv()
} }
// GetReleaseStatus returns the given release's status. // GetReleaseStatus returns the given release's status.
......
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