vitess.io/vitess@v0.16.2/go/cmd/vtctldclient/command/doc.go (about)

     1  /*
     2  Copyright 2021 The Vitess 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  /*
    18  Package command contains the commands used by vtctldclient. It is intended only
    19  for use in vtctldclient's main package and entrypoint. The rest of this
    20  documentation is intended for maintainers.
    21  
    22  Commands are grouped into files by the types of resources they interact with (
    23  e.g. GetTablet, CreateTablet, DeleteTablet, GetTablets) or by what they do (e.g.
    24  PlannedReparentShard, EmergencyReparentShard, InitShardPrimary). Please add the
    25  command to the appropriate existing file, alphabetically, or create a new
    26  grouping if one does not exist.
    27  
    28  The root command lives in root.go, and commands must attach themselves to this
    29  during an init function in order to be reachable from the CLI. root.go also
    30  contains the global variables available to any subcommand that are managed by
    31  the root command's pre- and post-run functions. Commands must not attempt to
    32  manage these, as that may conflict with Root's post-run cleanup actions. All
    33  commands should, at a minimum, use the commandCtx rather than creating their own
    34  context.Background to start, as it contains root tracing spans that would be
    35  lost.
    36  
    37  Commands should not keep their logic in an anonymous function on the
    38  cobra.Command struct, but instead in a separate function that is assigned to
    39  RunE. Commands should strive to keep declaration, function definition, and flag
    40  initialization located as closely together as possible, to make the code easier
    41  to follow and understand (the global variables declared near Root are the
    42  exception here, not the rule). Commands should also prevent individual flag
    43  names from polluting the package namespace.
    44  
    45  A good pattern we have found is to do the following:
    46  
    47  	package command
    48  
    49  	// (imports ...)
    50  
    51  	var (
    52  		CreateTablet = &cobra.Command{
    53  			Use: "CreateTablet [options] --keyspace=<keyspace> --shard=<shard-range> <tablet-alias> <tablet-type>",
    54  			Args: cobra.ExactArgs(2),
    55  			RunE: commandCreateTablet,
    56  		}
    57  		GetTablet = &cobra.Command{
    58  			Use: "GetTablet <tablet-alias>",
    59  			Args: cobra.ExactArgs(1),
    60  			RunE: commandGetTablet,
    61  		}
    62  	)
    63  
    64  	var createTabletOptions = struct {
    65  		Opt1 string
    66  		Opt2 bool
    67  		Keyspace string
    68  		Shard string
    69  	}{}
    70  
    71  	func commandCreateTablet(cmd *cobra.Command, args []string) error {
    72  		aliasStr := cmd.Flags().Args(0)
    73  		tabletTypeStr := cmd.Flags().Args(1)
    74  
    75  		// do stuff with:
    76  		// - client
    77  		// - commandCtx
    78  		// - createTabletOptions
    79  		// - aliasStr
    80  		// - tabletTypeStr
    81  
    82  		return nil
    83  	}
    84  
    85  	// GetTablet takes no flags, so it needs no anonymous struct to store them
    86  	func commandGetTablet(cmd *cobra.Command, args []string) error {
    87  		aliasStr := cmd.Flags().Arg(0)
    88  
    89  		// do stuff with:
    90  		// - client
    91  		// - commandCtx
    92  		// - aliasStr
    93  
    94  		return nil
    95  	}
    96  
    97  	// finally, hook up all the commands in this file to Root, and add any flags
    98  	// to each of those commands
    99  
   100  	func init() {
   101  		CreateTablet.Flags().StringVar(&createTabletOptions.Opt1, "opt1", "default", "help")
   102  		CreateTablet.Flags().BoolVar(&createTabletOptions.Opt2, "opt2", false, "help")
   103  		CreateTablet.Flags().StringVarP(&createTabletOptions.Keyspace, "keyspace", "k", "keyspace of tablet")
   104  		CreateTablet.MarkFlagRequired("keyspace")
   105  		CreateTablet.Flags().StringVarP(&createTabletOptions.Shard, "shard", "s", "shard range of tablet")
   106  		CreateTablet.MarkFlagRequired("shard")
   107  		Root.AddCommand(CreateTablet)
   108  
   109  		Root.AddCommand(GetTablet)
   110  	}
   111  
   112  A note on RunE and SilenceUsage:
   113  
   114  We prefer using RunE over Run for the entrypoint to our subcommands, because it
   115  allows us return errors back up to the vtctldclient main function and do error
   116  handling, logging, and exit-code management once, in one place, rather than on a
   117  per-command basis. However, cobra treats errors returned from a command's RunE
   118  as usage errors, and therefore will print the command's full usage text to
   119  stderr when RunE returns non-nil, in addition to propagating that error back up
   120  to the result of the root command's Execute() method. This is decidedly not what
   121  we want. There is no plan to address this in cobra v1. [1]
   122  
   123  The suggested workaround for this issue is to set SilenceUsage: true, either on
   124  the root command or on every subcommand individually. This also does not work
   125  for vtctldclient, because not every flag can be parsed during pflag.Parse time,
   126  and for certain flags (mutually exclusive options, optional flags that require
   127  other flags to be set with them, etc) we do additional parsing and validation of
   128  flags in an individual subcommand. We want errors during this phase to be
   129  treated as usage errors, so setting SilenceUsage=true before this point would
   130  not cause usage text to be printed for us.
   131  
   132  So, for us, we want to individually set cmd.SilenceUsage = true at *particular
   133  points* in each command, dependending on whether that command needs to do
   134  an additional parse & validation pass. In most cases, the command does not need
   135  to post-validate its options, and can set cmd.SilencUsage = true as their first
   136  line. We feel, though, that a line that reads "SilenceUsage = true" to be
   137  potentially confusing in how it reads. A maintainer without sufficient context
   138  may read this and say "Silence usage? We don't want that" and remove the lines,
   139  so we provide a wrapper function that communicates intent, cli.FinishedParsing,
   140  that each subcommand should call when they have transitioned from the parsing &
   141  validation phase of their entrypoint to the actual logic.
   142  
   143  [1]: https://github.com/spf13/cobra/issues/340
   144  */
   145  package command