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 }