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 }