github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/cli/cmd/plugin/search.go (about) 1 /* 2 Copyright (C) 2022-2023 ApeCloud Co., Ltd 3 4 This file is part of KubeBlocks project 5 6 This program is free software: you can redistribute it and/or modify 7 it under the terms of the GNU Affero General Public License as published by 8 the Free Software Foundation, either version 3 of the License, or 9 (at your option) any later version. 10 11 This program is distributed in the hope that it will be useful 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU Affero General Public License for more details. 15 16 You should have received a copy of the GNU Affero General Public License 17 along with this program. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 package plugin 21 22 import ( 23 "io" 24 "os" 25 "strings" 26 27 "github.com/pkg/errors" 28 "github.com/sahilm/fuzzy" 29 "github.com/spf13/cobra" 30 "k8s.io/cli-runtime/pkg/genericiooptions" 31 cmdutil "k8s.io/kubectl/pkg/cmd/util" 32 "k8s.io/kubectl/pkg/util/templates" 33 34 "github.com/1aal/kubeblocks/pkg/cli/printer" 35 ) 36 37 var ( 38 pluginSearchExample = templates.Examples(` 39 # search a kbcli or kubectl plugin with keywords 40 kbcli plugin search keyword1 keyword2 41 `) 42 ) 43 44 type pluginSearchOptions struct { 45 keyword string 46 limit int 47 48 genericiooptions.IOStreams 49 } 50 51 func NewPluginSearchCmd(streams genericiooptions.IOStreams) *cobra.Command { 52 o := &pluginSearchOptions{ 53 IOStreams: streams, 54 } 55 56 cmd := &cobra.Command{ 57 Use: "search", 58 Short: "Search kbcli or kubectl plugins", 59 Long: "Search kbcli or kubectl plugins by keywords", 60 Example: pluginSearchExample, 61 Args: cobra.MinimumNArgs(1), 62 Run: func(cmd *cobra.Command, args []string) { 63 cmdutil.CheckErr(o.complete(args)) 64 cmdutil.CheckErr(o.run()) 65 }, 66 } 67 68 cmd.Flags().IntVar(&o.limit, "limit", 50, "Limit the number of plugin descriptions to output") 69 return cmd 70 } 71 72 func (o *pluginSearchOptions) complete(args []string) error { 73 o.keyword = strings.Join(args, "") 74 75 return nil 76 } 77 78 func (o *pluginSearchOptions) run() error { 79 indexes, err := ListIndexes(paths) 80 if err != nil { 81 return errors.Wrap(err, "failed to list indexes") 82 } 83 84 var plugins []pluginEntry 85 for _, index := range indexes { 86 ps, err := LoadPluginListFromFS(paths.IndexPluginsPath(index.Name)) 87 if err != nil { 88 return errors.Wrapf(err, "failed to load plugin list from the index %s", index.Name) 89 } 90 for _, p := range ps { 91 plugins = append(plugins, pluginEntry{ 92 index: index.Name, 93 plugin: p, 94 }) 95 } 96 } 97 98 searchPrinter := NewPluginSearchPrinter(o.Out) 99 for _, p := range plugins { 100 // fuzzy search 101 if fuzzySearchByNameAndDesc(o.keyword, p.plugin.Name, p.plugin.Spec.ShortDescription) { 102 _, err := os.Stat(paths.PluginInstallReceiptPath(p.plugin.Name)) 103 addPluginSearchRow(p.index, p.plugin.Name, limitString(p.plugin.Spec.ShortDescription, o.limit), !os.IsNotExist(err), searchPrinter) 104 } 105 } 106 searchPrinter.Print() 107 return nil 108 } 109 110 func NewPluginSearchPrinter(out io.Writer) *printer.TablePrinter { 111 t := printer.NewTablePrinter(out) 112 t.SetHeader("INDEX", "NAME", "DESCRIPTION", "INSTALLED") 113 return t 114 } 115 116 func addPluginSearchRow(index, plugin, description string, installed bool, p *printer.TablePrinter) { 117 if installed { 118 p.AddRow(index, plugin, description, "yes") 119 } else { 120 p.AddRow(index, plugin, description, "no") 121 } 122 } 123 124 func fuzzySearchByNameAndDesc(keyword, name, description string) bool { 125 if keyword == "" { 126 return false 127 } 128 129 // find by name and description 130 matches := fuzzy.Find(keyword, []string{name}) 131 if len(matches) > 0 { 132 return true 133 } 134 135 matches = fuzzy.Find(keyword, []string{description}) 136 if len(matches) > 0 && matches[0].Score > 0 { 137 return true 138 } 139 140 return false 141 142 } 143 144 func limitString(s string, length int) string { 145 if len(s) > length && length > 3 { 146 s = s[:length] + "..." 147 } 148 return s 149 }