Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add client config #8820

Closed
wants to merge 14 commits into from
Closed
152 changes: 152 additions & 0 deletions client/config/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package config

import (
"errors"
"fmt"
"os"
"path"
"strconv"

"github.com/spf13/cobra"
"github.com/spf13/viper"

"github.com/cosmos/cosmos-sdk/client/flags"
tmcli "github.com/tendermint/tendermint/libs/cli"
)

var ErrWrongNumberOfArgs = fmt.Errorf("wrong number of arguments")

type ClientConfig struct {
ChainID string `mapstructure:"chain-id"`
KeyringBackend string `mapstructure:"keyring-backend"`
Output string `mapstructure:"output"`
Node string `mapstructure:"node"`
BroadcastMode string `mapstructure:"broadcast-mode"`
Trace bool `mapstructure:"trace"`
}

func DefaultClientConfig() *ClientConfig {
return &ClientConfig{
ChainID: "",
KeyringBackend: "os",
Output: "text",
Node: "tcp://localhost:26657",
BroadcastMode: "sync",
Trace: false,
}
}

// Cmd returns a CLI command to interactively create an application CLI
// config file.
func Cmd(defaultCLIHome string) *cobra.Command {
cmd := &cobra.Command{
Use: "config <key> [value]",
Short: "Create or query an application CLI configuration file",
RunE: runConfigCmd,
Args: cobra.RangeArgs(0, 2),
}

cmd.Flags().String(flags.FlagHome, defaultCLIHome,
"set client's home directory for configuration")
// cmd.Flags().Bool(flagGet, false,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we dont need a flagGet flag here, do we?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's correct! as per Alessio's proposal #8820 (comment)

// "print configuration value or its default if unset")
return cmd
}

func runConfigCmd(cmd *cobra.Command, args []string) error {

v := viper.New()

cfgPath, err := ensureCfgPath(v.GetString(flags.FlagHome))
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to make config path: %v\n", err)
return err
}

cliConfig, err := getClientConfig(cfgPath, v)
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to get client config: %v\n", err)
return err
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alessio Following your suggestion, I print an error here to os.Stderr. I am unable to add os.Exit(1) cause the runConfigCmd is supposed to return an error.

}

switch len(args) {
case 0:
// print all client config fields to sdt out
// TODO implement method to print out all client config fields
fmt.Println(cliConfig.ChainID)
fmt.Println(cliConfig.KeyringBackend)
fmt.Println(cliConfig.Output)
fmt.Println(cliConfig.Node)
fmt.Println(cliConfig.BroadcastMode)
fmt.Println(cliConfig.Trace)

case 1:
// it's a get
// TODO implement method for get
// should i implement getters here?
key := args[0]
switch key {
case flags.FlagChainID:
fmt.Println(cliConfig.ChainID)
case flags.FlagKeyringBackend:
fmt.Println(cliConfig.KeyringBackend)
case tmcli.OutputFlag:
fmt.Println(cliConfig.Output)
case flags.FlagNode:
fmt.Println(cliConfig.Node)
case flags.FlagBroadcastMode:
fmt.Println(cliConfig.BroadcastMode)
case "trace":
fmt.Println(cliConfig.Trace)
default:
err := errUnknownConfigKey(key)
fmt.Fprintf(os.Stderr, "Unable to get the value for the key: %v, error: %v\n", key, err)
return err
}

case 2:
// it's set
// TODO impement method for set
// TODO implement setters

key, value := args[0], args[1]

switch key {
case flags.FlagChainID:
cliConfig.ChainID = value
case flags.FlagKeyringBackend:
cliConfig.KeyringBackend = value
case tmcli.OutputFlag:
cliConfig.Output = value
case flags.FlagNode:
cliConfig.Node = value
case flags.FlagBroadcastMode:
cliConfig.BroadcastMode = value
case "trace":
boolVal, err := strconv.ParseBool(value)
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to parse value to bool, err: %v\n", err)
return err
}
cliConfig.Trace = boolVal
default:
return errUnknownConfigKey(key)
}

configTemplate := InitConfigTemplate()
cfgFile := path.Join(cfgPath, "config.toml")
WriteConfigFile(cfgFile, cliConfig, configTemplate)

default:
// print error
err := errors.New("unable to execute config command")
fmt.Fprintf(os.Stderr, "%v\n", err)
return err
}

return nil
}

func errUnknownConfigKey(key string) error {
return fmt.Errorf("unknown configuration key: %q", key)
}
61 changes: 61 additions & 0 deletions client/config/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package config

