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 }