forked from TrueCloudLab/frostfs-node
[#125] node: Move viper creation to internal/common/config
Signed-off-by: Anton Nikiforov <an.nikiforov@yadro.com>
This commit is contained in:
parent
b2123bfd1a
commit
d390f093e0
9 changed files with 101 additions and 76 deletions
|
@ -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.
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
65
cmd/internal/common/config/viper.go
Normal file
65
cmd/internal/common/config/viper.go
Normal 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
|
||||||
|
}
|
Loading…
Reference in a new issue