import (
"io/ioutil"
"os"
"path/filepath"
"testing"

"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

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

// For https://github.com/cosmos/cosmos-sdk/issues/3899
func Test_runConfigCmdTwiceWithShorterNodeValue(t *testing.T) {
// Prepare environment
t.Parallel()
configHome, cleanup := tmpDir(t)
defer cleanup()
_ = os.RemoveAll(filepath.Join(configHome, "config"))
viper.Set(flags.FlagHome, configHome)

// Init command config
cmd := Cmd(configHome)
assert.NotNil(t, cmd)

err := cmd.RunE(cmd, []string{"node", "tcp://localhost:26657"})
assert.Nil(t, err)

err = cmd.RunE(cmd, []string{"node", "--get"})
assert.Nil(t, err)

err = cmd.RunE(cmd, []string{"node", "tcp://local:26657"})
assert.Nil(t, err)

err = cmd.RunE(cmd, []string{"node", "--get"})
assert.Nil(t, err)

err = cmd.RunE(cmd, nil)
assert.Nil(t, err)

err = cmd.RunE(cmd, []string{"invalidKey", "--get"})
require.Equal(t, err, errUnknownConfigKey("invalidKey"))

err = cmd.RunE(cmd, []string{"invalidArg1"})
require.Equal(t, err, ErrWrongNumberOfArgs)

err = cmd.RunE(cmd, []string{"invalidKey", "invalidValue"})
require.Equal(t, err, errUnknownConfigKey("invalidKey"))

// TODO add testing of pririty environmental variable, flag and file

}

func tmpDir(t *testing.T) (string, func()) {
dir, err := ioutil.TempDir("", t.Name()+"_")
require.NoError(t, err)
return dir, func() { _ = os.RemoveAll(dir) }
}
88 changes: 88 additions & 0 deletions client/config/toml.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package config

import (
"bytes"
"os"
"path"
"text/template"

"github.com/spf13/viper"
tmos "github.com/tendermint/tendermint/libs/os"
)

const defaultConfigTemplate = `# This is a TOML config file.
# For more information, see https://github.com/toml-lang/toml

###############################################################################
### Client Configuration ###
###############################################################################


chain-id = "{{ .ClientConfig.ChainID }}"
keyring-backend = "{{ .ClientConfig.KeyringBackend }}"
output = "{{ .ClientConfig.Output }}"
node = "{{ .ClientConfig.Node }}"
broadcast-mode = "{{ .ClientConfig.BroadcastMode }}"
trace = "{{ .ClientConfig.Trace }}"
`

// InitConfigTemplate initiates config template that will be used in
// WriteConfigFile
func InitConfigTemplate() *template.Template {
tmpl := template.New("clientConfigFileTemplate")
configTemplate, err := tmpl.Parse(defaultConfigTemplate)
if err != nil {
panic(err)
}

return configTemplate
}

// ParseConfig retrieves the default environment configuration for the
// application.
func ParseConfig(v *viper.Viper) (*ClientConfig, error) {
conf := DefaultClientConfig()
if err := v.Unmarshal(conf); err != nil {
return nil, err
}

return conf, nil
}

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

if err := configTemplate.Execute(&buffer, config); err != nil {
panic(err)
}

tmos.MustWriteFile(cfgFile, buffer.Bytes(), 0644)
}

func ensureCfgPath(rootDir string) (string, error) {
cfgPath := path.Join(rootDir, "config")
if err := os.MkdirAll(cfgPath, os.ModePerm); err != nil { // config directory
return "", err
}

return cfgPath, nil
}

func getClientConfig(cfgPath string, v *viper.Viper) (*ClientConfig, error) {
v.AddConfigPath(cfgPath)
v.SetConfigName("client")
v.SetConfigType("toml")
v.AutomaticEnv()
if err := v.ReadInConfig(); err != nil {
return nil, err
}

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

return conf, nil
}
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@ require (
github.com/improbable-eng/grpc-web v0.14.0
github.com/magiconair/properties v1.8.4
github.com/mattn/go-isatty v0.0.12
github.com/mitchellh/mapstructure v1.3.3
github.com/otiai10/copy v1.5.0
github.com/pelletier/go-toml v1.8.1 // indirect
github.com/pelletier/go-toml v1.8.1
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.9.0
github.com/prometheus/common v0.18.0
Expand Down
13 changes: 13 additions & 0 deletions server/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"syscall"
"time"

clicfg "github.com/cosmos/cosmos-sdk/client/config"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -242,6 +243,18 @@ func interceptConfigs(rootViper *viper.Viper) (*tmcfg.Config, error) {
return nil, fmt.Errorf("failed to merge configuration: %w", err)
}

// Adding default ClientConfig and writing it into "client.toml"
cliCfgFilePath := filepath.Join(configPath, "client.toml")
if _, err := os.Stat(cliCfgFilePath); os.IsNotExist(err) {
cliConfig, err := clicfg.ParseConfig(rootViper)
if err != nil {
return nil, fmt.Errorf("failed to parse %s: %w", cliCfgFilePath, err)
}

configTemplate := clicfg.InitConfigTemplate()
clicfg.WriteConfigFile(cliCfgFilePath, cliConfig, configTemplate)
}

return conf, nil
}

Expand Down
7 changes: 5 additions & 2 deletions simapp/simd/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (

"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/client"
clicfg "github.com/cosmos/cosmos-sdk/client/config"
"github.com/cosmos/cosmos-sdk/client/debug"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/client/keys"
Expand Down Expand Up @@ -83,16 +84,18 @@ func initRootCmd(rootCmd *cobra.Command, encodingConfig params.EncodingConfig) {
a := appCreator{encodingConfig}
server.AddCommands(rootCmd, simapp.DefaultNodeHome, a.newApp, a.appExport, addModuleInitFlags)

// add keybase, auxiliary RPC, query, and tx child commands
// add keybase, auxiliary RPC, query and tx child commands
rootCmd.AddCommand(
rpc.StatusCommand(),
queryCommand(),
txCommand(),
keys.Commands(simapp.DefaultNodeHome),
)

// add rosetta
rootCmd.AddCommand(server.RosettaCommand(encodingConfig.InterfaceRegistry, encodingConfig.Marshaler))

// add client config
rootCmd.AddCommand(clicfg.Cmd(flags.FlagHome))
}

func addModuleInitFlags(startCmd *cobra.Command) {
Expand Down