github.com/latiif/helm@v2.15.0+incompatible/cmd/helm/search.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  	"fmt"
    21  	"io"
    22  	"strings"
    23  
    24  	"github.com/Masterminds/semver"
    25  	"github.com/gosuri/uitable"
    26  	"github.com/spf13/cobra"
    27  
    28  	"k8s.io/helm/cmd/helm/search"
    29  	"k8s.io/helm/pkg/helm/helmpath"
    30  	"k8s.io/helm/pkg/repo"
    31  )
    32  
    33  const searchDesc = `
    34  Search reads through all of the repositories configured on the system, and
    35  looks for matches.
    36  
    37  Repositories are managed with 'helm repo' commands.
    38  
    39  To look for charts with a particular name (such as stable/mysql), try
    40  searching using vertical tabs (\v). Vertical tabs are used as the delimiter
    41  between search fields. For example:
    42  
    43      helm search --regexp '\vstable/mysql\v'
    44  
    45  To search for charts using common keywords (such as "database" or
    46  "key-value store"), use
    47  
    48      helm search database
    49  
    50  or
    51  
    52      helm search key-value store
    53  `
    54  
    55  // searchMaxScore suggests that any score higher than this is not considered a match.
    56  const searchMaxScore = 25
    57  
    58  type searchCmd struct {
    59  	out      io.Writer
    60  	helmhome helmpath.Home
    61  
    62  	versions bool
    63  	regexp   bool
    64  	version  string
    65  	colWidth uint
    66  	output   string
    67  }
    68  
    69  type chartElement struct {
    70  	Name        string
    71  	Version     string
    72  	AppVersion  string
    73  	Description string
    74  }
    75  
    76  func newSearchCmd(out io.Writer) *cobra.Command {
    77  	sc := &searchCmd{out: out}
    78  
    79  	cmd := &cobra.Command{
    80  		Use:   "search [keyword]",
    81  		Short: "Search for a keyword in charts",
    82  		Long:  searchDesc,
    83  		RunE: func(cmd *cobra.Command, args []string) error {
    84  			sc.helmhome = settings.Home
    85  			return sc.run(args)
    86  		},
    87  	}
    88  
    89  	f := cmd.Flags()
    90  	f.BoolVarP(&sc.regexp, "regexp", "r", false, "Use regular expressions for searching")
    91  	f.BoolVarP(&sc.versions, "versions", "l", false, "Show the long listing, with each version of each chart on its own line")
    92  	f.StringVarP(&sc.version, "version", "v", "", "Search using semantic versioning constraints")
    93  	f.UintVar(&sc.colWidth, "col-width", 60, "Specifies the max column width of output")
    94  	bindOutputFlag(cmd, &sc.output)
    95  
    96  	return cmd
    97  }
    98  
    99  func (s *searchCmd) run(args []string) error {
   100  	index, err := s.buildIndex()
   101  	if err != nil {
   102  		return err
   103  	}
   104  
   105  	var res []*search.Result
   106  	if len(args) == 0 {
   107  		res = index.All()
   108  	} else {
   109  		q := strings.Join(args, " ")
   110  		res, err = index.Search(q, searchMaxScore, s.regexp)
   111  		if err != nil {
   112  			return err
   113  		}
   114  	}
   115  
   116  	search.SortScore(res)
   117  	data, err := s.applyConstraint(res)
   118  	if err != nil {
   119  		return err
   120  	}
   121  
   122  	return write(s.out, &searchWriter{data, s.colWidth}, outputFormat(s.output))
   123  }
   124  
   125  func (s *searchCmd) applyConstraint(res []*search.Result) ([]*search.Result, error) {
   126  	if len(s.version) == 0 {
   127  		return res, nil
   128  	}
   129  
   130  	constraint, err := semver.NewConstraint(s.version)
   131  	if err != nil {
   132  		return res, fmt.Errorf("an invalid version/constraint format: %s", err)
   133  	}
   134  
   135  	data := res[:0]
   136  	foundNames := map[string]bool{}
   137  	for _, r := range res {
   138  		if _, found := foundNames[r.Name]; found {
   139  			continue
   140  		}
   141  		v, err := semver.NewVersion(r.Chart.Version)
   142  		if err != nil || constraint.Check(v) {
   143  			data = append(data, r)
   144  			if !s.versions {
   145  				foundNames[r.Name] = true // If user hasn't requested all versions, only show the latest that matches
   146  			}
   147  		}
   148  	}
   149  
   150  	return data, nil
   151  }
   152  
   153  func (s *searchCmd) buildIndex() (*search.Index, error) {
   154  	// Load the repositories.yaml
   155  	rf, err := repo.LoadRepositoriesFile(s.helmhome.RepositoryFile())
   156  	if err != nil {
   157  		return nil, err
   158  	}
   159  
   160  	i := search.NewIndex()
   161  	for _, re := range rf.Repositories {
   162  		n := re.Name
   163  		f := s.helmhome.CacheIndex(n)
   164  		ind, err := repo.LoadIndexFile(f)
   165  		if err != nil {
   166  			fmt.Fprintf(s.out, "WARNING: Repo %q is corrupt or missing. Try 'helm repo update'.\n", n)
   167  			continue
   168  		}
   169  
   170  		i.AddRepo(n, ind, s.versions || len(s.version) > 0)
   171  	}
   172  	return i, nil
   173  }
   174  
   175  //////////// Printer implementation below here
   176  type searchWriter struct {
   177  	results     []*search.Result
   178  	columnWidth uint
   179  }
   180  
   181  func (r *searchWriter) WriteTable(out io.Writer) error {
   182  	if len(r.results) == 0 {
   183  		_, err := out.Write([]byte("No results found\n"))
   184  		if err != nil {
   185  			return fmt.Errorf("unable to write results: %s", err)
   186  		}
   187  		return nil
   188  	}
   189  	table := uitable.New()
   190  	table.MaxColWidth = r.columnWidth
   191  	table.AddRow("NAME", "CHART VERSION", "APP VERSION", "DESCRIPTION")
   192  	for _, r := range r.results {
   193  		table.AddRow(r.Name, r.Chart.Version, r.Chart.AppVersion, r.Chart.Description)
   194  	}
   195  	return encodeTable(out, table)
   196  }
   197  
   198  func (r *searchWriter) WriteJSON(out io.Writer) error {
   199  	return r.encodeByFormat(out, outputJSON)
   200  }
   201  
   202  func (r *searchWriter) WriteYAML(out io.Writer) error {
   203  	return r.encodeByFormat(out, outputYAML)
   204  }
   205  
   206  func (r *searchWriter) encodeByFormat(out io.Writer, format outputFormat) error {
   207  	var chartList []chartElement
   208  
   209  	for _, r := range r.results {
   210  		chartList = append(chartList, chartElement{r.Name, r.Chart.Version, r.Chart.AppVersion, r.Chart.Description})
   211  	}
   212  
   213  	switch format {
   214  	case outputJSON:
   215  		return encodeJSON(out, chartList)
   216  	case outputYAML:
   217  		return encodeYAML(out, chartList)
   218  	}
   219  
   220  	// Because this is a non-exported function and only called internally by
   221  	// WriteJSON and WriteYAML, we shouldn't get invalid types
   222  	return nil
   223  }