...

Source file src/k8s.io/kubernetes/test/e2e/framework/config/config.go

Documentation: k8s.io/kubernetes/test/e2e/framework/config

     1  /*
     2  Copyright 2018 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  // Package config simplifies the declaration of configuration options.
    18  // Right now the implementation maps them directly to command line
    19  // flags. When combined with test/e2e/framework/viperconfig in a test
    20  // suite, those flags then can also be read from a config file.
    21  //
    22  // The command line flags all get stored in a private flag set. The
    23  // developer of the E2E test suite decides how they are exposed. Options
    24  // include:
    25  //   - exposing as normal flags in the actual command line:
    26  //     CopyFlags(Flags, flag.CommandLine)
    27  //   - populate via test/e2e/framework/viperconfig:
    28  //     viperconfig.ViperizeFlags("my-config.yaml", "", Flags)
    29  //   - a combination of both:
    30  //     CopyFlags(Flags, flag.CommandLine)
    31  //     viperconfig.ViperizeFlags("my-config.yaml", "", flag.CommandLine)
    32  //
    33  // Instead of defining flags one-by-one, test developers annotate a
    34  // structure with tags and then call a single function. This is the
    35  // same approach as in https://godoc.org/github.com/jessevdk/go-flags,
    36  // but implemented so that a test suite can continue to use the normal
    37  // "flag" package.
    38  //
    39  // For example, a file storage/csi.go might define:
    40  //
    41  //	var scaling struct {
    42  //	        NumNodes int  `default:"1" description:"number of nodes to run on"`
    43  //	        Master string
    44  //	}
    45  //	_ = config.AddOptions(&scaling, "storage.csi.scaling")
    46  //
    47  // This defines the following command line flags:
    48  //
    49  //	-storage.csi.scaling.numNodes=<int>  - number of nodes to run on (default: 1)
    50  //	-storage.csi.scaling.master=<string>
    51  //
    52  // All fields in the structure must be exported and have one of the following
    53  // types (same as in the `flag` package):
    54  // - bool
    55  // - time.Duration
    56  // - float64
    57  // - string
    58  // - int
    59  // - int64
    60  // - uint
    61  // - uint64
    62  // - and/or nested or embedded structures containing those basic types.
    63  //
    64  // Each basic entry may have a tag with these optional keys:
    65  //
    66  //	usage:   additional explanation of the option
    67  //	default: the default value, in the same format as it would
    68  //	         be given on the command line and true/false for
    69  //	         a boolean
    70  //
    71  // The names of the final configuration options are a combination of an
    72  // optional common prefix for all options in the structure and the
    73  // name of the fields, concatenated with a dot. To get names that are
    74  // consistent with the command line flags defined by `ginkgo`, the
    75  // initial character of each field name is converted to lower case.
    76  //
    77  // There is currently no support for aliases, so renaming the fields
    78  // or the common prefix will be visible to users of the test suite and
    79  // may breaks scripts which use the old names.
    80  //
    81  // The variable will be filled with the actual values by the test
    82  // suite before running tests. Beware that the code which registers
    83  // Ginkgo tests cannot use those config options, because registering
    84  // tests and options both run before the E2E test suite handles
    85  // parameters.
    86  package config
    87  
    88  import (
    89  	"flag"
    90  	"fmt"
    91  	"reflect"
    92  	"strconv"
    93  	"time"
    94  	"unicode"
    95  	"unicode/utf8"
    96  )
    97  
    98  // Flags is the flag set that AddOptions adds to. Test authors should
    99  // also use it instead of directly adding to the global command line.
   100  var Flags = flag.NewFlagSet("", flag.ContinueOnError)
   101  
   102  // CopyFlags ensures that all flags that are defined in the source flag
   103  // set appear in the target flag set as if they had been defined there
   104  // directly. From the flag package it inherits the behavior that there
   105  // is a panic if the target already contains a flag from the source.
   106  func CopyFlags(source *flag.FlagSet, target *flag.FlagSet) {
   107  	source.VisitAll(func(flag *flag.Flag) {
   108  		// We don't need to copy flag.DefValue. The original
   109  		// default (from, say, flag.String) was stored in
   110  		// the value and gets extracted by Var for the help
   111  		// message.
   112  		target.Var(flag.Value, flag.Name, flag.Usage)
   113  	})
   114  }
   115  
   116  // AddOptions analyzes the options value and creates the necessary
   117  // flags to populate it.
   118  //
   119  // The prefix can be used to root the options deeper in the overall
   120  // set of options, with a dot separating different levels.
   121  //
   122  // The function always returns true, to enable this simplified
   123  // registration of options:
   124  // _ = AddOptions(...)
   125  //
   126  // It panics when it encounters an error, like unsupported types
   127  // or option name conflicts.
   128  func AddOptions(options interface{}, prefix string) bool {
   129  	return AddOptionsToSet(Flags, options, prefix)
   130  }
   131  
   132  // AddOptionsToSet is the same as AddOption, except that it allows choosing the flag set.
   133  func AddOptionsToSet(flags *flag.FlagSet, options interface{}, prefix string) bool {
   134  	optionsType := reflect.TypeOf(options)
   135  	if optionsType == nil {
   136  		panic("options parameter without a type - nil?!")
   137  	}
   138  	if optionsType.Kind() != reflect.Pointer || optionsType.Elem().Kind() != reflect.Struct {
   139  		panic(fmt.Sprintf("need a pointer to a struct, got instead: %T", options))
   140  	}
   141  	addStructFields(flags, optionsType.Elem(), reflect.Indirect(reflect.ValueOf(options)), prefix)
   142  	return true
   143  }
   144  
   145  func addStructFields(flags *flag.FlagSet, structType reflect.Type, structValue reflect.Value, prefix string) {
   146  	for i := 0; i < structValue.NumField(); i++ {
   147  		entry := structValue.Field(i)
   148  		addr := entry.Addr()
   149  		structField := structType.Field(i)
   150  		name := structField.Name
   151  		r, n := utf8.DecodeRuneInString(name)
   152  		name = string(unicode.ToLower(r)) + name[n:]
   153  		usage := structField.Tag.Get("usage")
   154  		def := structField.Tag.Get("default")
   155  		if prefix != "" {
   156  			name = prefix + "." + name
   157  		}
   158  		if structField.PkgPath != "" {
   159  			panic(fmt.Sprintf("struct entry %q not exported", name))
   160  		}
   161  		ptr := addr.Interface()
   162  		if structField.Anonymous {
   163  			// Entries in embedded fields are treated like
   164  			// entries, in the struct itself, i.e. we add
   165  			// them with the same prefix.
   166  			addStructFields(flags, structField.Type, entry, prefix)
   167  			continue
   168  		}
   169  		if structField.Type.Kind() == reflect.Struct {
   170  			// Add nested options.
   171  			addStructFields(flags, structField.Type, entry, name)
   172  			continue
   173  		}
   174  		// We could switch based on structField.Type. Doing a
   175  		// switch after getting an interface holding the
   176  		// pointer to the entry has the advantage that we
   177  		// immediately have something that we can add as flag
   178  		// variable.
   179  		//
   180  		// Perhaps generics will make this entire switch redundant someday...
   181  		switch ptr := ptr.(type) {
   182  		case *bool:
   183  			var defValue bool
   184  			parseDefault(&defValue, name, def)
   185  			flags.BoolVar(ptr, name, defValue, usage)
   186  		case *time.Duration:
   187  			var defValue time.Duration
   188  			parseDefault(&defValue, name, def)
   189  			flags.DurationVar(ptr, name, defValue, usage)
   190  		case *float64:
   191  			var defValue float64
   192  			parseDefault(&defValue, name, def)
   193  			flags.Float64Var(ptr, name, defValue, usage)
   194  		case *string:
   195  			flags.StringVar(ptr, name, def, usage)
   196  		case *int:
   197  			var defValue int
   198  			parseDefault(&defValue, name, def)
   199  			flags.IntVar(ptr, name, defValue, usage)
   200  		case *int64:
   201  			var defValue int64
   202  			parseDefault(&defValue, name, def)
   203  			flags.Int64Var(ptr, name, defValue, usage)
   204  		case *uint:
   205  			var defValue uint
   206  			parseDefault(&defValue, name, def)
   207  			flags.UintVar(ptr, name, defValue, usage)
   208  		case *uint64:
   209  			var defValue uint64
   210  			parseDefault(&defValue, name, def)
   211  			flags.Uint64Var(ptr, name, defValue, usage)
   212  		default:
   213  			panic(fmt.Sprintf("unsupported struct entry type %q: %T", name, entry.Interface()))
   214  		}
   215  	}
   216  }
   217  
   218  // parseDefault is necessary because "flag" wants the default in the
   219  // actual type and cannot take a string. It would be nice to reuse the
   220  // existing code for parsing from the "flag" package, but it isn't
   221  // exported.
   222  func parseDefault(value interface{}, name, def string) {
   223  	if def == "" {
   224  		return
   225  	}
   226  	checkErr := func(err error, value interface{}) {
   227  		if err != nil {
   228  			panic(fmt.Sprintf("invalid default %q for %T entry %s: %s", def, value, name, err))
   229  		}
   230  	}
   231  	switch value := value.(type) {
   232  	case *bool:
   233  		v, err := strconv.ParseBool(def)
   234  		checkErr(err, *value)
   235  		*value = v
   236  	case *time.Duration:
   237  		v, err := time.ParseDuration(def)
   238  		checkErr(err, *value)
   239  		*value = v
   240  	case *float64:
   241  		v, err := strconv.ParseFloat(def, 64)
   242  		checkErr(err, *value)
   243  		*value = v
   244  	case *int:
   245  		v, err := strconv.Atoi(def)
   246  		checkErr(err, *value)
   247  		*value = v
   248  	case *int64:
   249  		v, err := strconv.ParseInt(def, 0, 64)
   250  		checkErr(err, *value)
   251  		*value = v
   252  	case *uint:
   253  		v, err := strconv.ParseUint(def, 0, strconv.IntSize)
   254  		checkErr(err, *value)
   255  		*value = uint(v)
   256  	case *uint64:
   257  		v, err := strconv.ParseUint(def, 0, 64)
   258  		checkErr(err, *value)
   259  		*value = v
   260  	default:
   261  		panic(fmt.Sprintf("%q: setting defaults not supported for type %T", name, value))
   262  	}
   263  }
   264  

View as plain text