github.com/uhthomas/helm@v3.0.0-beta.3+incompatible/cmd/helm/search_repo.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  	"path/filepath"
    23  	"strings"
    24  
    25  	"github.com/Masterminds/semver"
    26  	"github.com/gosuri/uitable"
    27  	"github.com/pkg/errors"
    28  	"github.com/spf13/cobra"
    29  
    30  	"helm.sh/helm/cmd/helm/search"
    31  	"helm.sh/helm/pkg/helmpath"
    32  	"helm.sh/helm/pkg/repo"
    33  )
    34  
    35  const searchRepoDesc = `
    36  Search reads through all of the repositories configured on the system, and
    37  looks for matches. Search of these repositories uses the metadata stored on
    38  the system.
    39  
    40  Repositories are managed with 'helm repo' commands.
    41  `
    42  
    43  // searchMaxScore suggests that any score higher than this is not considered a match.
    44  const searchMaxScore = 25
    45  
    46  type searchRepoOptions struct {
    47  	versions     bool
    48  	regexp       bool
    49  	version      string
    50  	maxColWidth  uint
    51  	repoFile     string
    52  	repoCacheDir string
    53  }
    54  
    55  func newSearchRepoCmd(out io.Writer) *cobra.Command {
    56  	o := &searchRepoOptions{}
    57  
    58  	cmd := &cobra.Command{
    59  		Use:   "repo [keyword]",
    60  		Short: "search repositories for a keyword in charts",
    61  		Long:  searchRepoDesc,
    62  		RunE: func(cmd *cobra.Command, args []string) error {
    63  			o.repoFile = settings.RepositoryConfig
    64  			o.repoCacheDir = settings.RepositoryCache
    65  			return o.run(out, args)
    66  		},
    67  	}
    68  
    69  	f := cmd.Flags()
    70  	f.BoolVarP(&o.regexp, "regexp", "r", false, "use regular expressions for searching repositories you have added")
    71  	f.BoolVarP(&o.versions, "versions", "l", false, "show the long listing, with each version of each chart on its own line, for repositories you have added")
    72  	f.StringVar(&o.version, "version", "", "search using semantic versioning constraints on repositories you have added")
    73  	f.UintVar(&o.maxColWidth, "max-col-width", 50, "maximum column width for output table")
    74  
    75  	return cmd
    76  }
    77  
    78  func (o *searchRepoOptions) run(out io.Writer, args []string) error {
    79  	index, err := o.buildIndex(out)
    80  	if err != nil {
    81  		return err
    82  	}
    83  
    84  	var res []*search.Result
    85  	if len(args) == 0 {
    86  		res = index.All()
    87  	} else {
    88  		q := strings.Join(args, " ")
    89  		res, err = index.Search(q, searchMaxScore, o.regexp)
    90  		if err != nil {
    91  			return err
    92  		}
    93  	}
    94  
    95  	search.SortScore(res)
    96  	data, err := o.applyConstraint(res)
    97  	if err != nil {
    98  		return err
    99  	}
   100  
   101  	fmt.Fprintln(out, o.formatSearchResults(data))
   102  
   103  	return nil
   104  }
   105  
   106  func (o *searchRepoOptions) applyConstraint(res []*search.Result) ([]*search.Result, error) {
   107  	if len(o.version) == 0 {
   108  		return res, nil
   109  	}
   110  
   111  	constraint, err := semver.NewConstraint(o.version)
   112  	if err != nil {
   113  		return res, errors.Wrap(err, "an invalid version/constraint format")
   114  	}
   115  
   116  	data := res[:0]
   117  	foundNames := map[string]bool{}
   118  	for _, r := range res {
   119  		if _, found := foundNames[r.Name]; found {
   120  			continue
   121  		}
   122  		v, err := semver.NewVersion(r.Chart.Version)
   123  		if err != nil || constraint.Check(v) {
   124  			data = append(data, r)
   125  			if !o.versions {
   126  				foundNames[r.Name] = true // If user hasn't requested all versions, only show the latest that matches
   127  			}
   128  		}
   129  	}
   130  
   131  	return data, nil
   132  }
   133  
   134  func (o *searchRepoOptions) formatSearchResults(res []*search.Result) string {
   135  	if len(res) == 0 {
   136  		return "No results found"
   137  	}
   138  	table := uitable.New()
   139  	table.MaxColWidth = o.maxColWidth
   140  	table.AddRow("NAME", "CHART VERSION", "APP VERSION", "DESCRIPTION")
   141  	for _, r := range res {
   142  		table.AddRow(r.Name, r.Chart.Version, r.Chart.AppVersion, r.Chart.Description)
   143  	}
   144  	return table.String()
   145  }
   146  
   147  func (o *searchRepoOptions) buildIndex(out io.Writer) (*search.Index, error) {
   148  	// Load the repositories.yaml
   149  	rf, err := repo.LoadFile(o.repoFile)
   150  	if isNotExist(err) || len(rf.Repositories) == 0 {
   151  		return nil, errors.New("no repositories configured")
   152  	}
   153  
   154  	i := search.NewIndex()
   155  	for _, re := range rf.Repositories {
   156  		n := re.Name
   157  		f := filepath.Join(o.repoCacheDir, helmpath.CacheIndexFile(n))
   158  		ind, err := repo.LoadIndexFile(f)
   159  		if err != nil {
   160  			// TODO should print to stderr
   161  			fmt.Fprintf(out, "WARNING: Repo %q is corrupt or missing. Try 'helm repo update'.", n)
   162  			continue
   163  		}
   164  
   165  		i.AddRepo(n, ind, o.versions || len(o.version) > 0)
   166  	}
   167  	return i, nil
   168  }