Replace viper with koanf (#4845)

* Migrate to koanf
* Use temp logger for crashes before config is initialised
* Remove snake case hacks
* Add migration for config file keys
* Add migration note for new migration
* Renamed viper functions
* Remove front-end viper workaround
* Correctly default scan options
This commit is contained in:
WithoutPants
2024-05-21 11:24:47 +10:00
committed by GitHub
parent 0fa71be697
commit bfc60bb23f
17 changed files with 594 additions and 437 deletions

View File

@@ -8,8 +8,10 @@ import (
"path/filepath"
"strings"
"github.com/knadh/koanf"
"github.com/knadh/koanf/providers/env"
"github.com/knadh/koanf/providers/posflag"
"github.com/spf13/pflag"
"github.com/spf13/viper"
"github.com/stashapp/stash/pkg/fsutil"
"github.com/stashapp/stash/pkg/logger"
@@ -20,7 +22,30 @@ type flagStruct struct {
nobrowser bool
}
var flags flagStruct
var (
flags flagStruct
homeDir, _ = os.UserHomeDir()
defaultConfigLocations = []string{
"config.yml",
filepath.Join(homeDir, ".stash", "config.yml"),
}
// map of env vars to config keys
envBinds = map[string]string{
"host": Host,
"port": Port,
"external_host": ExternalHost,
"generated": Generated,
"metadata": Metadata,
"cache": Cache,
"stash": Stash,
"ui": UILocation,
}
)
var errConfigNotFound = errors.New("config file not found")
func init() {
pflag.IP("host", net.IPv4(0, 0, 0, 0), "ip address for the host")
@@ -33,8 +58,8 @@ func init() {
// Called at startup
func Initialize() (*Config, error) {
cfg := &Config{
main: viper.New(),
overrides: viper.New(),
main: koanf.New("."),
overrides: koanf.New("."),
}
cfg.initOverrides()
@@ -77,54 +102,49 @@ func Initialize() (*Config, error) {
// Called by tests to initialize an empty config
func InitializeEmpty() *Config {
cfg := &Config{
main: viper.New(),
overrides: viper.New(),
main: koanf.New("."),
overrides: koanf.New("."),
}
instance = cfg
return instance
}
func bindEnv(v *viper.Viper, key ...string) {
if err := v.BindEnv(key...); err != nil {
panic(fmt.Sprintf("unable to set environment key (%v): %v", key, err))
func (i *Config) loadFromCommandLine() {
v := i.overrides
if err := v.Load(posflag.ProviderWithFlag(pflag.CommandLine, ".", v, func(f *pflag.Flag) (string, interface{}) {
// ignore flags that have not been changed
if !f.Changed {
return "", nil
}
return f.Name, posflag.FlagVal(pflag.CommandLine, f)
}), nil); err != nil {
logger.Errorf("failed to load flags: %v", err)
}
}
func (i *Config) loadFromEnv() {
v := i.overrides
if err := v.Load(env.ProviderWithValue("STASH_", ".", func(key, value string) (string, interface{}) {
key = strings.ToLower(strings.TrimPrefix(key, "STASH_"))
if newKey, ok := envBinds[key]; ok {
return newKey, value
}
return "", nil
}), nil); err != nil {
logger.Errorf("failed to load envs: %v", err)
}
}
func (i *Config) initOverrides() {
v := i.overrides
// replace dashes with underscores in the flag names
normalizeFn := pflag.CommandLine.GetNormalizeFunc()
pflag.CommandLine.SetNormalizeFunc(func(f *pflag.FlagSet, name string) pflag.NormalizedName {
result := normalizeFn(f, name)
name = strings.ReplaceAll(string(result), "-", "_")
return pflag.NormalizedName(name)
})
if err := v.BindPFlags(pflag.CommandLine); err != nil {
logger.Infof("failed to bind flags: %v", err)
}
v.SetEnvPrefix("stash") // will be uppercased automatically
bindEnv(v, "host") // STASH_HOST
bindEnv(v, "port") // STASH_PORT
bindEnv(v, "external_host") // STASH_EXTERNAL_HOST
bindEnv(v, "generated") // STASH_GENERATED
bindEnv(v, "metadata") // STASH_METADATA
bindEnv(v, "cache") // STASH_CACHE
bindEnv(v, "stash") // STASH_STASH
bindEnv(v, "ui_location", "STASH_UI") // STASH_UI
i.loadFromCommandLine()
i.loadFromEnv()
}
func (i *Config) initConfig() error {
v := i.main
// The config file is called config. Leave off the file extension.
v.SetConfigName("config")
v.AddConfigPath(".") // Look for config in the working directory
v.AddConfigPath(filepath.FromSlash("$HOME/.stash")) // Look for the config in the home directory
configFile := ""
envConfigFile := os.Getenv("STASH_CONFIG_FILE")
@@ -135,8 +155,6 @@ func (i *Config) initConfig() error {
}
if configFile != "" {
v.SetConfigFile(configFile)
// if file does not exist, assume it is a new system
if exists, _ := fsutil.FileExists(configFile); !exists {
i.isNewSystem = true
@@ -150,18 +168,33 @@ func (i *Config) initConfig() error {
}
return nil
} else {
// load from provided config file
if err := i.loadFirstFromFiles([]string{configFile}); err != nil {
return err
}
}
}
} else {
// load from default locations
if err := i.loadFirstFromFiles(defaultConfigLocations); err != nil {
if errors.Is(err, errConfigNotFound) {
i.isNewSystem = true
return nil
}
err := v.ReadInConfig() // Find and read the config file
// if not found, assume its a new system
var notFoundErr viper.ConfigFileNotFoundError
if errors.As(err, &notFoundErr) {
i.isNewSystem = true
return nil
} else if err != nil {
return err
return err
}
}
return nil
}
func (i *Config) loadFirstFromFiles(f []string) error {
for _, ff := range f {
if exists, _ := fsutil.FileExists(ff); exists {
return i.load(ff)
}
}
return errConfigNotFound
}