viperconfig.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. /*
  2. Copyright 2018 The Kubernetes Authors.
  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. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package e2e
  14. import (
  15. "flag"
  16. "fmt"
  17. "path/filepath"
  18. "github.com/pkg/errors"
  19. "github.com/spf13/viper"
  20. )
  21. // viperizeFlags checks whether a configuration file was specified,
  22. // reads it, and updates the configuration variables in the specified
  23. // flag set accordingly. Must be called after framework.HandleFlags()
  24. // and before framework.AfterReadingAllFlags().
  25. //
  26. // The logic is so that a required configuration file must be present. If empty,
  27. // the optional configuration file is used instead, unless also empty.
  28. //
  29. // Files can be specified with just a base name ("e2e", matches "e2e.json/yaml/..." in
  30. // the current directory) or with path and suffix.
  31. func viperizeFlags(requiredConfig, optionalConfig string, flags *flag.FlagSet) error {
  32. viperConfig := optionalConfig
  33. required := false
  34. if requiredConfig != "" {
  35. viperConfig = requiredConfig
  36. required = true
  37. }
  38. if viperConfig == "" {
  39. return nil
  40. }
  41. viper.SetConfigName(filepath.Base(viperConfig))
  42. viper.AddConfigPath(filepath.Dir(viperConfig))
  43. wrapError := func(err error) error {
  44. if err == nil {
  45. return nil
  46. }
  47. errorPrefix := fmt.Sprintf("viper config %q", viperConfig)
  48. actualFile := viper.ConfigFileUsed()
  49. if actualFile != "" && actualFile != viperConfig {
  50. errorPrefix = fmt.Sprintf("%s = %q", errorPrefix, actualFile)
  51. }
  52. return errors.Wrap(err, errorPrefix)
  53. }
  54. if err := viper.ReadInConfig(); err != nil {
  55. // If the user specified a file suffix, the Viper won't
  56. // find the file because it always appends its known set
  57. // of file suffices. Therefore try once more without
  58. // suffix.
  59. ext := filepath.Ext(viperConfig)
  60. if _, ok := err.(viper.ConfigFileNotFoundError); ok && ext != "" {
  61. viper.SetConfigName(filepath.Base(viperConfig[0 : len(viperConfig)-len(ext)]))
  62. err = viper.ReadInConfig()
  63. }
  64. if err != nil {
  65. // If a config was required, then parsing must
  66. // succeed. This catches syntax errors and
  67. // "file not found". Unfortunately error
  68. // messages are sometimes hard to understand,
  69. // so try to help the user a bit.
  70. switch err.(type) {
  71. case viper.ConfigFileNotFoundError:
  72. if required {
  73. return wrapError(errors.New("not found"))
  74. }
  75. // Proceed without config.
  76. return nil
  77. case viper.UnsupportedConfigError:
  78. if required {
  79. return wrapError(errors.New("not using a supported file format"))
  80. }
  81. // Proceed without config.
  82. return nil
  83. default:
  84. // Something isn't right in the file.
  85. return wrapError(err)
  86. }
  87. }
  88. }
  89. // Update all flag values not already set with values found
  90. // via Viper. We do this ourselves instead of calling
  91. // something like viper.Unmarshal(&TestContext) because we
  92. // want to support all values, regardless where they are
  93. // stored.
  94. return wrapError(viperUnmarshal(flags))
  95. }
  96. // viperUnmarshall updates all flags with the corresponding values found
  97. // via Viper, regardless whether the flag value is stored in TestContext, some other
  98. // context or a local variable.
  99. func viperUnmarshal(flags *flag.FlagSet) error {
  100. var result error
  101. set := make(map[string]bool)
  102. // Determine which values were already set explicitly via
  103. // flags. Those we don't overwrite because command line
  104. // flags have a higher priority.
  105. flags.Visit(func(f *flag.Flag) {
  106. set[f.Name] = true
  107. })
  108. flags.VisitAll(func(f *flag.Flag) {
  109. if result != nil ||
  110. set[f.Name] ||
  111. !viper.IsSet(f.Name) {
  112. return
  113. }
  114. // In contrast to viper.Unmarshal(), values
  115. // that have the wrong type (for example, a
  116. // list instead of a plain string) will not
  117. // trigger an error here. This could be fixed
  118. // by checking the type ourselves, but
  119. // probably isn't worth the effort.
  120. //
  121. // "%v" correctly turns bool, int, strings into
  122. // the representation expected by flag, so those
  123. // can be used in config files. Plain strings
  124. // always work there, just as on the command line.
  125. str := fmt.Sprintf("%v", viper.Get(f.Name))
  126. if err := f.Value.Set(str); err != nil {
  127. result = fmt.Errorf("setting option %q from config file value: %s", f.Name, err)
  128. }
  129. })
  130. return result
  131. }