github.com/vseinstrumentiru/lego@v1.0.2/internal/lego/config/provider.go (about) 1 package config 2 3 import ( 4 "emperror.dev/emperror" 5 "emperror.dev/errors" 6 "fmt" 7 dynamicstruct "github.com/ompluscator/dynamic-struct" 8 "github.com/spf13/pflag" 9 "github.com/spf13/viper" 10 "github.com/vseinstrumentiru/lego/internal/lego" 11 "github.com/vseinstrumentiru/lego/internal/lego/build" 12 "os" 13 "reflect" 14 "strings" 15 ) 16 17 const ( 18 defaultEnvPrefix = "app" 19 ) 20 21 type ErrConfigFileNotFound = viper.ConfigFileNotFoundError 22 23 func IsFileNotFound(err error) bool { 24 _, ok := err.(viper.ConfigFileNotFoundError) 25 26 return ok 27 } 28 29 func configure() (*viper.Viper, *pflag.FlagSet) { 30 v, p := viper.New(), pflag.NewFlagSet("lego", pflag.ExitOnError) 31 32 v.AddConfigPath(".") 33 v.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_")) 34 v.AllowEmptyEnv(true) 35 v.AutomaticEnv() 36 37 p.String("config", "", "Configuration file") 38 p.String("config-path", "", "Search path for configuration file") 39 p.Bool("version", false, "Show version information") 40 41 v.AddConfigPath(".") 42 43 return v, p 44 } 45 46 func prepareCustomConfig(customCfg lego.Config) (lego.Config, bool) { 47 if customCfg == lego.Config(nil) { 48 return nil, false 49 } 50 51 cfgVal := reflect.ValueOf(customCfg) 52 if !cfgVal.IsValid() { 53 emperror.Panic(errors.New("config is nil pointer")) 54 } 55 56 return customCfg, true 57 } 58 59 func buildConfig(v *viper.Viper, customCfg lego.Config, customCfgPrefix string) (cfg Config, err error) { 60 builder := dynamicstruct.NewStruct(). 61 AddField("Srv", Config{}, "") 62 63 var hasCustomCfg bool 64 65 if customCfg != nil { 66 hasCustomCfg = true 67 envPrefix := customCfgPrefix 68 69 if cApp, ok := customCfg.(lego.ConfigWithCustomEnvPrefix); ok { 70 envPrefix = cApp.GetEnvPrefix() 71 } 72 v.AddConfigPath(fmt.Sprintf("$%s_CONFIG_DIR/", strings.ToUpper(envPrefix))) 73 74 builder = builder.AddField("Custom", customCfg, fmt.Sprintf(`mapstructure:"%s"`, customCfgPrefix)) 75 } 76 77 unmarshalStruct := builder.Build().New() 78 if err = v.Unmarshal(&unmarshalStruct); err != nil { 79 return Config{}, err 80 } 81 82 structReflect := reflect.ValueOf(unmarshalStruct).Elem() 83 cfg = structReflect.FieldByName("Srv").Interface().(Config) 84 if hasCustomCfg { 85 cfg.Custom = structReflect.FieldByName("Custom").Interface().(lego.Config) 86 } 87 88 cfg.Build = build.New() 89 90 return cfg, nil 91 } 92 93 type Option func(env *viper.Viper, flags *pflag.FlagSet) 94 95 func WithDefaultName(name string) Option { 96 return func(env *viper.Viper, flags *pflag.FlagSet) { 97 env.SetDefault("srv.name", name) 98 } 99 } 100 101 func Provide(customCfg lego.Config, options ...Option) (Config, error) { 102 v, p := configure() 103 104 for _, opt := range options { 105 opt(v, p) 106 } 107 108 _ = p.Parse(os.Args[1:]) 109 110 configBySrvName := false 111 112 if c, _ := p.GetString("config"); c != "" { 113 v.SetConfigFile(c) 114 } else if c, _ := p.GetString("config-path"); c != "" { 115 v.AddConfigPath(c) 116 } else if name := v.GetString("srv.name"); name != "" { 117 v.SetConfigName("config." + name) 118 configBySrvName = true 119 } 120 121 returnErr := v.ReadInConfig() 122 123 if IsFileNotFound(returnErr) && configBySrvName { 124 v.SetConfigName("config") 125 returnErr = v.ReadInConfig() 126 } 127 128 if !IsFileNotFound(returnErr) { 129 emperror.Panic(errors.Wrap(returnErr, "failed to read configuration")) 130 } 131 132 (Config{}).SetDefaults(v, p) 133 134 var hasAppCfg bool 135 customCfg, hasAppCfg = prepareCustomConfig(customCfg) 136 137 if hasAppCfg { 138 customCfg.SetDefaults(v, p) 139 } 140 141 cfg, err := buildConfig(v, customCfg, defaultEnvPrefix) 142 emperror.Panic(errors.Wrap(err, "failed to unmarshal application configuration")) 143 144 if v, _ := p.GetBool("version"); v { 145 fmt.Printf("%s version %s (%s) built on %s\n", cfg.Name, cfg.Build.Version, cfg.Build.CommitHash, cfg.Build.BuildDate) 146 147 os.Exit(0) 148 } 149 150 return cfg, returnErr 151 }