github.com/latiif/helm@v2.15.0+incompatible/cmd/helm/list.go (about)

     1  /*
     2  Copyright The Helm Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package main
    18  
    19  import (
    20  	"encoding/json"
    21  	"fmt"
    22  	"io"
    23  	"strings"
    24  
    25  	"github.com/ghodss/yaml"
    26  	"github.com/gosuri/uitable"
    27  	"github.com/spf13/cobra"
    28  
    29  	"k8s.io/helm/pkg/helm"
    30  	"k8s.io/helm/pkg/proto/hapi/release"
    31  	"k8s.io/helm/pkg/proto/hapi/services"
    32  	"k8s.io/helm/pkg/timeconv"
    33  )
    34  
    35  var listHelp = `
    36  This command lists all of the releases.
    37  
    38  By default, it lists only releases that are deployed or failed. Flags like
    39  '--deleted' and '--all' will alter this behavior. Such flags can be combined:
    40  '--deleted --failed'.
    41  
    42  By default, items are sorted alphabetically. Use the '-d' flag to sort by
    43  release date.
    44  
    45  If an argument is provided, it will be treated as a filter. Filters are
    46  regular expressions (Perl compatible) that are applied to the list of releases.
    47  Only items that match the filter will be returned.
    48  
    49  	$ helm list 'ara[a-z]+'
    50  	NAME            	UPDATED                 	CHART
    51  	maudlin-arachnid	Mon May  9 16:07:08 2016	alpine-0.1.0
    52  
    53  If no results are found, 'helm list' will exit 0, but with no output (or in
    54  the case of no '-q' flag, only headers).
    55  
    56  By default, up to 256 items may be returned. To limit this, use the '--max' flag.
    57  Setting '--max' to 0 will not return all results. Rather, it will return the
    58  server's default, which may be much higher than 256. Pairing the '--max'
    59  flag with the '--offset' flag allows you to page through results.
    60  `
    61  
    62  type listCmd struct {
    63  	filter      string
    64  	short       bool
    65  	limit       int
    66  	offset      string
    67  	byDate      bool
    68  	sortDesc    bool
    69  	out         io.Writer
    70  	all         bool
    71  	deleted     bool
    72  	deleting    bool
    73  	deployed    bool
    74  	failed      bool
    75  	namespace   string
    76  	superseded  bool
    77  	pending     bool
    78  	client      helm.Interface
    79  	colWidth    uint
    80  	output      string
    81  	byChartName bool
    82  }
    83  
    84  type listResult struct {
    85  	Next     string
    86  	Releases []listRelease
    87  }
    88  
    89  type listRelease struct {
    90  	Name       string
    91  	Revision   int32
    92  	Updated    string
    93  	Status     string
    94  	Chart      string
    95  	AppVersion string
    96  	Namespace  string
    97  }
    98  
    99  func newListCmd(client helm.Interface, out io.Writer) *cobra.Command {
   100  	list := &listCmd{
   101  		out:    out,
   102  		client: client,
   103  	}
   104  
   105  	cmd := &cobra.Command{
   106  		Use:     "list [flags] [FILTER]",
   107  		Short:   "List releases",
   108  		Long:    listHelp,
   109  		Aliases: []string{"ls"},
   110  		PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() },
   111  		RunE: func(cmd *cobra.Command, args []string) error {
   112  			if len(args) > 0 {
   113  				list.filter = strings.Join(args, " ")
   114  			}
   115  			if list.client == nil {
   116  				list.client = newClient()
   117  			}
   118  			return list.run()
   119  		},
   120  	}
   121  
   122  	f := cmd.Flags()
   123  	settings.AddFlagsTLS(f)
   124  	f.BoolVarP(&list.short, "short", "q", false, "Output short (quiet) listing format")
   125  	f.BoolVarP(&list.byDate, "date", "d", false, "Sort by release date")
   126  	f.BoolVarP(&list.sortDesc, "reverse", "r", false, "Reverse the sort order")
   127  	f.IntVarP(&list.limit, "max", "m", 256, "Maximum number of releases to fetch")
   128  	f.StringVarP(&list.offset, "offset", "o", "", "Next release name in the list, used to offset from start value")
   129  	f.BoolVarP(&list.all, "all", "a", false, "Show all releases, not just the ones marked DEPLOYED")
   130  	f.BoolVar(&list.deleted, "deleted", false, "Show deleted releases")
   131  	f.BoolVar(&list.deleting, "deleting", false, "Show releases that are currently being deleted")
   132  	f.BoolVar(&list.deployed, "deployed", false, "Show deployed releases. If no other is specified, this will be automatically enabled")
   133  	f.BoolVar(&list.failed, "failed", false, "Show failed releases")
   134  	f.BoolVar(&list.pending, "pending", false, "Show pending releases")
   135  	f.StringVar(&list.namespace, "namespace", "", "Show releases within a specific namespace")
   136  	f.UintVar(&list.colWidth, "col-width", 60, "Specifies the max column width of output")
   137  	f.StringVar(&list.output, "output", "", "Output the specified format (json or yaml)")
   138  	f.BoolVarP(&list.byChartName, "chart-name", "c", false, "Sort by chart name")
   139  
   140  	// TODO: Do we want this as a feature of 'helm list'?
   141  	//f.BoolVar(&list.superseded, "history", true, "show historical releases")
   142  
   143  	// set defaults from environment
   144  	settings.InitTLS(f)
   145  
   146  	return cmd
   147  }
   148  
   149  func (l *listCmd) run() error {
   150  	sortBy := services.ListSort_NAME
   151  	if l.byDate {
   152  		sortBy = services.ListSort_LAST_RELEASED
   153  	}
   154  	if l.byChartName {
   155  		sortBy = services.ListSort_CHART_NAME
   156  	}
   157  
   158  	sortOrder := services.ListSort_ASC
   159  	if l.sortDesc {
   160  		sortOrder = services.ListSort_DESC
   161  	}
   162  
   163  	stats := l.statusCodes()
   164  
   165  	res, err := l.client.ListReleases(
   166  		helm.ReleaseListLimit(l.limit),
   167  		helm.ReleaseListOffset(l.offset),
   168  		helm.ReleaseListFilter(l.filter),
   169  		helm.ReleaseListSort(int32(sortBy)),
   170  		helm.ReleaseListOrder(int32(sortOrder)),
   171  		helm.ReleaseListStatuses(stats),
   172  		helm.ReleaseListNamespace(l.namespace),
   173  	)
   174  
   175  	if err != nil {
   176  		return prettyError(err)
   177  	}
   178  	if res == nil {
   179  		return nil
   180  	}
   181  
   182  	rels := filterList(res.GetReleases())
   183  
   184  	result := getListResult(rels, res.Next)
   185  
   186  	output, err := formatResult(l.output, l.short, result, l.colWidth)
   187  
   188  	if err != nil {
   189  		return prettyError(err)
   190  	}
   191  
   192  	fmt.Fprintln(l.out, output)
   193  	return nil
   194  }
   195  
   196  // filterList returns a list scrubbed of old releases.
   197  func filterList(rels []*release.Release) []*release.Release {
   198  	idx := map[string]int32{}
   199  
   200  	for _, r := range rels {
   201  		name, version := r.GetName(), r.GetVersion()
   202  		if max, ok := idx[name]; ok {
   203  			// check if we have a greater version already
   204  			if max > version {
   205  				continue
   206  			}
   207  		}
   208  		idx[name] = version
   209  	}
   210  
   211  	uniq := make([]*release.Release, 0, len(idx))
   212  	for _, r := range rels {
   213  		if idx[r.GetName()] == r.GetVersion() {
   214  			uniq = append(uniq, r)
   215  		}
   216  	}
   217  	return uniq
   218  }
   219  
   220  // statusCodes gets the list of status codes that are to be included in the results.
   221  func (l *listCmd) statusCodes() []release.Status_Code {
   222  	if l.all {
   223  		return []release.Status_Code{
   224  			release.Status_UNKNOWN,
   225  			release.Status_DEPLOYED,
   226  			release.Status_DELETED,
   227  			release.Status_DELETING,
   228  			release.Status_FAILED,
   229  			release.Status_PENDING_INSTALL,
   230  			release.Status_PENDING_UPGRADE,
   231  			release.Status_PENDING_ROLLBACK,
   232  		}
   233  	}
   234  	status := []release.Status_Code{}
   235  	if l.deployed {
   236  		status = append(status, release.Status_DEPLOYED)
   237  	}
   238  	if l.deleted {
   239  		status = append(status, release.Status_DELETED)
   240  	}
   241  	if l.deleting {
   242  		status = append(status, release.Status_DELETING)
   243  	}
   244  	if l.failed {
   245  		status = append(status, release.Status_FAILED)
   246  	}
   247  	if l.superseded {
   248  		status = append(status, release.Status_SUPERSEDED)
   249  	}
   250  	if l.pending {
   251  		status = append(status, release.Status_PENDING_INSTALL, release.Status_PENDING_UPGRADE, release.Status_PENDING_ROLLBACK)
   252  	}
   253  
   254  	// Default case.
   255  	if len(status) == 0 {
   256  		status = append(status, release.Status_DEPLOYED, release.Status_FAILED)
   257  	}
   258  	return status
   259  }
   260  
   261  func getListResult(rels []*release.Release, next string) listResult {
   262  	listReleases := []listRelease{}
   263  	for _, r := range rels {
   264  		md := r.GetChart().GetMetadata()
   265  		t := "-"
   266  		if tspb := r.GetInfo().GetLastDeployed(); tspb != nil {
   267  			t = timeconv.String(tspb)
   268  		}
   269  
   270  		lr := listRelease{
   271  			Name:       r.GetName(),
   272  			Revision:   r.GetVersion(),
   273  			Updated:    t,
   274  			Status:     r.GetInfo().GetStatus().GetCode().String(),
   275  			Chart:      fmt.Sprintf("%s-%s", md.GetName(), md.GetVersion()),
   276  			AppVersion: md.GetAppVersion(),
   277  			Namespace:  r.GetNamespace(),
   278  		}
   279  		listReleases = append(listReleases, lr)
   280  	}
   281  
   282  	return listResult{
   283  		Releases: listReleases,
   284  		Next:     next,
   285  	}
   286  }
   287  
   288  func shortenListResult(result listResult) []string {
   289  	names := []string{}
   290  	for _, r := range result.Releases {
   291  		names = append(names, r.Name)
   292  	}
   293  
   294  	return names
   295  }
   296  
   297  func formatResult(format string, short bool, result listResult, colWidth uint) (string, error) {
   298  	var output string
   299  	var err error
   300  
   301  	var shortResult []string
   302  	var finalResult interface{}
   303  	if short {
   304  		shortResult = shortenListResult(result)
   305  		finalResult = shortResult
   306  	} else {
   307  		finalResult = result
   308  	}
   309  
   310  	switch format {
   311  	case "":
   312  		if short {
   313  			output = formatTextShort(shortResult)
   314  		} else {
   315  			output = formatText(result, colWidth)
   316  		}
   317  	case "json":
   318  		o, e := json.Marshal(finalResult)
   319  		if e != nil {
   320  			err = fmt.Errorf("Failed to Marshal JSON output: %s", e)
   321  		} else {
   322  			output = string(o)
   323  		}
   324  	case "yaml":
   325  		o, e := yaml.Marshal(finalResult)
   326  		if e != nil {
   327  			err = fmt.Errorf("Failed to Marshal YAML output: %s", e)
   328  		} else {
   329  			output = string(o)
   330  		}
   331  	default:
   332  		err = fmt.Errorf("Unknown output format \"%s\"", format)
   333  	}
   334  	return output, err
   335  }
   336  
   337  func formatText(result listResult, colWidth uint) string {
   338  	nextOutput := ""
   339  	if result.Next != "" {
   340  		nextOutput = fmt.Sprintf("\tnext: %s\n", result.Next)
   341  	}
   342  
   343  	table := uitable.New()
   344  	table.MaxColWidth = colWidth
   345  	table.AddRow("NAME", "REVISION", "UPDATED", "STATUS", "CHART", "APP VERSION", "NAMESPACE")
   346  	for _, lr := range result.Releases {
   347  		table.AddRow(lr.Name, lr.Revision, lr.Updated, lr.Status, lr.Chart, lr.AppVersion, lr.Namespace)
   348  	}
   349  
   350  	return fmt.Sprintf("%s%s", nextOutput, table.String())
   351  }
   352  
   353  func formatTextShort(shortResult []string) string {
   354  	return strings.Join(shortResult, "\n")
   355  }