Feature/44 multiple configs #45
11 changed files with 183 additions and 9 deletions
|
@ -11,6 +11,7 @@ Changelog for FrostFS Node
|
||||||
- Add command `frostfs-adm morph netmap-candidates` (#1889)
|
- Add command `frostfs-adm morph netmap-candidates` (#1889)
|
||||||
- `object.delete.tombstone_lifetime` config parameter to set tombstone lifetime in the DELETE service (#2246)
|
- `object.delete.tombstone_lifetime` config parameter to set tombstone lifetime in the DELETE service (#2246)
|
||||||
- Reload config for pprof and metrics on SIGHUP in `neofs-node` (#1868)
|
- Reload config for pprof and metrics on SIGHUP in `neofs-node` (#1868)
|
||||||
|
- Multiple configs support (#44)
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Change `frostfs_node_engine_container_size` to counting sizes of logical objects
|
- Change `frostfs_node_engine_container_size` to counting sizes of logical objects
|
||||||
|
|
|
@ -5,6 +5,9 @@ const (
|
||||||
ConfigFlagShorthand = "c"
|
ConfigFlagShorthand = "c"
|
||||||
ConfigFlagUsage = "Config file"
|
ConfigFlagUsage = "Config file"
|
||||||
|
|
||||||
|
ConfigDirFlag = "config-dir"
|
||||||
|
ConfigDirFlagUsage = "Config directory"
|
||||||
|
|
||||||
Verbose = "verbose"
|
Verbose = "verbose"
|
||||||
VerboseShorthand = "v"
|
VerboseShorthand = "v"
|
||||||
VerboseUsage = "Verbose output"
|
VerboseUsage = "Verbose output"
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"github.com/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/storagecfg"
|
"github.com/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/storagecfg"
|
||||||
"github.com/TrueCloudLab/frostfs-node/misc"
|
"github.com/TrueCloudLab/frostfs-node/misc"
|
||||||
"github.com/TrueCloudLab/frostfs-node/pkg/util/autocomplete"
|
"github.com/TrueCloudLab/frostfs-node/pkg/util/autocomplete"
|
||||||
|
utilConfig "github.com/TrueCloudLab/frostfs-node/pkg/util/config"
|
||||||
"github.com/TrueCloudLab/frostfs-node/pkg/util/gendoc"
|
"github.com/TrueCloudLab/frostfs-node/pkg/util/gendoc"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
@ -34,6 +35,7 @@ func init() {
|
||||||
rootCmd.SetOut(os.Stdout)
|
rootCmd.SetOut(os.Stdout)
|
||||||
|
|
||||||
rootCmd.PersistentFlags().StringP(commonflags.ConfigFlag, commonflags.ConfigFlagShorthand, "", commonflags.ConfigFlagUsage)
|
rootCmd.PersistentFlags().StringP(commonflags.ConfigFlag, commonflags.ConfigFlagShorthand, "", commonflags.ConfigFlagUsage)
|
||||||
|
rootCmd.PersistentFlags().String(commonflags.ConfigDirFlag, "", commonflags.ConfigDirFlagUsage)
|
||||||
rootCmd.PersistentFlags().BoolP(commonflags.Verbose, commonflags.VerboseShorthand, false, commonflags.VerboseUsage)
|
rootCmd.PersistentFlags().BoolP(commonflags.Verbose, commonflags.VerboseShorthand, false, commonflags.VerboseUsage)
|
||||||
_ = viper.BindPFlag(commonflags.Verbose, rootCmd.PersistentFlags().Lookup(commonflags.Verbose))
|
_ = viper.BindPFlag(commonflags.Verbose, rootCmd.PersistentFlags().Lookup(commonflags.Verbose))
|
||||||
rootCmd.Flags().Bool("version", false, "Application version")
|
rootCmd.Flags().Bool("version", false, "Application version")
|
||||||
|
@ -62,11 +64,22 @@ func entryPoint(cmd *cobra.Command, args []string) error {
|
||||||
|
|
||||||
func initConfig(cmd *cobra.Command) {
|
func initConfig(cmd *cobra.Command) {
|
||||||
configFile, err := cmd.Flags().GetString(commonflags.ConfigFlag)
|
configFile, err := cmd.Flags().GetString(commonflags.ConfigFlag)
|
||||||
if err != nil || configFile == "" {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
viper.SetConfigType("yml")
|
if configFile != "" {
|
||||||
viper.SetConfigFile(configFile)
|
viper.SetConfigType("yml")
|
||||||
_ = viper.ReadInConfig() // if config file is set but unavailable, ignore it
|
viper.SetConfigFile(configFile)
|
||||||
|
_ = viper.ReadInConfig() // if config file is set but unavailable, ignore it
|
||||||
|
}
|
||||||
|
|
||||||
|
configDir, err := cmd.Flags().GetString(commonflags.ConfigDirFlag)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if configDir != "" {
|
||||||
|
_ = utilConfig.ReadConfigDir(viper.GetViper(), configDir) // if config files cannot be read, ignore it
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ import (
|
||||||
utilCli "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/util"
|
utilCli "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/util"
|
||||||
commonCmd "github.com/TrueCloudLab/frostfs-node/cmd/internal/common"
|
commonCmd "github.com/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||||
"github.com/TrueCloudLab/frostfs-node/misc"
|
"github.com/TrueCloudLab/frostfs-node/misc"
|
||||||
|
"github.com/TrueCloudLab/frostfs-node/pkg/util/config"
|
||||||
"github.com/TrueCloudLab/frostfs-node/pkg/util/gendoc"
|
"github.com/TrueCloudLab/frostfs-node/pkg/util/gendoc"
|
||||||
"github.com/mitchellh/go-homedir"
|
"github.com/mitchellh/go-homedir"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
@ -32,6 +33,7 @@ const (
|
||||||
// Global scope flags.
|
// Global scope flags.
|
||||||
var (
|
var (
|
||||||
cfgFile string
|
cfgFile string
|
||||||
|
cfgDir string
|
||||||
)
|
)
|
||||||
|
|
||||||
// rootCmd represents the base command when called without any subcommands.
|
// rootCmd represents the base command when called without any subcommands.
|
||||||
|
@ -64,6 +66,7 @@ func init() {
|
||||||
// Cobra supports persistent flags, which, if defined here,
|
// Cobra supports persistent flags, which, if defined here,
|
||||||
// will be global for your application.
|
// will be global for your application.
|
||||||
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "Config file (default is $HOME/.config/frostfs-cli/config.yaml)")
|
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "Config file (default is $HOME/.config/frostfs-cli/config.yaml)")
|
||||||
|
rootCmd.PersistentFlags().StringVar(&cfgDir, "config-dir", "", "Config directory")
|
||||||
rootCmd.PersistentFlags().BoolP(commonflags.Verbose, commonflags.VerboseShorthand,
|
rootCmd.PersistentFlags().BoolP(commonflags.Verbose, commonflags.VerboseShorthand,
|
||||||
false, commonflags.VerboseUsage)
|
false, commonflags.VerboseUsage)
|
||||||
|
|
||||||
|
@ -121,4 +124,10 @@ func initConfig() {
|
||||||
if err := viper.ReadInConfig(); err == nil {
|
if err := viper.ReadInConfig(); err == nil {
|
||||||
common.PrintVerbose(rootCmd, "Using config file: %s", viper.ConfigFileUsed())
|
common.PrintVerbose(rootCmd, "Using config file: %s", viper.ConfigFileUsed())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cfgDir != "" {
|
||||||
|
if err := config.ReadConfigDir(viper.GetViper(), cfgDir); err != nil {
|
||||||
|
commonCmd.ExitOnErr(rootCmd, "failed to read config dir: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,10 +4,11 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/TrueCloudLab/frostfs-node/pkg/util/config"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newConfig(path string) (*viper.Viper, error) {
|
func newConfig(path, directory string) (*viper.Viper, error) {
|
||||||
const envPrefix = "FROSTFS_IR"
|
const envPrefix = "FROSTFS_IR"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -28,7 +29,13 @@ func newConfig(path string) (*viper.Viper, error) {
|
||||||
} else {
|
} else {
|
||||||
v.SetConfigType("yml")
|
v.SetConfigType("yml")
|
||||||
}
|
}
|
||||||
err = v.ReadInConfig()
|
if err = v.ReadInConfig(); err != nil {
|
||||||
|
return v, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if directory != "" {
|
||||||
|
err = config.ReadConfigDir(v, directory)
|
||||||
}
|
}
|
||||||
|
|
||||||
return v, err
|
return v, err
|
||||||
|
|
|
@ -35,6 +35,7 @@ func exitErr(err error) {
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
configFile := flag.String("config", "", "path to config")
|
configFile := flag.String("config", "", "path to config")
|
||||||
|
configDir := flag.String("config-dir", "", "path to config directory")
|
||||||
versionFlag := flag.Bool("version", false, "frostfs-ir node version")
|
versionFlag := flag.Bool("version", false, "frostfs-ir node version")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
|
@ -44,7 +45,7 @@ func main() {
|
||||||
os.Exit(SuccessReturnCode)
|
os.Exit(SuccessReturnCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg, err := newConfig(*configFile)
|
cfg, err := newConfig(*configFile, *configDir)
|
||||||
exitErr(err)
|
exitErr(err)
|
||||||
|
|
||||||
var logPrm logger.Prm
|
var logPrm logger.Prm
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/internal"
|
"github.com/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/internal"
|
||||||
|
"github.com/TrueCloudLab/frostfs-node/pkg/util/config"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -54,6 +55,12 @@ func New(_ Prm, opts ...Option) *Config {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
opts: *o,
|
opts: *o,
|
||||||
|
@ -69,5 +76,11 @@ func (x *Config) Reload() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
type opts struct {
|
type opts struct {
|
||||||
path string
|
path string
|
||||||
|
configDir string
|
||||||
}
|
}
|
||||||
|
|
||||||
func defaultOpts() *opts {
|
func defaultOpts() *opts {
|
||||||
|
@ -18,3 +19,11 @@ func WithConfigFile(path string) Option {
|
||||||
o.path = path
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -34,6 +34,7 @@ func fatalOnErrDetails(details string, err error) {
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
configFile := flag.String("config", "", "path to config")
|
configFile := flag.String("config", "", "path to config")
|
||||||
|
configDir := flag.String("config-dir", "", "path to config directory")
|
||||||
versionFlag := flag.Bool("version", false, "frostfs node version")
|
versionFlag := flag.Bool("version", false, "frostfs node version")
|
||||||
dryRunFlag := flag.Bool("check", false, "validate configuration and exit")
|
dryRunFlag := flag.Bool("check", false, "validate configuration and exit")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
@ -44,7 +45,7 @@ func main() {
|
||||||
os.Exit(SuccessReturnCode)
|
os.Exit(SuccessReturnCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
appCfg := config.New(config.Prm{}, config.WithConfigFile(*configFile))
|
appCfg := config.New(config.Prm{}, config.WithConfigFile(*configFile), config.WithConfigDir(*configDir))
|
||||||
|
|
||||||
err := validateConfig(appCfg)
|
err := validateConfig(appCfg)
|
||||||
fatalOnErr(err)
|
fatalOnErr(err)
|
||||||
|
|
|
@ -16,6 +16,68 @@ It is not recommended transferring these configs for real application launches.
|
||||||
- CLI
|
- CLI
|
||||||
- YAML: `cli.yaml`
|
- YAML: `cli.yaml`
|
||||||
|
|
||||||
|
### Multiple configs
|
||||||
|
|
||||||
|
You can split your configuration to several files.
|
||||||
|
For example, you can use separate yaml file for each shard or each service (pprof, prometheus).
|
||||||
|
You must use `--config-dir` flag to process several configs:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ ./bin/frotsfs-node --config ./config/example/node.yaml --config-dir ./dir/with/additional/configs
|
||||||
|
```
|
||||||
|
|
||||||
|
When the `--config-dir` flag set, the application:
|
||||||
|
* reads all `*.y[a]ml` files from provided directory,
|
||||||
|
* use Viper's [MergeConfig](https://pkg.go.dev/github.com/spf13/viper#MergeConfig) functionality to produce the final configuration,
|
||||||
|
* files are being processing in alphanumerical order so that `01.yaml` may be extended with contents of `02.yaml`, so
|
||||||
|
if a field is specified in multiple files, the latest occurrence takes effect.
|
||||||
|
|
||||||
|
So if we have the following files:
|
||||||
|
```yaml
|
||||||
|
# 00.yaml
|
||||||
|
logger:
|
||||||
|
level: debug
|
||||||
|
pprof:
|
||||||
|
enabled: true
|
||||||
|
address: localhost:6060
|
||||||
|
prometheus:
|
||||||
|
enabled: true
|
||||||
|
address: localhost:9090
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# dir/01.yaml
|
||||||
|
logger:
|
||||||
|
level: info
|
||||||
|
pprof:
|
||||||
|
enabled: false
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# dir/02.yaml
|
||||||
|
logger:
|
||||||
|
level: warn
|
||||||
|
prometheus:
|
||||||
|
address: localhost:9091
|
||||||
|
```
|
||||||
|
|
||||||
|
and provide the following flags:
|
||||||
|
```shell
|
||||||
|
$ ./bin/frotsfs-node --config 00.yaml --config-dir dir
|
||||||
|
```
|
||||||
|
|
||||||
|
result config will be:
|
||||||
|
```yaml
|
||||||
|
logger:
|
||||||
|
level: warn
|
||||||
|
pprof:
|
||||||
|
enabled: false
|
||||||
|
address: localhost:6060
|
||||||
|
prometheus:
|
||||||
|
enabled: true
|
||||||
|
address: localhost:9091
|
||||||
|
```
|
||||||
|
|
||||||
## Environment variables
|
## Environment variables
|
||||||
|
|
||||||
- Storage node: `node.env`
|
- Storage node: `node.env`
|
||||||
|
|
55
pkg/util/config/dir.go
Normal file
55
pkg/util/config/dir.go
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ReadConfigDir reads all config files from provided directory in alphabetical order
|
||||||
|
// and merge its content with current viper configuration.
|
||||||
|
func ReadConfigDir(v *viper.Viper, configDir string) error {
|
||||||
|
entries, err := os.ReadDir(configDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, entry := range entries {
|
||||||
|
if entry.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ext := path.Ext(entry.Name())
|
||||||
|
if ext != ".yaml" && ext != ".yml" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = mergeConfig(v, path.Join(configDir, entry.Name())); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// mergeConfig reads config file and merge its content with current viper.
|
||||||
|
func mergeConfig(v *viper.Viper, fileName string) (err error) {
|
||||||
|
var cfgFile *os.File
|
||||||
|
cfgFile, err = os.Open(fileName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
errClose := cfgFile.Close()
|
||||||
|
if err == nil {
|
||||||
|
err = errClose
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err = v.MergeConfig(cfgFile); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in a new issue