diff --git a/cmd/frostfs-node/config/calls.go b/cmd/frostfs-node/config/calls.go index 6cf3baa039..36e53ea7c9 100644 --- a/cmd/frostfs-node/config/calls.go +++ b/cmd/frostfs-node/config/calls.go @@ -2,6 +2,8 @@ package config import ( "strings" + + configViper "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common/config" ) // Sub returns a subsection of the Config by name. @@ -38,11 +40,11 @@ func (x *Config) Sub(name string) *Config { // // Returns nil if config is nil. func (x *Config) Value(name string) any { - value := x.v.Get(strings.Join(append(x.path, name), separator)) + value := x.v.Get(strings.Join(append(x.path, name), configViper.Separator)) if value != nil || x.defaultPath == nil { return value } - return x.v.Get(strings.Join(append(x.defaultPath, name), separator)) + return x.v.Get(strings.Join(append(x.defaultPath, name), configViper.Separator)) } // SetDefault sets fallback config for missing values. diff --git a/cmd/frostfs-node/config/calls_test.go b/cmd/frostfs-node/config/calls_test.go index 9b283c8f6d..68bf1c6794 100644 --- a/cmd/frostfs-node/config/calls_test.go +++ b/cmd/frostfs-node/config/calls_test.go @@ -7,6 +7,7 @@ import ( "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config" configtest "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/test" + configViper "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common/config" "github.com/stretchr/testify/require" ) @@ -36,7 +37,7 @@ func TestConfigEnv(t *testing.T) { ) envName := strings.ToUpper( - strings.Join([]string{config.EnvPrefix, section, name}, config.EnvSeparator)) + strings.Join([]string{config.EnvPrefix, section, name}, configViper.EnvSeparator)) err := os.Setenv(envName, value) require.NoError(t, err) diff --git a/cmd/frostfs-node/config/config.go b/cmd/frostfs-node/config/config.go index cee6a1ea6d..cf8ca0a4d6 100644 --- a/cmd/frostfs-node/config/config.go +++ b/cmd/frostfs-node/config/config.go @@ -1,10 +1,7 @@ package config import ( - "fmt" - "strings" - - "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/config" + configViper "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common/config" "github.com/spf13/viper" ) @@ -17,21 +14,16 @@ import ( type Config struct { v *viper.Viper - opts opts + opts configViper.Opts defaultPath []string path []string } const ( - separator = "." - // EnvPrefix is a prefix of ENV variables related // to storage node configuration. EnvPrefix = "FROSTFS" - - // EnvSeparator is a section separator in ENV variables. - EnvSeparator = "_" ) // Prm groups required parameters of the Config. @@ -42,34 +34,8 @@ type Prm struct{} // If file option is provided (WithConfigFile), // configuration values are read from it. // Otherwise, Config is a degenerate tree. -func New(_ Prm, opts ...Option) *Config { - v := viper.New() - - o := defaultOpts() - for i := range opts { - opts[i](o) - } - - if o.envPrefix != "" { - v.SetEnvPrefix(o.envPrefix) - v.AutomaticEnv() - v.SetEnvKeyReplacer(strings.NewReplacer(separator, EnvSeparator)) - } - - if o.path != "" { - v.SetConfigFile(o.path) - - err := v.ReadInConfig() - if err != nil { - panic(fmt.Errorf("failed to read config: %w", err)) - } - } - - if o.configDir != "" { - if err := config.ReadConfigDir(v, o.configDir); err != nil { - panic(fmt.Errorf("failed to read config dir: %w", err)) - } - } +func New(_ Prm, opts ...configViper.Option) *Config { + v, o := configViper.CreateViper(opts...) return &Config{ v: v, @@ -79,18 +45,5 @@ func New(_ Prm, opts ...Option) *Config { // Reload reads configuration path if it was provided to New. func (x *Config) Reload() error { - if x.opts.path != "" { - err := x.v.ReadInConfig() - if err != nil { - return fmt.Errorf("rereading configuration file: %w", err) - } - } - - if x.opts.configDir != "" { - if err := config.ReadConfigDir(x.v, x.opts.configDir); err != nil { - return fmt.Errorf("rereading configuration dir: %w", err) - } - } - - return nil + return configViper.ReloadViper(x.v, x.opts) } diff --git a/cmd/frostfs-node/config/configdir_test.go b/cmd/frostfs-node/config/configdir_test.go index 2c32556705..2e2e831a12 100644 --- a/cmd/frostfs-node/config/configdir_test.go +++ b/cmd/frostfs-node/config/configdir_test.go @@ -5,6 +5,7 @@ import ( "path" "testing" + configViper "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common/config" "github.com/spf13/cast" "github.com/stretchr/testify/require" ) @@ -18,7 +19,7 @@ func TestConfigDir(t *testing.T) { require.NoError(t, os.WriteFile(cfgFileName0, []byte(`{"storage":{"shard_pool_size":15}}`), 0777)) require.NoError(t, os.WriteFile(cfgFileName1, []byte("logger:\n level: debug"), 0777)) - c := New(Prm{}, WithConfigDir(dir)) + c := New(Prm{}, configViper.WithConfigDir(dir)) require.Equal(t, "debug", cast.ToString(c.Sub("logger").Value("level"))) require.EqualValues(t, 15, cast.ToUint32(c.Sub("storage").Value("shard_pool_size"))) } diff --git a/cmd/frostfs-node/config/test/config.go b/cmd/frostfs-node/config/test/config.go index 808a481f92..b3f659030e 100644 --- a/cmd/frostfs-node/config/test/config.go +++ b/cmd/frostfs-node/config/test/config.go @@ -7,6 +7,7 @@ import ( "testing" "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config" + configViper "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common/config" "github.com/stretchr/testify/require" ) @@ -16,7 +17,7 @@ func fromFile(path string) *config.Config { os.Clearenv() // ENVs have priority over config files, so we do this in tests return config.New(p, - config.WithConfigFile(path), + configViper.WithConfigFile(path), ) } @@ -25,7 +26,7 @@ func fromEnvFile(t testing.TB, path string) *config.Config { loadEnv(t, path) // github.com/joho/godotenv can do that as well - return config.New(p, config.WithEnvPrefix(config.EnvPrefix)) + return config.New(p, configViper.WithEnvPrefix(config.EnvPrefix)) } func forEachFile(paths []string, f func(*config.Config)) { @@ -53,7 +54,7 @@ func ForEnvFileType(t testing.TB, pref string, f func(*config.Config)) { func EmptyConfig() *config.Config { var p config.Prm - return config.New(p, config.WithEnvPrefix(config.EnvPrefix)) + return config.New(p, configViper.WithEnvPrefix(config.EnvPrefix)) } // loadEnv reads .env file, parses `X=Y` records and sets OS ENVs. diff --git a/cmd/frostfs-node/main.go b/cmd/frostfs-node/main.go index b8bec87b52..877a80f6ad 100644 --- a/cmd/frostfs-node/main.go +++ b/cmd/frostfs-node/main.go @@ -8,6 +8,7 @@ import ( "os" "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config" + configViper "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common/config" "git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs" "git.frostfs.info/TrueCloudLab/frostfs-node/misc" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control" @@ -47,8 +48,8 @@ func main() { } appCfg := config.New(config.Prm{}, - config.WithConfigFile(*configFile), config.WithConfigDir(*configDir), - config.WithEnvPrefix(config.EnvPrefix)) + configViper.WithConfigFile(*configFile), configViper.WithConfigDir(*configDir), + configViper.WithEnvPrefix(config.EnvPrefix)) err := validateConfig(appCfg) fatalOnErr(err) diff --git a/cmd/frostfs-node/validate_test.go b/cmd/frostfs-node/validate_test.go index e35fc1afdc..74903884b6 100644 --- a/cmd/frostfs-node/validate_test.go +++ b/cmd/frostfs-node/validate_test.go @@ -7,6 +7,7 @@ import ( "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config" configtest "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/test" + configViper "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common/config" "github.com/stretchr/testify/require" ) @@ -26,13 +27,13 @@ func TestValidate(t *testing.T) { t.Run("mainnet", func(t *testing.T) { os.Clearenv() // ENVs have priority over config files, so we do this in tests p := filepath.Join(exampleConfigPrefix, "mainnet/config.yml") - c := config.New(config.Prm{}, config.WithConfigFile(p)) + c := config.New(config.Prm{}, configViper.WithConfigFile(p)) require.NoError(t, validateConfig(c)) }) t.Run("testnet", func(t *testing.T) { os.Clearenv() // ENVs have priority over config files, so we do this in tests p := filepath.Join(exampleConfigPrefix, "testnet/config.yml") - c := config.New(config.Prm{}, config.WithConfigFile(p)) + c := config.New(config.Prm{}, configViper.WithConfigFile(p)) require.NoError(t, validateConfig(c)) }) } diff --git a/cmd/frostfs-node/config/opts.go b/cmd/internal/common/config/opts.go similarity index 65% rename from cmd/frostfs-node/config/opts.go rename to cmd/internal/common/config/opts.go index f49e1db692..a28b2b522a 100644 --- a/cmd/frostfs-node/config/opts.go +++ b/cmd/internal/common/config/opts.go @@ -1,38 +1,38 @@ package config -type opts struct { - path string - configDir string - envPrefix string +type Opts struct { + Path string + ConfigDir string + EnvPrefix string } -func defaultOpts() *opts { - return new(opts) +func DefaultOpts() *Opts { + return new(Opts) } // Option allows to set an optional parameter of the Config. -type Option func(*opts) +type Option func(*Opts) // WithConfigFile returns an option to set the system path // to the configuration file. func WithConfigFile(path string) Option { - return func(o *opts) { - o.path = path + return func(o *Opts) { + o.Path = path } } // WithConfigDir returns an option to set the system path // to the directory with configuration files. func WithConfigDir(path string) Option { - return func(o *opts) { - o.configDir = path + return func(o *Opts) { + o.ConfigDir = path } } // WithEnvPrefix returns an option to defines // a prefix that ENVIRONMENT variables will use. func WithEnvPrefix(envPrefix string) Option { - return func(o *opts) { - o.envPrefix = envPrefix + return func(o *Opts) { + o.EnvPrefix = envPrefix } } diff --git a/cmd/internal/common/config/viper.go b/cmd/internal/common/config/viper.go new file mode 100644 index 0000000000..63e8ba05c6 --- /dev/null +++ b/cmd/internal/common/config/viper.go @@ -0,0 +1,65 @@ +package config + +import ( + "fmt" + "strings" + + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/config" + "github.com/spf13/viper" +) + +const ( + Separator = "." + + // EnvSeparator is a section separator in ENV variables. + EnvSeparator = "_" +) + +func CreateViper(opts ...Option) (*viper.Viper, *Opts) { + v := viper.New() + + o := DefaultOpts() + for i := range opts { + opts[i](o) + } + + if o.EnvPrefix != "" { + v.SetEnvPrefix(o.EnvPrefix) + v.AutomaticEnv() + v.SetEnvKeyReplacer(strings.NewReplacer(Separator, EnvSeparator)) + } + + if o.Path != "" { + v.SetConfigFile(o.Path) + + err := v.ReadInConfig() + if err != nil { + panic(fmt.Errorf("failed to read config: %w", err)) + } + } + + if o.ConfigDir != "" { + if err := config.ReadConfigDir(v, o.ConfigDir); err != nil { + panic(fmt.Errorf("failed to read config dir: %w", err)) + } + } + + return v, o +} + +func ReloadViper(v *viper.Viper, o Opts) error { + if o.Path != "" { + err := v.ReadInConfig() + if err != nil { + return fmt.Errorf("rereading configuration file: %w", err) + } + } + + if o.ConfigDir != "" { + if err := config.ReadConfigDir(v, o.ConfigDir); err != nil { + return fmt.Errorf("rereading configuration dir: %w", err) + } + } + + return nil +}