Skip to content

Commit

Permalink
fix: Rework InterceptConfigsPreRunHandler to make sure configuration … (
Browse files Browse the repository at this point in the history
cosmos#7501)

* fix: Rework InterceptConfigsPreRunHandler to make sure configuration is pulled from all Viper sources

* docs: Add configuration documentation in a new file
  • Loading branch information
hydrogen18 authored Oct 16, 2020
1 parent 6e569e1 commit 8384a5a
Show file tree
Hide file tree
Showing 3 changed files with 511 additions and 17 deletions.
27 changes: 27 additions & 0 deletions server/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
The commands from the SDK are defined with `cobra` and configured with the
`viper` package.
This takes place in the `InterceptConfigsPreRunHandler` function.
Since the `viper` package is used for configuration the precedence is dictated
by that package. That is
1. Command line switches
2. Environment variables
3. Files from configuration values
4. Default values
The global configuration instance exposed by the `viper` package is not
used by Cosmos SDK in this function. A new instance of `viper.Viper` is created
and the following is performed. The environmental variable prefix is set
to the current program name. Environmental variables consider the underscore
to be equivalent to the `.` or `-` character. This means that an configuration
value called `rpc.laddr` would be read from an environmental variable called
`MYTOOL_RPC_LADDR` if the current program name is `mytool`.
Running the `InterceptConfigsPreRunHandler` also reads `app.toml`
and `config.toml` from the home directory under the `config` directory.
If `config.toml` or `app.toml` do not exist then those files are created
and populated with default values.
*/
package server
55 changes: 38 additions & 17 deletions server/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import (
"net"
"os"
"os/signal"
"path"
"path/filepath"
"strconv"
"strings"
"syscall"
"time"

Expand Down Expand Up @@ -65,27 +67,44 @@ func NewContext(v *viper.Viper, config *tmcfg.Config, logger log.Logger) *Contex
// the application configuration. Command handlers can fetch the server Context
// to get the Tendermint configuration or to get access to Viper.
func InterceptConfigsPreRunHandler(cmd *cobra.Command) error {
rootViper := viper.New()
rootViper.BindPFlags(cmd.Flags())
rootViper.BindPFlags(cmd.PersistentFlags())

serverCtx := NewDefaultContext()
config, err := interceptConfigs(serverCtx, rootViper)

// Get the executable name and configure the viper instance so that environmental
// variables are checked based off that name. The underscore character is used
// as a separator
executableName, err := os.Executable()
if err != nil {
return err
}
basename := path.Base(executableName)

// Configure the viper instance
serverCtx.Viper.BindPFlags(cmd.Flags())
serverCtx.Viper.BindPFlags(cmd.PersistentFlags())
serverCtx.Viper.SetEnvPrefix(basename)
serverCtx.Viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_"))
serverCtx.Viper.AutomaticEnv()

// Intercept configuration files, using both Viper instances separately
config, err := interceptConfigs(serverCtx.Viper)
if err != nil {
return err
}
// Return value is a tendermint configuration object
serverCtx.Config = config

logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout))
logger, err = tmflags.ParseLogLevel(config.LogLevel, logger, tmcfg.DefaultLogLevel())
if err != nil {
return err
}

if rootViper.GetBool(tmcli.TraceFlag) {
// Check if the tendermint flag for trace logging is set
// if it is then setup a tracing logger in this app as well
if serverCtx.Viper.GetBool(tmcli.TraceFlag) {
logger = log.NewTracingLogger(logger)
}

serverCtx.Config = config
serverCtx.Logger = logger.With("module", "main")

return SetCmdServerContext(cmd, serverCtx)
Expand Down Expand Up @@ -120,7 +139,7 @@ func SetCmdServerContext(cmd *cobra.Command, serverCtx *Context) error {
// configuration file. The Tendermint configuration file is parsed given a root
// Viper object, whereas the application is parsed with the private package-aware
// viperCfg object.
func interceptConfigs(ctx *Context, rootViper *viper.Viper) (*tmcfg.Config, error) {
func interceptConfigs(rootViper *viper.Viper) (*tmcfg.Config, error) {
rootDir := rootViper.GetString(flags.FlagHome)
configPath := filepath.Join(rootDir, "config")
configFile := filepath.Join(configPath, "config.toml")
Expand All @@ -146,28 +165,30 @@ func interceptConfigs(ctx *Context, rootViper *viper.Viper) (*tmcfg.Config, erro
if err := rootViper.ReadInConfig(); err != nil {
return nil, fmt.Errorf("failed to read in app.toml: %w", err)
}

if err := rootViper.Unmarshal(conf); err != nil {
return nil, err
}
}

// Read into the configuration whatever data the viper instance has for it
// This may come from the configuration file above but also any of the other sources
// viper uses
if err := rootViper.Unmarshal(conf); err != nil {
return nil, err
}
conf.SetRoot(rootDir)

appConfigFilePath := filepath.Join(configPath, "app.toml")
if _, err := os.Stat(appConfigFilePath); os.IsNotExist(err) {
appConf, err := config.ParseConfig(ctx.Viper)
appConf, err := config.ParseConfig(rootViper)
if err != nil {
return nil, fmt.Errorf("failed to parse app.toml: %w", err)
}

config.WriteConfigFile(appConfigFilePath, appConf)
}

ctx.Viper.SetConfigType("toml")
ctx.Viper.SetConfigName("app")
ctx.Viper.AddConfigPath(configPath)
if err := ctx.Viper.ReadInConfig(); err != nil {
rootViper.SetConfigType("toml")
rootViper.SetConfigName("app")
rootViper.AddConfigPath(configPath)
if err := rootViper.ReadInConfig(); err != nil {
return nil, fmt.Errorf("failed to read in app.toml: %w", err)
}

Expand Down
Loading

0 comments on commit 8384a5a

Please sign in to comment.