From 7e11bf9a55d2ddeb468e1cffedff5533679d4b5b Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Fri, 21 May 2021 14:50:40 +0300 Subject: [PATCH] [#493] cmd/node: Implement a basic configuration component Create `config` package nearby storage node application. Implement `Config` as a wrapper over `viper.Viper` that provides the minimum functionality required by the application. The constructor allows you to read the config from the file. Methods are provided for reading subsections and values from the config tree. Helper functions are implemented to cast a value to native Go types. Signed-off-by: Leonard Lyubich --- cmd/neofs-node/config/calls.go | 28 ++++++++++++++ cmd/neofs-node/config/calls_test.go | 29 ++++++++++++++ cmd/neofs-node/config/cast.go | 30 +++++++++++++++ cmd/neofs-node/config/cast_test.go | 30 +++++++++++++++ cmd/neofs-node/config/config.go | 53 ++++++++++++++++++++++++++ cmd/neofs-node/config/config_test.go | 26 +++++++++++++ cmd/neofs-node/config/opts.go | 20 ++++++++++ cmd/neofs-node/config/test/config.json | 14 +++++++ cmd/neofs-node/config/test/config.yaml | 13 +++++++ cmd/neofs-node/config/util.go | 16 ++++++++ go.mod | 1 + 11 files changed, 260 insertions(+) create mode 100644 cmd/neofs-node/config/calls.go create mode 100644 cmd/neofs-node/config/calls_test.go create mode 100644 cmd/neofs-node/config/cast.go create mode 100644 cmd/neofs-node/config/cast_test.go create mode 100644 cmd/neofs-node/config/config.go create mode 100644 cmd/neofs-node/config/config_test.go create mode 100644 cmd/neofs-node/config/opts.go create mode 100644 cmd/neofs-node/config/test/config.json create mode 100644 cmd/neofs-node/config/test/config.yaml create mode 100644 cmd/neofs-node/config/util.go diff --git a/cmd/neofs-node/config/calls.go b/cmd/neofs-node/config/calls.go new file mode 100644 index 000000000..1541557e0 --- /dev/null +++ b/cmd/neofs-node/config/calls.go @@ -0,0 +1,28 @@ +package config + +import ( + "github.com/spf13/viper" +) + +// Sub returns sub-section of the Config by name. +func (x *Config) Sub(name string) *Config { + return (*Config)( + (*viper.Viper)(x).Sub(name), + ) +} + +// Value returns configuration value by name. +// +// Result can be casted to a particular type +// via corresponding function (e.g. StringSlice). +// Note: casting via Go `.()` operator is not +// recommended. +// +// Returns nil if config is nil. +func (x *Config) Value(name string) interface{} { + if x != nil { + return (*viper.Viper)(x).Get(name) + } + + return nil +} diff --git a/cmd/neofs-node/config/calls_test.go b/cmd/neofs-node/config/calls_test.go new file mode 100644 index 000000000..b66eea7c3 --- /dev/null +++ b/cmd/neofs-node/config/calls_test.go @@ -0,0 +1,29 @@ +package config_test + +import ( + "testing" + + "github.com/nspcc-dev/neofs-node/cmd/neofs-node/config" + "github.com/stretchr/testify/require" +) + +func TestConfigCommon(t *testing.T) { + forEachFileType("test/config", func(c *config.Config) { + val := c.Value("value") + require.NotNil(t, val) + + val = c.Value("non-existent value") + require.Nil(t, val) + + sub := c.Sub("section") + require.NotNil(t, sub) + + const nonExistentSub = "non-existent sub-section" + + sub = c.Sub(nonExistentSub) + require.Nil(t, sub) + + val = c.Sub(nonExistentSub).Value("value") + require.Nil(t, val) + }) +} diff --git a/cmd/neofs-node/config/cast.go b/cmd/neofs-node/config/cast.go new file mode 100644 index 000000000..dbd39b55c --- /dev/null +++ b/cmd/neofs-node/config/cast.go @@ -0,0 +1,30 @@ +package config + +import ( + "github.com/spf13/cast" +) + +func panicOnErr(err error) { + if err != nil { + panic(err) + } +} + +// StringSlice reads configuration value +// from c by name and casts it to []string. +// +// Panics if value can not be casted. +func StringSlice(c *Config, name string) []string { + x, err := cast.ToStringSliceE(c.Value(name)) + panicOnErr(err) + + return x +} + +// StringSliceSafe reads configuration value +// from c by name and casts it to []string. +// +// Returns nil if value can not be casted. +func StringSliceSafe(c *Config, name string) []string { + return cast.ToStringSlice(c.Value(name)) +} diff --git a/cmd/neofs-node/config/cast_test.go b/cmd/neofs-node/config/cast_test.go new file mode 100644 index 000000000..9eefd50c1 --- /dev/null +++ b/cmd/neofs-node/config/cast_test.go @@ -0,0 +1,30 @@ +package config_test + +import ( + "testing" + + "github.com/nspcc-dev/neofs-node/cmd/neofs-node/config" + "github.com/stretchr/testify/require" +) + +func TestStringSlice(t *testing.T) { + forEachFileType("test/config", func(c *config.Config) { + cStringSlice := c.Sub("string_slice") + + val := config.StringSlice(cStringSlice, "empty") + require.Empty(t, val) + + val = config.StringSlice(cStringSlice, "filled") + require.Equal(t, []string{ + "string1", + "string2", + }, val) + + require.Panics(t, func() { + config.StringSlice(cStringSlice, "incorrect") + }) + + val = config.StringSliceSafe(cStringSlice, "incorrect") + require.Nil(t, val) + }) +} diff --git a/cmd/neofs-node/config/config.go b/cmd/neofs-node/config/config.go new file mode 100644 index 000000000..2a560fd12 --- /dev/null +++ b/cmd/neofs-node/config/config.go @@ -0,0 +1,53 @@ +package config + +import ( + "fmt" + "strings" + + "github.com/spf13/viper" +) + +// Config represents a group of named values structured +// by tree type. +// +// Sub-trees are named configuration sub-sections, +// leaves are named configuration values. +// Names are of string type. +type Config viper.Viper + +const ( + separator = "." + envPrefix = "neofs" + envSeparator = "_" +) + +// Prm groups required parameters of the Config. +type Prm struct{} + +// New creates a new Config instance. +// +// 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.path != "" { + v.SetEnvPrefix(envPrefix) + v.AutomaticEnv() + v.SetEnvKeyReplacer(strings.NewReplacer(separator, envSeparator)) + v.SetConfigFile(o.path) + + err := v.ReadInConfig() + if err != nil { + panic(fmt.Errorf("failed to read config: %v", err)) + } + } + + return (*Config)(v) +} diff --git a/cmd/neofs-node/config/config_test.go b/cmd/neofs-node/config/config_test.go new file mode 100644 index 000000000..231f59b2f --- /dev/null +++ b/cmd/neofs-node/config/config_test.go @@ -0,0 +1,26 @@ +package config_test + +import ( + "github.com/nspcc-dev/neofs-node/cmd/neofs-node/config" +) + +func fromFile(path string) *config.Config { + var p config.Prm + + return config.New(p, + config.WithConfigFile(path), + ) +} + +func forEachFile(paths []string, f func(*config.Config)) { + for i := range paths { + f(fromFile(paths[i])) + } +} + +func forEachFileType(pref string, f func(*config.Config)) { + forEachFile([]string{ + pref + ".yaml", + pref + ".json", + }, f) +} diff --git a/cmd/neofs-node/config/opts.go b/cmd/neofs-node/config/opts.go new file mode 100644 index 000000000..871d9c959 --- /dev/null +++ b/cmd/neofs-node/config/opts.go @@ -0,0 +1,20 @@ +package config + +type opts struct { + path string +} + +func defaultOpts() *opts { + return new(opts) +} + +// Option allows to set optional parameter of the Config. +type Option func(*opts) + +// WithConfigFile returns option to set system path +// to the configuration file. +func WithConfigFile(path string) Option { + return func(o *opts) { + o.path = path + } +} diff --git a/cmd/neofs-node/config/test/config.json b/cmd/neofs-node/config/test/config.json new file mode 100644 index 000000000..f10856acc --- /dev/null +++ b/cmd/neofs-node/config/test/config.json @@ -0,0 +1,14 @@ +{ + "value": "some value", + "section": { + "any": "thing" + }, + "string_slice": { + "empty": [], + "filled": [ + "string1", + "string2" + ], + "incorrect": null + } +} diff --git a/cmd/neofs-node/config/test/config.yaml b/cmd/neofs-node/config/test/config.yaml new file mode 100644 index 000000000..e383eae81 --- /dev/null +++ b/cmd/neofs-node/config/test/config.yaml @@ -0,0 +1,13 @@ +value: some value + +section: + any: thing + +string_slice: + empty: [] + + filled: + - string1 + - string2 + + incorrect: \ No newline at end of file diff --git a/cmd/neofs-node/config/util.go b/cmd/neofs-node/config/util.go new file mode 100644 index 000000000..07b439021 --- /dev/null +++ b/cmd/neofs-node/config/util.go @@ -0,0 +1,16 @@ +package config + +import ( + "github.com/nspcc-dev/neofs-node/misc" +) + +// DebugValue returns debug configuration value. +// +// Returns nil if misc.Debug is not set to "true". +func DebugValue(c *Config, name string) interface{} { + if misc.Debug == "true" { + return c.Value(name) + } + + return nil +} diff --git a/go.mod b/go.mod index 21a26c12a..603072dd7 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,7 @@ require ( github.com/panjf2000/ants/v2 v2.3.0 github.com/paulmach/orb v0.2.1 github.com/prometheus/client_golang v1.6.0 + github.com/spf13/cast v1.3.0 github.com/spf13/cobra v1.0.0 github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/viper v1.7.0