github.com/cactusblossom/fabric-ca@v0.0.0-20200611062428-0082fc643826/util/flag.go (about)

     1  /*
     2  Copyright IBM Corp. 2017 All Rights Reserved.
     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 util
    18  
    19  import (
    20  	"fmt"
    21  	"reflect"
    22  	"strconv"
    23  	"strings"
    24  	"time"
    25  
    26  	"github.com/cloudflare/cfssl/log"
    27  	"github.com/mitchellh/mapstructure"
    28  	logging "github.com/op/go-logging"
    29  	"github.com/pkg/errors"
    30  	"github.com/spf13/cast"
    31  	"github.com/spf13/pflag"
    32  	"github.com/spf13/viper"
    33  )
    34  
    35  const (
    36  	// TagDefault is the tag name for a default value of a field as recognized
    37  	// by RegisterFlags.
    38  	TagDefault = "def"
    39  	// TagHelp is the tag name for a help message of a field as recognized
    40  	// by RegisterFlags.
    41  	TagHelp = "help"
    42  	// TagOpt is the tag name for a one character option of a field as recognized
    43  	// by RegisterFlags.  For example, a value of "d" reserves "-d" for the
    44  	// command line argument.
    45  	TagOpt = "opt"
    46  	// TagSkip is the tag name which causes the field to be skipped by
    47  	// RegisterFlags.
    48  	TagSkip = "skip"
    49  	// TagHide is the tag name which causes the field to be hidden
    50  	TagHide = "hide"
    51  )
    52  
    53  // RegisterFlags registers flags for all fields in an arbitrary 'config' object.
    54  // This method recognizes the following field tags:
    55  // "def" - the default value of the field;
    56  // "opt" - the optional one character short name to use on the command line;
    57  // "help" - the help message to display on the command line;
    58  // "skip" - to skip the field.
    59  func RegisterFlags(v *viper.Viper, flags *pflag.FlagSet, config interface{},
    60  	tags map[string]string) error {
    61  	fr := &flagRegistrar{flags: flags, tags: tags, viper: v}
    62  	return ParseObj(config, fr.Register, tags)
    63  }
    64  
    65  type flagRegistrar struct {
    66  	flags *pflag.FlagSet
    67  	tags  map[string]string
    68  	viper *viper.Viper
    69  }
    70  
    71  func (fr *flagRegistrar) Register(f *Field) (err error) {
    72  	// Don't register non-leaf fields
    73  	if !f.Leaf {
    74  		return nil
    75  	}
    76  	// Don't register fields with no address
    77  	if f.Addr == nil {
    78  		return errors.Errorf("Field is not addressable: %s", f.Path)
    79  	}
    80  	skip := fr.getTag(f, TagSkip)
    81  	if skip != "" {
    82  		return nil
    83  	}
    84  
    85  	help := fr.getTag(f, TagHelp)
    86  	opt := fr.getTag(f, TagOpt)
    87  	def := fr.getTag(f, TagDefault)
    88  	hide := fr.getHideBooleanTag(f)
    89  	switch f.Kind {
    90  
    91  	case reflect.String:
    92  		if help == "" && !hide {
    93  			return errors.Errorf("Field is missing a help tag: %s", f.Path)
    94  		}
    95  		fr.flags.StringVarP(f.Addr.(*string), f.Path, opt, def, help)
    96  	case reflect.Int:
    97  		if help == "" && !hide {
    98  			return errors.Errorf("Field is missing a help tag: %s", f.Path)
    99  		}
   100  		var intDef int
   101  		if def != "" {
   102  			intDef, err = strconv.Atoi(def)
   103  			if err != nil {
   104  				return errors.Errorf("Invalid integer value in 'def' tag of %s field", f.Path)
   105  			}
   106  		}
   107  		fr.flags.IntVarP(f.Addr.(*int), f.Path, opt, intDef, help)
   108  	case reflect.Int64:
   109  		if help == "" && !hide {
   110  			return errors.Errorf("Field is missing a help tag: %s", f.Path)
   111  		}
   112  		d, ok := f.Addr.(*time.Duration)
   113  		if !ok {
   114  			var intDef int64
   115  			if def != "" {
   116  				intDef, err = strconv.ParseInt(def, 10, 64)
   117  				if err != nil {
   118  					return errors.Errorf("Invalid int64 value in 'def' tag of %s field", f.Path)
   119  				}
   120  			}
   121  			fr.flags.Int64VarP(f.Addr.(*int64), f.Path, opt, intDef, help)
   122  		} else {
   123  			var intDef time.Duration
   124  			if def != "" {
   125  				intDef, err = time.ParseDuration(def)
   126  				if err != nil {
   127  					return errors.Errorf("Invalid duration value in 'def' tag of %s field", f.Path)
   128  				}
   129  			}
   130  			fr.flags.DurationVarP(d, f.Path, opt, intDef, help)
   131  		}
   132  	case reflect.Bool:
   133  		if help == "" && !hide {
   134  			return errors.Errorf("Field is missing a help tag: %s", f.Path)
   135  		}
   136  		var boolDef bool
   137  		if def != "" {
   138  			boolDef, err = strconv.ParseBool(def)
   139  			if err != nil {
   140  				return errors.Errorf("Invalid boolean value in 'def' tag of %s field", f.Path)
   141  			}
   142  		}
   143  		fr.flags.BoolVarP(f.Addr.(*bool), f.Path, opt, boolDef, help)
   144  	case reflect.Slice:
   145  		if f.Type.Elem().Kind() == reflect.String {
   146  			if help == "" && !hide {
   147  				return errors.Errorf("Field is missing a help tag: %s", f.Path)
   148  			}
   149  			fr.flags.StringSliceVarP(f.Addr.(*[]string), f.Path, opt, nil, help)
   150  		} else {
   151  			return nil
   152  		}
   153  	default:
   154  		log.Debugf("Not registering flag for '%s' because it is a currently unsupported type: %s\n",
   155  			f.Path, f.Kind)
   156  		return nil
   157  	}
   158  	if hide {
   159  		fr.flags.MarkHidden(f.Path)
   160  	}
   161  	bindFlag(fr.viper, fr.flags, f.Path)
   162  	return nil
   163  }
   164  
   165  func (fr *flagRegistrar) getTag(f *Field, tagName string) string {
   166  	var key, val string
   167  	key = fmt.Sprintf("%s.%s", tagName, f.Path)
   168  	if fr.tags != nil {
   169  		val = fr.tags[key]
   170  	}
   171  	if val == "" {
   172  		val = f.Tag.Get(tagName)
   173  	}
   174  	return val
   175  }
   176  
   177  func (fr *flagRegistrar) getHideBooleanTag(f *Field) bool {
   178  	boolVal, err := strconv.ParseBool(f.Hide)
   179  	if err != nil {
   180  		return false
   181  	}
   182  	return boolVal
   183  }
   184  
   185  // CmdRunBegin is called at the beginning of each cobra run function
   186  func CmdRunBegin(v *viper.Viper) {
   187  	// If -d or --debug, set debug logging level
   188  	if v.GetBool("debug") {
   189  		log.Level = log.LevelDebug
   190  
   191  		logging.SetLevel(logging.INFO, "bccsp")
   192  		logging.SetLevel(logging.INFO, "bccsp_p11")
   193  		logging.SetLevel(logging.INFO, "bccsp_sw")
   194  	}
   195  }
   196  
   197  // FlagString sets up a flag for a string, binding it to its name
   198  func FlagString(v *viper.Viper, flags *pflag.FlagSet, name, short string, def string, desc string) {
   199  	flags.StringP(name, short, def, desc)
   200  	bindFlag(v, flags, name)
   201  }
   202  
   203  // common binding function
   204  func bindFlag(v *viper.Viper, flags *pflag.FlagSet, name string) {
   205  	flag := flags.Lookup(name)
   206  	if flag == nil {
   207  		panic(fmt.Errorf("failed to lookup '%s'", name))
   208  	}
   209  	v.BindPFlag(name, flag)
   210  }
   211  
   212  // ViperUnmarshal is a work around for a bug in viper.Unmarshal
   213  // This can be removed once https://github.com/spf13/viper/issues/327 is fixed
   214  // and vendored.
   215  func ViperUnmarshal(cfg interface{}, stringSliceFields []string, vp *viper.Viper) error {
   216  	decoderConfig := &mapstructure.DecoderConfig{
   217  		Metadata:         nil,
   218  		Result:           cfg,
   219  		WeaklyTypedInput: true,
   220  		DecodeHook:       mapstructure.StringToTimeDurationHookFunc(),
   221  	}
   222  	decoder, err := mapstructure.NewDecoder(decoderConfig)
   223  	if err != nil {
   224  		return errors.Wrap(err, "Failed to create decoder")
   225  	}
   226  	settings := vp.AllSettings()
   227  	for _, field := range stringSliceFields {
   228  		var ok bool
   229  		path := strings.Split(field, ".")
   230  		m := settings
   231  		name := path[0]
   232  		// If it is a top level option check to see if nil before continuing
   233  		if _, ok = m[name]; !ok {
   234  			continue
   235  		}
   236  
   237  		if len(path) > 1 {
   238  			for _, field2 := range path[1:] {
   239  				m = m[name].(map[string]interface{})
   240  				name = field2
   241  
   242  				// Inspect nested options to see if nil before proceeding with loop
   243  				if _, ok = m[name]; !ok {
   244  					break
   245  				}
   246  			}
   247  		}
   248  		// Only do casting if path was valid
   249  		if ok {
   250  			m[name] = cast.ToStringSlice(m[name])
   251  		}
   252  	}
   253  
   254  	return decoder.Decode(settings)
   255  }