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 &params
   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  }