viperconfig.go 4.4 KB

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