github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/cli/flags.go (about)

     1  // Copyright 2015 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package cli
    12  
    13  import (
    14  	"flag"
    15  	"net"
    16  	"path/filepath"
    17  	"regexp"
    18  	"strings"
    19  	"time"
    20  
    21  	"github.com/cockroachdb/cockroach/pkg/base"
    22  	"github.com/cockroachdb/cockroach/pkg/cli/cliflags"
    23  	"github.com/cockroachdb/cockroach/pkg/security"
    24  	"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
    25  	"github.com/cockroachdb/cockroach/pkg/util/envutil"
    26  	"github.com/cockroachdb/cockroach/pkg/util/humanizeutil"
    27  	"github.com/cockroachdb/cockroach/pkg/util/log"
    28  	"github.com/cockroachdb/cockroach/pkg/util/log/logflags"
    29  	"github.com/cockroachdb/cockroach/pkg/util/netutil"
    30  	"github.com/cockroachdb/errors"
    31  	"github.com/spf13/cobra"
    32  	"github.com/spf13/pflag"
    33  )
    34  
    35  // special global variables used by flag variable definitions below.
    36  // These do not correspond directly to the configuration parameters
    37  // used as input by the CLI commands (these are defined in context
    38  // structs in context.go). Instead, they are used at the *end* of
    39  // command-line parsing to override the defaults in the context
    40  // structs.
    41  //
    42  // Corollaries:
    43  // - it would be a programming error to access these variables directly
    44  //   outside of this file (flags.go)
    45  // - the underlying context parameters must receive defaults in
    46  //   initCLIDefaults() even when they are otherwise overridden by the
    47  //   flags logic, because some tests to not use the flag logic at all.
    48  var serverListenPort, serverSocketDir string
    49  var serverAdvertiseAddr, serverAdvertisePort string
    50  var serverSQLAddr, serverSQLPort string
    51  var serverSQLAdvertiseAddr, serverSQLAdvertisePort string
    52  var serverHTTPAddr, serverHTTPPort string
    53  var localityAdvertiseHosts localityList
    54  
    55  // initPreFlagsDefaults initializes the values of the global variables
    56  // defined above.
    57  func initPreFlagsDefaults() {
    58  	serverListenPort = base.DefaultPort
    59  	serverSocketDir = ""
    60  	serverAdvertiseAddr = ""
    61  	serverAdvertisePort = ""
    62  
    63  	serverSQLAddr = ""
    64  	serverSQLPort = ""
    65  	serverSQLAdvertiseAddr = ""
    66  	serverSQLAdvertisePort = ""
    67  
    68  	serverHTTPAddr = ""
    69  	serverHTTPPort = base.DefaultHTTPPort
    70  
    71  	localityAdvertiseHosts = localityList{}
    72  }
    73  
    74  // AddPersistentPreRunE add 'fn' as a persistent pre-run function to 'cmd'.
    75  // If the command has an existing pre-run function, it is saved and will be called
    76  // at the beginning of 'fn'.
    77  // This allows an arbitrary number of pre-run functions with ordering based
    78  // on the order in which AddPersistentPreRunE is called (usually package init order).
    79  func AddPersistentPreRunE(cmd *cobra.Command, fn func(*cobra.Command, []string) error) {
    80  	// Save any existing hooks.
    81  	wrapped := cmd.PersistentPreRunE
    82  
    83  	cmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
    84  		// Run the previous hook if it exists.
    85  		if wrapped != nil {
    86  			if err := wrapped(cmd, args); err != nil {
    87  				return err
    88  			}
    89  		}
    90  
    91  		// Now we can call the new function.
    92  		return fn(cmd, args)
    93  	}
    94  }
    95  
    96  // StringFlag creates a string flag and registers it with the FlagSet.
    97  func StringFlag(f *pflag.FlagSet, valPtr *string, flagInfo cliflags.FlagInfo, defaultVal string) {
    98  	f.StringVarP(valPtr, flagInfo.Name, flagInfo.Shorthand, defaultVal, flagInfo.Usage())
    99  
   100  	registerEnvVarDefault(f, flagInfo)
   101  }
   102  
   103  // IntFlag creates an int flag and registers it with the FlagSet.
   104  func IntFlag(f *pflag.FlagSet, valPtr *int, flagInfo cliflags.FlagInfo, defaultVal int) {
   105  	f.IntVarP(valPtr, flagInfo.Name, flagInfo.Shorthand, defaultVal, flagInfo.Usage())
   106  
   107  	registerEnvVarDefault(f, flagInfo)
   108  }
   109  
   110  // BoolFlag creates a bool flag and registers it with the FlagSet.
   111  func BoolFlag(f *pflag.FlagSet, valPtr *bool, flagInfo cliflags.FlagInfo, defaultVal bool) {
   112  	f.BoolVarP(valPtr, flagInfo.Name, flagInfo.Shorthand, defaultVal, flagInfo.Usage())
   113  
   114  	registerEnvVarDefault(f, flagInfo)
   115  }
   116  
   117  // DurationFlag creates a duration flag and registers it with the FlagSet.
   118  func DurationFlag(
   119  	f *pflag.FlagSet, valPtr *time.Duration, flagInfo cliflags.FlagInfo, defaultVal time.Duration,
   120  ) {
   121  	f.DurationVarP(valPtr, flagInfo.Name, flagInfo.Shorthand, defaultVal, flagInfo.Usage())
   122  
   123  	registerEnvVarDefault(f, flagInfo)
   124  }
   125  
   126  // VarFlag creates a custom-variable flag and registers it with the FlagSet.
   127  func VarFlag(f *pflag.FlagSet, value pflag.Value, flagInfo cliflags.FlagInfo) {
   128  	f.VarP(value, flagInfo.Name, flagInfo.Shorthand, flagInfo.Usage())
   129  
   130  	registerEnvVarDefault(f, flagInfo)
   131  }
   132  
   133  // StringSlice creates a string slice flag and registers it with the FlagSet.
   134  func StringSlice(
   135  	f *pflag.FlagSet, valPtr *[]string, flagInfo cliflags.FlagInfo, defaultVal []string,
   136  ) {
   137  	f.StringSliceVar(valPtr, flagInfo.Name, defaultVal, flagInfo.Usage())
   138  	registerEnvVarDefault(f, flagInfo)
   139  }
   140  
   141  // aliasStrVar wraps a string configuration option and is meant
   142  // to be used in addition to / next to another flag that targets the
   143  // same option. It does not implement "default values" so that the
   144  // main flag can perform the default logic.
   145  type aliasStrVar struct{ p *string }
   146  
   147  // String implements the pflag.Value interface.
   148  func (a aliasStrVar) String() string { return "" }
   149  
   150  // Set implements the pflag.Value interface.
   151  func (a aliasStrVar) Set(v string) error {
   152  	if v != "" {
   153  		*a.p = v
   154  	}
   155  	return nil
   156  }
   157  
   158  // Type implements the pflag.Value interface.
   159  func (a aliasStrVar) Type() string { return "string" }
   160  
   161  // addrSetter wraps a address/port configuration option pair and
   162  // enables setting them both with a single command-line flag.
   163  type addrSetter struct {
   164  	addr *string
   165  	port *string
   166  }
   167  
   168  // String implements the pflag.Value interface.
   169  func (a addrSetter) String() string {
   170  	return net.JoinHostPort(*a.addr, *a.port)
   171  }
   172  
   173  // Type implements the pflag.Value interface.
   174  func (a addrSetter) Type() string { return "<addr/host>[:<port>]" }
   175  
   176  // Set implements the pflag.Value interface.
   177  func (a addrSetter) Set(v string) error {
   178  	addr, port, err := netutil.SplitHostPort(v, *a.port)
   179  	if err != nil {
   180  		return err
   181  	}
   182  	*a.addr = addr
   183  	*a.port = port
   184  	return nil
   185  }
   186  
   187  // clusterNameSetter wraps the cluster name variable
   188  // and verifies its format during configuration.
   189  type clusterNameSetter struct {
   190  	clusterName *string
   191  }
   192  
   193  // String implements the pflag.Value interface.
   194  func (a clusterNameSetter) String() string { return *a.clusterName }
   195  
   196  // Type implements the pflag.Value interface.
   197  func (a clusterNameSetter) Type() string { return "<identifier>" }
   198  
   199  // Set implements the pflag.Value interface.
   200  func (a clusterNameSetter) Set(v string) error {
   201  	if v == "" {
   202  		return errors.New("cluster name cannot be empty")
   203  	}
   204  	if len(v) > maxClusterNameLength {
   205  		return errors.Newf(`cluster name can contain at most %d characters`, maxClusterNameLength)
   206  	}
   207  	if !clusterNameRe.MatchString(v) {
   208  		return errClusterNameInvalidFormat
   209  	}
   210  	*a.clusterName = v
   211  	return nil
   212  }
   213  
   214  var errClusterNameInvalidFormat = errors.New(`cluster name must contain only letters, numbers or the "-" and "." characters`)
   215  
   216  // clusterNameRe matches valid cluster names.
   217  // For example, "a", "a123" and "a-b" are OK,
   218  // but "0123", "a-" and "123a" are not OK.
   219  var clusterNameRe = regexp.MustCompile(`^[a-zA-Z](?:[-a-zA-Z0-9]*[a-zA-Z0-9]|)$`)
   220  
   221  const maxClusterNameLength = 256
   222  
   223  const backgroundEnvVar = "COCKROACH_BACKGROUND_RESTART"
   224  
   225  // flagSetForCmd is a replacement for cmd.Flag() that properly merges
   226  // persistent and local flags, until the upstream bug
   227  // https://github.com/spf13/cobra/issues/961 has been fixed.
   228  func flagSetForCmd(cmd *cobra.Command) *pflag.FlagSet {
   229  	_ = cmd.LocalFlags() // force merge persistent+local flags
   230  	return cmd.Flags()
   231  }
   232  
   233  func init() {
   234  	initCLIDefaults()
   235  	defer func() {
   236  		if err := processEnvVarDefaults(); err != nil {
   237  			panic(err)
   238  		}
   239  	}()
   240  
   241  	// Every command but start will inherit the following setting.
   242  	AddPersistentPreRunE(cockroachCmd, func(cmd *cobra.Command, _ []string) error {
   243  		if err := extraClientFlagInit(); err != nil {
   244  			return err
   245  		}
   246  		return setDefaultStderrVerbosity(cmd, log.Severity_WARNING)
   247  	})
   248  
   249  	// Add a pre-run command for `start` and `start-single-node`.
   250  	for _, cmd := range StartCmds {
   251  		AddPersistentPreRunE(cmd, func(cmd *cobra.Command, _ []string) error {
   252  			// Finalize the configuration of network and logging settings.
   253  			if err := extraServerFlagInit(cmd); err != nil {
   254  				return err
   255  			}
   256  			return setDefaultStderrVerbosity(cmd, log.Severity_INFO)
   257  		})
   258  	}
   259  
   260  	// Map any flags registered in the standard "flag" package into the
   261  	// top-level cockroach command.
   262  	pf := cockroachCmd.PersistentFlags()
   263  	flag.VisitAll(func(f *flag.Flag) {
   264  		flag := pflag.PFlagFromGoFlag(f)
   265  		// TODO(peter): Decide if we want to make the lightstep flags visible.
   266  		if strings.HasPrefix(flag.Name, "lightstep_") {
   267  			flag.Hidden = true
   268  		}
   269  		if strings.HasPrefix(flag.Name, "httptest.") {
   270  			// If we test the cli commands in tests, we may end up transitively
   271  			// importing httptest, for example via `testify/assert`. Make sure
   272  			// it doesn't show up in the output or it will confuse tests.
   273  			flag.Hidden = true
   274  		}
   275  		switch flag.Name {
   276  		case logflags.NoRedirectStderrName:
   277  			flag.Hidden = true
   278  		case logflags.ShowLogsName:
   279  			flag.Hidden = true
   280  		case logflags.LogToStderrName:
   281  			// The actual default value for --logtostderr is overridden in
   282  			// cli.Main. We don't override it here as doing so would affect all of
   283  			// the cli tests and any package which depends on cli. The following line
   284  			// is only overriding the default value for the pflag package (and what
   285  			// is visible in help text), not the stdlib flag value.
   286  			flag.DefValue = "NONE"
   287  		case logflags.LogDirName,
   288  			logflags.LogFileMaxSizeName,
   289  			logflags.LogFilesCombinedMaxSizeName,
   290  			logflags.LogFileVerbosityThresholdName:
   291  			// The --log-dir* and --log-file* flags are specified only for the
   292  			// `start` and `demo` commands.
   293  			return
   294  		}
   295  		pf.AddFlag(flag)
   296  	})
   297  
   298  	// When a flag is specified but without a value, pflag assigns its
   299  	// NoOptDefVal to it via Set(). This is also the value used to
   300  	// generate the implicit assigned value in the usage text
   301  	// (e.g. "--logtostderr[=XXXXX]"). We can't populate a real default
   302  	// unfortunately, because the default depends on which command is
   303  	// run (`start` vs. the rest), and pflag does not support
   304  	// per-command NoOptDefVals. So we need some sentinel value here
   305  	// that we can recognize when setDefaultStderrVerbosity() is called
   306  	// after argument parsing. We could use UNKNOWN, but to ensure that
   307  	// the usage text is somewhat less confusing to the user, we use the
   308  	// special severity value DEFAULT instead.
   309  	pf.Lookup(logflags.LogToStderrName).NoOptDefVal = log.Severity_DEFAULT.String()
   310  
   311  	// Remember we are starting in the background as the `start` command will
   312  	// avoid printing some messages to standard output in that case.
   313  	_, startCtx.inBackground = envutil.EnvString(backgroundEnvVar, 1)
   314  
   315  	for _, cmd := range StartCmds {
   316  		f := cmd.Flags()
   317  
   318  		// Server flags.
   319  		VarFlag(f, addrSetter{&startCtx.serverListenAddr, &serverListenPort}, cliflags.ListenAddr)
   320  		VarFlag(f, addrSetter{&serverAdvertiseAddr, &serverAdvertisePort}, cliflags.AdvertiseAddr)
   321  		VarFlag(f, addrSetter{&serverSQLAddr, &serverSQLPort}, cliflags.ListenSQLAddr)
   322  		VarFlag(f, addrSetter{&serverSQLAdvertiseAddr, &serverSQLAdvertisePort}, cliflags.SQLAdvertiseAddr)
   323  		VarFlag(f, addrSetter{&serverHTTPAddr, &serverHTTPPort}, cliflags.ListenHTTPAddr)
   324  		StringFlag(f, &serverSocketDir, cliflags.SocketDir, serverSocketDir)
   325  		// --socket is deprecated as of 20.1.
   326  		// TODO(knz): remove in 20.2.
   327  		StringFlag(f, &serverCfg.SocketFile, cliflags.Socket, serverCfg.SocketFile)
   328  		_ = f.MarkDeprecated(cliflags.Socket.Name, "use the --socket-dir and --listen-addr flags instead")
   329  		BoolFlag(f, &startCtx.unencryptedLocalhostHTTP, cliflags.UnencryptedLocalhostHTTP, startCtx.unencryptedLocalhostHTTP)
   330  
   331  		// Backward-compatibility flags.
   332  
   333  		// These are deprecated but until we have qualitatively new
   334  		// functionality in the flags above, there is no need to nudge the
   335  		// user away from them with a deprecation warning. So we keep
   336  		// them, but hidden from docs so that they don't appear as
   337  		// redundant with the main flags.
   338  		VarFlag(f, aliasStrVar{&startCtx.serverListenAddr}, cliflags.ServerHost)
   339  		_ = f.MarkHidden(cliflags.ServerHost.Name)
   340  		VarFlag(f, aliasStrVar{&serverListenPort}, cliflags.ServerPort)
   341  		_ = f.MarkHidden(cliflags.ServerPort.Name)
   342  
   343  		VarFlag(f, aliasStrVar{&serverAdvertiseAddr}, cliflags.AdvertiseHost)
   344  		_ = f.MarkHidden(cliflags.AdvertiseHost.Name)
   345  		VarFlag(f, aliasStrVar{&serverAdvertisePort}, cliflags.AdvertisePort)
   346  		_ = f.MarkHidden(cliflags.AdvertisePort.Name)
   347  
   348  		VarFlag(f, aliasStrVar{&serverHTTPAddr}, cliflags.ListenHTTPAddrAlias)
   349  		_ = f.MarkHidden(cliflags.ListenHTTPAddrAlias.Name)
   350  		VarFlag(f, aliasStrVar{&serverHTTPPort}, cliflags.ListenHTTPPort)
   351  		_ = f.MarkHidden(cliflags.ListenHTTPPort.Name)
   352  
   353  		// More server flags.
   354  
   355  		VarFlag(f, &localityAdvertiseHosts, cliflags.LocalityAdvertiseAddr)
   356  
   357  		StringFlag(f, &serverCfg.Attrs, cliflags.Attrs, serverCfg.Attrs)
   358  		VarFlag(f, &serverCfg.Locality, cliflags.Locality)
   359  
   360  		VarFlag(f, &serverCfg.Stores, cliflags.Store)
   361  		VarFlag(f, &serverCfg.StorageEngine, cliflags.StorageEngine)
   362  		VarFlag(f, &serverCfg.MaxOffset, cliflags.MaxOffset)
   363  		StringFlag(f, &serverCfg.ClockDevicePath, cliflags.ClockDevice, "")
   364  
   365  		StringFlag(f, &startCtx.listeningURLFile, cliflags.ListeningURLFile, startCtx.listeningURLFile)
   366  
   367  		StringFlag(f, &startCtx.pidFile, cliflags.PIDFile, startCtx.pidFile)
   368  		StringFlag(f, &startCtx.geoLibsDir, cliflags.GeoLibsDir, startCtx.geoLibsDir)
   369  
   370  		// Use a separate variable to store the value of ServerInsecure.
   371  		// We share the default with the ClientInsecure flag.
   372  		BoolFlag(f, &startCtx.serverInsecure, cliflags.ServerInsecure, startCtx.serverInsecure)
   373  
   374  		// Enable/disable various external storage endpoints.
   375  		serverCfg.ExternalIODirConfig = base.ExternalIODirConfig{}
   376  		BoolFlag(f, &serverCfg.ExternalIODirConfig.DisableHTTP,
   377  			cliflags.ExternalIODisableHTTP, false)
   378  		BoolFlag(f, &serverCfg.ExternalIODirConfig.DisableImplicitCredentials,
   379  			cliflags.ExtenralIODisableImplicitCredentials, false)
   380  
   381  		// Certificates directory. Use a server-specific flag and value to ignore environment
   382  		// variables, but share the same default.
   383  		StringFlag(f, &startCtx.serverSSLCertsDir, cliflags.ServerCertsDir, startCtx.serverSSLCertsDir)
   384  
   385  		// Certificate principal map.
   386  		StringSlice(f, &startCtx.serverCertPrincipalMap,
   387  			cliflags.CertPrincipalMap, startCtx.serverCertPrincipalMap)
   388  
   389  		// Cluster joining flags. We need to enable this both for 'start'
   390  		// and 'start-single-node' although the latter does not support
   391  		// --join, because it delegates its logic to that of 'start', and
   392  		// 'start' will check that the flag is properly defined.
   393  		VarFlag(f, &serverCfg.JoinList, cliflags.Join)
   394  		BoolFlag(f, &serverCfg.JoinPreferSRVRecords, cliflags.JoinPreferSRVRecords, serverCfg.JoinPreferSRVRecords)
   395  		VarFlag(f, clusterNameSetter{&baseCfg.ClusterName}, cliflags.ClusterName)
   396  		BoolFlag(f, &baseCfg.DisableClusterNameVerification,
   397  			cliflags.DisableClusterNameVerification, baseCfg.DisableClusterNameVerification)
   398  		if cmd == startSingleNodeCmd {
   399  			// Even though all server flags are supported for
   400  			// 'start-single-node', we intend that command to be used by
   401  			// beginners / developers running on a single machine. To
   402  			// enhance the UX, we hide the flags since they are not directly
   403  			// relevant when running a single node.
   404  			_ = f.MarkHidden(cliflags.Join.Name)
   405  			_ = f.MarkHidden(cliflags.ClusterName.Name)
   406  			_ = f.MarkHidden(cliflags.DisableClusterNameVerification.Name)
   407  			_ = f.MarkHidden(cliflags.MaxOffset.Name)
   408  			_ = f.MarkHidden(cliflags.LocalityAdvertiseAddr.Name)
   409  			_ = f.MarkHidden(cliflags.AdvertiseAddr.Name)
   410  			_ = f.MarkHidden(cliflags.SQLAdvertiseAddr.Name)
   411  		}
   412  
   413  		// Engine flags.
   414  		VarFlag(f, cacheSizeValue, cliflags.Cache)
   415  		VarFlag(f, sqlSizeValue, cliflags.SQLMem)
   416  		// N.B. diskTempStorageSizeValue.ResolvePercentage() will be called after
   417  		// the stores flag has been parsed and the storage device that a percentage
   418  		// refers to becomes known.
   419  		VarFlag(f, diskTempStorageSizeValue, cliflags.SQLTempStorage)
   420  		StringFlag(f, &startCtx.tempDir, cliflags.TempDir, startCtx.tempDir)
   421  		StringFlag(f, &startCtx.externalIODir, cliflags.ExternalIODir, startCtx.externalIODir)
   422  
   423  		VarFlag(f, serverCfg.AuditLogDirName, cliflags.SQLAuditLogDirName)
   424  	}
   425  
   426  	// Log flags.
   427  	logCmds := append(StartCmds, demoCmd)
   428  	logCmds = append(logCmds, demoCmd.Commands()...)
   429  	for _, cmd := range logCmds {
   430  		f := cmd.Flags()
   431  		VarFlag(f, &startCtx.logDir, cliflags.LogDir)
   432  		VarFlag(f,
   433  			pflag.PFlagFromGoFlag(flag.Lookup(logflags.LogFilesCombinedMaxSizeName)).Value,
   434  			cliflags.LogDirMaxSize)
   435  		VarFlag(f,
   436  			pflag.PFlagFromGoFlag(flag.Lookup(logflags.LogFileMaxSizeName)).Value,
   437  			cliflags.LogFileMaxSize)
   438  		VarFlag(f,
   439  			pflag.PFlagFromGoFlag(flag.Lookup(logflags.LogFileVerbosityThresholdName)).Value,
   440  			cliflags.LogFileVerbosity)
   441  	}
   442  
   443  	for _, cmd := range certCmds {
   444  		f := cmd.Flags()
   445  		// All certs commands need the certificate directory.
   446  		StringFlag(f, &baseCfg.SSLCertsDir, cliflags.CertsDir, baseCfg.SSLCertsDir)
   447  		// All certs commands get the certificate principal map.
   448  		StringSlice(f, &cliCtx.certPrincipalMap,
   449  			cliflags.CertPrincipalMap, cliCtx.certPrincipalMap)
   450  	}
   451  
   452  	for _, cmd := range []*cobra.Command{createCACertCmd, createClientCACertCmd} {
   453  		f := cmd.Flags()
   454  		// CA certificates have a longer expiration time.
   455  		DurationFlag(f, &caCertificateLifetime, cliflags.CertificateLifetime, defaultCALifetime)
   456  		// The CA key can be re-used if it exists.
   457  		BoolFlag(f, &allowCAKeyReuse, cliflags.AllowCAKeyReuse, false)
   458  	}
   459  
   460  	for _, cmd := range []*cobra.Command{createNodeCertCmd, createClientCertCmd} {
   461  		f := cmd.Flags()
   462  		DurationFlag(f, &certificateLifetime, cliflags.CertificateLifetime, defaultCertLifetime)
   463  	}
   464  
   465  	// The remaining flags are shared between all cert-generating functions.
   466  	for _, cmd := range []*cobra.Command{createCACertCmd, createClientCACertCmd, createNodeCertCmd, createClientCertCmd} {
   467  		f := cmd.Flags()
   468  		StringFlag(f, &baseCfg.SSLCAKey, cliflags.CAKey, baseCfg.SSLCAKey)
   469  		IntFlag(f, &keySize, cliflags.KeySize, defaultKeySize)
   470  		BoolFlag(f, &overwriteFiles, cliflags.OverwriteFiles, false)
   471  	}
   472  	// PKCS8 key format is only available for the client cert command.
   473  	BoolFlag(createClientCertCmd.Flags(), &generatePKCS8Key, cliflags.GeneratePKCS8Key, false)
   474  
   475  	clientCmds := []*cobra.Command{
   476  		debugGossipValuesCmd,
   477  		debugTimeSeriesDumpCmd,
   478  		debugZipCmd,
   479  		dumpCmd,
   480  		genHAProxyCmd,
   481  		initCmd,
   482  		quitCmd,
   483  		sqlShellCmd,
   484  		/* StartCmds are covered above */
   485  	}
   486  	clientCmds = append(clientCmds, authCmds...)
   487  	clientCmds = append(clientCmds, nodeCmds...)
   488  	clientCmds = append(clientCmds, systemBenchCmds...)
   489  	clientCmds = append(clientCmds, nodeLocalCmds...)
   490  	for _, cmd := range clientCmds {
   491  		f := cmd.PersistentFlags()
   492  		VarFlag(f, addrSetter{&cliCtx.clientConnHost, &cliCtx.clientConnPort}, cliflags.ClientHost)
   493  		StringFlag(f, &cliCtx.clientConnPort, cliflags.ClientPort, cliCtx.clientConnPort)
   494  		_ = f.MarkHidden(cliflags.ClientPort.Name)
   495  
   496  		BoolFlag(f, &baseCfg.Insecure, cliflags.ClientInsecure, baseCfg.Insecure)
   497  
   498  		// Certificate flags.
   499  		StringFlag(f, &baseCfg.SSLCertsDir, cliflags.CertsDir, baseCfg.SSLCertsDir)
   500  		// Certificate principal map.
   501  		StringSlice(f, &cliCtx.certPrincipalMap,
   502  			cliflags.CertPrincipalMap, cliCtx.certPrincipalMap)
   503  	}
   504  
   505  	// Auth commands.
   506  	{
   507  		f := loginCmd.Flags()
   508  		DurationFlag(f, &authCtx.validityPeriod, cliflags.AuthTokenValidityPeriod, authCtx.validityPeriod)
   509  		BoolFlag(f, &authCtx.onlyCookie, cliflags.OnlyCookie, authCtx.onlyCookie)
   510  	}
   511  
   512  	timeoutCmds := []*cobra.Command{
   513  		statusNodeCmd,
   514  		lsNodesCmd,
   515  		debugZipCmd,
   516  		// If you add something here, make sure the actual implementation
   517  		// of the command uses `cmdTimeoutContext(.)` or it will ignore
   518  		// the timeout.
   519  	}
   520  
   521  	for _, cmd := range timeoutCmds {
   522  		DurationFlag(cmd.Flags(), &cliCtx.cmdTimeout, cliflags.Timeout, cliCtx.cmdTimeout)
   523  	}
   524  
   525  	// Node Status command.
   526  	{
   527  		f := statusNodeCmd.Flags()
   528  		BoolFlag(f, &nodeCtx.statusShowRanges, cliflags.NodeRanges, nodeCtx.statusShowRanges)
   529  		BoolFlag(f, &nodeCtx.statusShowStats, cliflags.NodeStats, nodeCtx.statusShowStats)
   530  		BoolFlag(f, &nodeCtx.statusShowAll, cliflags.NodeAll, nodeCtx.statusShowAll)
   531  		BoolFlag(f, &nodeCtx.statusShowDecommission, cliflags.NodeDecommission, nodeCtx.statusShowDecommission)
   532  	}
   533  
   534  	// HDD Bench command.
   535  	{
   536  		f := seqWriteBench.Flags()
   537  		VarFlag(f, humanizeutil.NewBytesValue(&systemBenchCtx.writeSize), cliflags.WriteSize)
   538  		VarFlag(f, humanizeutil.NewBytesValue(&systemBenchCtx.syncInterval), cliflags.SyncInterval)
   539  	}
   540  
   541  	// Network Bench command.
   542  	{
   543  		f := networkBench.Flags()
   544  		BoolFlag(f, &networkBenchCtx.server, cliflags.BenchServer, networkBenchCtx.server)
   545  		IntFlag(f, &networkBenchCtx.port, cliflags.BenchPort, networkBenchCtx.port)
   546  		StringSlice(f, &networkBenchCtx.addresses, cliflags.BenchAddresses, networkBenchCtx.addresses)
   547  		BoolFlag(f, &networkBenchCtx.latency, cliflags.BenchLatency, networkBenchCtx.latency)
   548  	}
   549  
   550  	// Bench command.
   551  	{
   552  		for _, cmd := range systemBenchCmds {
   553  			f := cmd.Flags()
   554  			IntFlag(f, &systemBenchCtx.concurrency, cliflags.BenchConcurrency, systemBenchCtx.concurrency)
   555  			DurationFlag(f, &systemBenchCtx.duration, cliflags.BenchDuration, systemBenchCtx.duration)
   556  			StringFlag(f, &systemBenchCtx.tempDir, cliflags.TempDir, systemBenchCtx.tempDir)
   557  		}
   558  	}
   559  
   560  	// Zip command.
   561  	{
   562  		f := debugZipCmd.Flags()
   563  		VarFlag(f, &zipCtx.nodes.inclusive, cliflags.ZipNodes)
   564  		VarFlag(f, &zipCtx.nodes.exclusive, cliflags.ZipExcludeNodes)
   565  	}
   566  
   567  	// Decommission command.
   568  	VarFlag(decommissionNodeCmd.Flags(), &nodeCtx.nodeDecommissionWait, cliflags.Wait)
   569  
   570  	// Quit and node drain commands.
   571  	for _, cmd := range []*cobra.Command{quitCmd, drainNodeCmd} {
   572  		f := cmd.Flags()
   573  		DurationFlag(f, &quitCtx.drainWait, cliflags.DrainWait, quitCtx.drainWait)
   574  	}
   575  
   576  	// SQL and demo commands.
   577  	for _, cmd := range append([]*cobra.Command{sqlShellCmd, demoCmd}, demoCmd.Commands()...) {
   578  		f := cmd.Flags()
   579  		VarFlag(f, &sqlCtx.setStmts, cliflags.Set)
   580  		VarFlag(f, &sqlCtx.execStmts, cliflags.Execute)
   581  		DurationFlag(f, &sqlCtx.repeatDelay, cliflags.Watch, sqlCtx.repeatDelay)
   582  		BoolFlag(f, &sqlCtx.safeUpdates, cliflags.SafeUpdates, sqlCtx.safeUpdates)
   583  		BoolFlag(f, &sqlCtx.debugMode, cliflags.CliDebugMode, sqlCtx.debugMode)
   584  	}
   585  
   586  	VarFlag(dumpCmd.Flags(), &dumpCtx.dumpMode, cliflags.DumpMode)
   587  	StringFlag(dumpCmd.Flags(), &dumpCtx.asOf, cliflags.DumpTime, dumpCtx.asOf)
   588  	BoolFlag(dumpCmd.Flags(), &dumpCtx.dumpAll, cliflags.DumpAll, dumpCtx.dumpAll)
   589  
   590  	// Commands that establish a SQL connection.
   591  	sqlCmds := []*cobra.Command{sqlShellCmd, dumpCmd, demoCmd}
   592  	sqlCmds = append(sqlCmds, authCmds...)
   593  	sqlCmds = append(sqlCmds, demoCmd.Commands()...)
   594  	sqlCmds = append(sqlCmds, nodeLocalCmds...)
   595  	for _, cmd := range sqlCmds {
   596  		f := cmd.Flags()
   597  		BoolFlag(f, &sqlCtx.echo, cliflags.EchoSQL, sqlCtx.echo)
   598  
   599  		if cmd != demoCmd {
   600  			VarFlag(f, urlParser{cmd, &cliCtx, false /* strictSSL */}, cliflags.URL)
   601  			StringFlag(f, &cliCtx.sqlConnUser, cliflags.User, cliCtx.sqlConnUser)
   602  
   603  			// Even though SQL commands take their connection parameters via
   604  			// --url / --user (see above), the urlParser{} struct internally
   605  			// needs the ClientHost and ClientPort flags to be defined -
   606  			// even if they are invisible - due to the way initialization from
   607  			// env vars is implemented.
   608  			//
   609  			// TODO(knz): if/when env var option initialization is deferred
   610  			// to parse time, this can be removed.
   611  			VarFlag(f, addrSetter{&cliCtx.clientConnHost, &cliCtx.clientConnPort}, cliflags.ClientHost)
   612  			_ = f.MarkHidden(cliflags.ClientHost.Name)
   613  			StringFlag(f, &cliCtx.clientConnPort, cliflags.ClientPort, cliCtx.clientConnPort)
   614  			_ = f.MarkHidden(cliflags.ClientPort.Name)
   615  
   616  		}
   617  
   618  		if cmd == sqlShellCmd {
   619  			StringFlag(f, &cliCtx.sqlConnDBName, cliflags.Database, cliCtx.sqlConnDBName)
   620  		}
   621  	}
   622  
   623  	// Make the non-SQL client commands also recognize --url in strict SSL mode
   624  	// and ensure they can connect to clusters that use a cluster-name.
   625  	for _, cmd := range clientCmds {
   626  		if f := flagSetForCmd(cmd).Lookup(cliflags.URL.Name); f != nil {
   627  			// --url already registered above, nothing to do.
   628  			continue
   629  		}
   630  		f := cmd.PersistentFlags()
   631  		VarFlag(f, urlParser{cmd, &cliCtx, true /* strictSSL */}, cliflags.URL)
   632  		VarFlag(f, clusterNameSetter{&baseCfg.ClusterName}, cliflags.ClusterName)
   633  		BoolFlag(f, &baseCfg.DisableClusterNameVerification,
   634  			cliflags.DisableClusterNameVerification, baseCfg.DisableClusterNameVerification)
   635  	}
   636  
   637  	// Commands that print tables.
   638  	tableOutputCommands := append(
   639  		[]*cobra.Command{sqlShellCmd, genSettingsListCmd, demoCmd},
   640  		demoCmd.Commands()...)
   641  	tableOutputCommands = append(tableOutputCommands, nodeCmds...)
   642  	tableOutputCommands = append(tableOutputCommands, authCmds...)
   643  
   644  	// By default, these commands print their output as pretty-formatted
   645  	// tables on terminals, and TSV when redirected to a file. The user
   646  	// can override with --format.
   647  	// By default, query times are not displayed. The default is overridden
   648  	// in the CLI shell.
   649  	for _, cmd := range tableOutputCommands {
   650  		f := cmd.PersistentFlags()
   651  		VarFlag(f, &cliCtx.tableDisplayFormat, cliflags.TableDisplayFormat)
   652  	}
   653  
   654  	// demo command.
   655  	demoFlags := demoCmd.PersistentFlags()
   656  	// We add this command as a persistent flag so you can do stuff like
   657  	// ./cockroach demo movr --nodes=3.
   658  	IntFlag(demoFlags, &demoCtx.nodes, cliflags.DemoNodes, demoCtx.nodes)
   659  	BoolFlag(demoFlags, &demoCtx.runWorkload, cliflags.RunDemoWorkload, demoCtx.runWorkload)
   660  	VarFlag(demoFlags, &demoCtx.localities, cliflags.DemoNodeLocality)
   661  	BoolFlag(demoFlags, &demoCtx.geoPartitionedReplicas, cliflags.DemoGeoPartitionedReplicas, demoCtx.geoPartitionedReplicas)
   662  	VarFlag(demoFlags, demoNodeSQLMemSizeValue, cliflags.DemoNodeSQLMemSize)
   663  	VarFlag(demoFlags, demoNodeCacheSizeValue, cliflags.DemoNodeCacheSize)
   664  	BoolFlag(demoFlags, &demoCtx.insecure, cliflags.ClientInsecure, demoCtx.insecure)
   665  	BoolFlag(demoFlags, &demoCtx.disableLicenseAcquisition, cliflags.DemoNoLicense, demoCtx.disableLicenseAcquisition)
   666  	// Mark the --global flag as hidden until we investigate it more.
   667  	BoolFlag(demoFlags, &demoCtx.simulateLatency, cliflags.Global, demoCtx.simulateLatency)
   668  	_ = demoFlags.MarkHidden(cliflags.Global.Name)
   669  	// The --empty flag is only valid for the top level demo command,
   670  	// so we use the regular flag set.
   671  	BoolFlag(demoCmd.Flags(), &demoCtx.useEmptyDatabase, cliflags.UseEmptyDatabase, demoCtx.useEmptyDatabase)
   672  	StringFlag(demoFlags, &demoCtx.geoLibsDir, cliflags.GeoLibsDir, demoCtx.geoLibsDir)
   673  
   674  	// sqlfmt command.
   675  	fmtFlags := sqlfmtCmd.Flags()
   676  	VarFlag(fmtFlags, &sqlfmtCtx.execStmts, cliflags.Execute)
   677  	cfg := tree.DefaultPrettyCfg()
   678  	IntFlag(fmtFlags, &sqlfmtCtx.len, cliflags.SQLFmtLen, cfg.LineWidth)
   679  	BoolFlag(fmtFlags, &sqlfmtCtx.useSpaces, cliflags.SQLFmtSpaces, !cfg.UseTabs)
   680  	IntFlag(fmtFlags, &sqlfmtCtx.tabWidth, cliflags.SQLFmtTabWidth, cfg.TabWidth)
   681  	BoolFlag(fmtFlags, &sqlfmtCtx.noSimplify, cliflags.SQLFmtNoSimplify, !cfg.Simplify)
   682  	BoolFlag(fmtFlags, &sqlfmtCtx.align, cliflags.SQLFmtAlign, (cfg.Align != tree.PrettyNoAlign))
   683  
   684  	// Debug commands.
   685  	{
   686  		f := debugKeysCmd.Flags()
   687  		VarFlag(f, (*mvccKey)(&debugCtx.startKey), cliflags.From)
   688  		VarFlag(f, (*mvccKey)(&debugCtx.endKey), cliflags.To)
   689  		IntFlag(f, &debugCtx.maxResults, cliflags.Limit, debugCtx.maxResults)
   690  		BoolFlag(f, &debugCtx.values, cliflags.Values, debugCtx.values)
   691  		BoolFlag(f, &debugCtx.sizes, cliflags.Sizes, debugCtx.sizes)
   692  	}
   693  	{
   694  		f := debugRangeDataCmd.Flags()
   695  		BoolFlag(f, &debugCtx.replicated, cliflags.Replicated, debugCtx.replicated)
   696  		IntFlag(f, &debugCtx.maxResults, cliflags.Limit, debugCtx.maxResults)
   697  	}
   698  	{
   699  		f := debugGossipValuesCmd.Flags()
   700  		StringFlag(f, &debugCtx.inputFile, cliflags.GossipInputFile, debugCtx.inputFile)
   701  		BoolFlag(f, &debugCtx.printSystemConfig, cliflags.PrintSystemConfig, debugCtx.printSystemConfig)
   702  	}
   703  	{
   704  		f := debugBallastCmd.Flags()
   705  		VarFlag(f, &debugCtx.ballastSize, cliflags.Size)
   706  	}
   707  }
   708  
   709  // processEnvVarDefaults injects the current value of flag-related
   710  // environment variables into the initial value of the settings linked
   711  // to the flags, during initialization and before the command line is
   712  // actually parsed. For example, it will inject the value of
   713  // $COCKROACH_URL into the urlParser object linked to the --url flag.
   714  func processEnvVarDefaults() error {
   715  	for _, d := range envVarDefaults {
   716  		f := d.flagSet.Lookup(d.flagName)
   717  		if f == nil {
   718  			panic(errors.AssertionFailedf("unknown flag: %s", d.flagName))
   719  		}
   720  		var err error
   721  		if url, ok := f.Value.(urlParser); ok {
   722  			// URLs are a special case: they can emit a warning if there's
   723  			// excess configuration for certain commands.
   724  			// Since the env-var initialization is ran for all commands
   725  			// all the time, regardless of which particular command is
   726  			// currently active, we want to silence this warning here.
   727  			//
   728  			// TODO(knz): rework this code to only pull env var values
   729  			// for the current command.
   730  			err = url.setInternal(d.envValue, false /* warn */)
   731  		} else {
   732  			err = d.flagSet.Set(d.flagName, d.envValue)
   733  		}
   734  		if err != nil {
   735  			return errors.Wrapf(err, "setting --%s from %s", d.flagName, d.envVar)
   736  		}
   737  	}
   738  	return nil
   739  }
   740  
   741  // envVarDefault describes a delayed default initialization of the
   742  // setting covered by a flag from the value of an environment
   743  // variable.
   744  type envVarDefault struct {
   745  	envVar   string
   746  	envValue string
   747  	flagName string
   748  	flagSet  *pflag.FlagSet
   749  }
   750  
   751  // envVarDefaults records the initializations from environment variables
   752  // for processing at the end of initialization, before flag parsing.
   753  var envVarDefaults []envVarDefault
   754  
   755  // registerEnvVarDefault registers a deferred initialization of a flag
   756  // from an environment variable.
   757  func registerEnvVarDefault(f *pflag.FlagSet, flagInfo cliflags.FlagInfo) {
   758  	if flagInfo.EnvVar == "" {
   759  		return
   760  	}
   761  	value, set := envutil.EnvString(flagInfo.EnvVar, 2)
   762  	if !set {
   763  		// Env var not set. Nothing to do.
   764  		return
   765  	}
   766  	envVarDefaults = append(envVarDefaults, envVarDefault{
   767  		envVar:   flagInfo.EnvVar,
   768  		envValue: value,
   769  		flagName: flagInfo.Name,
   770  		flagSet:  f,
   771  	})
   772  }
   773  
   774  // extraServerFlagInit configures the server.Config based on the command-line flags.
   775  // It is only called when the command being ran is one of the start commands.
   776  func extraServerFlagInit(cmd *cobra.Command) error {
   777  	if err := security.SetCertPrincipalMap(startCtx.serverCertPrincipalMap); err != nil {
   778  		return err
   779  	}
   780  	serverCfg.User = security.NodeUser
   781  	serverCfg.Insecure = startCtx.serverInsecure
   782  	serverCfg.SSLCertsDir = startCtx.serverSSLCertsDir
   783  
   784  	// Construct the main RPC listen address.
   785  	serverCfg.Addr = net.JoinHostPort(startCtx.serverListenAddr, serverListenPort)
   786  
   787  	fs := flagSetForCmd(cmd)
   788  
   789  	// Construct the socket name, if requested.
   790  	if !fs.Lookup(cliflags.Socket.Name).Changed && fs.Lookup(cliflags.SocketDir.Name).Changed {
   791  		// If --socket (DEPRECATED) was set, then serverCfg.SocketFile is
   792  		// already set and we don't want to change it.
   793  		// However, if --socket-dir is set, then we'll use that.
   794  		// There are two cases:
   795  		// --socket-dir is set and is empty; in this case the user is telling us "disable the socket".
   796  		// is set and non-empty. Then it should be used as specified.
   797  		if serverSocketDir == "" {
   798  			serverCfg.SocketFile = ""
   799  		} else {
   800  			serverCfg.SocketFile = filepath.Join(serverSocketDir, ".s.PGSQL."+serverListenPort)
   801  		}
   802  	}
   803  
   804  	// Fill in the defaults for --advertise-addr.
   805  	if serverAdvertiseAddr == "" {
   806  		serverAdvertiseAddr = startCtx.serverListenAddr
   807  	}
   808  	if serverAdvertisePort == "" {
   809  		serverAdvertisePort = serverListenPort
   810  	}
   811  	serverCfg.AdvertiseAddr = net.JoinHostPort(serverAdvertiseAddr, serverAdvertisePort)
   812  
   813  	// Fill in the defaults for --sql-addr.
   814  	if serverSQLAddr == "" {
   815  		serverSQLAddr = startCtx.serverListenAddr
   816  	}
   817  	if serverSQLPort == "" {
   818  		serverSQLPort = serverListenPort
   819  	}
   820  	serverCfg.SQLAddr = net.JoinHostPort(serverSQLAddr, serverSQLPort)
   821  	serverCfg.SplitListenSQL = fs.Lookup(cliflags.ListenSQLAddr.Name).Changed
   822  
   823  	// Fill in the defaults for --advertise-sql-addr.
   824  	advSpecified := fs.Lookup(cliflags.AdvertiseAddr.Name).Changed ||
   825  		fs.Lookup(cliflags.AdvertiseHost.Name).Changed
   826  	if serverSQLAdvertiseAddr == "" {
   827  		if advSpecified {
   828  			serverSQLAdvertiseAddr = serverAdvertiseAddr
   829  		} else {
   830  			serverSQLAdvertiseAddr = serverSQLAddr
   831  		}
   832  	}
   833  	if serverSQLAdvertisePort == "" {
   834  		if advSpecified && !serverCfg.SplitListenSQL {
   835  			serverSQLAdvertisePort = serverAdvertisePort
   836  		} else {
   837  			serverSQLAdvertisePort = serverSQLPort
   838  		}
   839  	}
   840  	serverCfg.SQLAdvertiseAddr = net.JoinHostPort(serverSQLAdvertiseAddr, serverSQLAdvertisePort)
   841  
   842  	// Fill in the defaults for --http-addr.
   843  	if serverHTTPAddr == "" {
   844  		serverHTTPAddr = startCtx.serverListenAddr
   845  	}
   846  	if startCtx.unencryptedLocalhostHTTP {
   847  		// If --unencrypted-localhost-http was specified, we want to
   848  		// override whatever was specified or derived from other flags for
   849  		// the host part of --http-addr.
   850  		//
   851  		// Before we do so, we'll check whether the user explicitly
   852  		// specified something contradictory, and tell them that's no
   853  		// good.
   854  		if (fs.Lookup(cliflags.ListenHTTPAddr.Name).Changed ||
   855  			fs.Lookup(cliflags.ListenHTTPAddrAlias.Name).Changed) &&
   856  			(serverHTTPAddr != "" && serverHTTPAddr != "localhost") {
   857  			return errors.WithHintf(
   858  				errors.Newf("--unencrypted-localhost-http is incompatible with --http-addr=%s:%s",
   859  					serverHTTPAddr, serverHTTPPort),
   860  				`When --unencrypted-localhost-http is specified, use --http-addr=:%s or omit --http-addr entirely.`, serverHTTPPort)
   861  		}
   862  
   863  		// Now do the override proper.
   864  		serverHTTPAddr = "localhost"
   865  		// We then also tell the server to disable TLS for the HTTP
   866  		// listener.
   867  		serverCfg.DisableTLSForHTTP = true
   868  	}
   869  	serverCfg.HTTPAddr = net.JoinHostPort(serverHTTPAddr, serverHTTPPort)
   870  
   871  	// Fill the advertise port into the locality advertise addresses.
   872  	for i, addr := range localityAdvertiseHosts {
   873  		host, port, err := netutil.SplitHostPort(addr.Address.AddressField, serverAdvertisePort)
   874  		if err != nil {
   875  			return err
   876  		}
   877  		localityAdvertiseHosts[i].Address.AddressField = net.JoinHostPort(host, port)
   878  	}
   879  	serverCfg.LocalityAddresses = localityAdvertiseHosts
   880  
   881  	return nil
   882  }
   883  
   884  func extraClientFlagInit() error {
   885  	if err := security.SetCertPrincipalMap(cliCtx.certPrincipalMap); err != nil {
   886  		return err
   887  	}
   888  	serverCfg.Addr = net.JoinHostPort(cliCtx.clientConnHost, cliCtx.clientConnPort)
   889  	serverCfg.AdvertiseAddr = serverCfg.Addr
   890  	serverCfg.SQLAddr = net.JoinHostPort(cliCtx.clientConnHost, cliCtx.clientConnPort)
   891  	serverCfg.SQLAdvertiseAddr = serverCfg.SQLAddr
   892  	if serverHTTPAddr == "" {
   893  		serverHTTPAddr = startCtx.serverListenAddr
   894  	}
   895  	serverCfg.HTTPAddr = net.JoinHostPort(serverHTTPAddr, serverHTTPPort)
   896  
   897  	// If CLI/SQL debug mode is requested, override the echo mode here,
   898  	// so that the initial client/server handshake reveals the SQL being
   899  	// sent.
   900  	if sqlCtx.debugMode {
   901  		sqlCtx.echo = true
   902  	}
   903  	return nil
   904  }
   905  
   906  func setDefaultStderrVerbosity(cmd *cobra.Command, defaultSeverity log.Severity) error {
   907  	vf := flagSetForCmd(cmd).Lookup(logflags.LogToStderrName)
   908  
   909  	// if `--logtostderr` was not specified and no log directory was
   910  	// set, or `--logtostderr` was specified but without explicit level,
   911  	// then set stderr logging to the level considered default by the
   912  	// specific command.
   913  	if (!vf.Changed && !log.DirSet()) ||
   914  		(vf.Changed && vf.Value.String() == log.Severity_DEFAULT.String()) {
   915  		if err := vf.Value.Set(defaultSeverity.String()); err != nil {
   916  			return err
   917  		}
   918  	}
   919  
   920  	return nil
   921  }