github.com/Mirantis/virtlet@v1.5.2-0.20191204181327-1659b8a48e9b/pkg/config/fields.go (about)

     1  /*
     2  Copyright 2018 Mirantis
     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 ≈git-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
    18  
    19  import (
    20  	"bytes"
    21  	"fmt"
    22  	"os"
    23  	"strconv"
    24  	"strings"
    25  
    26  	"github.com/golang/glog"
    27  	"github.com/kballard/go-shellquote"
    28  	flag "github.com/spf13/pflag"
    29  	apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
    30  )
    31  
    32  type configField interface {
    33  	typeName() string
    34  	fieldName() string
    35  	flagName() string
    36  	envName() string
    37  	defaultStr() string
    38  	applyDefault()
    39  	clear()
    40  	present() bool
    41  	addFlag(f *flag.FlagSet)
    42  	override(from configField)
    43  	setFromEnvValue(value string)
    44  	envValue() string
    45  	schemaProps() (string, apiext.JSONSchemaProps)
    46  	description() string
    47  }
    48  
    49  type fieldBase struct {
    50  	field     string
    51  	flag      string
    52  	shorthand string
    53  	desc      string
    54  	env       string
    55  }
    56  
    57  func (f *fieldBase) fieldName() string   { return f.field }
    58  func (f *fieldBase) flagName() string    { return f.flag }
    59  func (f *fieldBase) envName() string     { return f.env }
    60  func (f *fieldBase) description() string { return f.desc }
    61  
    62  type stringField struct {
    63  	fieldBase
    64  	defValue string
    65  	pattern  string
    66  	value    **string
    67  }
    68  
    69  var _ configField = &stringField{}
    70  
    71  func (sf *stringField) typeName() string   { return "string" }
    72  func (sf *stringField) defaultStr() string { return sf.defValue }
    73  func (sf *stringField) applyDefault() {
    74  	if *sf.value == nil {
    75  		*sf.value = &sf.defValue
    76  	}
    77  }
    78  
    79  func (sf *stringField) clear()        { *sf.value = nil }
    80  func (sf *stringField) present() bool { return *sf.value != nil }
    81  
    82  func (sf *stringField) addFlag(f *flag.FlagSet) {
    83  	f.StringVarP(*sf.value, sf.flag, sf.shorthand, sf.defValue, sf.desc)
    84  }
    85  
    86  func (sf *stringField) override(from configField) {
    87  	fromValue := *from.(*stringField).value
    88  	if fromValue != nil {
    89  		v := *fromValue
    90  		*sf.value = &v
    91  	}
    92  }
    93  
    94  func (sf *stringField) setFromEnvValue(value string) {
    95  	*sf.value = &value
    96  }
    97  
    98  func (sf *stringField) envValue() string {
    99  	if *sf.value == nil {
   100  		return ""
   101  	}
   102  	return **sf.value
   103  }
   104  
   105  func (sf *stringField) schemaProps() (string, apiext.JSONSchemaProps) {
   106  	return sf.field, apiext.JSONSchemaProps{
   107  		Type:    "string",
   108  		Pattern: sf.pattern,
   109  	}
   110  }
   111  
   112  type boolField struct {
   113  	fieldBase
   114  	defValue bool
   115  	value    **bool
   116  }
   117  
   118  var _ configField = &boolField{}
   119  
   120  func (bf *boolField) typeName() string   { return "boolean" }
   121  func (bf *boolField) defaultStr() string { return strconv.FormatBool(bf.defValue) }
   122  func (bf *boolField) applyDefault() {
   123  	if *bf.value == nil {
   124  		*bf.value = &bf.defValue
   125  	}
   126  }
   127  
   128  func (bf *boolField) clear()        { *bf.value = nil }
   129  func (bf *boolField) present() bool { return *bf.value != nil }
   130  
   131  func (bf *boolField) addFlag(f *flag.FlagSet) {
   132  	f.BoolVarP(*bf.value, bf.flag, bf.shorthand, bf.defValue, bf.desc)
   133  }
   134  
   135  func (bf *boolField) override(from configField) {
   136  	fromValue := *from.(*boolField).value
   137  	if fromValue != nil {
   138  		v := *fromValue
   139  		*bf.value = &v
   140  	}
   141  }
   142  
   143  func (bf *boolField) setFromEnvValue(value string) {
   144  	v := value != ""
   145  	*bf.value = &v
   146  }
   147  
   148  func (bf *boolField) envValue() string {
   149  	if *bf.value == nil || !**bf.value {
   150  		return ""
   151  	}
   152  	return "1"
   153  }
   154  
   155  func (bf *boolField) schemaProps() (string, apiext.JSONSchemaProps) {
   156  	return bf.field, apiext.JSONSchemaProps{
   157  		Type: "boolean",
   158  	}
   159  }
   160  
   161  type intField struct {
   162  	fieldBase
   163  	defValue int
   164  	min      int
   165  	max      int
   166  	value    **int
   167  }
   168  
   169  var _ configField = &intField{}
   170  
   171  func (intf *intField) typeName() string   { return "integer" }
   172  func (intf *intField) defaultStr() string { return strconv.Itoa(intf.defValue) }
   173  func (intf *intField) applyDefault() {
   174  	if *intf.value == nil {
   175  		*intf.value = &intf.defValue
   176  	}
   177  }
   178  
   179  func (intf *intField) clear()        { *intf.value = nil }
   180  func (intf *intField) present() bool { return *intf.value != nil }
   181  
   182  func (intf *intField) addFlag(f *flag.FlagSet) {
   183  	f.IntVarP(*intf.value, intf.flag, intf.shorthand, intf.defValue, intf.desc)
   184  }
   185  
   186  func (intf *intField) override(from configField) {
   187  	fromValue := *from.(*intField).value
   188  	if fromValue != nil {
   189  		v := *fromValue
   190  		*intf.value = &v
   191  	}
   192  }
   193  
   194  func (intf *intField) setFromEnvValue(value string) {
   195  	if v, err := strconv.Atoi(value); err != nil {
   196  		glog.Warningf("bad value for int field %s: %q", intf.field, value)
   197  	} else {
   198  		*intf.value = &v
   199  	}
   200  }
   201  
   202  func (intf *intField) envValue() string {
   203  	if *intf.value == nil {
   204  		return ""
   205  	}
   206  	return strconv.Itoa(**intf.value)
   207  }
   208  
   209  func (intf *intField) schemaProps() (string, apiext.JSONSchemaProps) {
   210  	min := float64(intf.min)
   211  	max := float64(intf.max)
   212  	return intf.field, apiext.JSONSchemaProps{
   213  		Type:    "integer",
   214  		Minimum: &min,
   215  		Maximum: &max,
   216  	}
   217  }
   218  
   219  type envLookup func(name string) (string, bool)
   220  
   221  type fieldSet struct {
   222  	fields   []configField
   223  	docTitle string
   224  	desc     string
   225  }
   226  
   227  func (fs *fieldSet) addStringFieldWithPattern(field, flag, shorthand, desc, env, defValue, pattern string, value **string) {
   228  	fs.addField(&stringField{
   229  		fieldBase{field, flag, shorthand, desc, env},
   230  		defValue,
   231  		pattern,
   232  		value,
   233  	})
   234  }
   235  
   236  func (fs *fieldSet) addStringField(field, flag, shorthand, desc, env, defValue string, value **string) {
   237  	fs.addStringFieldWithPattern(field, flag, shorthand, desc, env, defValue, "", value)
   238  }
   239  
   240  func (fs *fieldSet) addBoolField(field, flag, shorthand, desc, env string, defValue bool, value **bool) {
   241  	fs.addField(&boolField{
   242  		fieldBase{field, flag, shorthand, desc, env},
   243  		defValue,
   244  		value,
   245  	})
   246  }
   247  
   248  func (fs *fieldSet) addIntField(field, flag, shorthand, desc, env string, defValue, min, max int, value **int) {
   249  	fs.addField(&intField{
   250  		fieldBase{field, flag, shorthand, desc, env},
   251  		defValue,
   252  		min,
   253  		max,
   254  		value,
   255  	})
   256  }
   257  
   258  func (fs *fieldSet) addField(cf configField) {
   259  	fs.fields = append(fs.fields, cf)
   260  }
   261  
   262  func (fs *fieldSet) applyDefaults() {
   263  	for _, f := range fs.fields {
   264  		f.applyDefault()
   265  	}
   266  }
   267  
   268  func (fs *fieldSet) addFlags(flagSet *flag.FlagSet) {
   269  	for _, f := range fs.fields {
   270  		if f.flagName() != "" && !strings.Contains(f.flagName(), "+") {
   271  			f.addFlag(flagSet)
   272  		}
   273  	}
   274  }
   275  
   276  func (fs *fieldSet) override(from *fieldSet) {
   277  	for n, f := range fs.fields {
   278  		f.override(from.fields[n])
   279  	}
   280  }
   281  
   282  func (fs *fieldSet) copyFrom(from *fieldSet) {
   283  	for n, f := range fs.fields {
   284  		f.clear()
   285  		f.override(from.fields[n])
   286  	}
   287  }
   288  
   289  func (fs *fieldSet) clearFieldsNotInFlagSet(flagSet *flag.FlagSet) {
   290  	for _, f := range fs.fields {
   291  		if flagSet == nil || f.flagName() == "" || !flagSet.Changed(f.flagName()) {
   292  			f.clear()
   293  		}
   294  	}
   295  }
   296  
   297  func (fs *fieldSet) setFromEnv(lookupEnv envLookup) {
   298  	if lookupEnv == nil {
   299  		lookupEnv = os.LookupEnv
   300  	}
   301  	for _, f := range fs.fields {
   302  		if f.envName() == "" {
   303  			continue
   304  		}
   305  		if v, found := lookupEnv(f.envName()); found {
   306  			f.setFromEnvValue(v)
   307  		}
   308  	}
   309  }
   310  
   311  func (fs *fieldSet) dumpEnv() string {
   312  	var buf bytes.Buffer
   313  	for _, f := range fs.fields {
   314  		if f.envName() != "" && f.present() {
   315  			fmt.Fprintf(&buf, "export %s=%s\n", f.envName(), shellquote.Join(f.envValue()))
   316  		}
   317  	}
   318  	return buf.String()
   319  }
   320  
   321  func (fs *fieldSet) schemaProps() map[string]apiext.JSONSchemaProps {
   322  	r := make(map[string]apiext.JSONSchemaProps)
   323  	for _, f := range fs.fields {
   324  		field, props := f.schemaProps()
   325  		r[field] = props
   326  	}
   327  	return r
   328  }
   329  
   330  func (fs *fieldSet) generateDoc() string {
   331  	var buf bytes.Buffer
   332  	fmt.Fprintf(&buf,
   333  		"| Description | Config field | Default value | Type | Command line flag / Env |\n"+
   334  			"| --- | --- | --- | --- | --- |\n")
   335  	esc := func(s string) string { return strings.Replace(s, "|", "\\|", -1) }
   336  	code := func(s string) string {
   337  		if s == "" {
   338  			return ""
   339  		}
   340  		return fmt.Sprintf("`%s`", esc(s))
   341  	}
   342  	for _, f := range fs.fields {
   343  		if f.description() == "" {
   344  			continue
   345  		}
   346  		var flagEnv []string
   347  		if f.flagName() != "" {
   348  			flagEnv = append(flagEnv, code("--"+strings.Replace(f.flagName(), "+", "", -1)))
   349  		}
   350  		if f.envName() != "" {
   351  			flagEnv = append(flagEnv, code(f.envName()))
   352  		}
   353  		fmt.Fprintf(&buf, "| %s | %s | %s | %s | %s |\n",
   354  			esc(f.description()),
   355  			code(f.fieldName()),
   356  			code(f.defaultStr()),
   357  			f.typeName(),
   358  			strings.Join(flagEnv, " / "))
   359  	}
   360  	return buf.String()
   361  }