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

     1  // Copyright 2017 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  	gohex "encoding/hex"
    15  	"fmt"
    16  	"math"
    17  	"regexp"
    18  	"strconv"
    19  	"strings"
    20  
    21  	"github.com/cockroachdb/cockroach/pkg/keys"
    22  	"github.com/cockroachdb/cockroach/pkg/roachpb"
    23  	"github.com/cockroachdb/cockroach/pkg/server/status"
    24  	"github.com/cockroachdb/cockroach/pkg/storage"
    25  	"github.com/cockroachdb/cockroach/pkg/util"
    26  	"github.com/cockroachdb/cockroach/pkg/util/humanizeutil"
    27  	"github.com/cockroachdb/cockroach/pkg/util/keysutil"
    28  	"github.com/cockroachdb/errors"
    29  	humanize "github.com/dustin/go-humanize"
    30  	"github.com/elastic/gosigar"
    31  	"github.com/spf13/pflag"
    32  )
    33  
    34  type localityList []roachpb.LocalityAddress
    35  
    36  var _ pflag.Value = &localityList{}
    37  
    38  // Type implements the pflag.Value interface.
    39  func (l *localityList) Type() string { return "localityList" }
    40  
    41  // String implements the pflag.Value interface.
    42  func (l *localityList) String() string {
    43  	string := ""
    44  	for _, loc := range []roachpb.LocalityAddress(*l) {
    45  		string += loc.LocalityTier.Key + "=" + loc.LocalityTier.Value + "@" + loc.Address.String() + ","
    46  	}
    47  
    48  	return string
    49  }
    50  
    51  // Set implements the pflag.Value interface.
    52  func (l *localityList) Set(value string) error {
    53  	*l = []roachpb.LocalityAddress{}
    54  
    55  	values := strings.Split(value, ",")
    56  
    57  	for _, value := range values {
    58  		split := strings.Split(value, "@")
    59  		if len(split) != 2 {
    60  			return fmt.Errorf("invalid value for --locality-advertise-address: %s", l)
    61  		}
    62  
    63  		tierSplit := strings.Split(split[0], "=")
    64  		if len(tierSplit) != 2 {
    65  			return fmt.Errorf("invalid value for --locality-advertise-address: %s", l)
    66  		}
    67  
    68  		tier := roachpb.Tier{}
    69  		tier.Key = tierSplit[0]
    70  		tier.Value = tierSplit[1]
    71  
    72  		locAddress := roachpb.LocalityAddress{}
    73  		locAddress.LocalityTier = tier
    74  		locAddress.Address = util.MakeUnresolvedAddr("tcp", split[1])
    75  
    76  		*l = append(*l, locAddress)
    77  	}
    78  
    79  	return nil
    80  }
    81  
    82  // type used to implement parsing a list of localities for the cockroach demo command.
    83  type demoLocalityList []roachpb.Locality
    84  
    85  // Type implements the pflag.Value interface.
    86  func (l *demoLocalityList) Type() string { return "demoLocalityList" }
    87  
    88  // String implements the pflag.Value interface.
    89  func (l *demoLocalityList) String() string {
    90  	s := ""
    91  	for _, loc := range []roachpb.Locality(*l) {
    92  		s += loc.String()
    93  	}
    94  	return s
    95  }
    96  
    97  // Set implements the pflag.Value interface.
    98  func (l *demoLocalityList) Set(value string) error {
    99  	*l = []roachpb.Locality{}
   100  	locs := strings.Split(value, ":")
   101  	for _, value := range locs {
   102  		parsedLoc := &roachpb.Locality{}
   103  		if err := parsedLoc.Set(value); err != nil {
   104  			return err
   105  		}
   106  		*l = append(*l, *parsedLoc)
   107  	}
   108  	return nil
   109  }
   110  
   111  // This file contains definitions for data types suitable for use by
   112  // the flag+pflag packages.
   113  
   114  // statementsValue is an implementation of pflag.Value that appends any
   115  // argument to a slice.
   116  type statementsValue []string
   117  
   118  // Type implements the pflag.Value interface.
   119  func (s *statementsValue) Type() string { return "statementsValue" }
   120  
   121  // String implements the pflag.Value interface.
   122  func (s *statementsValue) String() string {
   123  	return strings.Join(*s, ";")
   124  }
   125  
   126  // Set implements the pflag.Value interface.
   127  func (s *statementsValue) Set(value string) error {
   128  	*s = append(*s, value)
   129  	return nil
   130  }
   131  
   132  type dumpMode int
   133  
   134  const (
   135  	dumpBoth dumpMode = iota
   136  	dumpSchemaOnly
   137  	dumpDataOnly
   138  )
   139  
   140  // Type implements the pflag.Value interface.
   141  func (m *dumpMode) Type() string { return "string" }
   142  
   143  // String implements the pflag.Value interface.
   144  func (m *dumpMode) String() string {
   145  	switch *m {
   146  	case dumpBoth:
   147  		return "both"
   148  	case dumpSchemaOnly:
   149  		return "schema"
   150  	case dumpDataOnly:
   151  		return "data"
   152  	}
   153  	return ""
   154  }
   155  
   156  // Set implements the pflag.Value interface.
   157  func (m *dumpMode) Set(s string) error {
   158  	switch s {
   159  	case "both":
   160  		*m = dumpBoth
   161  	case "schema":
   162  		*m = dumpSchemaOnly
   163  	case "data":
   164  		*m = dumpDataOnly
   165  	default:
   166  		return fmt.Errorf("invalid value for --dump-mode: %s", s)
   167  	}
   168  	return nil
   169  }
   170  
   171  type mvccKey storage.MVCCKey
   172  
   173  // Type implements the pflag.Value interface.
   174  func (k *mvccKey) Type() string { return "engine.MVCCKey" }
   175  
   176  // String implements the pflag.Value interface.
   177  func (k *mvccKey) String() string {
   178  	return storage.MVCCKey(*k).String()
   179  }
   180  
   181  // Set implements the pflag.Value interface.
   182  func (k *mvccKey) Set(value string) error {
   183  	var typ keyType
   184  	var keyStr string
   185  	i := strings.IndexByte(value, ':')
   186  	if i == -1 {
   187  		keyStr = value
   188  	} else {
   189  		var err error
   190  		typ, err = parseKeyType(value[:i])
   191  		if err != nil {
   192  			return err
   193  		}
   194  		keyStr = value[i+1:]
   195  	}
   196  
   197  	switch typ {
   198  	case hex:
   199  		b, err := gohex.DecodeString(keyStr)
   200  		if err != nil {
   201  			return err
   202  		}
   203  		newK, err := storage.DecodeMVCCKey(b)
   204  		if err != nil {
   205  			encoded := gohex.EncodeToString(storage.EncodeKey(storage.MakeMVCCMetadataKey(roachpb.Key(b))))
   206  			return errors.Wrapf(err, "perhaps this is just a hex-encoded key; you need an "+
   207  				"encoded MVCCKey (i.e. with a timestamp component); here's one with a zero timestamp: %s",
   208  				encoded)
   209  		}
   210  		*k = mvccKey(newK)
   211  	case raw:
   212  		unquoted, err := unquoteArg(keyStr)
   213  		if err != nil {
   214  			return err
   215  		}
   216  		*k = mvccKey(storage.MakeMVCCMetadataKey(roachpb.Key(unquoted)))
   217  	case human:
   218  		scanner := keysutil.MakePrettyScanner(nil /* tableParser */)
   219  		key, err := scanner.Scan(keyStr)
   220  		if err != nil {
   221  			return err
   222  		}
   223  		*k = mvccKey(storage.MakeMVCCMetadataKey(key))
   224  	case rangeID:
   225  		fromID, err := parseRangeID(keyStr)
   226  		if err != nil {
   227  			return err
   228  		}
   229  		*k = mvccKey(storage.MakeMVCCMetadataKey(keys.MakeRangeIDPrefix(fromID)))
   230  	default:
   231  		return fmt.Errorf("unknown key type %s", typ)
   232  	}
   233  
   234  	return nil
   235  }
   236  
   237  // unquoteArg unquotes the provided argument using Go double-quoted
   238  // string literal rules.
   239  func unquoteArg(arg string) (string, error) {
   240  	s, err := strconv.Unquote(`"` + arg + `"`)
   241  	if err != nil {
   242  		return "", errors.Wrapf(err, "invalid argument %q", arg)
   243  	}
   244  	return s, nil
   245  }
   246  
   247  type keyType int
   248  
   249  //go:generate stringer -type=keyType
   250  const (
   251  	raw keyType = iota
   252  	human
   253  	rangeID
   254  	hex
   255  )
   256  
   257  // _keyTypes stores the names of all the possible key types.
   258  var _keyTypes []string
   259  
   260  // keyTypes computes and memoizes the names of all the possible key
   261  // types, based on the definitions produces by Go's stringer (see
   262  // keytype_string.go).
   263  func keyTypes() []string {
   264  	if _keyTypes == nil {
   265  		for i := 0; i+1 < len(_keyType_index); i++ {
   266  			_keyTypes = append(_keyTypes, _keyType_name[_keyType_index[i]:_keyType_index[i+1]])
   267  		}
   268  	}
   269  	return _keyTypes
   270  }
   271  
   272  func parseKeyType(value string) (keyType, error) {
   273  	for i, typ := range keyTypes() {
   274  		if strings.EqualFold(value, typ) {
   275  			return keyType(i), nil
   276  		}
   277  	}
   278  	return 0, fmt.Errorf("unknown key type '%s'", value)
   279  }
   280  
   281  type nodeDecommissionWaitType int
   282  
   283  const (
   284  	nodeDecommissionWaitAll nodeDecommissionWaitType = iota
   285  	nodeDecommissionWaitLive
   286  	nodeDecommissionWaitNone
   287  )
   288  
   289  // Type implements the pflag.Value interface.
   290  func (s *nodeDecommissionWaitType) Type() string { return "string" }
   291  
   292  // String implements the pflag.Value interface.
   293  func (s *nodeDecommissionWaitType) String() string {
   294  	switch *s {
   295  	case nodeDecommissionWaitAll:
   296  		return "all"
   297  	case nodeDecommissionWaitLive:
   298  		return "live"
   299  	case nodeDecommissionWaitNone:
   300  		return "none"
   301  	}
   302  	return ""
   303  }
   304  
   305  // Set implements the pflag.Value interface.
   306  func (s *nodeDecommissionWaitType) Set(value string) error {
   307  	switch value {
   308  	case "all":
   309  		*s = nodeDecommissionWaitAll
   310  	case "live":
   311  		*s = nodeDecommissionWaitLive
   312  	case "none":
   313  		*s = nodeDecommissionWaitNone
   314  	default:
   315  		return fmt.Errorf("invalid node decommission parameter: %s "+
   316  			"(possible values: all, live, none)", value)
   317  	}
   318  	return nil
   319  }
   320  
   321  type tableDisplayFormat int
   322  
   323  const (
   324  	tableDisplayTSV tableDisplayFormat = iota
   325  	tableDisplayCSV
   326  	tableDisplayTable
   327  	tableDisplayRecords
   328  	tableDisplaySQL
   329  	tableDisplayHTML
   330  	tableDisplayRaw
   331  	tableDisplayLastFormat
   332  )
   333  
   334  // Type implements the pflag.Value interface.
   335  func (f *tableDisplayFormat) Type() string { return "string" }
   336  
   337  // String implements the pflag.Value interface.
   338  func (f *tableDisplayFormat) String() string {
   339  	switch *f {
   340  	case tableDisplayTSV:
   341  		return "tsv"
   342  	case tableDisplayCSV:
   343  		return "csv"
   344  	case tableDisplayTable:
   345  		return "table"
   346  	case tableDisplayRecords:
   347  		return "records"
   348  	case tableDisplaySQL:
   349  		return "sql"
   350  	case tableDisplayHTML:
   351  		return "html"
   352  	case tableDisplayRaw:
   353  		return "raw"
   354  	}
   355  	return ""
   356  }
   357  
   358  // Set implements the pflag.Value interface.
   359  func (f *tableDisplayFormat) Set(s string) error {
   360  	switch s {
   361  	case "tsv":
   362  		*f = tableDisplayTSV
   363  	case "csv":
   364  		*f = tableDisplayCSV
   365  	case "table":
   366  		*f = tableDisplayTable
   367  	case "records":
   368  		*f = tableDisplayRecords
   369  	case "sql":
   370  		*f = tableDisplaySQL
   371  	case "html":
   372  		*f = tableDisplayHTML
   373  	case "raw":
   374  		*f = tableDisplayRaw
   375  	default:
   376  		return fmt.Errorf("invalid table display format: %s "+
   377  			"(possible values: tsv, csv, table, records, sql, html, raw)", s)
   378  	}
   379  	return nil
   380  }
   381  
   382  // bytesOrPercentageValue is a flag that accepts an integer value, an integer
   383  // plus a unit (e.g. 32GB or 32GiB) or a percentage (e.g. 32%). In all these
   384  // cases, it transforms the string flag input into an int64 value.
   385  //
   386  // Since it accepts a percentage, instances need to be configured with
   387  // instructions on how to resolve a percentage to a number (i.e. the answer to
   388  // the question "a percentage of what?"). This is done by taking in a
   389  // percentResolverFunc. There are predefined ones: memoryPercentResolver and
   390  // diskPercentResolverFactory.
   391  //
   392  // bytesOrPercentageValue can be used in two ways:
   393  // 1. Upon flag parsing, it can write an int64 value through a pointer specified
   394  // by the caller.
   395  // 2. It can store the flag value as a string and only convert it to an int64 on
   396  // a subsequent Resolve() call. Input validation still happens at flag parsing
   397  // time.
   398  //
   399  // Option 2 is useful when percentages cannot be resolved at flag parsing time.
   400  // For example, we have flags that can be expressed as percentages of the
   401  // capacity of storage device. Which storage device is in question might only be
   402  // known once other flags are parsed (e.g. --max-disk-temp-storage=10% depends
   403  // on --store).
   404  type bytesOrPercentageValue struct {
   405  	val  *int64
   406  	bval *humanizeutil.BytesValue
   407  
   408  	origVal string
   409  
   410  	// percentResolver is used to turn a percent string into a value. See
   411  	// memoryPercentResolver() and diskPercentResolverFactory().
   412  	percentResolver percentResolverFunc
   413  }
   414  type percentResolverFunc func(percent int) (int64, error)
   415  
   416  // memoryPercentResolver turns a percent into the respective fraction of the
   417  // system's internal memory.
   418  func memoryPercentResolver(percent int) (int64, error) {
   419  	sizeBytes, _, err := status.GetTotalMemoryWithoutLogging()
   420  	if err != nil {
   421  		return 0, err
   422  	}
   423  	return (sizeBytes * int64(percent)) / 100, nil
   424  }
   425  
   426  // diskPercentResolverFactory takes in a path and produces a percentResolverFunc
   427  // bound to the respective storage device.
   428  //
   429  // An error is returned if dir does not exist.
   430  func diskPercentResolverFactory(dir string) (percentResolverFunc, error) {
   431  	fileSystemUsage := gosigar.FileSystemUsage{}
   432  	if err := fileSystemUsage.Get(dir); err != nil {
   433  		return nil, err
   434  	}
   435  	if fileSystemUsage.Total > math.MaxInt64 {
   436  		return nil, fmt.Errorf("unsupported disk size %s, max supported size is %s",
   437  			humanize.IBytes(fileSystemUsage.Total), humanizeutil.IBytes(math.MaxInt64))
   438  	}
   439  	deviceCapacity := int64(fileSystemUsage.Total)
   440  
   441  	return func(percent int) (int64, error) {
   442  		return (deviceCapacity * int64(percent)) / 100, nil
   443  	}, nil
   444  }
   445  
   446  // newBytesOrPercentageValue creates a bytesOrPercentageValue.
   447  //
   448  // v and percentResolver can be nil (either they're both specified or they're
   449  // both nil). If they're nil, then Resolve() has to be called later to get the
   450  // passed-in value.
   451  func newBytesOrPercentageValue(
   452  	v *int64, percentResolver func(percent int) (int64, error),
   453  ) *bytesOrPercentageValue {
   454  	if v == nil {
   455  		v = new(int64)
   456  	}
   457  	return &bytesOrPercentageValue{
   458  		val:             v,
   459  		bval:            humanizeutil.NewBytesValue(v),
   460  		percentResolver: percentResolver,
   461  	}
   462  }
   463  
   464  var fractionRE = regexp.MustCompile(`^0?\.[0-9]+$`)
   465  
   466  // Set implements the pflags.Flag interface.
   467  func (b *bytesOrPercentageValue) Set(s string) error {
   468  	b.origVal = s
   469  	if strings.HasSuffix(s, "%") || fractionRE.MatchString(s) {
   470  		multiplier := 100.0
   471  		if s[len(s)-1] == '%' {
   472  			// We have a percentage.
   473  			multiplier = 1.0
   474  			s = s[:len(s)-1]
   475  		}
   476  		// The user can express .123 or 0.123. Parse as float.
   477  		frac, err := strconv.ParseFloat(s, 32)
   478  		if err != nil {
   479  			return err
   480  		}
   481  		percent := int(frac * multiplier)
   482  		if percent < 1 || percent > 99 {
   483  			return fmt.Errorf("percentage %d%% out of range 1%% - 99%%", percent)
   484  		}
   485  
   486  		if b.percentResolver == nil {
   487  			// percentResolver not set means that this flag is not yet supposed to set
   488  			// any value.
   489  			return nil
   490  		}
   491  
   492  		absVal, err := b.percentResolver(percent)
   493  		if err != nil {
   494  			return err
   495  		}
   496  		s = fmt.Sprint(absVal)
   497  	}
   498  	return b.bval.Set(s)
   499  }
   500  
   501  // Resolve can be called to get the flag's value (if any). If the flag had been
   502  // previously set, *v will be written.
   503  func (b *bytesOrPercentageValue) Resolve(v *int64, percentResolver percentResolverFunc) error {
   504  	// The flag was not passed on the command line.
   505  	if b.origVal == "" {
   506  		return nil
   507  	}
   508  	b.percentResolver = percentResolver
   509  	b.val = v
   510  	b.bval = humanizeutil.NewBytesValue(v)
   511  	return b.Set(b.origVal)
   512  }
   513  
   514  // Type implements the pflag.Value interface.
   515  func (b *bytesOrPercentageValue) Type() string {
   516  	return b.bval.Type()
   517  }
   518  
   519  // String implements the pflag.Value interface.
   520  func (b *bytesOrPercentageValue) String() string {
   521  	return b.bval.String()
   522  }
   523  
   524  // IsSet returns true iff Set has successfully been called.
   525  func (b *bytesOrPercentageValue) IsSet() bool {
   526  	return b.bval.IsSet()
   527  }