github.com/khulnasoft/cli@v0.0.0-20240402070845-01bcad7beefa/opts/opts.go (about) 1 package opts 2 3 import ( 4 "fmt" 5 "math/big" 6 "net" 7 "path" 8 "regexp" 9 "strings" 10 11 "github.com/docker/docker/api/types/filters" 12 units "github.com/khulnasoft-lab/go-units" 13 "github.com/pkg/errors" 14 ) 15 16 var ( 17 alphaRegexp = regexp.MustCompile(`[a-zA-Z]`) 18 domainRegexp = regexp.MustCompile(`^(:?(:?[a-zA-Z0-9]|(:?[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9]))(:?\.(:?[a-zA-Z0-9]|(:?[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])))*)\.?\s*$`) 19 ) 20 21 // ListOpts holds a list of values and a validation function. 22 type ListOpts struct { 23 values *[]string 24 validator ValidatorFctType 25 } 26 27 // NewListOpts creates a new ListOpts with the specified validator. 28 func NewListOpts(validator ValidatorFctType) ListOpts { 29 var values []string 30 return *NewListOptsRef(&values, validator) 31 } 32 33 // NewListOptsRef creates a new ListOpts with the specified values and validator. 34 func NewListOptsRef(values *[]string, validator ValidatorFctType) *ListOpts { 35 return &ListOpts{ 36 values: values, 37 validator: validator, 38 } 39 } 40 41 func (opts *ListOpts) String() string { 42 if len(*opts.values) == 0 { 43 return "" 44 } 45 return fmt.Sprintf("%v", *opts.values) 46 } 47 48 // Set validates if needed the input value and adds it to the 49 // internal slice. 50 func (opts *ListOpts) Set(value string) error { 51 if opts.validator != nil { 52 v, err := opts.validator(value) 53 if err != nil { 54 return err 55 } 56 value = v 57 } 58 *opts.values = append(*opts.values, value) 59 return nil 60 } 61 62 // Delete removes the specified element from the slice. 63 func (opts *ListOpts) Delete(key string) { 64 for i, k := range *opts.values { 65 if k == key { 66 *opts.values = append((*opts.values)[:i], (*opts.values)[i+1:]...) 67 return 68 } 69 } 70 } 71 72 // GetMap returns the content of values in a map in order to avoid 73 // duplicates. 74 func (opts *ListOpts) GetMap() map[string]struct{} { 75 ret := make(map[string]struct{}) 76 for _, k := range *opts.values { 77 ret[k] = struct{}{} 78 } 79 return ret 80 } 81 82 // GetAll returns the values of slice. 83 func (opts *ListOpts) GetAll() []string { 84 return *opts.values 85 } 86 87 // GetAllOrEmpty returns the values of the slice 88 // or an empty slice when there are no values. 89 func (opts *ListOpts) GetAllOrEmpty() []string { 90 v := *opts.values 91 if v == nil { 92 return make([]string, 0) 93 } 94 return v 95 } 96 97 // Get checks the existence of the specified key. 98 func (opts *ListOpts) Get(key string) bool { 99 for _, k := range *opts.values { 100 if k == key { 101 return true 102 } 103 } 104 return false 105 } 106 107 // Len returns the amount of element in the slice. 108 func (opts *ListOpts) Len() int { 109 return len(*opts.values) 110 } 111 112 // Type returns a string name for this Option type 113 func (opts *ListOpts) Type() string { 114 return "list" 115 } 116 117 // WithValidator returns the ListOpts with validator set. 118 func (opts *ListOpts) WithValidator(validator ValidatorFctType) *ListOpts { 119 opts.validator = validator 120 return opts 121 } 122 123 // NamedOption is an interface that list and map options 124 // with names implement. 125 type NamedOption interface { 126 Name() string 127 } 128 129 // NamedListOpts is a ListOpts with a configuration name. 130 // This struct is useful to keep reference to the assigned 131 // field name in the internal configuration struct. 132 type NamedListOpts struct { 133 name string 134 ListOpts 135 } 136 137 var _ NamedOption = &NamedListOpts{} 138 139 // NewNamedListOptsRef creates a reference to a new NamedListOpts struct. 140 func NewNamedListOptsRef(name string, values *[]string, validator ValidatorFctType) *NamedListOpts { 141 return &NamedListOpts{ 142 name: name, 143 ListOpts: *NewListOptsRef(values, validator), 144 } 145 } 146 147 // Name returns the name of the NamedListOpts in the configuration. 148 func (o *NamedListOpts) Name() string { 149 return o.name 150 } 151 152 // MapOpts holds a map of values and a validation function. 153 type MapOpts struct { 154 values map[string]string 155 validator ValidatorFctType 156 } 157 158 // Set validates if needed the input value and add it to the 159 // internal map, by splitting on '='. 160 func (opts *MapOpts) Set(value string) error { 161 if opts.validator != nil { 162 v, err := opts.validator(value) 163 if err != nil { 164 return err 165 } 166 value = v 167 } 168 k, v, _ := strings.Cut(value, "=") 169 opts.values[k] = v 170 return nil 171 } 172 173 // GetAll returns the values of MapOpts as a map. 174 func (opts *MapOpts) GetAll() map[string]string { 175 return opts.values 176 } 177 178 func (opts *MapOpts) String() string { 179 return fmt.Sprintf("%v", opts.values) 180 } 181 182 // Type returns a string name for this Option type 183 func (opts *MapOpts) Type() string { 184 return "map" 185 } 186 187 // NewMapOpts creates a new MapOpts with the specified map of values and a validator. 188 func NewMapOpts(values map[string]string, validator ValidatorFctType) *MapOpts { 189 if values == nil { 190 values = make(map[string]string) 191 } 192 return &MapOpts{ 193 values: values, 194 validator: validator, 195 } 196 } 197 198 // NamedMapOpts is a MapOpts struct with a configuration name. 199 // This struct is useful to keep reference to the assigned 200 // field name in the internal configuration struct. 201 type NamedMapOpts struct { 202 name string 203 MapOpts 204 } 205 206 var _ NamedOption = &NamedMapOpts{} 207 208 // NewNamedMapOpts creates a reference to a new NamedMapOpts struct. 209 func NewNamedMapOpts(name string, values map[string]string, validator ValidatorFctType) *NamedMapOpts { 210 return &NamedMapOpts{ 211 name: name, 212 MapOpts: *NewMapOpts(values, validator), 213 } 214 } 215 216 // Name returns the name of the NamedMapOpts in the configuration. 217 func (o *NamedMapOpts) Name() string { 218 return o.name 219 } 220 221 // ValidatorFctType defines a validator function that returns a validated string and/or an error. 222 type ValidatorFctType func(val string) (string, error) 223 224 // ValidatorFctListType defines a validator function that returns a validated list of string and/or an error 225 type ValidatorFctListType func(val string) ([]string, error) 226 227 // ValidateIPAddress validates if the given value is a correctly formatted 228 // IP address, and returns the value in normalized form. Leading and trailing 229 // whitespace is allowed, but it does not allow IPv6 addresses surrounded by 230 // square brackets ("[::1]"). 231 // 232 // Refer to [net.ParseIP] for accepted formats. 233 func ValidateIPAddress(val string) (string, error) { 234 if ip := net.ParseIP(strings.TrimSpace(val)); ip != nil { 235 return ip.String(), nil 236 } 237 return "", fmt.Errorf("IP address is not correctly formatted: %s", val) 238 } 239 240 // ValidateMACAddress validates a MAC address. 241 func ValidateMACAddress(val string) (string, error) { 242 _, err := net.ParseMAC(strings.TrimSpace(val)) 243 if err != nil { 244 return "", err 245 } 246 return val, nil 247 } 248 249 // ValidateDNSSearch validates domain for resolvconf search configuration. 250 // A zero length domain is represented by a dot (.). 251 func ValidateDNSSearch(val string) (string, error) { 252 if val = strings.Trim(val, " "); val == "." { 253 return val, nil 254 } 255 return validateDomain(val) 256 } 257 258 func validateDomain(val string) (string, error) { 259 if alphaRegexp.FindString(val) == "" { 260 return "", fmt.Errorf("%s is not a valid domain", val) 261 } 262 ns := domainRegexp.FindSubmatch([]byte(val)) 263 if len(ns) > 0 && len(ns[1]) < 255 { 264 return string(ns[1]), nil 265 } 266 return "", fmt.Errorf("%s is not a valid domain", val) 267 } 268 269 // ValidateLabel validates that the specified string is a valid label, and returns it. 270 // 271 // Labels are in the form of key=value; key must be a non-empty string, and not 272 // contain whitespaces. A value is optional (defaults to an empty string if omitted). 273 // 274 // Leading whitespace is removed during validation but values are kept as-is 275 // otherwise, so any string value is accepted for both, which includes whitespace 276 // (for values) and quotes (surrounding, or embedded in key or value). 277 // 278 // TODO discuss if quotes (and other special characters) should be valid or invalid for keys 279 // TODO discuss if leading/trailing whitespace in keys should be preserved (and valid) 280 func ValidateLabel(value string) (string, error) { 281 key, _, _ := strings.Cut(value, "=") 282 key = strings.TrimLeft(key, whiteSpaces) 283 if key == "" { 284 return "", fmt.Errorf("invalid label '%s': empty name", value) 285 } 286 if strings.ContainsAny(key, whiteSpaces) { 287 return "", fmt.Errorf("label '%s' contains whitespaces", key) 288 } 289 return value, nil 290 } 291 292 // ValidateSysctl validates a sysctl and returns it. 293 func ValidateSysctl(val string) (string, error) { 294 validSysctlMap := map[string]bool{ 295 "kernel.msgmax": true, 296 "kernel.msgmnb": true, 297 "kernel.msgmni": true, 298 "kernel.sem": true, 299 "kernel.shmall": true, 300 "kernel.shmmax": true, 301 "kernel.shmmni": true, 302 "kernel.shm_rmid_forced": true, 303 } 304 validSysctlPrefixes := []string{ 305 "net.", 306 "fs.mqueue.", 307 } 308 k, _, ok := strings.Cut(val, "=") 309 if !ok || k == "" { 310 return "", fmt.Errorf("sysctl '%s' is not allowed", val) 311 } 312 if validSysctlMap[k] { 313 return val, nil 314 } 315 for _, vp := range validSysctlPrefixes { 316 if strings.HasPrefix(k, vp) { 317 return val, nil 318 } 319 } 320 return "", fmt.Errorf("sysctl '%s' is not allowed", val) 321 } 322 323 // FilterOpt is a flag type for validating filters 324 type FilterOpt struct { 325 filter filters.Args 326 } 327 328 // NewFilterOpt returns a new FilterOpt 329 func NewFilterOpt() FilterOpt { 330 return FilterOpt{filter: filters.NewArgs()} 331 } 332 333 func (o *FilterOpt) String() string { 334 repr, err := filters.ToJSON(o.filter) 335 if err != nil { 336 return "invalid filters" 337 } 338 return repr 339 } 340 341 // Set sets the value of the opt by parsing the command line value 342 func (o *FilterOpt) Set(value string) error { 343 if value == "" { 344 return nil 345 } 346 if !strings.Contains(value, "=") { 347 return errors.New("bad format of filter (expected name=value)") 348 } 349 name, val, _ := strings.Cut(value, "=") 350 351 // TODO(thaJeztah): these options should not be case-insensitive. 352 name = strings.ToLower(strings.TrimSpace(name)) 353 val = strings.TrimSpace(val) 354 o.filter.Add(name, val) 355 return nil 356 } 357 358 // Type returns the option type 359 func (o *FilterOpt) Type() string { 360 return "filter" 361 } 362 363 // Value returns the value of this option 364 func (o *FilterOpt) Value() filters.Args { 365 return o.filter 366 } 367 368 // NanoCPUs is a type for fixed point fractional number. 369 type NanoCPUs int64 370 371 // String returns the string format of the number 372 func (c *NanoCPUs) String() string { 373 if *c == 0 { 374 return "" 375 } 376 return big.NewRat(c.Value(), 1e9).FloatString(3) 377 } 378 379 // Set sets the value of the NanoCPU by passing a string 380 func (c *NanoCPUs) Set(value string) error { 381 cpus, err := ParseCPUs(value) 382 *c = NanoCPUs(cpus) 383 return err 384 } 385 386 // Type returns the type 387 func (c *NanoCPUs) Type() string { 388 return "decimal" 389 } 390 391 // Value returns the value in int64 392 func (c *NanoCPUs) Value() int64 { 393 return int64(*c) 394 } 395 396 // ParseCPUs takes a string ratio and returns an integer value of nano cpus 397 func ParseCPUs(value string) (int64, error) { 398 cpu, ok := new(big.Rat).SetString(value) 399 if !ok { 400 return 0, fmt.Errorf("failed to parse %v as a rational number", value) 401 } 402 nano := cpu.Mul(cpu, big.NewRat(1e9, 1)) 403 if !nano.IsInt() { 404 return 0, fmt.Errorf("value is too precise") 405 } 406 return nano.Num().Int64(), nil 407 } 408 409 // ParseLink parses and validates the specified string as a link format (name:alias) 410 func ParseLink(val string) (string, string, error) { 411 if val == "" { 412 return "", "", fmt.Errorf("empty string specified for links") 413 } 414 // We expect two parts, but restrict to three to allow detecting invalid formats. 415 arr := strings.SplitN(val, ":", 3) 416 417 // TODO(thaJeztah): clean up this logic!! 418 if len(arr) > 2 { 419 return "", "", fmt.Errorf("bad format for links: %s", val) 420 } 421 // TODO(thaJeztah): this should trim the "/" prefix as well?? 422 if len(arr) == 1 { 423 return val, val, nil 424 } 425 // This is kept because we can actually get a HostConfig with links 426 // from an already created container and the format is not `foo:bar` 427 // but `/foo:/c1/bar` 428 if strings.HasPrefix(arr[0], "/") { 429 // TODO(thaJeztah): clean up this logic!! 430 _, alias := path.Split(arr[1]) 431 return arr[0][1:], alias, nil 432 } 433 return arr[0], arr[1], nil 434 } 435 436 // ValidateLink validates that the specified string has a valid link format (containerName:alias). 437 func ValidateLink(val string) (string, error) { 438 _, _, err := ParseLink(val) 439 return val, err 440 } 441 442 // MemBytes is a type for human readable memory bytes (like 128M, 2g, etc) 443 type MemBytes int64 444 445 // String returns the string format of the human readable memory bytes 446 func (m *MemBytes) String() string { 447 // NOTE: In spf13/pflag/flag.go, "0" is considered as "zero value" while "0 B" is not. 448 // We return "0" in case value is 0 here so that the default value is hidden. 449 // (Sometimes "default 0 B" is actually misleading) 450 if m.Value() != 0 { 451 return units.BytesSize(float64(m.Value())) 452 } 453 return "0" 454 } 455 456 // Set sets the value of the MemBytes by passing a string 457 func (m *MemBytes) Set(value string) error { 458 val, err := units.RAMInBytes(value) 459 *m = MemBytes(val) 460 return err 461 } 462 463 // Type returns the type 464 func (m *MemBytes) Type() string { 465 return "bytes" 466 } 467 468 // Value returns the value in int64 469 func (m *MemBytes) Value() int64 { 470 return int64(*m) 471 } 472 473 // UnmarshalJSON is the customized unmarshaler for MemBytes 474 func (m *MemBytes) UnmarshalJSON(s []byte) error { 475 if len(s) <= 2 || s[0] != '"' || s[len(s)-1] != '"' { 476 return fmt.Errorf("invalid size: %q", s) 477 } 478 val, err := units.RAMInBytes(string(s[1 : len(s)-1])) 479 *m = MemBytes(val) 480 return err 481 } 482 483 // MemSwapBytes is a type for human readable memory bytes (like 128M, 2g, etc). 484 // It differs from MemBytes in that -1 is valid and the default. 485 type MemSwapBytes int64 486 487 // Set sets the value of the MemSwapBytes by passing a string 488 func (m *MemSwapBytes) Set(value string) error { 489 if value == "-1" { 490 *m = MemSwapBytes(-1) 491 return nil 492 } 493 val, err := units.RAMInBytes(value) 494 *m = MemSwapBytes(val) 495 return err 496 } 497 498 // Type returns the type 499 func (m *MemSwapBytes) Type() string { 500 return "bytes" 501 } 502 503 // Value returns the value in int64 504 func (m *MemSwapBytes) Value() int64 { 505 return int64(*m) 506 } 507 508 func (m *MemSwapBytes) String() string { 509 b := MemBytes(*m) 510 return b.String() 511 } 512 513 // UnmarshalJSON is the customized unmarshaler for MemSwapBytes 514 func (m *MemSwapBytes) UnmarshalJSON(s []byte) error { 515 b := MemBytes(*m) 516 return b.UnmarshalJSON(s) 517 }