diff --git a/cmd/tiller/release_history.go b/cmd/tiller/release_history.go index 70176b42e..99bb3bb1a 100644 --- a/cmd/tiller/release_history.go +++ b/cmd/tiller/release_history.go @@ -17,11 +17,9 @@ 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" + relutil "k8s.io/helm/pkg/releaseutil" ) func (s *releaseServer) GetHistory(ctx context.Context, req *tpb.GetHistoryRequest) (*tpb.GetHistoryResponse, error) { @@ -34,7 +32,7 @@ func (s *releaseServer) GetHistory(ctx context.Context, req *tpb.GetHistoryReque return nil, err } - sort.Sort(sort.Reverse(byRev(h))) + relutil.Reverse(h, relutil.SortByRevision) var resp tpb.GetHistoryResponse for i := 0; i < min(len(h), int(req.Max)); i++ { @@ -44,12 +42,6 @@ func (s *releaseServer) GetHistory(ctx context.Context, req *tpb.GetHistoryReque 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 diff --git a/cmd/tiller/release_server.go b/cmd/tiller/release_server.go index 850fedd72..60a245459 100644 --- a/cmd/tiller/release_server.go +++ b/cmd/tiller/release_server.go @@ -22,7 +22,6 @@ import ( "fmt" "log" "regexp" - "sort" "strings" "google.golang.org/grpc/metadata" @@ -36,6 +35,7 @@ import ( "k8s.io/helm/pkg/proto/hapi/chart" "k8s.io/helm/pkg/proto/hapi/release" "k8s.io/helm/pkg/proto/hapi/services" + relutil "k8s.io/helm/pkg/releaseutil" "k8s.io/helm/pkg/storage/driver" "k8s.io/helm/pkg/timeconv" "k8s.io/helm/pkg/version" @@ -123,9 +123,9 @@ func (s *releaseServer) ListReleases(req *services.ListReleasesRequest, stream s switch req.SortBy { case services.ListSort_NAME: - sort.Sort(byName(rels)) + relutil.SortByName(rels) case services.ListSort_LAST_RELEASED: - sort.Sort(byDate(rels)) + relutil.SortByDate(rels) } if req.SortOrder == services.ListSort_DESC { @@ -480,8 +480,8 @@ func (s *releaseServer) prepareRollback(req *services.RollbackReleaseRequest) (* return nil, nil, errors.New("no revision to rollback") } - sort.Sort(sort.Reverse(byRev(h))) - crls := h[0] + relutil.SortByRevision(h) + crls := h[len(h)-1] rbv := req.Version if req.Version == 0 { @@ -878,29 +878,6 @@ func (s *releaseServer) UninstallRelease(c ctx.Context, req *services.UninstallR return res, 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 -} - func splitManifests(bigfile string) map[string]string { // This is not the best way of doing things, but it's how k8s itself does it. // Basically, we're quickly splitting a stream of YAML documents into an diff --git a/pkg/storage/filter.go b/pkg/releaseutil/filter.go similarity index 81% rename from pkg/storage/filter.go rename to pkg/releaseutil/filter.go index c23f8c94d..fdd2cc381 100644 --- a/pkg/storage/filter.go +++ b/pkg/releaseutil/filter.go @@ -14,12 +14,12 @@ See the License for the specific language governing permissions and limitations under the License. */ -package storage // import "k8s.io/helm/pkg/storage" +package releaseutil // import "k8s.io/helm/pkg/releaseutil" import rspb "k8s.io/helm/pkg/proto/hapi/release" // FilterFunc returns true if the release object satisfies -// the predicate of the underlying func. +// the predicate of the underlying filter func. type FilterFunc func(*rspb.Release) bool // Check applies the FilterFunc to the release object. @@ -30,6 +30,17 @@ func (fn FilterFunc) Check(rls *rspb.Release) bool { return fn(rls) } +// Filter applies the filter(s) to the list of provided releases +// returning the list that satisfies the filtering predicate. +func (fn FilterFunc) Filter(rels []*rspb.Release) (rets []*rspb.Release) { + for _, rel := range rels { + if fn.Check(rel) { + rets = append(rets, rel) + } + } + return +} + // Any returns a FilterFunc that filters a list of releases // determined by the predicate 'f0 || f1 || ... || fn'. func Any(filters ...FilterFunc) FilterFunc { diff --git a/pkg/releaseutil/filter_test.go b/pkg/releaseutil/filter_test.go new file mode 100644 index 000000000..88cf88aa9 --- /dev/null +++ b/pkg/releaseutil/filter_test.go @@ -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 releaseutil // import "k8s.io/helm/pkg/releaseutil" + +import ( + rspb "k8s.io/helm/pkg/proto/hapi/release" + "testing" +) + +func TestFilterAny(t *testing.T) { + ls := Any(StatusFilter(rspb.Status_DELETED)).Filter(releases) + if len(ls) != 2 { + t.Fatalf("expected 2 results, got '%d'", len(ls)) + } + + r0, r1 := ls[0], ls[1] + switch { + case r0.Info.Status.Code != rspb.Status_DELETED: + t.Fatalf("expected DELETED result, got '%s'", r1.Info.Status.Code) + case r1.Info.Status.Code != rspb.Status_DELETED: + t.Fatalf("expected DELETED result, got '%s'", r1.Info.Status.Code) + } +} + +func TestFilterAll(t *testing.T) { + fn := FilterFunc(func(rls *rspb.Release) bool { + // true if not deleted and version < 4 + v0 := !StatusFilter(rspb.Status_DELETED).Check(rls) + v1 := rls.Version < 4 + return v0 && v1 + }) + + ls := All(fn).Filter(releases) + if len(ls) != 1 { + t.Fatalf("expected 1 result, got '%d'", len(ls)) + } + + switch r0 := ls[0]; { + case r0.Version == 4: + t.Fatal("got release with status revision 4") + case r0.Info.Status.Code == rspb.Status_DELETED: + t.Fatal("got release with status DELTED") + } +} diff --git a/pkg/releaseutil/sorter.go b/pkg/releaseutil/sorter.go new file mode 100644 index 000000000..1b744d72c --- /dev/null +++ b/pkg/releaseutil/sorter.go @@ -0,0 +1,77 @@ +/* +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 releaseutil // import "k8s.io/helm/pkg/releaseutil" + +import ( + "sort" + + rspb "k8s.io/helm/pkg/proto/hapi/release" +) + +type sorter struct { + list []*rspb.Release + less func(int, int) bool +} + +func (s *sorter) Len() int { return len(s.list) } +func (s *sorter) Less(i, j int) bool { return s.less(i, j) } +func (s *sorter) Swap(i, j int) { s.list[i], s.list[j] = s.list[j], s.list[i] } + +// Reverse reverses the list of releases sorted by the sort func. +func Reverse(list []*rspb.Release, sortFn func([]*rspb.Release)) { + sortFn(list) + for i, j := 0, len(list)-1; i < j; i, j = i+1, j-1 { + list[i], list[j] = list[j], list[i] + } +} + +// SortByName returns the list of releases sorted +// in lexicographical order. +func SortByName(list []*rspb.Release) { + s := &sorter{list: list} + s.less = func(i, j int) bool { + ni := s.list[i].Name + nj := s.list[j].Name + return ni < nj + } + sort.Sort(s) +} + +// SortByDate returns the list of releases sorted by a +// release's last deployed time (in seconds). +func SortByDate(list []*rspb.Release) { + s := &sorter{list: list} + + s.less = func(i, j int) bool { + ti := s.list[i].Info.LastDeployed.Seconds + tj := s.list[j].Info.LastDeployed.Seconds + return ti < tj + } + sort.Sort(s) +} + +// SortByRevision returns the list of releases sorted by a +// release's revision number (release.Version). +func SortByRevision(list []*rspb.Release) { + s := &sorter{list: list} + s.less = func(i, j int) bool { + vi := s.list[i].Version + vj := s.list[j].Version + return vi < vj + } + sort.Sort(s) +} diff --git a/pkg/releaseutil/sorter_test.go b/pkg/releaseutil/sorter_test.go new file mode 100644 index 000000000..526108107 --- /dev/null +++ b/pkg/releaseutil/sorter_test.go @@ -0,0 +1,81 @@ +/* +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 releaseutil // import "k8s.io/helm/pkg/releaseutil" + +import ( + rspb "k8s.io/helm/pkg/proto/hapi/release" + "k8s.io/helm/pkg/timeconv" + "testing" + "time" +) + +// note: this test data is shared with filter_test.go. + +var releases = []*rspb.Release{ + tsRelease("quiet-bear", 2, 2000, rspb.Status_SUPERSEDED), + tsRelease("angry-bird", 4, 3000, rspb.Status_DEPLOYED), + tsRelease("happy-cats", 1, 4000, rspb.Status_DELETED), + tsRelease("vocal-dogs", 3, 6000, rspb.Status_DELETED), +} + +func tsRelease(name string, vers int32, dur time.Duration, code rspb.Status_Code) *rspb.Release { + tmsp := timeconv.Timestamp(time.Now().Add(time.Duration(dur))) + info := &rspb.Info{Status: &rspb.Status{Code: code}, LastDeployed: tmsp} + return &rspb.Release{ + Name: name, + Version: vers, + Info: info, + } +} + +func check(t *testing.T, by string, fn func(int, int) bool) { + for i := len(releases) - 1; i > 0; i-- { + if fn(i, i-1) { + t.Errorf("release at positions '(%d,%d)' not sorted by %s", i-1, i, by) + } + } +} + +func TestSortByName(t *testing.T) { + SortByName(releases) + + check(t, "ByName", func(i, j int) bool { + ni := releases[i].Name + nj := releases[j].Name + return ni < nj + }) +} + +func TestSortByDate(t *testing.T) { + SortByDate(releases) + + check(t, "ByDate", func(i, j int) bool { + ti := releases[i].Info.LastDeployed.Seconds + tj := releases[j].Info.LastDeployed.Seconds + return ti < tj + }) +} + +func TestSortByRevision(t *testing.T) { + SortByRevision(releases) + + check(t, "ByRevision", func(i, j int) bool { + vi := releases[i].Version + vj := releases[j].Version + return vi < vj + }) +} diff --git a/pkg/storage/doc.go b/pkg/storage/doc.go deleted file mode 100644 index 231e30bb9..000000000 --- a/pkg/storage/doc.go +++ /dev/null @@ -1,22 +0,0 @@ -/* -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 storage implements storage for Tiller objects.The backend storage -mechanism may be implemented with different backends. This package and its -subpackages provide storage layers for Tiller objects. -*/ -package storage // import "k8s.io/helm/pkg/storage" diff --git a/pkg/storage/storage.go b/pkg/storage/storage.go index a7ee01fd5..25ab6dae4 100644 --- a/pkg/storage/storage.go +++ b/pkg/storage/storage.go @@ -21,6 +21,7 @@ import ( "log" rspb "k8s.io/helm/pkg/proto/hapi/release" + relutil "k8s.io/helm/pkg/releaseutil" "k8s.io/helm/pkg/storage/driver" ) @@ -73,7 +74,7 @@ func (s *Storage) ListReleases() ([]*rspb.Release, error) { func (s *Storage) ListDeleted() ([]*rspb.Release, error) { log.Println("List deleted releases in storage") return s.Driver.List(func(rls *rspb.Release) bool { - return StatusFilter(rspb.Status_DELETED).Check(rls) + return relutil.StatusFilter(rspb.Status_DELETED).Check(rls) }) } @@ -82,27 +83,27 @@ func (s *Storage) ListDeleted() ([]*rspb.Release, error) { func (s *Storage) ListDeployed() ([]*rspb.Release, error) { log.Println("Listing all deployed releases in storage") return s.Driver.List(func(rls *rspb.Release) bool { - return StatusFilter(rspb.Status_DEPLOYED).Check(rls) + return relutil.StatusFilter(rspb.Status_DEPLOYED).Check(rls) }) } // ListFilterAll returns the set of releases satisfying satisfying the predicate // (filter0 && filter1 && ... && filterN), i.e. a Release is included in the results // if and only if all filters return true. -func (s *Storage) ListFilterAll(filters ...FilterFunc) ([]*rspb.Release, error) { +func (s *Storage) ListFilterAll(fns ...relutil.FilterFunc) ([]*rspb.Release, error) { log.Println("Listing all releases with filter") return s.Driver.List(func(rls *rspb.Release) bool { - return All(filters...).Check(rls) + return relutil.All(fns...).Check(rls) }) } // ListFilterAny returns the set of releases satisfying satisfying the predicate // (filter0 || filter1 || ... || filterN), i.e. a Release is included in the results // if at least one of the filters returns true. -func (s *Storage) ListFilterAny(filters ...FilterFunc) ([]*rspb.Release, error) { +func (s *Storage) ListFilterAny(fns ...relutil.FilterFunc) ([]*rspb.Release, error) { log.Println("Listing any releases with filter") return s.Driver.List(func(rls *rspb.Release) bool { - return Any(filters...).Check(rls) + return relutil.Any(fns...).Check(rls) }) }