go.ligato.io/vpp-agent/v3@v3.5.0/cmd/agentctl/commands/root.go (about)

     1  // Copyright (c) 2017 Cisco and/or its affiliates.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at:
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package commands
    16  
    17  import (
    18  	"bytes"
    19  	"fmt"
    20  	"html/template"
    21  	"io"
    22  	"os"
    23  	"strings"
    24  
    25  	"github.com/common-nighthawk/go-figure"
    26  	"github.com/moby/term"
    27  	"github.com/spf13/cobra"
    28  	"github.com/spf13/pflag"
    29  	"go.ligato.io/cn-infra/v2/agent"
    30  	"go.ligato.io/cn-infra/v2/logging"
    31  
    32  	"go.ligato.io/vpp-agent/v3/cmd/agentctl/cli"
    33  	"go.ligato.io/vpp-agent/v3/pkg/debug"
    34  )
    35  
    36  // NewRootNamed returns new Root named with name.
    37  func NewRootNamed(name string, agentCli *cli.AgentCli) *Root {
    38  	var (
    39  		opts    *cli.ClientOptions
    40  		flags   *pflag.FlagSet
    41  		helpCmd *cobra.Command
    42  	)
    43  	cmd := &cobra.Command{
    44  		Use:                   fmt.Sprintf("%s [options]", name),
    45  		Short:                 "A CLI app for managing Ligato agents",
    46  		SilenceUsage:          true,
    47  		SilenceErrors:         true,
    48  		TraverseChildren:      true,
    49  		DisableFlagsInUseLine: true,
    50  		RunE: func(cmd *cobra.Command, args []string) error {
    51  			if len(args) == 0 {
    52  				return ShowHelp(agentCli.Err())(cmd, args)
    53  			}
    54  			return fmt.Errorf("%[1]s: '%[2]s' is not a %[1]s command.\nSee '%[1]s --help'", name, args[0])
    55  		},
    56  		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
    57  			logging.Debugf("running command: %q", cmd.CommandPath())
    58  			// TODO: isSupported?
    59  			return nil
    60  		},
    61  		Version: fmt.Sprintf("%s, commit %s", agent.BuildVersion, agent.CommitHash),
    62  	}
    63  
    64  	asciiLogo := figure.NewFigure(name, "slant", true)
    65  	cmd.Long = asciiLogo.String()
    66  
    67  	opts, flags, helpCmd = SetupRootCommand(cmd)
    68  
    69  	flags.BoolP("version", "v", false, "Print version info and quit")
    70  
    71  	cmd.SetHelpCommand(helpCmd)
    72  	cmd.SetOut(agentCli.Out())
    73  
    74  	AddBaseCommands(cmd, agentCli)
    75  
    76  	DisableFlagsInUseLine(cmd)
    77  
    78  	return newRoot(cmd, agentCli, opts, flags)
    79  }
    80  
    81  // PrepareCommand handles global flags and Initialize should be
    82  // called before executing returned cobra command.
    83  func (root *Root) PrepareCommand() (*cobra.Command, error) {
    84  	cmd, args, err := root.HandleGlobalFlags()
    85  	if err != nil {
    86  		return nil, fmt.Errorf("handle global flags failed: %v", err)
    87  	}
    88  	if debug.IsEnabledFor("flags") {
    89  		fmt.Printf("flag.Args() = %v\n", args)
    90  		cmd.DebugFlags()
    91  	}
    92  	cmd.SetArgs(args)
    93  	return cmd, nil
    94  }
    95  
    96  // Root encapsulates a top-level cobra command (either agentctl or custom one).
    97  type Root struct {
    98  	cmd      *cobra.Command
    99  	agentCli *cli.AgentCli
   100  	opts     *cli.ClientOptions
   101  	flags    *pflag.FlagSet
   102  	args     []string
   103  }
   104  
   105  func newRoot(cmd *cobra.Command, agentCli *cli.AgentCli, opts *cli.ClientOptions, flags *pflag.FlagSet) *Root {
   106  	return &Root{cmd, agentCli, opts, flags, os.Args[1:]}
   107  }
   108  
   109  // HandleGlobalFlags takes care of parsing global flags defined on the
   110  // command, it returns the underlying cobra command and the args it
   111  // will be called with (or an error).
   112  //
   113  // On success the caller is responsible for calling Initialize()
   114  // before calling `Execute` on the returned command.
   115  func (root *Root) HandleGlobalFlags() (*cobra.Command, []string, error) {
   116  	cmd := root.cmd
   117  	flags := pflag.NewFlagSet(cmd.Name(), pflag.ContinueOnError)
   118  	flags.SetInterspersed(false)
   119  
   120  	// We need the single parse to see both sets of flags.
   121  	flags.AddFlagSet(cmd.Flags())
   122  	flags.AddFlagSet(cmd.PersistentFlags())
   123  	// Now parse the global flags, up to (but not including) the
   124  	// first command. The result will be that all the remaining
   125  	// arguments are in `flags.Args()`.
   126  	if err := flags.Parse(root.args); err != nil {
   127  		// Our FlagErrorFunc uses the cli, make sure it is initialized
   128  		if err := root.Initialize(); err != nil {
   129  			return nil, nil, err
   130  		}
   131  		return nil, nil, cmd.FlagErrorFunc()(cmd, err)
   132  	}
   133  
   134  	return cmd, flags.Args(), nil
   135  }
   136  
   137  // Initialize finalises global option parsing and initializes the agentctl client.
   138  func (root *Root) Initialize(ops ...cli.InitializeOpt) error {
   139  	return root.agentCli.Initialize(root.opts, ops...)
   140  }
   141  
   142  // SetupRootCommand setups cobra command and returns CLI client options, flags and help command.
   143  func SetupRootCommand(rootCmd *cobra.Command) (*cli.ClientOptions, *pflag.FlagSet, *cobra.Command) {
   144  	opts := cli.NewClientOptions()
   145  
   146  	opts.InstallFlags(rootCmd.PersistentFlags())
   147  
   148  	cobra.AddTemplateFunc("add", func(a, b int) int { return a + b })
   149  	cobra.AddTemplateFunc("prefix", prefixTmpl)
   150  	cobra.AddTemplateFunc("cmdExample", cmdExample)
   151  	cobra.AddTemplateFunc("wrappedFlagUsages", wrappedFlagUsages)
   152  	cobra.AddTemplateFunc("wrappedGlobalFlagUsages", wrappedGlobalFlagUsages)
   153  
   154  	rootCmd.SetUsageTemplate(usageTemplate)
   155  	rootCmd.SetHelpTemplate(helpTemplate)
   156  	rootCmd.SetFlagErrorFunc(FlagErrorFunc)
   157  	rootCmd.SetHelpCommand(helpCommand)
   158  
   159  	rootCmd.PersistentFlags().BoolP("help", "h", false, "Print usage")
   160  	_ = rootCmd.PersistentFlags().MarkShorthandDeprecated("help", "please use --help")
   161  	rootCmd.PersistentFlags().Lookup("help").Hidden = true
   162  
   163  	return opts, rootCmd.Flags(), helpCommand
   164  }
   165  
   166  // FlagErrorFunc returns status error when err is not nil.
   167  // It includes usage string and error message.
   168  func FlagErrorFunc(cmd *cobra.Command, err error) error {
   169  	if err == nil {
   170  		return nil
   171  	}
   172  	var usage string
   173  	if cmd.HasSubCommands() {
   174  		usage = "\n\n" + cmd.UsageString()
   175  	}
   176  	return StatusError{
   177  		Status:     fmt.Sprintf("%s\nSee '%s --help'.%s", err, cmd.CommandPath(), usage),
   178  		StatusCode: 125,
   179  	}
   180  }
   181  
   182  // ShowHelp shows the command help.
   183  func ShowHelp(out io.Writer) func(*cobra.Command, []string) error {
   184  	return func(cmd *cobra.Command, args []string) error {
   185  		cmd.SetOutput(out)
   186  		cmd.HelpFunc()(cmd, args)
   187  		return nil
   188  	}
   189  }
   190  
   191  // VisitAll will traverse all commands from the root.
   192  // This is different from the VisitAll of cobra.Command where only parents
   193  // are checked.
   194  func VisitAll(root *cobra.Command, fn func(*cobra.Command)) {
   195  	for _, cmd := range root.Commands() {
   196  		VisitAll(cmd, fn)
   197  	}
   198  	fn(root)
   199  }
   200  
   201  // DisableFlagsInUseLine sets the DisableFlagsInUseLine flag on all
   202  // commands within the tree rooted at cmd.
   203  func DisableFlagsInUseLine(cmd *cobra.Command) {
   204  	VisitAll(cmd, func(ccmd *cobra.Command) {
   205  		// do not add a `[flags]` to the end of the usage line.
   206  		ccmd.DisableFlagsInUseLine = true
   207  	})
   208  }
   209  
   210  var helpCommand = &cobra.Command{
   211  	Use:               "help [command]",
   212  	Short:             "Help about the command",
   213  	PersistentPreRun:  func(cmd *cobra.Command, args []string) {},
   214  	PersistentPostRun: func(cmd *cobra.Command, args []string) {},
   215  	RunE: func(c *cobra.Command, args []string) error {
   216  		cmd, args, e := c.Root().Find(args)
   217  		if cmd == nil || e != nil || len(args) > 0 {
   218  			return fmt.Errorf("unknown help topic: %v", strings.Join(args, " "))
   219  		}
   220  		helpFunc := cmd.HelpFunc()
   221  		helpFunc(cmd, args)
   222  		return nil
   223  	},
   224  }
   225  
   226  var usageTemplate = `Usage:
   227  {{- if not .HasSubCommands}}	{{.UseLine}}{{end}}
   228  {{- if .HasAvailableSubCommands}}	{{ .CommandPath}}{{- if .HasAvailableFlags}} [options]{{end}} COMMAND{{ end}}
   229  
   230  {{if ne .Long ""}}{{ .Long  | trimRightSpace }}{{else }}{{ .Short | trimRightSpace }}
   231  
   232  {{- end}}
   233  {{- if gt .Aliases 0}}
   234  
   235  ALIASES
   236    {{.NameAndAliases}}
   237  
   238  {{- end}}
   239  {{- if .HasExample}}
   240  
   241  EXAMPLES
   242  {{ prefix (cmdExample . | trimRightSpace) "  "}}
   243  
   244  {{- end}}
   245  {{- if .HasAvailableSubCommands }}
   246  
   247  COMMANDS
   248  
   249  {{- range .Commands }}{{- if .IsAvailableCommand}}
   250    {{rpad .Name (add .NamePadding 1)}}{{.Short}}
   251  {{- end}}{{- end}}
   252  
   253  {{- end}}
   254  {{- if .HasLocalFlags}}
   255  
   256  OPTIONS:
   257  {{ wrappedFlagUsages . | trimRightSpace}}
   258  
   259  {{- end}}
   260  {{- if .HasInheritedFlags}}
   261  
   262  GLOBALS:
   263  {{ wrappedGlobalFlagUsages . | trimRightSpace}}
   264  
   265  {{- end}}
   266  {{- if .HasSubCommands }}
   267  
   268  Run '{{.CommandPath}} COMMAND --help' for more information on a command.
   269  {{- end}}
   270  `
   271  
   272  var helpTemplate = `
   273  {{- if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}`
   274  
   275  func cmdExample(cmd *cobra.Command) string {
   276  	t := template.New("example")
   277  	template.Must(t.Parse(cmd.Example))
   278  	var b bytes.Buffer
   279  	if err := t.Execute(&b, cmd); err != nil {
   280  		panic(err)
   281  	}
   282  	return b.String()
   283  }
   284  
   285  func wrappedFlagUsages(cmd *cobra.Command) string {
   286  	width := 80
   287  	if ws, err := term.GetWinsize(0); err == nil {
   288  		width = int(ws.Width)
   289  	}
   290  	return cmd.LocalFlags().FlagUsagesWrapped(width - 1)
   291  }
   292  
   293  func wrappedGlobalFlagUsages(cmd *cobra.Command) string {
   294  	width := 80
   295  	if ws, err := term.GetWinsize(0); err == nil {
   296  		width = int(ws.Width)
   297  	}
   298  	return cmd.InheritedFlags().FlagUsagesWrapped(width - 1)
   299  }