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