Skip to content

Commit

Permalink
feat(client): allow overwritting client.toml (#17513)
Browse files Browse the repository at this point in the history
(cherry picked from commit 6601713)

# Conflicts:
#	CHANGELOG.md
#	client/config/config.go
#	client/config/config_test.go
#	client/tx/factory.go
#	simapp/simd/cmd/commands.go
#	simapp/simd/cmd/root.go
#	simapp/simd/cmd/root_v2.go
#	simapp/simd/cmd/testnet.go
  • Loading branch information
julienrbrt authored and mergify[bot] committed Nov 10, 2023
1 parent b93bdea commit 3361ee6
Show file tree
Hide file tree
Showing 16 changed files with 480 additions and 85 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,14 @@ Ref: https://keepachangelog.com/en/1.0.0/

### Features

<<<<<<< HEAD
* (debug) [#18219](https://github.com/cosmos/cosmos-sdk/pull/18219) Add debug commands for application codec types.
* (client/keys) [#17639](https://github.com/cosmos/cosmos-sdk/pull/17639) Allows using and saving public keys encoded as base64.
* (server) [#17094](https://github.com/cosmos/cosmos-sdk/pull/17094) Add a `shutdown-grace` flag for waiting a given time before exit.
=======
* (client) [#17513](https://github.com/cosmos/cosmos-sdk/pull/17513) Allow overwritting `client.toml`. Use `client.CreateClientConfig` in place of `client.ReadFromClientConfig` and provide a custom template and a custom config.
* (x/bank) [#14224](https://github.com/cosmos/cosmos-sdk/pull/14224) Allow injection of restrictions on transfers using `AppendSendRestriction` or `PrependSendRestriction`.
>>>>>>> 6601713eb (feat(client): allow overwritting client.toml (#17513))
### Improvements

Expand Down
88 changes: 57 additions & 31 deletions client/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import (
"path/filepath"

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
)

func DefaultConfig() *ClientConfig {
return &ClientConfig{
// DefaultConfig returns default config for the client.toml
func DefaultConfig() *Config {
return &Config{
ChainID: "",
KeyringBackend: "os",
Output: "text",
Expand All @@ -18,60 +20,84 @@ func DefaultConfig() *ClientConfig {
}
}

type ClientConfig struct {
// ClientConfig is an alias for Config for backward compatibility
// Deprecated: use Config instead which avoid name stuttering
type ClientConfig Config

type Config struct {
ChainID string `mapstructure:"chain-id" json:"chain-id"`
KeyringBackend string `mapstructure:"keyring-backend" json:"keyring-backend"`
Output string `mapstructure:"output" json:"output"`
Node string `mapstructure:"node" json:"node"`
BroadcastMode string `mapstructure:"broadcast-mode" json:"broadcast-mode"`
}

func (c *ClientConfig) SetChainID(chainID string) {
c.ChainID = chainID
}

func (c *ClientConfig) SetKeyringBackend(keyringBackend string) {
c.KeyringBackend = keyringBackend
}

func (c *ClientConfig) SetOutput(output string) {
c.Output = output
}

func (c *ClientConfig) SetNode(node string) {
c.Node = node
}

func (c *ClientConfig) SetBroadcastMode(broadcastMode string) {
c.BroadcastMode = broadcastMode
// ReadFromClientConfig reads values from client.toml file and updates them in client.Context
// It uses CreateClientConfig internally with no custom template and custom config.
func ReadFromClientConfig(ctx client.Context) (client.Context, error) {
return CreateClientConfig(ctx, "", nil)
}

// ReadFromClientConfig reads values from client.toml file and updates them in client Context
func ReadFromClientConfig(ctx client.Context) (client.Context, error) {
// CreateClientConfig reads the client.toml file and returns a new populated client.Context
// If the client.toml file does not exist, it creates one with default values.
// It takes a customClientTemplate and customConfig as input that can be used to overwrite the default config and enhance the client.toml file.
// The custom template/config must be both provided or be "" and nil.
func CreateClientConfig(ctx client.Context, customClientTemplate string, customConfig interface{}) (client.Context, error) {
configPath := filepath.Join(ctx.HomeDir, "config")
configFilePath := filepath.Join(configPath, "client.toml")
conf := DefaultConfig()

// when client.toml does not exist create and init with default values
if _, err := os.Stat(configFilePath); os.IsNotExist(err) {
if err := os.MkdirAll(configPath, os.ModePerm); err != nil {
return ctx, fmt.Errorf("couldn't make client config: %v", err)
}

if ctx.ChainID != "" {
conf.ChainID = ctx.ChainID // chain-id will be written to the client.toml while initiating the chain.
if (customClientTemplate != "" && customConfig == nil) || (customClientTemplate == "" && customConfig != nil) {
return ctx, fmt.Errorf("customClientTemplate and customConfig should be both nil or not nil")
}

<<<<<<< HEAD

Check failure on line 59 in client/config/config.go

View workflow job for this annotation

GitHub Actions / dependency-review

expected statement, found '<<'
if err := writeConfigToFile(configFilePath, conf); err != nil {
return ctx, fmt.Errorf("could not write client config to the file: %v", err)
=======

Check failure on line 62 in client/config/config.go

View workflow job for this annotation

GitHub Actions / dependency-review

expected statement, found '=='
if customClientTemplate != "" {
if err := setConfigTemplate(customClientTemplate); err != nil {
return ctx, fmt.Errorf("couldn't set client config template: %w", err)
}

if ctx.ChainID != "" {
// chain-id will be written to the client.toml while initiating the chain.
ctx.Viper.Set(flags.FlagChainID, ctx.ChainID)
}

if err = ctx.Viper.Unmarshal(&customConfig); err != nil {
return ctx, fmt.Errorf("failed to parse custom client config: %w", err)
}

if err := writeConfigFile(configFilePath, customConfig); err != nil {
return ctx, fmt.Errorf("could not write client config to the file: %w", err)
}

} else {
conf := DefaultConfig()
if ctx.ChainID != "" {
// chain-id will be written to the client.toml while initiating the chain.
conf.ChainID = ctx.ChainID
}

if err := writeConfigFile(configFilePath, conf); err != nil {
return ctx, fmt.Errorf("could not write client config to the file: %w", err)
}
>>>>>>> 6601713eb (feat(client): allow overwritting client.toml (#17513))

Check failure on line 91 in client/config/config.go

View workflow job for this annotation

GitHub Actions / dependency-review

expected statement, found '>>'

Check failure on line 91 in client/config/config.go

View workflow job for this annotation

GitHub Actions / dependency-review

exponent has no digits

Check failure on line 91 in client/config/config.go

View workflow job for this annotation

GitHub Actions / dependency-review

illegal character U+0023 '#'
}
}

conf, err := getClientConfig(configPath, ctx.Viper)
if err != nil {
return ctx, fmt.Errorf("couldn't get client config: %v", err)
}
// we need to update KeyringDir field on Client Context first cause it is used in NewKeyringFromBackend

// we need to update KeyringDir field on client.Context first cause it is used in NewKeyringFromBackend
ctx = ctx.WithOutputFormat(conf.Output).
WithChainID(conf.ChainID).
WithKeyringDir(ctx.HomeDir)
Expand All @@ -81,17 +107,17 @@ func ReadFromClientConfig(ctx client.Context) (client.Context, error) {
return ctx, fmt.Errorf("couldn't get keyring: %w", err)
}

ctx = ctx.WithKeyring(keyring)

// https://github.com/cosmos/cosmos-sdk/issues/8986
client, err := client.NewClientFromNode(conf.Node)
if err != nil {
return ctx, fmt.Errorf("couldn't get client from nodeURI: %v", err)
}

ctx = ctx.WithNodeURI(conf.Node).
ctx = ctx.
WithNodeURI(conf.Node).
WithBroadcastMode(conf.BroadcastMode).
WithClient(client).
WithBroadcastMode(conf.BroadcastMode)
WithKeyring(keyring)

return ctx, nil
}

Check failure on line 123 in client/config/config.go

View workflow job for this annotation

GitHub Actions / dependency-review

expected ';', found 'EOF'

Check failure on line 123 in client/config/config.go

View workflow job for this annotation

GitHub Actions / dependency-review

expected ';', found 'EOF'

Check failure on line 123 in client/config/config.go

View workflow job for this annotation

GitHub Actions / dependency-review

expected ';', found 'EOF'

Check failure on line 123 in client/config/config.go

View workflow job for this annotation

GitHub Actions / dependency-review

expected '}', found 'EOF'

Check failure on line 123 in client/config/config.go

View workflow job for this annotation

GitHub Actions / dependency-review

expected '}', found 'EOF'

Check warning

Code scanning / CodeQL

Unreachable statement Warning

This statement is unreachable.
106 changes: 100 additions & 6 deletions client/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,30 @@ import (
)

const (
chainID = "test-chain"
nodeEnv = "NODE"
testNode1 = "http://localhost:1"
testNode2 = "http://localhost:2"
)

// initClientContext initiates client Context for tests
// initClientContext initiates client.Context for tests
func initClientContext(t *testing.T, envVar string) (client.Context, func()) {
<<<<<<< HEAD

Check failure on line 28 in client/config/config_test.go

View workflow job for this annotation

GitHub Actions / tests (00)

expected statement, found '<<'
=======
t.Helper()

clientCtx, cleanup, err := initClientContextWithTemplate(t, envVar, "", nil)
require.NoError(t, err)
require.Equal(t, chainID, clientCtx.ChainID)

return clientCtx, cleanup
}

// initClientContextWithTemplate initiates client.Context with custom config and template for tests
func initClientContextWithTemplate(t *testing.T, envVar, customTemplate string, customConfig interface{}) (client.Context, func(), error) {
t.Helper()
>>>>>>> 6601713eb (feat(client): allow overwritting client.toml (#17513))
home := t.TempDir()
chainID := "test-chain"
clientCtx := client.Context{}.
WithHomeDir(home).
WithViper("").
Expand All @@ -37,11 +52,89 @@ func initClientContext(t *testing.T, envVar string) (client.Context, func()) {
require.NoError(t, os.Setenv(nodeEnv, envVar))
}

clientCtx, err := config.ReadFromClientConfig(clientCtx)
require.NoError(t, err)
require.Equal(t, clientCtx.ChainID, chainID)
clientCtx, err := config.CreateClientConfig(clientCtx, customTemplate, customConfig)
return clientCtx, func() { _ = os.RemoveAll(home) }, err
}

func TestCustomTemplateAndConfig(t *testing.T) {
type GasConfig struct {
GasAdjustment float64 `mapstructure:"gas-adjustment"`
}

type CustomClientConfig struct {
config.Config `mapstructure:",squash"`

GasConfig GasConfig `mapstructure:"gas"`

Note string `mapstructure:"note"`
}

return clientCtx, func() { _ = os.RemoveAll(home) }
clientCfg := config.DefaultConfig()
// Overwrite the default keyring backend.
clientCfg.KeyringBackend = "test"

customClientConfig := CustomClientConfig{
Config: *clientCfg,
GasConfig: GasConfig{
GasAdjustment: 1.5,
},
Note: "Sent from the CLI.",
}

customClientConfigTemplate := config.DefaultClientConfigTemplate + `
# This is the gas adjustment factor used by the tx commands.
# Sets the default and can be overwriten by the --gas-adjustment flag in tx commands.
gas-adjustment = {{ .GasConfig.GasAdjustment }}
# Memo to include in all transactions.
note = "{{ .Note }}"
`

t.Run("custom template and config provided", func(t *testing.T) {
clientCtx, cleanup, err := initClientContextWithTemplate(t, "", customClientConfigTemplate, customClientConfig)
defer func() {
cleanup()
_ = os.Unsetenv(nodeEnv)
}()

require.NoError(t, err)
require.Equal(t, customClientConfig.KeyringBackend, clientCtx.Viper.Get(flags.FlagKeyringBackend))
require.Equal(t, customClientConfig.GasConfig.GasAdjustment, clientCtx.Viper.GetFloat64(flags.FlagGasAdjustment))
require.Equal(t, customClientConfig.Note, clientCtx.Viper.GetString(flags.FlagNote))
})

t.Run("no template and custom config provided", func(t *testing.T) {
_, cleanup, err := initClientContextWithTemplate(t, "", "", customClientConfig)
defer func() {
cleanup()
_ = os.Unsetenv(nodeEnv)
}()

require.Error(t, err)
})

t.Run("default template and custom config provided", func(t *testing.T) {
clientCtx, cleanup, err := initClientContextWithTemplate(t, "", config.DefaultClientConfigTemplate, customClientConfig)
defer func() {
cleanup()
_ = os.Unsetenv(nodeEnv)
}()

require.NoError(t, err)
require.Equal(t, customClientConfig.KeyringBackend, clientCtx.Viper.Get(flags.FlagKeyringBackend))
require.Nil(t, clientCtx.Viper.Get(flags.FlagGasAdjustment)) // nil because we do not read the flags
})

t.Run("no template and no config provided", func(t *testing.T) {
clientCtx, cleanup, err := initClientContextWithTemplate(t, "", "", nil)
defer func() {
cleanup()
_ = os.Unsetenv(nodeEnv)
}()

require.NoError(t, err)
require.Equal(t, config.DefaultConfig().KeyringBackend, clientCtx.Viper.Get(flags.FlagKeyringBackend))
require.Nil(t, clientCtx.Viper.Get(flags.FlagGasAdjustment)) // nil because we do not read the flags
})
}

func TestConfigCmdEnvFlag(t *testing.T) {
Expand Down Expand Up @@ -78,6 +171,7 @@ func TestConfigCmdEnvFlag(t *testing.T) {
cleanup()
_ = os.Unsetenv(nodeEnv)
}()

/*
env var is set with a flag
Expand Down
34 changes: 25 additions & 9 deletions client/config/toml.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import (
"github.com/spf13/viper"
)

const defaultConfigTemplate = `# This is a TOML config file.
const DefaultClientConfigTemplate = `# This is a TOML config file.
# For more information, see https://github.com/toml-lang/toml
###############################################################################
### Client Configuration ###
### Client Configuration ###
###############################################################################
# The network chain ID
Expand All @@ -27,17 +27,33 @@ node = "{{ .Node }}"
broadcast-mode = "{{ .BroadcastMode }}"
`

// writeConfigToFile parses defaultConfigTemplate, renders config using the template and writes it to
// configFilePath.
func writeConfigToFile(configFilePath string, config *ClientConfig) error {
var buffer bytes.Buffer
var configTemplate *template.Template

func init() {
var err error

tmpl := template.New("clientConfigFileTemplate")
configTemplate, err := tmpl.Parse(defaultConfigTemplate)
if err != nil {
if configTemplate, err = tmpl.Parse(DefaultClientConfigTemplate); err != nil {
panic(err)
}
}

// setConfigTemplate sets the custom app config template for
// the application
func setConfigTemplate(customTemplate string) error {
tmpl := template.New("clientConfigFileTemplate")
var err error
if configTemplate, err = tmpl.Parse(customTemplate); err != nil {
return err
}

return nil
}

// writeConfigFile renders config using the template and writes it to
// configFilePath.
func writeConfigFile(configFilePath string, config interface{}) error {
var buffer bytes.Buffer
if err := configTemplate.Execute(&buffer, config); err != nil {
return err
}
Expand All @@ -46,7 +62,7 @@ func writeConfigToFile(configFilePath string, config *ClientConfig) error {
}

// getClientConfig reads values from client.toml file and unmarshalls them into ClientConfig
func getClientConfig(configPath string, v *viper.Viper) (*ClientConfig, error) {
func getClientConfig(configPath string, v *viper.Viper) (*Config, error) {
v.AddConfigPath(configPath)
v.SetConfigName("client")
v.SetConfigType("toml")
Expand Down
Loading

0 comments on commit 3361ee6

Please sign in to comment.