diff --git a/price-feeder/CHANGELOG.md b/price-feeder/CHANGELOG.md index f31091e313..ebe73716ac 100644 --- a/price-feeder/CHANGELOG.md +++ b/price-feeder/CHANGELOG.md @@ -69,6 +69,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ - [#1170](https://github.com/umee-network/umee/pull/1170) Restrict price feeder quotes to USD, USDT, USDC, ETH, DAI, and BTC. - [#1175](https://github.com/umee-network/umee/pull/1175) Add ProviderName type to facilitate the reading of maps. - [#1215](https://github.com/umee-network/umee/pull/1215) Moved ProviderName to Name in provider package. +- [#1274](https://github.com/umee-network/umee/pull/1274) Add option to set config by env variables. ## [v0.2.5](https://github.com/umee-network/umee/releases/tag/price-feeder%2Fv0.2.5) - 2022-07-28 diff --git a/price-feeder/config/config.go b/price-feeder/config/config.go index c7316fde64..bce2b5b0c3 100644 --- a/price-feeder/config/config.go +++ b/price-feeder/config/config.go @@ -3,13 +3,13 @@ package config import ( "errors" "fmt" - "os" "strings" "time" - "github.com/BurntSushi/toml" + "github.com/cosmos/cosmos-sdk/telemetry" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/go-playground/validator/v10" + "github.com/spf13/viper" "github.com/umee-network/umee/price-feeder/oracle/provider" ) @@ -60,98 +60,67 @@ var ( type ( // Config defines all necessary price-feeder configuration parameters. Config struct { - Server Server `toml:"server"` - CurrencyPairs []CurrencyPair `toml:"currency_pairs" validate:"required,gt=0,dive,required"` - Deviations []Deviation `toml:"deviation_thresholds"` - Account Account `toml:"account" validate:"required,gt=0,dive,required"` - Keyring Keyring `toml:"keyring" validate:"required,gt=0,dive,required"` - RPC RPC `toml:"rpc" validate:"required,gt=0,dive,required"` - Telemetry Telemetry `toml:"telemetry"` - GasAdjustment float64 `toml:"gas_adjustment" validate:"required"` - ProviderTimeout string `toml:"provider_timeout"` - ProviderEndpoints []provider.Endpoint `toml:"provider_endpoints" validate:"dive"` + Server Server `mapstructure:"server"` + CurrencyPairs []CurrencyPair `mapstructure:"currency_pairs" validate:"required,gt=0,dive,required"` + Deviations []Deviation `mapstructure:"deviation_thresholds"` + Account Account `mapstructure:"account" validate:"required,gt=0,dive,required"` + Keyring Keyring `mapstructure:"keyring" validate:"required,gt=0,dive,required"` + RPC RPC `mapstructure:"rpc" validate:"required,gt=0,dive,required"` + Telemetry telemetry.Config `mapstructure:"telemetry"` + GasAdjustment float64 `mapstructure:"gas_adjustment" validate:"required"` + ProviderTimeout string `mapstructure:"provider_timeout"` + ProviderEndpoints []provider.Endpoint `mapstructure:"provider_endpoints" validate:"dive"` } // Server defines the API server configuration. Server struct { - ListenAddr string `toml:"listen_addr"` - WriteTimeout string `toml:"write_timeout"` - ReadTimeout string `toml:"read_timeout"` - VerboseCORS bool `toml:"verbose_cors"` - AllowedOrigins []string `toml:"allowed_origins"` + ListenAddr string `mapstructure:"listen_addr"` + WriteTimeout string `mapstructure:"write_timeout"` + ReadTimeout string `mapstructure:"read_timeout"` + VerboseCORS bool `mapstructure:"verbose_cors"` + AllowedOrigins []string `mapstructure:"allowed_origins"` } // CurrencyPair defines a price quote of the exchange rate for two different // currencies and the supported providers for getting the exchange rate. CurrencyPair struct { - Base string `toml:"base" validate:"required"` - Quote string `toml:"quote" validate:"required"` - Providers []provider.Name `toml:"providers" validate:"required,gt=0,dive,required"` + Base string `mapstructure:"base" validate:"required"` + Quote string `mapstructure:"quote" validate:"required"` + Providers []provider.Name `mapstructure:"providers" validate:"required,gt=0,dive,required"` } // Deviation defines a maximum amount of standard deviations that a given asset can // be from the median without being filtered out before voting. Deviation struct { - Base string `toml:"base" validate:"required"` - Threshold string `toml:"threshold" validate:"required"` + Base string `mapstructure:"base" validate:"required"` + Threshold string `mapstructure:"threshold" validate:"required"` } // Account defines account related configuration that is related to the Umee // network and transaction signing functionality. Account struct { - ChainID string `toml:"chain_id" validate:"required"` - Address string `toml:"address" validate:"required"` - Validator string `toml:"validator" validate:"required"` + ChainID string `mapstructure:"chain_id" validate:"required"` + Address string `mapstructure:"address" validate:"required"` + Validator string `mapstructure:"validator" validate:"required"` } // Keyring defines the required Umee keyring configuration. Keyring struct { - Backend string `toml:"backend" validate:"required"` - Dir string `toml:"dir" validate:"required"` + Backend string `mapstructure:"backend" validate:"required"` + Dir string `mapstructure:"dir" validate:"required"` } // RPC defines RPC configuration of both the Umee gRPC and Tendermint nodes. RPC struct { - TMRPCEndpoint string `toml:"tmrpc_endpoint" validate:"required"` - GRPCEndpoint string `toml:"grpc_endpoint" validate:"required"` - RPCTimeout string `toml:"rpc_timeout" validate:"required"` - } - - // Telemetry defines the configuration options for application telemetry. - Telemetry struct { - // Prefixed with keys to separate services - ServiceName string `toml:"service_name" mapstructure:"service-name"` - - // Enabled enables the application telemetry functionality. When enabled, - // an in-memory sink is also enabled by default. Operators may also enabled - // other sinks such as Prometheus. - Enabled bool `toml:"enabled" mapstructure:"enabled"` - - // Enable prefixing gauge values with hostname - EnableHostname bool `toml:"enable_hostname" mapstructure:"enable-hostname"` - - // Enable adding hostname to labels - EnableHostnameLabel bool `toml:"enable_hostname_label" mapstructure:"enable-hostname-label"` - - // Enable adding service to labels - EnableServiceLabel bool `toml:"enable_service_label" mapstructure:"enable-service-label"` - - // GlobalLabels defines a global set of name/value label tuples applied to all - // metrics emitted using the wrapper functions defined in telemetry package. - // - // Example: - // [["chain_id", "cosmoshub-1"]] - GlobalLabels [][]string `toml:"global_labels" mapstructure:"global-labels"` - - // PrometheusRetentionTime, when positive, enables a Prometheus metrics sink. - // It defines the retention duration in seconds. - PrometheusRetentionTime int64 `toml:"prometheus_retention" mapstructure:"prometheus-retention-time"` + TMRPCEndpoint string `mapstructure:"tmrpc_endpoint" validate:"required"` + GRPCEndpoint string `mapstructure:"grpc_endpoint" validate:"required"` + RPCTimeout string `mapstructure:"rpc_timeout" validate:"required"` } ) // telemetryValidation is custom validation for the Telemetry struct. func telemetryValidation(sl validator.StructLevel) { - tel := sl.Current().Interface().(Telemetry) + tel := sl.Current().Interface().(telemetry.Config) if tel.Enabled && (len(tel.GlobalLabels) == 0 || len(tel.ServiceName) == 0) { sl.ReportError(tel.Enabled, "enabled", "Enabled", "enabledNoOptions", "") @@ -172,7 +141,7 @@ func endpointValidation(sl validator.StructLevel) { // Validate returns an error if the Config object is invalid. func (c Config) Validate() error { - validate.RegisterStructValidation(telemetryValidation, Telemetry{}) + validate.RegisterStructValidation(telemetryValidation, telemetry.Config{}) validate.RegisterStructValidation(endpointValidation, provider.Endpoint{}) return validate.Struct(c) } @@ -186,12 +155,14 @@ func ParseConfig(configPath string) (Config, error) { return cfg, ErrEmptyConfigPath } - configData, err := os.ReadFile(configPath) - if err != nil { + viper.AutomaticEnv() + viper.SetConfigFile(configPath) + + if err := viper.ReadInConfig(); err != nil { return cfg, fmt.Errorf("failed to read config: %w", err) } - if _, err := toml.Decode(string(configData), &cfg); err != nil { + if err := viper.Unmarshal(&cfg); err != nil { return cfg, fmt.Errorf("failed to decode config: %w", err) } diff --git a/price-feeder/config/config_test.go b/price-feeder/config/config_test.go index ef8487c1b4..11e9d376e9 100644 --- a/price-feeder/config/config_test.go +++ b/price-feeder/config/config_test.go @@ -5,6 +5,7 @@ import ( "os" "testing" + "github.com/cosmos/cosmos-sdk/telemetry" "github.com/stretchr/testify/require" "github.com/umee-network/umee/price-feeder/config" "github.com/umee-network/umee/price-feeder/oracle/provider" @@ -35,7 +36,7 @@ func TestValidate(t *testing.T) { GRPCEndpoint: "localhost:9090", RPCTimeout: "100ms", }, - Telemetry: config.Telemetry{ + Telemetry: telemetry.Config{ ServiceName: "price-feeder", Enabled: true, EnableHostname: true, @@ -131,7 +132,7 @@ func TestValidate(t *testing.T) { } func TestParseConfig_Valid(t *testing.T) { - tmpFile, err := ioutil.TempFile("", "price-feeder.toml") + tmpFile, err := ioutil.TempFile("", "price-feeder*.toml") require.NoError(t, err) defer os.Remove(tmpFile.Name()) @@ -187,13 +188,13 @@ grpc_endpoint = "localhost:9090" rpc_timeout = "100ms" [telemetry] -service_name = "price-feeder" +service-name = "price-feeder" enabled = true -enable_hostname = true -enable_hostname_label = true -enable_service_label = true -prometheus_retention = 120 -global_labels = [["chain-id", "umee-local-testnet"]] +enable-hostname = true +enable-hostname-label = true +enable-service-label = true +prometheus-retention = 120 +global-labels = [["chain-id", "umee-local-testnet"]] `) _, err = tmpFile.Write(content) require.NoError(t, err) @@ -214,7 +215,7 @@ global_labels = [["chain-id", "umee-local-testnet"]] } func TestParseConfig_Valid_NoTelemetry(t *testing.T) { - tmpFile, err := ioutil.TempFile("", "price-feeder.toml") + tmpFile, err := ioutil.TempFile("", "price-feeder*.toml") require.NoError(t, err) defer os.Remove(tmpFile.Name()) @@ -292,7 +293,7 @@ enabled = false } func TestParseConfig_InvalidProvider(t *testing.T) { - tmpFile, err := ioutil.TempFile("", "price-feeder.toml") + tmpFile, err := ioutil.TempFile("", "price-feeder*.toml") require.NoError(t, err) defer os.Remove(tmpFile.Name()) @@ -323,7 +324,7 @@ providers = [ } func TestParseConfig_NonUSDQuote(t *testing.T) { - tmpFile, err := ioutil.TempFile("", "price-feeder.toml") + tmpFile, err := ioutil.TempFile("", "price-feeder*.toml") require.NoError(t, err) defer os.Remove(tmpFile.Name()) @@ -354,7 +355,7 @@ providers = [ } func TestParseConfig_Valid_Deviations(t *testing.T) { - tmpFile, err := ioutil.TempFile("", "price-feeder.toml") + tmpFile, err := ioutil.TempFile("", "price-feeder*.toml") require.NoError(t, err) defer os.Remove(tmpFile.Name()) @@ -418,13 +419,13 @@ grpc_endpoint = "localhost:9090" rpc_timeout = "100ms" [telemetry] -service_name = "price-feeder" +service-name = "price-feeder" enabled = true -enable_hostname = true -enable_hostname_label = true -enable_service_label = true -prometheus_retention = 120 -global_labels = [["chain-id", "umee-local-testnet"]] +enable-hostname = true +enable-hostname-label = true +enable-service-label = true +prometheus-retention = 120 +global-labels = [["chain-id", "umee-local-testnet"]] `) _, err = tmpFile.Write(content) require.NoError(t, err) @@ -449,7 +450,7 @@ global_labels = [["chain-id", "umee-local-testnet"]] } func TestParseConfig_Invalid_Deviations(t *testing.T) { - tmpFile, err := ioutil.TempFile("", "price-feeder.toml") + tmpFile, err := ioutil.TempFile("", "price-feeder*.toml") require.NoError(t, err) defer os.Remove(tmpFile.Name()) @@ -513,13 +514,13 @@ grpc_endpoint = "localhost:9090" rpc_timeout = "100ms" [telemetry] -service_name = "price-feeder" +service-name = "price-feeder" enabled = true -enable_hostname = true -enable_hostname_label = true -enable_service_label = true -prometheus_retention = 120 -global_labels = [["chain-id", "umee-local-testnet"]] +enable-hostname = true +enable-hostname_label = true +enable-service_label = true +prometheus-retention = 120 +global-labels = [["chain-id", "umee-local-testnet"]] `) _, err = tmpFile.Write(content) require.NoError(t, err) @@ -527,3 +528,92 @@ global_labels = [["chain-id", "umee-local-testnet"]] _, err = config.ParseConfig(tmpFile.Name()) require.Error(t, err) } + +func TestParseConfig_Env_Vars(t *testing.T) { + tmpFile, err := ioutil.TempFile("", "price-feeder*.toml") + require.NoError(t, err) + defer os.Remove(tmpFile.Name()) + + content := []byte(` +gas_adjustment = 1.5 + +[server] +listen_addr = "0.0.0.0:99999" +read_timeout = "20s" +verbose_cors = true +write_timeout = "20s" + +[[currency_pairs]] +base = "ATOM" +quote = "USDT" +providers = [ + "kraken", + "binance", + "huobi" +] + +[[currency_pairs]] +base = "UMEE" +quote = "USDT" +providers = [ + "kraken", + "binance", + "huobi" +] + +[[currency_pairs]] +base = "USDT" +quote = "USD" +providers = [ + "kraken", + "binance", + "huobi" +] + +[account] +address = "umee15nejfgcaanqpw25ru4arvfd0fwy6j8clccvwx4" +validator = "umeevalcons14rjlkfzp56733j5l5nfk6fphjxymgf8mj04d5p" +chain_id = "umee-local-testnet" + +[keyring] +backend = "test" +dir = "/Users/username/.umee" +pass = "keyringPassword" + +[rpc] +tmrpc_endpoint = "http://localhost:26657" +grpc_endpoint = "localhost:9090" +rpc_timeout = "100ms" + +[telemetry] +service-name = "price-feeder" +enabled = true +enable-hostname = true +enable-hostname_label = true +enable-service_label = true +prometheus-retention = 120 +global-labels = [["chain-id", "umee-local-testnet"]] +`) + _, err = tmpFile.Write(content) + require.NoError(t, err) + + // Set env variables to overwrite config files + os.Setenv("SERVER.LISTEN_ADDR", "0.0.0.0:888888") + os.Setenv("SERVER.WRITE_TIMEOUT", "10s") + os.Setenv("SERVER.READ_TIMEOUT", "10s") + os.Setenv("SERVER.VERBOSE_CORS", "false") + + cfg, err := config.ParseConfig(tmpFile.Name()) + require.NoError(t, err) + + require.Equal(t, "0.0.0.0:888888", cfg.Server.ListenAddr) + require.Equal(t, "10s", cfg.Server.WriteTimeout) + require.Equal(t, "10s", cfg.Server.ReadTimeout) + require.False(t, cfg.Server.VerboseCORS) + require.Len(t, cfg.CurrencyPairs, 3) + require.Equal(t, "ATOM", cfg.CurrencyPairs[0].Base) + require.Equal(t, "USDT", cfg.CurrencyPairs[0].Quote) + require.Len(t, cfg.CurrencyPairs[0].Providers, 3) + require.Equal(t, provider.ProviderKraken, cfg.CurrencyPairs[0].Providers[0]) + require.Equal(t, provider.ProviderBinance, cfg.CurrencyPairs[0].Providers[1]) +} diff --git a/price-feeder/price-feeder.example.toml b/price-feeder/price-feeder.example.toml index 3c67ad381f..fd8d5524d7 100644 --- a/price-feeder/price-feeder.example.toml +++ b/price-feeder/price-feeder.example.toml @@ -49,12 +49,12 @@ rpc_timeout = "100ms" tmrpc_endpoint = "http://localhost:26657" [telemetry] -enable_hostname = true -enable_hostname_label = true -enable_service_label = true +enable-hostname = true +enable-hostname-label = true +enable-service-label = true enabled = true -global_labels = [["chain-id", "umee-local-testnet"]] -service_name = "price-feeder" +global-labels = [["chain-id", "umee-local-testnet"]] +service-name = "price-feeder" type = "prometheus" [[provider_endpoints]] diff --git a/tests/e2e/scripts/price_feeder_bootstrap.sh b/tests/e2e/scripts/price_feeder_bootstrap.sh index d5ff7b1103..0b2d0e0ad5 100755 --- a/tests/e2e/scripts/price_feeder_bootstrap.sh +++ b/tests/e2e/scripts/price_feeder_bootstrap.sh @@ -60,13 +60,13 @@ rpc_timeout = "100ms" tmrpc_endpoint = 'http://$UMEE_E2E_UMEE_VAL_HOST:26657' [telemetry] -service_name = "price-feeder" +service-name = "price-feeder" enabled = true -enable_hostname = true -enable_hostname_label = true -enable_service_label = true +enable-hostname = true +enable-hostname-label = true +enable-service-label = true type = "prometheus" -global_labels = [["chain-id", "umee-local-testnet"]] +global-labels = [["chain-id", "umee-local-testnet"]] EOF # start price-feeder