github.com/inspektor-gadget/inspektor-gadget@v0.28.1/pkg/params/params.go (about) 1 // Copyright 2022-2023 The Inspektor Gadget authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 /* 16 Package params provides a generic way to describe parameters used by gadgets, operators 17 and runtimes including validation. They can easily be serialized and handed over to different 18 frameworks like cobra for use in CLI or a webinterface using JSON. 19 */ 20 package params 21 22 import ( 23 "bytes" 24 "compress/zlib" 25 "encoding/base64" 26 "errors" 27 "fmt" 28 "io" 29 "net" 30 "strconv" 31 "strings" 32 "time" 33 34 "golang.org/x/text/cases" 35 "golang.org/x/text/language" 36 ) 37 38 type ( 39 Params []*Param 40 ParamDescs []*ParamDesc 41 DescCollection map[string]*ParamDescs 42 Collection map[string]*Params 43 ) 44 45 var ErrNotFound = errors.New("not found") 46 47 // ParamDesc holds parameter information and validators 48 type ParamDesc struct { 49 // Key is the name under which this param is registered; this will also be the key when 50 // getting a key/value map 51 Key string `json:"key" yaml:"key"` 52 53 // Alias is a shortcut for this parameter, usually a single character used for command line 54 // interfaces 55 Alias string `json:"alias" yaml:"alias,omitempty"` 56 57 // Title is an optional (pretty) alternative to key and used in user interfaces 58 Title string `json:"title" yaml:"title,omitempty"` 59 60 // DefaultValue is the value that will be used if no other value has been assigned 61 DefaultValue string `json:"defaultValue" yaml:"defaultValue"` 62 63 // Description holds an optional explanation for this parameter; shown in user interfaces 64 Description string `json:"description" yaml:"description"` 65 66 // IsMandatory will be considered when validating; if the param has no value assigned and 67 // also no DefaultValue is set, validation will fail 68 IsMandatory bool `json:"isMandatory" yaml:"isMandatory,omitempty"` 69 70 // Tags can be used to skip parameters not needed for a specific environment 71 Tags []string `json:"tags" yaml:"tags,omitempty"` 72 73 // Validator is an optional function that will be called upon validation; may or may 74 // not be called in user interfaces. Setting TypeHint is preferred, but can also be used 75 // in combination with the Validator. Example: additionally to setting the TypeHint to 76 // TypeInt, the validator could be used to make sure the given int is in a specific range. 77 Validator ParamValidator `json:"-" yaml:"-"` 78 79 // TypeHint is the preferred way to set the type of this parameter as it will invoke a 80 // matching validator automatically; if unset, a value of "string" is assumed 81 TypeHint TypeHint `json:"type" yaml:"type,omitempty"` 82 83 // ValueHint can give a hint on what content is expected here - for example, it can be 84 // used to hint that the param expects a kubernetes namespace, a list of nodes and so on. 85 // This is helpful for frontends to provide autocompletion/selections or set defaults 86 // accordingly. 87 ValueHint ValueHint `json:"valueHint" yaml:"valueHint,omitempty"` 88 89 // PossibleValues holds all possible values for this parameter and will be considered 90 // when validating 91 PossibleValues []string `json:"possibleValues" yaml:"possibleValues,omitempty"` 92 } 93 94 // Param holds a ParamDesc but can additionally store a value 95 type Param struct { 96 *ParamDesc 97 value string 98 isSet bool 99 } 100 101 // GetTitle returns a human friendly title of the field; if no Title has been specified, 102 // the Key will be used with the first letter upper-cased 103 func (p *ParamDesc) GetTitle() string { 104 if p.Title != "" { 105 return p.Title 106 } 107 return cases.Title(language.English).String(p.Key) 108 } 109 110 func (p *ParamDesc) ToParam() *Param { 111 return &Param{ 112 ParamDesc: p, 113 value: p.DefaultValue, 114 } 115 } 116 117 // Validate validates a string against the given parameter 118 func (p *ParamDesc) Validate(value string) error { 119 if value == "" && p.IsMandatory { 120 return fmt.Errorf("expected value for %q", p.Key) 121 } 122 123 if len(p.PossibleValues) > 0 { 124 for _, v := range p.PossibleValues { 125 if v == value { 126 return nil 127 } 128 } 129 return fmt.Errorf("invalid value %q as %q: valid values are: %s", value, p.Key, strings.Join(p.PossibleValues, ", ")) 130 } 131 if typeValidator, ok := typeHintValidators[p.TypeHint]; ok { 132 if err := typeValidator(value); err != nil { 133 return fmt.Errorf("invalid value %q as %q: %w", value, p.Key, err) 134 } 135 } 136 if p.Validator != nil { 137 if err := p.Validator(value); err != nil { 138 return fmt.Errorf("invalid value %q as %q: %w", value, p.Key, err) 139 } 140 } 141 142 return nil 143 } 144 145 // Type is a member of the pflag.Value interface, which is used by cobra 146 func (p *ParamDesc) Type() string { 147 if p.TypeHint != "" { 148 // returning a proper type here will display it as type for cobra params as well 149 return string(p.TypeHint) 150 } 151 return "string" 152 } 153 154 func (p *ParamDesc) IsBoolFlag() bool { 155 return p.TypeHint == TypeBool 156 } 157 158 func (p ParamDescs) ToParams() *Params { 159 params := make(Params, 0, len(p)) 160 for _, param := range p { 161 params = append(params, param.ToParam()) 162 } 163 return ¶ms 164 } 165 166 func (p *ParamDescs) Add(other ...*ParamDesc) { 167 for _, v := range other { 168 *p = append(*p, v) 169 } 170 } 171 172 // Get returns the parameter with the given key or nil 173 func (p *ParamDescs) Get(key string) *ParamDesc { 174 for _, param := range *p { 175 if key == param.Key { 176 return param 177 } 178 } 179 return nil 180 } 181 182 func (p DescCollection) ToParams() Collection { 183 coll := make(Collection) 184 for key, param := range p { 185 if param != nil { 186 coll[key] = param.ToParams() 187 } 188 } 189 return coll 190 } 191 192 func (p *Params) Add(other ...*Param) { 193 for _, v := range other { 194 *p = append(*p, v) 195 } 196 } 197 198 func (p *Params) AddKeyValuePair(key, value string) { 199 *p = append(*p, &Param{ 200 ParamDesc: &ParamDesc{Key: key}, 201 value: value, 202 }) 203 } 204 205 // Get returns the parameter with the given key or nil 206 func (p *Params) Get(key string) *Param { 207 for _, param := range *p { 208 if key == param.Key { 209 return param 210 } 211 } 212 return nil 213 } 214 215 func (p *Params) Set(key, val string) error { 216 for _, e := range *p { 217 if e.Key == key { 218 return e.Set(val) 219 } 220 } 221 return ErrNotFound 222 } 223 224 func (p *Params) ParamMap() (res map[string]string) { 225 res = make(map[string]string) 226 for _, v := range *p { 227 res[v.Key] = v.String() 228 } 229 return 230 } 231 232 func (p *Params) ValidateStringMap(cfg map[string]string) error { 233 for _, param := range *p { 234 value, ok := cfg[param.Key] 235 if !ok && param.IsMandatory { 236 return fmt.Errorf("expected value for %q", param.Key) 237 } 238 if param.Validator != nil { 239 if err := param.Validator(value); err != nil { 240 return fmt.Errorf("invalid value %q as %q: %w", value, param.Key, err) 241 } 242 } 243 } 244 return nil 245 } 246 247 func compressAndB64Encode(s string) string { 248 // Create a new zlib.Writer, which will write to a bytes.Buffer 249 var b bytes.Buffer 250 w := zlib.NewWriter(&b) 251 252 // Write the contents of the file to the zlib.Writer 253 if _, err := io.Copy(w, strings.NewReader(s)); err != nil { 254 panic("failed to copy file to zlib.Writer") 255 } 256 // Close the zlib.Writer to ensure that all data has been written 257 if err := w.Close(); err != nil { 258 panic("failed to close zlib.Writer") 259 } 260 return base64.StdEncoding.EncodeToString(b.Bytes()) 261 } 262 263 func b64DecodeAndDecompress(s string) ([]byte, error) { 264 sDec, err := base64.StdEncoding.DecodeString(s) 265 if err != nil { 266 return nil, fmt.Errorf("decoding string: %w", err) 267 } 268 reader := bytes.NewReader(sDec) 269 gzreader, err := zlib.NewReader(reader) 270 if err != nil { 271 return nil, fmt.Errorf("creating new zlib reader: %w", err) 272 } 273 bytes, err := io.ReadAll(gzreader) 274 if err != nil { 275 return nil, fmt.Errorf("reading from zlib reader:: %w", err) 276 } 277 return bytes, nil 278 } 279 280 func (p *Params) CopyToMap(target map[string]string, prefix string) { 281 for _, param := range *p { 282 if param.TypeHint == TypeBytes { 283 target[prefix+param.Key] = compressAndB64Encode(param.String()) 284 } else { 285 target[prefix+param.Key] = param.String() 286 } 287 } 288 } 289 290 func (p *Params) CopyFromMap(source map[string]string, prefix string) error { 291 for k, v := range source { 292 if strings.HasPrefix(k, prefix) { 293 param := p.Get(strings.TrimPrefix(k, prefix)) 294 if param == nil || param.value == v { 295 continue 296 } 297 if param.TypeHint == TypeBytes { 298 bytes, err := b64DecodeAndDecompress(v) 299 if err != nil { 300 return err 301 } 302 v = string(bytes) 303 } 304 err := param.Set(v) 305 if err != nil && !errors.Is(err, ErrNotFound) { 306 return err 307 } 308 } 309 } 310 return nil 311 } 312 313 func (p Collection) Set(entry, key, val string) error { 314 if _, ok := p[entry]; !ok { 315 return fmt.Errorf("%q is not part of the collection", entry) 316 } 317 return p[entry].Set(key, val) 318 } 319 320 func (p Collection) CopyToMap(target map[string]string, prefix string) { 321 for collectionKey, params := range p { 322 params.CopyToMap(target, prefix+collectionKey+".") 323 } 324 } 325 326 func (p Collection) CopyFromMap(source map[string]string, prefix string) error { 327 for collectionKey, params := range p { 328 err := params.CopyFromMap(source, prefix+collectionKey+".") 329 if err != nil { 330 return err 331 } 332 } 333 return nil 334 } 335 336 // String is a member of the pflag.Value interface, which is used by cobra 337 func (p *Param) String() string { 338 if p == nil { 339 return "" 340 } 341 342 return p.value 343 } 344 345 // Set validates and sets the new value; it is also a member of the pflag.Value interface, 346 // which is used by cobra 347 func (p *Param) Set(val string) error { 348 err := p.Validate(val) 349 if err != nil { 350 return err 351 } 352 p.value = val 353 p.isSet = true 354 return nil 355 } 356 357 func (p *Param) IsSet() bool { 358 return p.isSet 359 } 360 361 func (p *Param) IsDefault() bool { 362 return p.DefaultValue == p.value 363 } 364 365 // AsAny returns the value of the parameter according to its type hint. If there is not any type 366 // hint, it returns the value as string. 367 func (p *Param) AsAny() any { 368 switch p.TypeHint { 369 case TypeBool: 370 return p.AsBool() 371 case TypeString: 372 return p.AsString() 373 case TypeBytes: 374 return p.AsBytes() 375 case TypeInt: 376 return p.AsInt() 377 case TypeInt8: 378 return p.AsInt8() 379 case TypeInt16: 380 return p.AsInt16() 381 case TypeInt32: 382 return p.AsInt32() 383 case TypeInt64: 384 return p.AsInt64() 385 case TypeUint: 386 return p.AsUint() 387 case TypeUint8: 388 return p.AsUint8() 389 case TypeUint16: 390 return p.AsUint16() 391 case TypeUint32: 392 return p.AsUint32() 393 case TypeUint64: 394 return p.AsUint64() 395 case TypeFloat32: 396 return p.AsFloat32() 397 case TypeFloat64: 398 return p.AsFloat64() 399 case TypeDuration: 400 return p.AsDuration() 401 case TypeIP: 402 return p.AsIP() 403 default: 404 return p.value 405 } 406 } 407 408 func (p *Param) AsFloat32() float32 { 409 n, _ := strconv.ParseFloat(p.value, 32) 410 return float32(n) 411 } 412 413 func (p *Param) AsFloat64() float64 { 414 n, _ := strconv.ParseFloat(p.value, 64) 415 return n 416 } 417 418 func (p *Param) AsInt() int { 419 n, _ := strconv.ParseInt(p.value, 10, strconv.IntSize) 420 return int(n) 421 } 422 423 func (p *Param) AsInt8() int8 { 424 n, _ := strconv.ParseInt(p.value, 10, 8) 425 return int8(n) 426 } 427 428 func (p *Param) AsInt16() int16 { 429 n, _ := strconv.ParseInt(p.value, 10, 16) 430 return int16(n) 431 } 432 433 func (p *Param) AsInt32() int32 { 434 n, _ := strconv.ParseInt(p.value, 10, 32) 435 return int32(n) 436 } 437 438 func (p *Param) AsInt64() int64 { 439 n, _ := strconv.ParseInt(p.value, 10, 64) 440 return int64(n) 441 } 442 443 func (p *Param) AsUint() uint { 444 n, _ := strconv.ParseUint(p.value, 10, strconv.IntSize) 445 return uint(n) 446 } 447 448 func (p *Param) AsUint8() uint8 { 449 n, _ := strconv.ParseUint(p.value, 10, 8) 450 return uint8(n) 451 } 452 453 func (p *Param) AsUint16() uint16 { 454 n, _ := strconv.ParseUint(p.value, 10, 16) 455 return uint16(n) 456 } 457 458 func (p *Param) AsUint32() uint32 { 459 n, _ := strconv.ParseUint(p.value, 10, 32) 460 return uint32(n) 461 } 462 463 func (p *Param) AsUint64() uint64 { 464 n, _ := strconv.ParseUint(p.value, 10, 64) 465 return uint64(n) 466 } 467 468 func (p *Param) AsString() string { 469 return p.value 470 } 471 472 func (p *Param) AsBytes() []byte { 473 return []byte(p.value) 474 } 475 476 func (p *Param) AsStringSlice() []string { 477 if p.value == "" { 478 return []string{} 479 } 480 return strings.Split(p.value, ",") 481 } 482 483 func (p *Param) AsBool() bool { 484 return strings.ToLower(p.value) == "true" 485 } 486 487 // AsUint16Slice is useful for handling network ports. 488 func (p *Param) AsUint16Slice() []uint16 { 489 strs := p.AsStringSlice() 490 out := make([]uint16, 0, len(strs)) 491 492 for _, entry := range strs { 493 n, _ := strconv.ParseUint(entry, 10, 16) 494 out = append(out, uint16(n)) 495 } 496 497 return out 498 } 499 500 func (p *Param) AsUint64Slice() []uint64 { 501 strs := p.AsStringSlice() 502 out := make([]uint64, 0, len(strs)) 503 504 for _, entry := range strs { 505 n, _ := strconv.ParseUint(entry, 10, 64) 506 out = append(out, n) 507 } 508 509 return out 510 } 511 512 func (p *Param) AsInt64Slice() []int64 { 513 strs := p.AsStringSlice() 514 out := make([]int64, 0, len(strs)) 515 516 for _, entry := range strs { 517 n, _ := strconv.ParseInt(entry, 10, 64) 518 out = append(out, n) 519 } 520 521 return out 522 } 523 524 func (p *Param) AsDuration() time.Duration { 525 d, _ := time.ParseDuration(p.value) 526 return d 527 } 528 529 func (p *Param) AsIP() net.IP { 530 return net.ParseIP(p.value) 531 }