mirror of https://github.com/helm/helm
parent
837d0a8880
commit
e9dd302a9c
@ -0,0 +1,107 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/gosuri/uitable"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"k8s.io/helm/pkg/helm"
|
||||
"k8s.io/helm/pkg/proto/hapi/release"
|
||||
"k8s.io/helm/pkg/timeconv"
|
||||
)
|
||||
|
||||
var historyHelp = `
|
||||
History prints historical revisions for a given release.
|
||||
|
||||
A default maximum of 256 revisions will be returned. Setting '--max'
|
||||
configures the maximum length of the revision list returned.
|
||||
|
||||
The historical release set is printed as a formatted table, e.g:
|
||||
|
||||
$ helm history angry-bird --max=4
|
||||
REVISION UPDATED STATUS CHART
|
||||
4 Mon Oct 3 10:15:13 2016 DEPLOYED alpine-0.1.0
|
||||
3 Mon Oct 3 10:15:13 2016 SUPERSEDED alpine-0.1.0
|
||||
2 Mon Oct 3 10:15:13 2016 SUPERSEDED alpine-0.1.0
|
||||
1 Mon Oct 3 10:15:13 2016 SUPERSEDED alpine-0.1.0
|
||||
`
|
||||
|
||||
type historyCmd struct {
|
||||
max int32
|
||||
rls string
|
||||
out io.Writer
|
||||
helmc helm.Interface
|
||||
}
|
||||
|
||||
func newHistoryCmd(c helm.Interface, w io.Writer) *cobra.Command {
|
||||
his := &historyCmd{out: w, helmc: c}
|
||||
cmd := &cobra.Command{
|
||||
Use: "history [flags] RELEASE_NAME",
|
||||
Long: historyHelp,
|
||||
Short: "fetch release history",
|
||||
Aliases: []string{"hist"},
|
||||
PersistentPreRunE: setupConnection,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
switch {
|
||||
case len(args) == 0:
|
||||
return errReleaseRequired
|
||||
case his.helmc == nil:
|
||||
his.helmc = helm.NewClient(helm.Host(tillerHost))
|
||||
}
|
||||
his.rls = args[0]
|
||||
return his.run()
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().Int32Var(&his.max, "max", 256, "maximum number of revision to include in history")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (cmd *historyCmd) run() error {
|
||||
opts := []helm.HistoryOption{
|
||||
helm.WithMaxHistory(cmd.max),
|
||||
}
|
||||
|
||||
r, err := cmd.helmc.ReleaseHistory(cmd.rls, opts...)
|
||||
if err != nil {
|
||||
return prettyError(err)
|
||||
}
|
||||
if len(r.Releases) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Fprintln(cmd.out, formatHistory(r.Releases))
|
||||
return nil
|
||||
}
|
||||
|
||||
func formatHistory(rls []*release.Release) string {
|
||||
tbl := uitable.New()
|
||||
tbl.MaxColWidth = 30
|
||||
tbl.AddRow("REVISION", "UPDATED", "STATUS", "CHART")
|
||||
for _, r := range rls {
|
||||
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
|
||||
tbl.AddRow(v, t, s, c)
|
||||
}
|
||||
return tbl.String()
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
rpb "k8s.io/helm/pkg/proto/hapi/release"
|
||||
)
|
||||
|
||||
func TestHistoryCmd(t *testing.T) {
|
||||
mk := func(name string, vers int32, code rpb.Status_Code) *rpb.Release {
|
||||
return releaseMock(&releaseOptions{
|
||||
name: name,
|
||||
version: vers,
|
||||
statusCode: code,
|
||||
})
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
cmds string
|
||||
desc string
|
||||
args []string
|
||||
resp []*rpb.Release
|
||||
xout string
|
||||
}{
|
||||
{
|
||||
cmds: "helm history RELEASE_NAME",
|
||||
desc: "get history for release",
|
||||
args: []string{"angry-bird"},
|
||||
resp: []*rpb.Release{
|
||||
mk("angry-bird", 4, rpb.Status_DEPLOYED),
|
||||
mk("angry-bird", 3, rpb.Status_SUPERSEDED),
|
||||
mk("angry-bird", 2, rpb.Status_SUPERSEDED),
|
||||
mk("angry-bird", 1, rpb.Status_SUPERSEDED),
|
||||
},
|
||||
xout: "REVISION\tUPDATED \tSTATUS \tCHART \n4 \t(.*)\tDEPLOYED \tfoo-0.1.0-beta.1\n3 \t(.*)\tSUPERSEDED\tfoo-0.1.0-beta.1\n2 \t(.*)\tSUPERSEDED\tfoo-0.1.0-beta.1\n1 \t(.*)\tSUPERSEDED\tfoo-0.1.0-beta.1\n",
|
||||
},
|
||||
{
|
||||
cmds: "helm history --max=MAX RELEASE_NAME",
|
||||
desc: "get history with max limit set",
|
||||
args: []string{"--max=2", "angry-bird"},
|
||||
resp: []*rpb.Release{
|
||||
mk("angry-bird", 4, rpb.Status_DEPLOYED),
|
||||
mk("angry-bird", 3, rpb.Status_SUPERSEDED),
|
||||
},
|
||||
xout: "REVISION\tUPDATED \tSTATUS \tCHART \n4 \t(.*)\tDEPLOYED \tfoo-0.1.0-beta.1\n3 \t(.*)\tSUPERSEDED\tfoo-0.1.0-beta.1\n",
|
||||
},
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
for _, tt := range tests {
|
||||
frc := &fakeReleaseClient{rels: tt.resp}
|
||||
cmd := newHistoryCmd(frc, &buf)
|
||||
cmd.ParseFlags(tt.args)
|
||||
|
||||
if err := cmd.RunE(cmd, tt.args); err != nil {
|
||||
t.Fatalf("%q\n\t%s: unexpected error: %v", tt.cmds, tt.desc, err)
|
||||
}
|
||||
re := regexp.MustCompile(tt.xout)
|
||||
if !re.Match(buf.Bytes()) {
|
||||
t.Fatalf("%q\n\t%s:\nexpected\n\t%q\nactual\n\t%q", tt.cmds, tt.desc, tt.xout, buf.String())
|
||||
}
|
||||
buf.Reset()
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
rpb "k8s.io/helm/pkg/proto/hapi/release"
|
||||
tpb "k8s.io/helm/pkg/proto/hapi/services"
|
||||
)
|
||||
|
||||
func (s *releaseServer) GetHistory(ctx context.Context, req *tpb.GetHistoryRequest) (*tpb.GetHistoryResponse, error) {
|
||||
if !checkClientVersion(ctx) {
|
||||
return nil, errIncompatibleVersion
|
||||
}
|
||||
|
||||
h, err := s.env.Releases.History(req.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sort.Sort(sort.Reverse(byRev(h)))
|
||||
|
||||
var resp tpb.GetHistoryResponse
|
||||
for i := 0; i < min(len(h), int(req.Max)); i++ {
|
||||
resp.Releases = append(resp.Releases, h[i])
|
||||
}
|
||||
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
type byRev []*rpb.Release
|
||||
|
||||
func (s byRev) Len() int { return len(s) }
|
||||
func (s byRev) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
func (s byRev) Less(i, j int) bool { return s[i].Version < s[j].Version }
|
||||
|
||||
func min(x, y int) int {
|
||||
if x < y {
|
||||
return x
|
||||
}
|
||||
return y
|
||||
}
|
@ -0,0 +1,116 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"k8s.io/helm/pkg/helm"
|
||||
|
||||
rpb "k8s.io/helm/pkg/proto/hapi/release"
|
||||
tpb "k8s.io/helm/pkg/proto/hapi/services"
|
||||
)
|
||||
|
||||
func TestGetHistory_WithRevisions(t *testing.T) {
|
||||
mk := func(name string, vers int32, code rpb.Status_Code) *rpb.Release {
|
||||
return &rpb.Release{
|
||||
Name: name,
|
||||
Version: vers,
|
||||
Info: &rpb.Info{Status: &rpb.Status{Code: code}},
|
||||
}
|
||||
}
|
||||
|
||||
// GetReleaseHistoryTests
|
||||
tests := []struct {
|
||||
desc string
|
||||
req *tpb.GetHistoryRequest
|
||||
res *tpb.GetHistoryResponse
|
||||
}{
|
||||
{
|
||||
desc: "get release with history and default limit (max=256)",
|
||||
req: &tpb.GetHistoryRequest{Name: "angry-bird", Max: 256},
|
||||
res: &tpb.GetHistoryResponse{Releases: []*rpb.Release{
|
||||
mk("angry-bird", 4, rpb.Status_DEPLOYED),
|
||||
mk("angry-bird", 3, rpb.Status_SUPERSEDED),
|
||||
mk("angry-bird", 2, rpb.Status_SUPERSEDED),
|
||||
mk("angry-bird", 1, rpb.Status_SUPERSEDED),
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "get release with history using result limit (max=2)",
|
||||
req: &tpb.GetHistoryRequest{Name: "angry-bird", Max: 2},
|
||||
res: &tpb.GetHistoryResponse{Releases: []*rpb.Release{
|
||||
mk("angry-bird", 4, rpb.Status_DEPLOYED),
|
||||
mk("angry-bird", 3, rpb.Status_SUPERSEDED),
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
// test release history for release 'angry-bird'
|
||||
hist := []*rpb.Release{
|
||||
mk("angry-bird", 4, rpb.Status_DEPLOYED),
|
||||
mk("angry-bird", 3, rpb.Status_SUPERSEDED),
|
||||
mk("angry-bird", 2, rpb.Status_SUPERSEDED),
|
||||
mk("angry-bird", 1, rpb.Status_SUPERSEDED),
|
||||
}
|
||||
|
||||
srv := rsFixture()
|
||||
for _, rls := range hist {
|
||||
if err := srv.env.Releases.Create(rls); err != nil {
|
||||
t.Fatalf("Failed to create release: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// run tests
|
||||
for _, tt := range tests {
|
||||
res, err := srv.GetHistory(helm.NewContext(), tt.req)
|
||||
if err != nil {
|
||||
t.Fatalf("%s:\nFailed to get History of %q: %s", tt.desc, tt.req.Name, err)
|
||||
}
|
||||
if !reflect.DeepEqual(res, tt.res) {
|
||||
t.Fatalf("%s:\nExpected:\n\t%+v\nActual\n\t%+v", tt.desc, tt.res, res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetHistory_WithNoRevisions(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
req *tpb.GetHistoryRequest
|
||||
}{
|
||||
{
|
||||
desc: "get release with no history",
|
||||
req: &tpb.GetHistoryRequest{Name: "sad-panda", Max: 256},
|
||||
},
|
||||
}
|
||||
|
||||
// create release 'sad-panda' with no revision history
|
||||
rls := namedReleaseStub("sad-panda", rpb.Status_DEPLOYED)
|
||||
srv := rsFixture()
|
||||
srv.env.Releases.Create(rls)
|
||||
|
||||
for _, tt := range tests {
|
||||
res, err := srv.GetHistory(helm.NewContext(), tt.req)
|
||||
if err != nil {
|
||||
t.Fatalf("%s:\nFailed to get History of %q: %s", tt.desc, tt.req.Name, err)
|
||||
}
|
||||
if len(res.Releases) > 1 {
|
||||
t.Fatalf("%s:\nExpected zero items, got %d", tt.desc, len(res.Releases))
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in new issue