github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/cli/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/docker/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 vals := strings.SplitN(value, "=", 2) 169 if len(vals) == 1 { 170 (opts.values)[vals[0]] = "" 171 } else { 172 (opts.values)[vals[0]] = vals[1] 173 } 174 return nil 175 } 176 177 // GetAll returns the values of MapOpts as a map. 178 func (opts *MapOpts) GetAll() map[string]string { 179 return opts.values 180 } 181 182 func (opts *MapOpts) String() string { 183 return fmt.Sprintf("%v", opts.values) 184 } 185 186 // Type returns a string name for this Option type 187 func (opts *MapOpts) Type() string { 188 return "map" 189 } 190 191 // NewMapOpts creates a new MapOpts with the specified map of values and a validator. 192 func NewMapOpts(values map[string]string, validator ValidatorFctType) *MapOpts { 193 if values == nil { 194 values = make(map[string]string) 195 } 196 return &MapOpts{ 197 values: values, 198 validator: validator, 199 } 200 } 201 202 // NamedMapOpts is a MapOpts struct with a configuration name. 203 // This struct is useful to keep reference to the assigned 204 // field name in the internal configuration struct. 205 type NamedMapOpts struct { 206 name string 207 MapOpts 208 } 209 210 var _ NamedOption = &NamedMapOpts{} 211 212 // NewNamedMapOpts creates a reference to a new NamedMapOpts struct. 213 func NewNamedMapOpts(name string, values map[string]string, validator ValidatorFctType) *NamedMapOpts { 214 return &NamedMapOpts{ 215 name: name, 216 MapOpts: *NewMapOpts(values, validator), 217 } 218 } 219 220 // Name returns the name of the NamedMapOpts in the configuration. 221 func (o *NamedMapOpts) Name() string { 222 return o.name 223 } 224 225 // ValidatorFctType defines a validator function that returns a validated string and/or an error. 226 type ValidatorFctType func(val string) (string, error) 227 228 // ValidatorFctListType defines a validator function that returns a validated list of string and/or an error 229 type ValidatorFctListType func(val string) ([]string, error) 230 231 // ValidateIPAddress validates an Ip address. 232 func ValidateIPAddress(val string) (string, error) { 233 var ip = net.ParseIP(strings.TrimSpace(val)) 234 if ip != nil { 235 return ip.String(), nil 236 } 237 return "", fmt.Errorf("%s is not an ip address", 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(val string) (string, error) { 281 arr := strings.SplitN(val, "=", 2) 282 key := strings.TrimLeft(arr[0], whiteSpaces) 283 if key == "" { 284 return "", fmt.Errorf("invalid label '%s': empty name", val) 285 } 286 if strings.ContainsAny(key, whiteSpaces) { 287 return "", fmt.Errorf("label '%s' contains whitespaces", key) 288 } 289 return val, 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 arr := strings.Split(val, "=") 309 if len(arr) < 2 { 310 return "", fmt.Errorf("sysctl '%s' is not whitelisted", val) 311 } 312 if validSysctlMap[arr[0]] { 313 return val, nil 314 } 315 316 for _, vp := range validSysctlPrefixes { 317 if strings.HasPrefix(arr[0], vp) { 318 return val, nil 319 } 320 } 321 return "", fmt.Errorf("sysctl '%s' is not whitelisted", val) 322 } 323 324 // ValidateProgressOutput errors out if an invalid value is passed to --progress 325 func ValidateProgressOutput(val string) error { 326 valid := []string{"auto", "plain", "tty"} 327 for _, s := range valid { 328 if s == val { 329 return nil 330 } 331 } 332 return fmt.Errorf("invalid value %q passed to --progress, valid values are: %s", val, strings.Join(valid, ", ")) 333 } 334 335 // FilterOpt is a flag type for validating filters 336 type FilterOpt struct { 337 filter filters.Args 338 } 339 340 // NewFilterOpt returns a new FilterOpt 341 func NewFilterOpt() FilterOpt { 342 return FilterOpt{filter: filters.NewArgs()} 343 } 344 345 func (o *FilterOpt) String() string { 346 repr, err := filters.ToJSON(o.filter) 347 if err != nil { 348 return "invalid filters" 349 } 350 return repr 351 } 352 353 // Set sets the value of the opt by parsing the command line value 354 func (o *FilterOpt) Set(value string) error { 355 if value == "" { 356 return nil 357 } 358 if !strings.Contains(value, "=") { 359 return errors.New("bad format of filter (expected name=value)") 360 } 361 f := strings.SplitN(value, "=", 2) 362 name := strings.ToLower(strings.TrimSpace(f[0])) 363 value = strings.TrimSpace(f[1]) 364 365 o.filter.Add(name, value) 366 return nil 367 } 368 369 // Type returns the option type 370 func (o *FilterOpt) Type() string { 371 return "filter" 372 } 373 374 // Value returns the value of this option 375 func (o *FilterOpt) Value() filters.Args { 376 return o.filter 377 } 378 379 // NanoCPUs is a type for fixed point fractional number. 380 type NanoCPUs int64 381 382 // String returns the string format of the number 383 func (c *NanoCPUs) String() string { 384 if *c == 0 { 385 return "" 386 } 387 return big.NewRat(c.Value(), 1e9).FloatString(3) 388 } 389 390 // Set sets the value of the NanoCPU by passing a string 391 func (c *NanoCPUs) Set(value string) error { 392 cpus, err := ParseCPUs(value) 393 *c = NanoCPUs(cpus) 394 return err 395 } 396 397 // Type returns the type 398 func (c *NanoCPUs) Type() string { 399 return "decimal" 400 } 401 402 // Value returns the value in int64 403 func (c *NanoCPUs) Value() int64 { 404 return int64(*c) 405 } 406 407 // ParseCPUs takes a string ratio and returns an integer value of nano cpus 408 func ParseCPUs(value string) (int64, error) { 409 cpu, ok := new(big.Rat).SetString(value) 410 if !ok { 411 return 0, fmt.Errorf("failed to parse %v as a rational number", value) 412 } 413 nano := cpu.Mul(cpu, big.NewRat(1e9, 1)) 414 if !nano.IsInt() { 415 return 0, fmt.Errorf("value is too precise") 416 } 417 return nano.Num().Int64(), nil 418 } 419 420 // ParseLink parses and validates the specified string as a link format (name:alias) 421 func ParseLink(val string) (string, string, error) { 422 if val == "" { 423 return "", "", fmt.Errorf("empty string specified for links") 424 } 425 arr := strings.Split(val, ":") 426 if len(arr) > 2 { 427 return "", "", fmt.Errorf("bad format for links: %s", val) 428 } 429 if len(arr) == 1 { 430 return val, val, nil 431 } 432 // This is kept because we can actually get a HostConfig with links 433 // from an already created container and the format is not `foo:bar` 434 // but `/foo:/c1/bar` 435 if strings.HasPrefix(arr[0], "/") { 436 _, alias := path.Split(arr[1]) 437 return arr[0][1:], alias, nil 438 } 439 return arr[0], arr[1], nil 440 } 441 442 // ValidateLink validates that the specified string has a valid link format (containerName:alias). 443 func ValidateLink(val string) (string, error) { 444 _, _, err := ParseLink(val) 445 return val, err 446 } 447 448 // MemBytes is a type for human readable memory bytes (like 128M, 2g, etc) 449 type MemBytes int64 450 451 // String returns the string format of the human readable memory bytes 452 func (m *MemBytes) String() string { 453 // NOTE: In spf13/pflag/flag.go, "0" is considered as "zero value" while "0 B" is not. 454 // We return "0" in case value is 0 here so that the default value is hidden. 455 // (Sometimes "default 0 B" is actually misleading) 456 if m.Value() != 0 { 457 return units.BytesSize(float64(m.Value())) 458 } 459 return "0" 460 } 461 462 // Set sets the value of the MemBytes by passing a string 463 func (m *MemBytes) Set(value string) error { 464 val, err := units.RAMInBytes(value) 465 *m = MemBytes(val) 466 return err 467 } 468 469 // Type returns the type 470 func (m *MemBytes) Type() string { 471 return "bytes" 472 } 473 474 // Value returns the value in int64 475 func (m *MemBytes) Value() int64 { 476 return int64(*m) 477 } 478 479 // UnmarshalJSON is the customized unmarshaler for MemBytes 480 func (m *MemBytes) UnmarshalJSON(s []byte) error { 481 if len(s) <= 2 || s[0] != '"' || s[len(s)-1] != '"' { 482 return fmt.Errorf("invalid size: %q", s) 483 } 484 val, err := units.RAMInBytes(string(s[1 : len(s)-1])) 485 *m = MemBytes(val) 486 return err 487 } 488 489 // MemSwapBytes is a type for human readable memory bytes (like 128M, 2g, etc). 490 // It differs from MemBytes in that -1 is valid and the default. 491 type MemSwapBytes int64 492 493 // Set sets the value of the MemSwapBytes by passing a string 494 func (m *MemSwapBytes) Set(value string) error { 495 if value == "-1" { 496 *m = MemSwapBytes(-1) 497 return nil 498 } 499 val, err := units.RAMInBytes(value) 500 *m = MemSwapBytes(val) 501 return err 502 } 503 504 // Type returns the type 505 func (m *MemSwapBytes) Type() string { 506 return "bytes" 507 } 508 509 // Value returns the value in int64 510 func (m *MemSwapBytes) Value() int64 { 511 return int64(*m) 512 } 513 514 func (m *MemSwapBytes) String() string { 515 b := MemBytes(*m) 516 return b.String() 517 } 518 519 // UnmarshalJSON is the customized unmarshaler for MemSwapBytes 520 func (m *MemSwapBytes) UnmarshalJSON(s []byte) error { 521 b := MemBytes(*m) 522 return b.UnmarshalJSON(s) 523 }