[#125] node: Move viper creation to internal/common/config

Signed-off-by: Anton Nikiforov <an.nikiforov@yadro.com>
This commit is contained in:
Anton Nikiforov 2023-04-20 12:44:39 +03:00
parent b2123bfd1a
commit d390f093e0
9 changed files with 101 additions and 76 deletions

View file

@ -2,6 +2,8 @@ package config
import ( import (
"strings" "strings"
configViper "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common/config"
) )
// Sub returns a subsection of the Config by name. // 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. // Returns nil if config is nil.
func (x *Config) Value(name string) any { 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 { if value != nil || x.defaultPath == nil {
return value 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. // SetDefault sets fallback config for missing values.

View file

@ -7,6 +7,7 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config" "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config"
configtest "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/test" 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" "github.com/stretchr/testify/require"
) )
@ -36,7 +37,7 @@ func TestConfigEnv(t *testing.T) {
) )
envName := strings.ToUpper( 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) err := os.Setenv(envName, value)
require.NoError(t, err) require.NoError(t, err)

View file

@ -1,10 +1,7 @@
package config package config
import ( import (
"fmt" configViper "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common/config"
"strings"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/config"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
@ -17,21 +14,16 @@ import (
type Config struct { type Config struct {
v *viper.Viper v *viper.Viper
opts opts opts configViper.Opts
defaultPath []string defaultPath []string
path []string path []string
} }
const ( const (
separator = "."
// EnvPrefix is a prefix of ENV variables related // EnvPrefix is a prefix of ENV variables related
// to storage node configuration. // to storage node configuration.
EnvPrefix = "FROSTFS" EnvPrefix = "FROSTFS"
// EnvSeparator is a section separator in ENV variables.
EnvSeparator = "_"
) )
// Prm groups required parameters of the Config. // Prm groups required parameters of the Config.
@ -42,34 +34,8 @@ type Prm struct{}
// If file option is provided (WithConfigFile), // If file option is provided (WithConfigFile),
// configuration values are read from it. // configuration values are read from it.
// Otherwise, Config is a degenerate tree. // Otherwise, Config is a degenerate tree.
func New(_ Prm, opts ...Option) *Config { func New(_ Prm, opts ...configViper.Option) *Config {
v := viper.New() v, o := configViper.CreateViper(opts...)
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 &Config{ return &Config{
v: v, v: v,
@ -79,18 +45,5 @@ func New(_ Prm, opts ...Option) *Config {
// Reload reads configuration path if it was provided to New. // Reload reads configuration path if it was provided to New.
func (x *Config) Reload() error { func (x *Config) Reload() error {
if x.opts.path != "" { return configViper.ReloadViper(x.v, x.opts)
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
} }

View file

@ -5,6 +5,7 @@ import (
"path" "path"
"testing" "testing"
configViper "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common/config"
"github.com/spf13/cast" "github.com/spf13/cast"
"github.com/stretchr/testify/require" "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(cfgFileName0, []byte(`{"storage":{"shard_pool_size":15}}`), 0777))
require.NoError(t, os.WriteFile(cfgFileName1, []byte("logger:\n level: debug"), 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.Equal(t, "debug", cast.ToString(c.Sub("logger").Value("level")))
require.EqualValues(t, 15, cast.ToUint32(c.Sub("storage").Value("shard_pool_size"))) require.EqualValues(t, 15, cast.ToUint32(c.Sub("storage").Value("shard_pool_size")))
} }

View file

@ -7,6 +7,7 @@ import (
"testing" "testing"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config" "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" "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 os.Clearenv() // ENVs have priority over config files, so we do this in tests
return config.New(p, 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 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)) { 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 { func EmptyConfig() *config.Config {
var p config.Prm 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. // loadEnv reads .env file, parses `X=Y` records and sets OS ENVs.

View file

@ -8,6 +8,7 @@ import (
"os" "os"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config" "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/internal/logs"
"git.frostfs.info/TrueCloudLab/frostfs-node/misc" "git.frostfs.info/TrueCloudLab/frostfs-node/misc"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control"
@ -47,8 +48,8 @@ func main() {
} }
appCfg := config.New(config.Prm{}, appCfg := config.New(config.Prm{},
config.WithConfigFile(*configFile), config.WithConfigDir(*configDir), configViper.WithConfigFile(*configFile), configViper.WithConfigDir(*configDir),
config.WithEnvPrefix(config.EnvPrefix)) configViper.WithEnvPrefix(config.EnvPrefix))
err := validateConfig(appCfg) err := validateConfig(appCfg)
fatalOnErr(err) fatalOnErr(err)

View file

@ -7,6 +7,7 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config" "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config"
configtest "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/test" 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" "github.com/stretchr/testify/require"
) )
@ -26,13 +27,13 @@ func TestValidate(t *testing.T) {
t.Run("mainnet", func(t *testing.T) { t.Run("mainnet", func(t *testing.T) {
os.Clearenv() // ENVs have priority over config files, so we do this in tests os.Clearenv() // ENVs have priority over config files, so we do this in tests
p := filepath.Join(exampleConfigPrefix, "mainnet/config.yml") 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)) require.NoError(t, validateConfig(c))
}) })
t.Run("testnet", func(t *testing.T) { t.Run("testnet", func(t *testing.T) {
os.Clearenv() // ENVs have priority over config files, so we do this in tests os.Clearenv() // ENVs have priority over config files, so we do this in tests
p := filepath.Join(exampleConfigPrefix, "testnet/config.yml") 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)) require.NoError(t, validateConfig(c))
}) })
} }

View file

@ -1,38 +1,38 @@
package config package config
type opts struct { type Opts struct {
path string Path string
configDir string ConfigDir string
envPrefix string EnvPrefix string
} }
func defaultOpts() *opts { func DefaultOpts() *Opts {
return new(opts) return new(Opts)
} }
// Option allows to set an optional parameter of the Config. // 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 // WithConfigFile returns an option to set the system path
// to the configuration file. // to the configuration file.
func WithConfigFile(path string) Option { func WithConfigFile(path string) Option {
return func(o *opts) { return func(o *Opts) {
o.path = path o.Path = path
} }
} }
// WithConfigDir returns an option to set the system path // WithConfigDir returns an option to set the system path
// to the directory with configuration files. // to the directory with configuration files.
func WithConfigDir(path string) Option { func WithConfigDir(path string) Option {
return func(o *opts) { return func(o *Opts) {
o.configDir = path o.ConfigDir = path
} }
} }
// WithEnvPrefix returns an option to defines // WithEnvPrefix returns an option to defines
// a prefix that ENVIRONMENT variables will use. // a prefix that ENVIRONMENT variables will use.
func WithEnvPrefix(envPrefix string) Option { func WithEnvPrefix(envPrefix string) Option {
return func(o *opts) { return func(o *Opts) {
o.envPrefix = envPrefix o.EnvPrefix = envPrefix
} }
} }

View file

@ -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
}