github.com/abayer/test-infra@v0.0.5/mungegithub/options/options.go (about)

     1  /*
     2  Copyright 2017 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 options
    18  
    19  import (
    20  	"bytes"
    21  	"flag"
    22  	"fmt"
    23  	"io/ioutil"
    24  	"reflect"
    25  	"sort"
    26  	"strconv"
    27  	"strings"
    28  	"sync"
    29  	"time"
    30  
    31  	"k8s.io/apimachinery/pkg/util/sets"
    32  
    33  	"github.com/ghodss/yaml"
    34  	"github.com/golang/glog"
    35  )
    36  
    37  // Options represents the configuration options for mungegithub.
    38  //
    39  // Options are loaded from a yaml string->string configmap and are updated whenever Load is called.
    40  // Options must be registered at least once before they can be retrieved, but registration and
    41  // loading may happen in any order (this makes Options compatible with a plugin architecture).
    42  // Option keys must be unique across all options of all types.
    43  // Options may be registered multiple times safely so long as the option is always bound to the same
    44  // pointer. (registration is idempotent)
    45  // The defaultVal is used if the options does not have a value specified.
    46  // The description explains the option as an entry in the text returned by Descriptions().
    47  type Options struct {
    48  	rawConfig map[string]string
    49  	options   map[string]*option
    50  
    51  	callbacks []UpdateCallback
    52  
    53  	sync.Mutex
    54  }
    55  
    56  func New() *Options {
    57  	return &Options{options: map[string]*option{}}
    58  }
    59  
    60  type UpdateCallbackError struct {
    61  	err error
    62  }
    63  
    64  func (e *UpdateCallbackError) Error() string {
    65  	return fmt.Sprintf("error from option update callback: %v", e.err)
    66  }
    67  
    68  type UpdateCallback func(changed sets.String) error
    69  
    70  func (o *Options) RegisterUpdateCallback(callback UpdateCallback) {
    71  	o.callbacks = append(o.callbacks, callback)
    72  }
    73  
    74  type optionType string
    75  
    76  const (
    77  	typeString      optionType = "string"
    78  	typeStringSlice optionType = "[]string"
    79  	typeInt         optionType = "int"
    80  	typeUint64      optionType = "uint64"
    81  	typeBool        optionType = "bool"
    82  	typeDuration    optionType = "time.Duration"
    83  	// typeSecret is the same as typeString except that values are not printed.
    84  	typeSecret optionType = "SECRET"
    85  	// typeUnknown is assigned to options that appear in the configmap, but are not registered.
    86  	// Options of this type are represented as strings.
    87  	typeUnknown optionType = "UNKNOWN"
    88  )
    89  
    90  type option struct {
    91  	description string
    92  	optType     optionType
    93  	// val and defaultVal include a level of pointer indirection.
    94  	// (e.g. If optType=="string", val and defaultVal are of type *string not string.)
    95  	val        interface{}
    96  	defaultVal interface{}
    97  
    98  	raw string
    99  }
   100  
   101  // ToFlags registers all options as string flags with the flag.CommandLine flagset.
   102  // All options should be registered before ToFlags is called.
   103  func (o *Options) ToFlags() {
   104  	for key, opt := range o.options {
   105  		flag.String(key, strings.Trim(toString(opt.optType, opt.defaultVal), "\""), opt.description)
   106  	}
   107  }
   108  
   109  // Load updates options based on the contents of a config file and returns the set of changed options.
   110  func (o *Options) Load(file string) (sets.String, error) {
   111  	firstLoad := o.rawConfig == nil
   112  
   113  	b, err := ioutil.ReadFile(file)
   114  	if err != nil || b == nil {
   115  		return nil, fmt.Errorf("could not read config file %q: %v", file, err)
   116  	}
   117  	changed, err := o.populateFromYaml(b)
   118  	if err != nil {
   119  		return changed, err
   120  	}
   121  
   122  	if len(changed) > 0 && !firstLoad {
   123  		for _, callback := range o.callbacks {
   124  			if err = callback(changed); err != nil {
   125  				return changed, &UpdateCallbackError{err: err}
   126  			}
   127  		}
   128  	}
   129  	return changed, nil
   130  }
   131  
   132  // PopulateFromString loads values from the provided yaml string and returns the set of changed options.
   133  // This function should only be used in tests where the config is not loaded from a file.
   134  func (o *Options) PopulateFromString(yaml string) sets.String {
   135  	changed, err := o.populateFromYaml([]byte(yaml))
   136  	if err != nil {
   137  		glog.Fatalf("Failed to populate Options with values from %q. Err: %v.", yaml, err)
   138  	}
   139  	return changed
   140  }
   141  
   142  // PopulateFromFlags loads values into options from command line flags.
   143  // This function must be proceeded by a call to ToFlags and the flags must have been parsed since
   144  // then.
   145  func (o *Options) PopulateFromFlags() {
   146  	if !flag.Parsed() {
   147  		flag.Parse()
   148  	}
   149  
   150  	flags := map[string]string{}
   151  	flag.Visit(func(f *flag.Flag) {
   152  		flags[f.Name] = f.Value.String()
   153  	})
   154  
   155  	o.populateFromMap(flags)
   156  }
   157  
   158  // FlagsSpecified returns the names of the flags that were specified that correspond to options.
   159  // This function must have been proceeded by a call to ToFlags and the flags must have been parsed
   160  // since then.
   161  func (o *Options) FlagsSpecified() sets.String {
   162  	if !flag.Parsed() {
   163  		flag.Parse()
   164  	}
   165  
   166  	specified := sets.String{}
   167  	flag.Visit(func(f *flag.Flag) {
   168  		if _, ok := o.options[f.Name]; ok {
   169  			specified.Insert(f.Name)
   170  		}
   171  	})
   172  	return specified
   173  }
   174  
   175  func (o *Options) populateFromYaml(rawCM []byte) (sets.String, error) {
   176  	var configmap map[string]string
   177  	if err := yaml.Unmarshal(rawCM, &configmap); err != nil {
   178  		return nil, fmt.Errorf("failed to unmarshal configmap from yaml: %v", err)
   179  	}
   180  
   181  	return o.populateFromMap(configmap), nil
   182  }
   183  
   184  func (o *Options) populateFromMap(configmap map[string]string) sets.String {
   185  	o.Lock()
   186  	defer o.Unlock()
   187  
   188  	changed := sets.NewString()
   189  	for key, opt := range o.options {
   190  		if opt.optType == typeUnknown {
   191  			delete(o.options, key)
   192  			continue
   193  		}
   194  		if raw, ok := configmap[key]; ok {
   195  			opt.raw = raw
   196  			if opt.fromString() {
   197  				// The value changed.
   198  				changed.Insert(key)
   199  			}
   200  			delete(configmap, key)
   201  		} else {
   202  			if opt.moveToVal(opt.defaultVal) {
   203  				// The value changed.
   204  				changed.Insert(key)
   205  			}
   206  		}
   207  	}
   208  	for key, raw := range configmap {
   209  		o.options[key] = &option{
   210  			optType: typeUnknown,
   211  			raw:     raw,
   212  		}
   213  	}
   214  	o.rawConfig = configmap
   215  	return changed
   216  }
   217  
   218  // fromString converts opt.raw to opt.optType and moves the resulting value into opt.val.
   219  // iff the value changed 'true' is returned.
   220  func (opt *option) fromString() bool {
   221  	var err error
   222  	var newVal interface{}
   223  	switch opt.optType {
   224  	case typeString, typeSecret:
   225  		newVal = &opt.raw
   226  	case typeStringSlice:
   227  		slice := []string{}
   228  		for _, raw := range strings.Split(opt.raw, ",") {
   229  			if raw = strings.TrimSpace(raw); len(raw) > 0 {
   230  				slice = append(slice, raw)
   231  			}
   232  		}
   233  		newVal = &slice
   234  	case typeInt:
   235  		var i int
   236  		if i, err = strconv.Atoi(opt.raw); err != nil {
   237  			glog.Fatalf("Cannot convert %q to type 'int'.", opt.raw)
   238  		}
   239  		newVal = &i
   240  	case typeUint64:
   241  		var ui uint64
   242  		if ui, err = strconv.ParseUint(opt.raw, 10, 64); err != nil {
   243  			glog.Fatalf("Cannot convert %q to type 'uint64'.", opt.raw)
   244  		}
   245  		newVal = &ui
   246  	case typeBool:
   247  		var b bool
   248  		if b, err = strconv.ParseBool(opt.raw); err != nil {
   249  			glog.Fatalf("Cannot convert %q to type 'bool'.", opt.raw)
   250  		}
   251  		newVal = &b
   252  	case typeDuration:
   253  		var dur time.Duration
   254  		if dur, err = time.ParseDuration(opt.raw); err != nil {
   255  			glog.Fatalf("Cannot convert %q to type 'time.Duration'.", opt.raw)
   256  		}
   257  		newVal = &dur
   258  	default:
   259  		glog.Fatalf("Unrecognized type '%s'.", opt.optType)
   260  	}
   261  	return opt.moveToVal(newVal)
   262  }
   263  
   264  // moveToVal moves the specified value to 'val', maintaining the original 'val' ptr.
   265  // iff the value changed 'true' is returned.
   266  func (opt *option) moveToVal(newVal interface{}) bool {
   267  	changed := !reflect.DeepEqual(opt.val, newVal)
   268  	switch opt.optType {
   269  	case typeString, typeSecret:
   270  		*opt.val.(*string) = *newVal.(*string)
   271  	case typeStringSlice:
   272  		*opt.val.(*[]string) = *newVal.(*[]string)
   273  	case typeInt:
   274  		*opt.val.(*int) = *newVal.(*int)
   275  	case typeUint64:
   276  		*opt.val.(*uint64) = *newVal.(*uint64)
   277  	case typeBool:
   278  		*opt.val.(*bool) = *newVal.(*bool)
   279  	case typeDuration:
   280  		*opt.val.(*time.Duration) = *newVal.(*time.Duration)
   281  	default:
   282  		glog.Fatalf("Unrecognized type '%s'.", opt.optType)
   283  	}
   284  	return changed
   285  }
   286  
   287  // register tries to register an option of any optionType (with the exception of typeUnknown).
   288  // register may be called before or after the configmap is loaded, but options cannot be retrieved
   289  // until they are registered.
   290  func (o *Options) register(optType optionType, key, description string, val, defaultVal interface{}) interface{} {
   291  	if optType == typeUnknown {
   292  		glog.Fatalf("Key '%s' cannot be registered as type 'typeUnknown'.", key)
   293  	}
   294  	opt, ok := o.options[key]
   295  	if ok {
   296  		if opt.optType == typeUnknown {
   297  			// Convert opt.raw to optType.
   298  			opt.val = val
   299  			opt.optType = optType
   300  			opt.defaultVal = defaultVal
   301  			opt.description = description
   302  			opt.fromString()
   303  		} else if opt.optType != optType {
   304  			glog.Fatalf(
   305  				"Cannot register key: '%s' as a '%s'. It is already registered as a '%s'.",
   306  				key,
   307  				optType,
   308  				opt.optType,
   309  			)
   310  		} else if opt.val != val {
   311  			glog.Fatalf(
   312  				"Cannot register key: '%s' to pointer %p. It is already bound to %p.",
   313  				key,
   314  				val,
   315  				opt.val,
   316  			)
   317  		} else if description != opt.description {
   318  			glog.Fatalf(
   319  				"Cannot register key: '%s' with description %q. It already has description %q.",
   320  				key,
   321  				description,
   322  				opt.description,
   323  			)
   324  		} else if !reflect.DeepEqual(defaultVal, opt.defaultVal) {
   325  			glog.Fatalf(
   326  				"Cannot register key: '%s' with default value %s. It already has default value %s.",
   327  				key,
   328  				toString(optType, defaultVal),
   329  				toString(optType, opt.defaultVal),
   330  			)
   331  		}
   332  	} else {
   333  		opt = &option{
   334  			description: description,
   335  			optType:     optType,
   336  			val:         val,
   337  			defaultVal:  defaultVal,
   338  		}
   339  		o.options[key] = opt
   340  		opt.moveToVal(defaultVal)
   341  	}
   342  	return opt.val
   343  }
   344  
   345  // RegisterString registers a `string` option under the specified key.
   346  func (o *Options) RegisterString(ptr *string, key string, defaultVal string, description string) {
   347  	o.register(typeString, key, description, ptr, &defaultVal)
   348  }
   349  
   350  // RegisterStringSlice registers a `[]string` option under the specified key.
   351  func (o *Options) RegisterStringSlice(ptr *[]string, key string, defaultVal []string, description string) {
   352  	*ptr = defaultVal
   353  	o.register(typeStringSlice, key, description, ptr, &defaultVal)
   354  }
   355  
   356  // RegisterInt registers an `int` option under the specified key.
   357  func (o *Options) RegisterInt(ptr *int, key string, defaultVal int, description string) {
   358  	o.register(typeInt, key, description, ptr, &defaultVal)
   359  }
   360  
   361  // RegisterUint64 registers a `uint64` option under the specified key.
   362  func (o *Options) RegisterUint64(ptr *uint64, key string, defaultVal uint64, description string) {
   363  	o.register(typeUint64, key, description, ptr, &defaultVal)
   364  }
   365  
   366  // RegisterBool registers a `bool` option under the specified key.
   367  func (o *Options) RegisterBool(ptr *bool, key string, defaultVal bool, description string) {
   368  	o.register(typeBool, key, description, ptr, &defaultVal)
   369  }
   370  
   371  // RegisterDuration registers a `time.Duration` option under the specified key.
   372  func (o *Options) RegisterDuration(ptr *time.Duration, key string, defaultVal time.Duration, description string) {
   373  	o.register(typeDuration, key, description, ptr, &defaultVal)
   374  }
   375  
   376  // GetString gets the `string` option under the specified key.
   377  func (o *Options) GetString(key string) *string {
   378  	opt, ok := o.options[key]
   379  	if !ok {
   380  		glog.Fatalf("Programmer Error: option key '%s' is not registered!", key)
   381  	}
   382  	if opt.optType != typeString {
   383  		glog.Fatalf("The option with key '%s' has type '%s' not '%s'.", key, opt.optType, typeString)
   384  	}
   385  	return o.options[key].val.(*string)
   386  }
   387  
   388  // GetStringSlice gets the `[]string` option under the specified key.
   389  func (o *Options) GetStringSlice(key string) *[]string {
   390  	opt, ok := o.options[key]
   391  	if !ok {
   392  		glog.Fatalf("Programmer Error: option key '%s' is not registered!", key)
   393  	}
   394  	if opt.optType != typeStringSlice {
   395  		glog.Fatalf("The option with key '%s' has type '%s' not '%s'.",
   396  			key,
   397  			opt.optType,
   398  			typeStringSlice,
   399  		)
   400  	}
   401  	return o.options[key].val.(*[]string)
   402  }
   403  
   404  // GetInt gets then `int` option under the specified key.
   405  func (o *Options) GetInt(key string) *int {
   406  	opt, ok := o.options[key]
   407  	if !ok {
   408  		glog.Fatalf("Programmer Error: option key '%s' is not registered!", key)
   409  	}
   410  	if opt.optType != typeInt {
   411  		glog.Fatalf("The option with key '%s' has type '%s' not '%s'.", key, opt.optType, typeInt)
   412  	}
   413  	return o.options[key].val.(*int)
   414  }
   415  
   416  // GetUint64 gets the `uint64` option under the specified key.
   417  func (o *Options) GetUint64(key string) *uint64 {
   418  	opt, ok := o.options[key]
   419  	if !ok {
   420  		glog.Fatalf("Programmer Error: option key '%s' is not registered!", key)
   421  	}
   422  	if opt.optType != typeUint64 {
   423  		glog.Fatalf("The option with key '%s' has type '%s' not '%s'.", key, opt.optType, typeUint64)
   424  	}
   425  	return o.options[key].val.(*uint64)
   426  }
   427  
   428  // GetBool gets the `bool` option under the specified key.
   429  func (o *Options) GetBool(key string) *bool {
   430  	opt, ok := o.options[key]
   431  	if !ok {
   432  		glog.Fatalf("Programmer Error: option key '%s' is not registered!", key)
   433  	}
   434  	if opt.optType != typeBool {
   435  		glog.Fatalf("The option with key '%s' has type '%s' not '%s'.", key, opt.optType, typeBool)
   436  	}
   437  	return o.options[key].val.(*bool)
   438  }
   439  
   440  // GetDuration gets the `time.Duration` option under the specified key.
   441  func (o *Options) GetDuration(key string) *time.Duration {
   442  	opt, ok := o.options[key]
   443  	if !ok {
   444  		glog.Fatalf("Programmer Error: option key '%s' is not registered!", key)
   445  	}
   446  	if opt.optType != typeDuration {
   447  		glog.Fatalf("The option with key '%s' has type '%s' not '%s'.", key, opt.optType, typeDuration)
   448  	}
   449  	return o.options[key].val.(*time.Duration)
   450  }
   451  
   452  func toString(optType optionType, val interface{}) string {
   453  	switch optType {
   454  	case typeString:
   455  		return fmt.Sprintf("%q", *val.(*string))
   456  	case typeStringSlice:
   457  		if len(*val.(*[]string)) == 0 {
   458  			return "[]"
   459  		}
   460  		return fmt.Sprintf("[\"%s\"]", strings.Join(*val.(*[]string), "\", \""))
   461  	case typeInt:
   462  		return fmt.Sprintf("%d", *val.(*int))
   463  	case typeUint64:
   464  		return fmt.Sprintf("%d", *val.(*uint64))
   465  	case typeBool:
   466  		return fmt.Sprintf("%v", *val.(*bool))
   467  	case typeDuration:
   468  		return fmt.Sprintf("%v", *val.(*time.Duration))
   469  	case typeSecret:
   470  		return "<REDACTED>"
   471  	case typeUnknown:
   472  		return fmt.Sprintf("<UNREGISTERED> %q", val)
   473  	default:
   474  		glog.Fatalf("Unrecognized type '%s'.", optType)
   475  		return ""
   476  	}
   477  }
   478  
   479  func (o *Options) keysSortedAndWidth() ([]string, int) {
   480  	keys := make([]string, 0, len(o.options))
   481  	width := 0
   482  	for key := range o.options {
   483  		keys = append(keys, key)
   484  		if len(key) > width {
   485  			width = len(key)
   486  		}
   487  	}
   488  	sort.Strings(keys)
   489  	return keys, width
   490  }
   491  
   492  func (o *Options) Descriptions() string {
   493  	var buf bytes.Buffer
   494  	fmt.Fprint(&buf, "The below options are available. They are listed in the format 'option: (default value) \"Description\"'.\n")
   495  	sorted, width := o.keysSortedAndWidth()
   496  	width++
   497  	for _, key := range sorted {
   498  		if opt := o.options[key]; opt.optType != typeUnknown {
   499  			fmt.Fprintf(&buf, "%-*s (%s) %q\n", width, key+":", toString(opt.optType, opt.defaultVal), opt.description)
   500  		}
   501  	}
   502  	return buf.String()
   503  }
   504  
   505  func (o *Options) CurrentValues() string {
   506  	var buf bytes.Buffer
   507  	fmt.Fprint(&buf, "Currently configured option values:\n")
   508  	sorted, width := o.keysSortedAndWidth()
   509  	width++
   510  	for _, key := range sorted {
   511  		if opt := o.options[key]; opt.optType != typeUnknown {
   512  			fmt.Fprintf(&buf, "%-*s %s\n", width, key+":", toString(opt.optType, opt.val))
   513  		}
   514  	}
   515  	return buf.String()
   516  }