feat: replace client/server internals with action package

While we removed Tiller, we left the internal client/server architecture mostly intact. This replaces that architecture with the `pkg/action` package.

This implements the action package for list, but nothing else.

Signed-off-by: Matt Butcher <matt.butcher@microsoft.com>
pull/5077/head
Matt Butcher 7 years ago
parent d07b95d0ae
commit 2016109616
No known key found for this signature in database
GPG Key ID: DCD5F5E5EF32C345

@ -26,9 +26,11 @@ import (
"k8s.io/cli-runtime/pkg/genericclioptions"
_ "k8s.io/client-go/plugin/pkg/client/auth"
"k8s.io/helm/pkg/action"
"k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/helm/environment"
"k8s.io/helm/pkg/kube"
"k8s.io/helm/pkg/storage"
"k8s.io/helm/pkg/storage/driver"
)
@ -50,7 +52,7 @@ func logf(format string, v ...interface{}) {
}
func main() {
cmd := newRootCmd(nil, os.Stdout, os.Args[1:])
cmd := newRootCmd(nil, newActionConfig(false), os.Stdout, os.Args[1:])
if err := cmd.Execute(); err != nil {
logf("%+v", err)
os.Exit(1)
@ -102,13 +104,28 @@ func newActionConfig(allNamespaces bool) *action.Configuration {
if !allNamespaces {
namespace = getNamespace()
}
// TODO add other backends
var store *storage.Storage
switch os.Getenv("HELM_DRIVER") {
case "secret", "secrets", "":
d := driver.NewSecrets(clientset.CoreV1().Secrets(namespace))
d.Log = logf
store = storage.Init(d)
case "configmap", "configmaps":
d := driver.NewConfigMaps(clientset.CoreV1().ConfigMaps(namespace))
d.Log = logf
store = storage.Init(d)
case "memory":
d := driver.NewMemory()
store = storage.Init(d)
default:
// Not sure what to do here.
panic("Unknown driver in HELM_DRIVER: " + os.Getenv("HELM_DRIVER"))
}
c := &action.Configuration{
KubeClient: helm.KubeClient(kc),
Storage: storage
return &action.Configuration{
KubeClient: kc,
Releases: store,
}
}

@ -27,10 +27,13 @@ import (
"github.com/spf13/cobra"
"k8s.io/helm/internal/test"
"k8s.io/helm/pkg/action"
"k8s.io/helm/pkg/hapi/release"
"k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/helm/helmpath"
"k8s.io/helm/pkg/repo"
"k8s.io/helm/pkg/storage"
"k8s.io/helm/pkg/storage/driver"
)
// base temp directory
@ -82,6 +85,51 @@ func runTestCmd(t *testing.T, tests []cmdTestCase) {
}
}
func runTestActionCmd(t *testing.T, tests []cmdTestCase) {
t.Helper()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
defer resetEnv()()
store := storageFixture()
for _, rel := range tt.rels {
store.Create(rel)
}
_, out, err := executeActionCommandC(store, tt.cmd)
if (err != nil) != tt.wantError {
t.Errorf("expected error, got '%v'", err)
}
if tt.golden != "" {
test.AssertGoldenString(t, out, tt.golden)
}
})
}
}
func storageFixture() *storage.Storage {
return storage.Init(driver.NewMemory())
}
func executeActionCommandC(store *storage.Storage, cmd string) (*cobra.Command, string, error) {
args, err := shellwords.Parse(cmd)
if err != nil {
return nil, "", err
}
buf := new(bytes.Buffer)
actionConfig := &action.Configuration{
Releases: store,
}
root := newRootCmd(nil, actionConfig, buf, args)
root.SetOutput(buf)
root.SetArgs(args)
c, err := root.ExecuteC()
return c, buf.String(), err
}
// cmdTestCase describes a test case that works with releases.
type cmdTestCase struct {
name string
@ -93,18 +141,26 @@ type cmdTestCase struct {
testRunStatus map[string]release.TestRunStatus
}
// deprecated: Switch to executeActionCommandC
func executeCommand(c helm.Interface, cmd string) (string, error) {
_, output, err := executeCommandC(c, cmd)
return output, err
}
// deprecated: Switch to executeActionCommandC
func executeCommandC(client helm.Interface, cmd string) (*cobra.Command, string, error) {
args, err := shellwords.Parse(cmd)
if err != nil {
return nil, "", err
}
buf := new(bytes.Buffer)
root := newRootCmd(client, buf, args)
actionConfig := &action.Configuration{
Releases: storage.Init(driver.NewMemory()),
//KubeClient: client.
}
root := newRootCmd(client, actionConfig, buf, args)
root.SetOutput(buf)
root.SetArgs(args)

@ -26,10 +26,7 @@ import (
"k8s.io/helm/cmd/helm/require"
"k8s.io/helm/pkg/action"
"k8s.io/helm/pkg/hapi"
"k8s.io/helm/pkg/hapi/release"
"k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/storage"
)
var listHelp = `
@ -70,7 +67,7 @@ type listOptions struct {
deployed bool // --deployed
failed bool // --failed
limit int // --max
offset string // --offset
offset int // --offset
pending bool // --pending
short bool // --short
sortDesc bool // --reverse
@ -78,11 +75,11 @@ type listOptions struct {
filter string
client helm.Interface
//client helm.Interface
}
func newListCmd(client helm.Interface, out io.Writer) *cobra.Command {
o := &listOptions{client: client}
func newListCmd(actionConfig *action.Configuration, out io.Writer) *cobra.Command {
o := &listOptions{}
cmd := &cobra.Command{
Use: "list [FILTER]",
@ -94,14 +91,40 @@ func newListCmd(client helm.Interface, out io.Writer) *cobra.Command {
if len(args) > 0 {
o.filter = strings.Join(args, " ")
}
o.client = ensureHelmClient(o.client, o.allNamespaces)
lister := action.NewList(action.Configuration{
Releases: storage.Init(driver.ConfigMap),
KubeClient: newClient(o.all),
})
//return o.run(out)
_, err := lister.Run()
if o.allNamespaces {
actionConfig = newActionConfig(true)
}
lister := action.NewList(actionConfig)
lister.All = o.limit == -1
lister.AllNamespaces = o.allNamespaces
lister.Limit = o.limit
lister.Offset = o.offset
lister.Filter = o.filter
// Set StateMask
lister.StateMask = o.setStateMask()
// Set sorter
lister.Sort = action.ByNameAsc
if o.sortDesc {
lister.Sort = action.ByNameDesc
}
if o.byDate {
lister.Sort = action.ByDate
}
results, err := lister.Run()
if o.short {
for _, res := range results {
fmt.Fprintln(out, res.Name)
}
return err
}
fmt.Fprintln(out, formatList(results, 90))
return err
},
}
@ -111,7 +134,7 @@ func newListCmd(client helm.Interface, out io.Writer) *cobra.Command {
f.BoolVarP(&o.byDate, "date", "d", false, "sort by release date")
f.BoolVarP(&o.sortDesc, "reverse", "r", false, "reverse the sort order")
f.IntVarP(&o.limit, "max", "m", 256, "maximum number of releases to fetch")
f.StringVarP(&o.offset, "offset", "o", "", "next release name in the list, used to offset from start value")
f.IntVarP(&o.offset, "offset", "o", 0, "next release name in the list, used to offset from start value")
f.BoolVarP(&o.all, "all", "a", false, "show all releases, not just the ones marked deployed")
f.BoolVar(&o.uninstalled, "uninstalled", false, "show uninstalled releases")
f.BoolVar(&o.superseded, "superseded", false, "show superseded releases")
@ -125,111 +148,35 @@ func newListCmd(client helm.Interface, out io.Writer) *cobra.Command {
return cmd
}
func (o *listOptions) run(out io.Writer) error {
sortBy := hapi.SortByName
if o.byDate {
sortBy = hapi.SortByLastReleased
}
sortOrder := hapi.SortAsc
if o.sortDesc {
sortOrder = hapi.SortDesc
}
stats := o.statusCodes()
res, err := o.client.ListReleases(
helm.ReleaseListLimit(o.limit),
helm.ReleaseListOffset(o.offset),
helm.ReleaseListFilter(o.filter),
helm.ReleaseListSort(sortBy),
helm.ReleaseListOrder(sortOrder),
helm.ReleaseListStatuses(stats),
)
if err != nil {
return err
}
if len(res) == 0 {
return nil
}
rels := filterList(res)
if o.short {
for _, r := range rels {
fmt.Fprintln(out, r.Name)
}
return nil
}
fmt.Fprintln(out, formatList(rels, o.colWidth))
return nil
}
// filterList returns a list scrubbed of old releases.
func filterList(rels []*release.Release) []*release.Release {
idx := map[string]int{}
for _, r := range rels {
name, version := r.Name, r.Version
if max, ok := idx[name]; ok {
// check if we have a greater version already
if max > version {
continue
}
}
idx[name] = version
}
uniq := make([]*release.Release, 0, len(idx))
for _, r := range rels {
if idx[r.Name] == r.Version {
uniq = append(uniq, r)
}
// setStateMask calculates the state mask based on parameters.
func (o *listOptions) setStateMask() action.ListStates {
if o.all {
return action.ListAll
}
return uniq
}
// statusCodes gets the list of status codes that are to be included in the results.
func (o *listOptions) statusCodes() []release.ReleaseStatus {
if o.all {
return []release.ReleaseStatus{
release.StatusUnknown,
release.StatusDeployed,
release.StatusUninstalled,
release.StatusUninstalling,
release.StatusFailed,
release.StatusPendingInstall,
release.StatusPendingUpgrade,
release.StatusPendingRollback,
}
}
status := []release.ReleaseStatus{}
state := action.ListStates(0)
if o.deployed {
status = append(status, release.StatusDeployed)
state |= action.ListDeployed
}
if o.uninstalled {
status = append(status, release.StatusUninstalled)
state |= action.ListUninstalled
}
if o.uninstalling {
status = append(status, release.StatusUninstalling)
}
if o.failed {
status = append(status, release.StatusFailed)
}
if o.superseded {
status = append(status, release.StatusSuperseded)
state |= action.ListUninstalling
}
if o.pending {
status = append(status, release.StatusPendingInstall, release.StatusPendingUpgrade, release.StatusPendingRollback)
state |= action.ListPendingInstall | action.ListPendingRollback | action.ListPendingUpgrade
}
if o.failed {
state |= action.ListFailed
}
// Default case.
if len(status) == 0 {
status = append(status, release.StatusDeployed, release.StatusFailed)
// Apply a default
if state == 0 {
return action.ListDeployed | action.ListFailed
}
return status
return state
}
func formatList(rels []*release.Release, colWidth uint) string {

@ -1,115 +0,0 @@
/*
Copyright The Helm Authors.
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 (
"testing"
"k8s.io/helm/pkg/hapi/release"
"k8s.io/helm/pkg/helm"
)
func TestListCmd(t *testing.T) {
tests := []cmdTestCase{{
name: "with a release",
cmd: "list",
rels: []*release.Release{
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide"}),
},
golden: "output/list-with-release.txt",
}, {
name: "list",
cmd: "list",
rels: []*release.Release{
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas"}),
},
golden: "output/list.txt",
}, {
name: "list, one deployed, one failed",
cmd: "list -q",
rels: []*release.Release{
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide", Status: release.StatusFailed}),
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas-guide", Status: release.StatusDeployed}),
},
golden: "output/list-with-failed.txt",
}, {
name: "with a release, multiple flags",
cmd: "list --uninstalled --deployed --failed -q",
rels: []*release.Release{
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide", Status: release.StatusUninstalled}),
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas-guide", Status: release.StatusDeployed}),
},
// Note: We're really only testing that the flags parsed correctly. Which results are returned
// depends on the backend. And until pkg/helm is done, we can't mock this.
golden: "output/list-with-mulitple-flags.txt",
}, {
name: "with a release, multiple flags",
cmd: "list --all -q",
rels: []*release.Release{
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide", Status: release.StatusUninstalled}),
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas-guide", Status: release.StatusDeployed}),
},
// See note on previous test.
golden: "output/list-with-mulitple-flags2.txt",
}, {
name: "with a release, multiple flags, deleting",
cmd: "list --all -q",
rels: []*release.Release{
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide", Status: release.StatusUninstalling}),
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas-guide", Status: release.StatusDeployed}),
},
// See note on previous test.
golden: "output/list-with-mulitple-flags-deleting.txt",
}, {
name: "namespace defined, multiple flags",
cmd: "list --all -q --namespace test123",
rels: []*release.Release{
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide", Namespace: "test123"}),
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas-guide", Namespace: "test321"}),
},
// See note on previous test.
golden: "output/list-with-mulitple-flags-namespaced.txt",
}, {
name: "with a pending release, multiple flags",
cmd: "list --all -q",
rels: []*release.Release{
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide", Status: release.StatusPendingInstall}),
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas-guide", Status: release.StatusDeployed}),
},
golden: "output/list-with-mulitple-flags-pending.txt",
}, {
name: "with a pending release, pending flag",
cmd: "list --pending -q",
rels: []*release.Release{
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide", Status: release.StatusPendingInstall}),
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "wild-idea", Status: release.StatusPendingUpgrade}),
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "crazy-maps", Status: release.StatusPendingRollback}),
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas-guide", Status: release.StatusDeployed}),
},
golden: "output/list-with-pending.txt",
}, {
name: "with old releases",
cmd: "list",
rels: []*release.Release{
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide"}),
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide", Status: release.StatusFailed}),
},
golden: "output/list-with-old-releases.txt",
}}
runTestCmd(t, tests)
}

@ -22,6 +22,7 @@ import (
"github.com/spf13/cobra"
"k8s.io/helm/cmd/helm/require"
"k8s.io/helm/pkg/action"
"k8s.io/helm/pkg/helm"
)
@ -42,11 +43,13 @@ Common actions from this point include:
Environment:
$HELM_HOME set an alternative location for Helm files. By default, these are stored in ~/.helm
$HELM_DRIVER set the backend storage driver. Values are: configmap, secret, memory
$HELM_NO_PLUGINS disable plugins. Set HELM_NO_PLUGINS=1 to disable plugins.
$KUBECONFIG set an alternative Kubernetes configuration file (default "~/.kube/config")
`
func newRootCmd(c helm.Interface, out io.Writer, args []string) *cobra.Command {
// TODO: 'c helm.Interface' is deprecated in favor of actionConfig
func newRootCmd(c helm.Interface, actionConfig *action.Configuration, out io.Writer, args []string) *cobra.Command {
cmd := &cobra.Command{
Use: "helm",
Short: "The Helm package manager for Kubernetes.",
@ -74,7 +77,7 @@ func newRootCmd(c helm.Interface, out io.Writer, args []string) *cobra.Command {
newGetCmd(c, out),
newHistoryCmd(c, out),
newInstallCmd(c, out),
newListCmd(c, out),
newListCmd(actionConfig, out),
newReleaseTestCmd(c, out),
newRollbackCmd(c, out),
newStatusCmd(c, out),

@ -1,2 +1,2 @@
thomas-guide
atlas-guide
thomas-guide

@ -1,2 +1,2 @@
thomas-guide
atlas-guide
thomas-guide

@ -1,2 +1,2 @@
thomas-guide
atlas-guide
thomas-guide

@ -1,2 +1,2 @@
thomas-guide
atlas-guide
thomas-guide

@ -1,2 +1,2 @@
thomas-guide
atlas-guide
thomas-guide

@ -1,2 +1,2 @@
thomas-guide
atlas-guide
thomas-guide

@ -1,3 +1,3 @@
NAME REVISION UPDATED STATUS CHART NAMESPACE
thomas-guide 1 1977-09-02 22:04:05 +0000 UTC deployed foo-0.1.0-beta.1 default
thomas-guide 1 1977-09-02 22:04:05 +0000 UTC failed foo-0.1.0-beta.1 default
thomas-guide 2 1977-09-02 22:04:05 +0000 UTC failed foo-0.1.0-beta.1 default

@ -1,4 +1,4 @@
atlas-guide
crazy-maps
thomas-guide
wild-idea
crazy-maps
atlas-guide

@ -1,3 +1,19 @@
/*
Copyright The Helm Authors.
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 action
import (

@ -1,3 +1,19 @@
/*
Copyright The Helm Authors.
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 action contains the logic for each action that Helm can perform.
//
// This is a library for calling top-level Helm actions like 'install',

@ -1,14 +1,32 @@
/*
Copyright The Helm Authors.
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 action
import (
"errors"
"regexp"
"sort"
"k8s.io/helm/pkg/hapi/release"
"k8s.io/helm/pkg/releaseutil"
)
// ListStates represents zero or more status codes that a list item may have set
//
// Because this is used as a bitmask filter, more than one one bit can be flipped
// in the ListStates.
type ListStates uint
const (
@ -32,18 +50,48 @@ const (
ListUnknown
)
type ByDate struct{}
type ByNameAsc struct{}
type ByNameDesc struct{}
// ReleaseSorter is a sorter for releases
type ReleaseSorter interface {
SetList([]*release.Release)
Len() int
Less(i, j int) bool
Swap(i, j int)
// FromName takes a state name and returns a ListStates representation.
//
// Currently, there are only names for individual flipped bits, so the returned
// ListStates will only match one of the constants. However, it is possible that
// this behavior could change in the future.
func (s ListStates) FromName(str string) ListStates {
switch str {
case "deployed":
return ListDeployed
case "uninstalled":
return ListUninstalled
case "superseded":
return ListSuperseded
case "failed":
return ListFailed
case "uninstalling":
return ListUninstalling
case "pending-install":
return ListPendingInstall
case "pending-upgrade":
return ListPendingUpgrade
case "pending-rollback":
return ListPendingRollback
}
return ListUnknown
}
// ListAll is a convenience for enabling all list filters
const ListAll = ListDeployed | ListUninstalled | ListUninstalling | ListPendingInstall | ListPendingRollback | ListPendingUpgrade | ListSuperseded | ListFailed
// Sorter is a top-level sort
type Sorter uint
const (
// ByDate sorts by date
ByDate Sorter = iota
// ByNameAsc sorts by ascending lexicographic order
ByNameAsc
// ByNameDesc sorts by descending lexicographic order
ByNameDesc
)
// List is the action for listing releases.
//
// It provides, for example, the implementation of 'helm list'.
@ -55,7 +103,7 @@ type List struct {
// Sort indicates the sort to use
//
// see pkg/releaseutil for several useful sorters
Sort ReleaseSorter
Sort Sorter
// StateMask accepts a bitmask of states for items to show.
// The default is ListDeployed
StateMask ListStates
@ -73,56 +121,65 @@ type List struct {
func NewList(cfg *Configuration) *List {
return &List{
StateMask: ListDeployed | ListFailed,
cfg: cfg,
}
}
// Run executes the list command, returning a set of matches.
func (a *List) Run() ([]*release.Release, error) {
return []*release.Release{}, errors.New("not implemented")
}
func (a *List) listReleases() ([]*release.Release, error) {
rels, err := a.cfg.Releases.ListReleases()
offset := 0
limit := 0
var filter *regexp.Regexp
if a.Filter != "" {
var err error
filter, err = regexp.Compile(a.Filter)
if err != nil {
return rels, err
return nil, err
}
// TODO: add the rest of the filters here
// Run filter
rels, err = a.filterReleases(rels)
if err != nil {
return rels, err
}
// Run sort
rels = a.sort(rels)
return rels, nil
}
func (a *List) filterReleases(rels []*release.Release) ([]*release.Release, error) {
if a.Filter == "" {
return rels, nil
results, err := a.cfg.Releases.List(func(rel *release.Release) bool {
// If we haven't reached offset, skip because there
// is nothing to add.
if offset < a.Offset {
offset++
return false
}
preg, err := regexp.Compile(a.Filter)
if err != nil {
return rels, err
// If over limit, return. This is rather inefficient
if limit >= a.Limit {
return false
}
// Skip anything that the mask doesn't cover
currentStatus := a.StateMask.FromName(rel.Info.Status.String())
if a.StateMask&currentStatus == 0 {
return false
}
matches := []*release.Release{}
for _, r := range rels {
if preg.MatchString(r.Name) {
matches = append(matches, r)
// Skip anything that doesn't match the filter.
if filter != nil && !filter.MatchString(rel.Name) {
return false
}
limit++
return true
})
if results != nil {
a.sort(results)
}
return matches, nil
return results, err
}
func (a *List) sort(rels []*release.Release) []*release.Release {
if a.Sort == nil {
// sort is an in-place sort where order is based on the value of a.Sort
func (a *List) sort(rels []*release.Release) {
switch a.Sort {
case ByDate:
releaseutil.SortByDate(rels)
case ByNameDesc:
releaseutil.Reverse(rels, releaseutil.SortByName)
default:
releaseutil.SortByName(rels)
return rels
}
a.Sort.SetList(rels)
sort.Sort(a.Sort)
return rels
}

@ -0,0 +1,50 @@
/*
Copyright The Helm Authors.
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 action
import "testing"
func TestListStates(t *testing.T) {
for input, expect := range map[string]ListStates{
"deployed": ListDeployed,
"uninstalled": ListUninstalled,
"uninstalling": ListUninstalling,
"superseded": ListSuperseded,
"failed": ListFailed,
"pending-install": ListPendingInstall,
"pending-rollback": ListPendingRollback,
"pending-upgrade": ListPendingUpgrade,
"unknown": ListUnknown,
"totally made up key": ListUnknown,
} {
if expect != expect.FromName(input) {
t.Errorf("Expected %d for %s", expect, input)
}
// This is a cheap way to verify that ListAll actually allows everything but Unknown
if got := expect.FromName(input); got != ListUnknown && got&ListAll == 0 {
t.Errorf("Expected %s to match the ListAll filter", input)
}
}
filter := ListDeployed | ListPendingRollback
if status := filter.FromName("deployed"); filter&status == 0 {
t.Errorf("Expected %d to match mask %d", status, filter)
}
if status := filter.FromName("failed"); filter&status != 0 {
t.Errorf("Expected %d to fail to match mask %d", status, filter)
}
}

@ -52,19 +52,6 @@ func (c *Client) Option(opts ...Option) *Client {
return c
}
// ListReleases lists the current releases.
func (c *Client) ListReleases(opts ...ReleaseListOption) ([]*release.Release, error) {
reqOpts := c.opts
for _, opt := range opts {
opt(&reqOpts)
}
req := &reqOpts.listReq
if err := reqOpts.runBefore(req); err != nil {
return nil, err
}
return c.tiller.ListReleases(req)
}
// InstallRelease loads a chart from chstr, installs it, and returns the release response.
func (c *Client) InstallRelease(chstr, ns string, opts ...InstallOption) (*release.Release, error) {
// load the chart to install

@ -46,11 +46,6 @@ func (c *FakeClient) Option(opts ...Option) Interface {
var _ Interface = &FakeClient{}
var _ Interface = (*FakeClient)(nil)
// ListReleases lists the current releases
func (c *FakeClient) ListReleases(opts ...ReleaseListOption) ([]*release.Release, error) {
return c.Rels, nil
}
// InstallRelease creates a new release and returns the release
func (c *FakeClient) InstallRelease(chStr, ns string, opts ...InstallOption) (*release.Release, error) {
chart := &chart.Chart{}

@ -26,7 +26,6 @@ import (
cpb "k8s.io/helm/pkg/chart"
"k8s.io/helm/pkg/chart/loader"
"k8s.io/helm/pkg/hapi"
rls "k8s.io/helm/pkg/hapi/release"
)
// Path to example charts relative to pkg/helm.
@ -36,61 +35,6 @@ const chartsDir = "../../docs/examples/"
var errSkip = errors.New("test: skip")
// Verify each ReleaseListOption is applied to a ListReleasesRequest correctly.
func TestListReleases_VerifyOptions(t *testing.T) {
// Options testdata
var limit = 2
var offset = "offset"
var filter = "filter"
var sortBy = hapi.SortByLastReleased
var sortOrd = hapi.SortAsc
var codes = []rls.ReleaseStatus{
rls.StatusFailed,
rls.StatusUninstalled,
rls.StatusDeployed,
rls.StatusSuperseded,
}
// Expected ListReleasesRequest message
exp := &hapi.ListReleasesRequest{
Limit: int64(limit),
Offset: offset,
Filter: filter,
SortBy: sortBy,
SortOrder: sortOrd,
StatusCodes: codes,
}
// Options used in ListReleases
ops := []ReleaseListOption{
ReleaseListSort(sortBy),
ReleaseListOrder(sortOrd),
ReleaseListLimit(limit),
ReleaseListOffset(offset),
ReleaseListFilter(filter),
ReleaseListStatuses(codes),
}
// BeforeCall option to intercept Helm client ListReleasesRequest
b4c := BeforeCall(func(msg interface{}) error {
switch act := msg.(type) {
case *hapi.ListReleasesRequest:
t.Logf("ListReleasesRequest: %#+v\n", act)
assert(t, exp, act)
default:
t.Fatalf("expected message of type ListReleasesRequest, got %T\n", act)
}
return errSkip
})
client := NewClient(b4c)
if _, err := client.ListReleases(ops...); err != errSkip {
t.Fatalf("did not expect error but got (%v)\n``", err)
}
// ensure options for call are not saved to client
assert(t, "", client.opts.listReq.Filter)
}
// Verify each InstallOption is applied to an InstallReleaseRequest correctly.
func TestInstallRelease_VerifyOptions(t *testing.T) {

@ -24,7 +24,6 @@ import (
// Interface for helm client for mocking in tests
type Interface interface {
ListReleases(opts ...ReleaseListOption) ([]*release.Release, error)
InstallRelease(chStr, namespace string, opts ...InstallOption) (*release.Release, error)
InstallReleaseFromChart(chart *chart.Chart, namespace string, opts ...InstallOption) (*release.Release, error)
UninstallRelease(rlsName string, opts ...UninstallOption) (*hapi.UninstallReleaseResponse, error)

@ -20,7 +20,6 @@ import (
"k8s.io/client-go/discovery"
"k8s.io/helm/pkg/hapi"
"k8s.io/helm/pkg/hapi/release"
"k8s.io/helm/pkg/storage/driver"
"k8s.io/helm/pkg/tiller/environment"
)
@ -87,56 +86,6 @@ func BeforeCall(fn func(interface{}) error) Option {
}
}
// ReleaseListOption allows specifying various settings
// configurable by the helm client user for overriding
// the defaults used when running the `helm list` command.
type ReleaseListOption func(*options)
// ReleaseListOffset specifies the offset into a list of releases.
func ReleaseListOffset(offset string) ReleaseListOption {
return func(opts *options) {
opts.listReq.Offset = offset
}
}
// ReleaseListFilter specifies a filter to apply a list of releases.
func ReleaseListFilter(filter string) ReleaseListOption {
return func(opts *options) {
opts.listReq.Filter = filter
}
}
// ReleaseListLimit set an upper bound on the number of releases returned.
func ReleaseListLimit(limit int) ReleaseListOption {
return func(opts *options) {
opts.listReq.Limit = int64(limit)
}
}
// ReleaseListOrder specifies how to order a list of releases.
func ReleaseListOrder(order hapi.SortOrder) ReleaseListOption {
return func(opts *options) {
opts.listReq.SortOrder = order
}
}
// ReleaseListSort specifies how to sort a release list.
func ReleaseListSort(sort hapi.SortBy) ReleaseListOption {
return func(opts *options) {
opts.listReq.SortBy = sort
}
}
// ReleaseListStatuses specifies which status codes should be returned.
func ReleaseListStatuses(statuses []release.ReleaseStatus) ReleaseListOption {
return func(opts *options) {
if len(statuses) == 0 {
statuses = []release.ReleaseStatus{release.StatusDeployed}
}
opts.listReq.StatusCodes = statuses
}
}
// InstallOption allows specifying various settings
// configurable by the helm client user for overriding
// the defaults used when running the `helm install` command.

@ -1,83 +0,0 @@
/*
Copyright The Helm Authors.
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 tiller
import (
"regexp"
"k8s.io/helm/pkg/hapi"
"k8s.io/helm/pkg/hapi/release"
relutil "k8s.io/helm/pkg/releaseutil"
)
// ListReleases lists the releases found by the server.
func (s *ReleaseServer) ListReleases(req *hapi.ListReleasesRequest) ([]*release.Release, error) {
if len(req.StatusCodes) == 0 {
req.StatusCodes = []release.ReleaseStatus{release.StatusDeployed}
}
rels, err := s.Releases.ListFilterAll(func(r *release.Release) bool {
for _, sc := range req.StatusCodes {
if sc == r.Info.Status {
return true
}
}
return false
})
if err != nil {
return nil, err
}
if len(req.Filter) != 0 {
rels, err = filterReleases(req.Filter, rels)
if err != nil {
return nil, err
}
}
switch req.SortBy {
case hapi.SortByName:
relutil.SortByName(rels)
case hapi.SortByLastReleased:
relutil.SortByDate(rels)
}
if req.SortOrder == hapi.SortDesc {
ll := len(rels)
rr := make([]*release.Release, ll)
for i, item := range rels {
rr[ll-i-1] = item
}
rels = rr
}
return rels, nil
}
func filterReleases(filter string, rels []*release.Release) ([]*release.Release, error) {
preg, err := regexp.Compile(filter)
if err != nil {
return rels, err
}
matches := []*release.Release{}
for _, r := range rels {
if preg.MatchString(r.Name) {
matches = append(matches, r)
}
}
return matches, nil
}

@ -1,191 +0,0 @@
/*
Copyright The Helm Authors.
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 tiller
import (
"fmt"
"testing"
"k8s.io/helm/pkg/hapi"
"k8s.io/helm/pkg/hapi/release"
)
func TestListReleases(t *testing.T) {
rs := rsFixture(t)
num := 7
for i := 0; i < num; i++ {
rel := releaseStub()
rel.Name = fmt.Sprintf("rel-%d", i)
if err := rs.Releases.Create(rel); err != nil {
t.Fatalf("Could not store mock release: %s", err)
}
}
rels, err := rs.ListReleases(&hapi.ListReleasesRequest{})
if err != nil {
t.Fatalf("Failed listing: %s", err)
}
if len(rels) != num {
t.Errorf("Expected %d releases, got %d", num, len(rels))
}
}
func TestListReleasesByStatus(t *testing.T) {
rs := rsFixture(t)
stubs := []*release.Release{
namedReleaseStub("kamal", release.StatusDeployed),
namedReleaseStub("astrolabe", release.StatusUninstalled),
namedReleaseStub("octant", release.StatusFailed),
namedReleaseStub("sextant", release.StatusUnknown),
}
for _, stub := range stubs {
if err := rs.Releases.Create(stub); err != nil {
t.Fatalf("Could not create stub: %s", err)
}
}
tests := []struct {
statusCodes []release.ReleaseStatus
names []string
}{
{
names: []string{"kamal"},
statusCodes: []release.ReleaseStatus{release.StatusDeployed},
},
{
names: []string{"astrolabe"},
statusCodes: []release.ReleaseStatus{release.StatusUninstalled},
},
{
names: []string{"kamal", "octant"},
statusCodes: []release.ReleaseStatus{release.StatusDeployed, release.StatusFailed},
},
{
names: []string{"kamal", "astrolabe", "octant", "sextant"},
statusCodes: []release.ReleaseStatus{
release.StatusDeployed,
release.StatusUninstalled,
release.StatusFailed,
release.StatusUnknown,
},
},
}
for i, tt := range tests {
rels, err := rs.ListReleases(&hapi.ListReleasesRequest{StatusCodes: tt.statusCodes, Offset: "", Limit: 64})
if err != nil {
t.Fatalf("Failed listing %d: %s", i, err)
}
if len(tt.names) != len(rels) {
t.Fatalf("Expected %d releases, got %d", len(tt.names), len(rels))
}
for _, name := range tt.names {
found := false
for _, rel := range rels {
if rel.Name == name {
found = true
}
}
if !found {
t.Errorf("%d: Did not find name %q", i, name)
}
}
}
}
func TestListReleasesSort(t *testing.T) {
rs := rsFixture(t)
// Put them in by reverse order so that the mock doesn't "accidentally"
// sort.
num := 7
for i := num; i > 0; i-- {
rel := releaseStub()
rel.Name = fmt.Sprintf("rel-%d", i)
if err := rs.Releases.Create(rel); err != nil {
t.Fatalf("Could not store mock release: %s", err)
}
}
limit := 6
req := &hapi.ListReleasesRequest{
Offset: "",
Limit: int64(limit),
SortBy: hapi.SortByName,
}
rels, err := rs.ListReleases(req)
if err != nil {
t.Fatalf("Failed listing: %s", err)
}
// if len(rels) != limit {
// t.Errorf("Expected %d releases, got %d", limit, len(rels))
// }
for i := 0; i < limit; i++ {
n := fmt.Sprintf("rel-%d", i+1)
if rels[i].Name != n {
t.Errorf("Expected %q, got %q", n, rels[i].Name)
}
}
}
func TestListReleasesFilter(t *testing.T) {
rs := rsFixture(t)
names := []string{
"axon",
"dendrite",
"neuron",
"neuroglia",
"synapse",
"nucleus",
"organelles",
}
num := 7
for i := 0; i < num; i++ {
rel := releaseStub()
rel.Name = names[i]
if err := rs.Releases.Create(rel); err != nil {
t.Fatalf("Could not store mock release: %s", err)
}
}
req := &hapi.ListReleasesRequest{
Offset: "",
Limit: 64,
Filter: "neuro[a-z]+",
SortBy: hapi.SortByName,
}
rels, err := rs.ListReleases(req)
if err != nil {
t.Fatalf("Failed listing: %s", err)
}
if len(rels) != 2 {
t.Errorf("Expected 2 releases, got %d", len(rels))
}
if rels[0].Name != "neuroglia" {
t.Errorf("Unexpected sort order: %v.", rels)
}
if rels[1].Name != "neuron" {
t.Errorf("Unexpected sort order: %v.", rels)
}
}
Loading…
Cancel
Save