github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/cli/cmd/plugin/index.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  	"fmt"
    24  	"io"
    25  	"os"
    26  
    27  	"github.com/pkg/errors"
    28  	"github.com/spf13/cobra"
    29  	"k8s.io/cli-runtime/pkg/genericiooptions"
    30  	"k8s.io/klog/v2"
    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  	"github.com/1aal/kubeblocks/pkg/cli/util"
    36  )
    37  
    38  var (
    39  	pluginListIndexExample = templates.Examples(`
    40  	# List all configured plugin indexes
    41  	kbcli plugin index list
    42  	`)
    43  
    44  	pluginAddIndexExample = templates.Examples(`
    45  	# Add a new plugin index
    46  	kbcli plugin index add myIndex
    47  	`)
    48  
    49  	pluginDeleteIndexExample = templates.Examples(`
    50  	# Delete a plugin index
    51  	kbcli plugin index delete myIndex
    52  	`)
    53  )
    54  
    55  func NewPluginIndexCmd(streams genericiooptions.IOStreams) *cobra.Command {
    56  	cmd := &cobra.Command{
    57  		Use:   "index",
    58  		Short: "Manage custom plugin indexes",
    59  		Long:  "Manage which repositories are used to discover plugins and install plugins from",
    60  	}
    61  
    62  	cmd.AddCommand(NewPluginIndexListCmd(streams))
    63  	cmd.AddCommand(NewPluginIndexAddCmd(streams))
    64  	cmd.AddCommand(NewPluginIndexDeleteCmd(streams))
    65  	cmd.AddCommand(NewPluginIndexUpdateCmd(streams))
    66  	return cmd
    67  }
    68  
    69  type PluginIndexOptions struct {
    70  	IndexName string
    71  	URL       string
    72  
    73  	genericiooptions.IOStreams
    74  }
    75  
    76  func (o *PluginIndexOptions) ListIndex() error {
    77  	indexes, err := ListIndexes(paths)
    78  	if err != nil {
    79  		return errors.Wrap(err, "failed to list indexes")
    80  	}
    81  
    82  	p := NewPluginIndexPrinter(o.IOStreams.Out)
    83  	for _, index := range indexes {
    84  		addPluginIndexRow(index.Name, index.URL, p)
    85  	}
    86  	p.Print()
    87  
    88  	return nil
    89  }
    90  
    91  func (o *PluginIndexOptions) AddIndex() error {
    92  	err := AddIndex(paths, o.IndexName, o.URL)
    93  	if err != nil {
    94  		return err
    95  	}
    96  	return nil
    97  }
    98  
    99  func (o *PluginIndexOptions) DeleteIndex() error {
   100  	err := DeleteIndex(paths, o.IndexName)
   101  	if err != nil {
   102  		return err
   103  	}
   104  	return nil
   105  }
   106  
   107  func (o *PluginIndexOptions) UpdateIndex() error {
   108  	indexes, err := ListIndexes(paths)
   109  	if err != nil {
   110  		return errors.Wrap(err, "failed to list indexes")
   111  	}
   112  
   113  	for _, idx := range indexes {
   114  		indexPath := paths.IndexPath(idx.Name)
   115  		klog.V(1).Infof("Updating the local copy of plugin index (%s)", indexPath)
   116  		if err := util.EnsureUpdated(idx.URL, indexPath); err != nil {
   117  			klog.Warningf("failed to update index %q: %v", idx.Name, err)
   118  			continue
   119  		}
   120  
   121  		fmt.Fprintf(o.Out, "Updated the local copy of plugin index %q\n", idx.Name)
   122  	}
   123  
   124  	return nil
   125  }
   126  
   127  func NewPluginIndexListCmd(streams genericiooptions.IOStreams) *cobra.Command {
   128  	o := &PluginIndexOptions{
   129  		IOStreams: streams,
   130  	}
   131  
   132  	cmd := &cobra.Command{
   133  		Use:     "list",
   134  		Short:   "List configured indexes",
   135  		Example: pluginListIndexExample,
   136  		Run: func(cmd *cobra.Command, args []string) {
   137  			cmdutil.CheckErr(o.ListIndex())
   138  		},
   139  	}
   140  
   141  	return cmd
   142  }
   143  
   144  func NewPluginIndexAddCmd(streams genericiooptions.IOStreams) *cobra.Command {
   145  	o := &PluginIndexOptions{
   146  		IOStreams: streams,
   147  	}
   148  
   149  	cmd := &cobra.Command{
   150  		Use:     "add",
   151  		Short:   "Add a new index",
   152  		Example: pluginAddIndexExample,
   153  		Args:    cobra.ExactArgs(2),
   154  		Run: func(cmd *cobra.Command, args []string) {
   155  			o.IndexName = args[0]
   156  			o.URL = args[1]
   157  			cmdutil.CheckErr(o.AddIndex())
   158  		},
   159  	}
   160  
   161  	return cmd
   162  }
   163  
   164  func NewPluginIndexDeleteCmd(streams genericiooptions.IOStreams) *cobra.Command {
   165  	o := &PluginIndexOptions{
   166  		IOStreams: streams,
   167  	}
   168  
   169  	cmd := &cobra.Command{
   170  		Use:     "delete",
   171  		Short:   "Remove a configured index",
   172  		Example: pluginDeleteIndexExample,
   173  		Args:    cobra.ExactArgs(1),
   174  		Run: func(cmd *cobra.Command, args []string) {
   175  			o.IndexName = args[0]
   176  			cmdutil.CheckErr(o.DeleteIndex())
   177  		},
   178  	}
   179  
   180  	return cmd
   181  }
   182  
   183  func NewPluginIndexUpdateCmd(streams genericiooptions.IOStreams) *cobra.Command {
   184  	o := &PluginIndexOptions{
   185  		IOStreams: streams,
   186  	}
   187  
   188  	cmd := &cobra.Command{
   189  		Use:   "update",
   190  		Short: "update all configured indexes",
   191  		Run: func(cmd *cobra.Command, args []string) {
   192  			cmdutil.CheckErr(o.UpdateIndex())
   193  		},
   194  	}
   195  
   196  	return cmd
   197  }
   198  
   199  func NewPluginIndexPrinter(out io.Writer) *printer.TablePrinter {
   200  	t := printer.NewTablePrinter(out)
   201  	t.SetHeader("INDEX", "URL")
   202  	return t
   203  }
   204  
   205  func addPluginIndexRow(index, url string, p *printer.TablePrinter) {
   206  	p.AddRow(index, url)
   207  }
   208  
   209  // ListIndexes returns a slice of Index objects. The path argument is used as
   210  // the base path of the index.
   211  func ListIndexes(paths *Paths) ([]Index, error) {
   212  	entries, err := os.ReadDir(paths.IndexBase())
   213  	if err != nil {
   214  		return nil, err
   215  	}
   216  
   217  	var indexes []Index
   218  	for _, e := range entries {
   219  		if !e.IsDir() {
   220  			continue
   221  		}
   222  		indexName := e.Name()
   223  		remote, err := util.GitGetRemoteURL(paths.IndexPath(indexName))
   224  		if err != nil {
   225  			return nil, errors.Wrapf(err, "failed to list the remote URL for index %s", indexName)
   226  		}
   227  
   228  		indexes = append(indexes, Index{
   229  			Name: indexName,
   230  			URL:  remote,
   231  		})
   232  	}
   233  	return indexes, nil
   234  }
   235  
   236  // AddIndex initializes a new index to install plugins from.
   237  func AddIndex(paths *Paths, name, url string) error {
   238  	if name == "" {
   239  		return errors.New("index name must be specified")
   240  	}
   241  	dir := paths.IndexPath(name)
   242  	if _, err := os.Stat(dir); os.IsNotExist(err) {
   243  		return util.EnsureCloned(url, dir)
   244  	} else if err != nil {
   245  		return err
   246  	}
   247  	return fmt.Errorf("index %q already exists", name)
   248  }
   249  
   250  // DeleteIndex removes specified index name. If index does not exist, returns an error that can be tested by os.IsNotExist.
   251  func DeleteIndex(paths *Paths, name string) error {
   252  	dir := paths.IndexPath(name)
   253  	if _, err := os.Stat(dir); err != nil {
   254  		return err
   255  	}
   256  
   257  	return os.RemoveAll(dir)
   258  }