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
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion client/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ func ReadPersistentCommandFlags(clientCtx Context, flagSet *pflag.FlagSet) (Cont
keyringBackend, _ := flagSet.GetString(flags.FlagKeyringBackend)

if keyringBackend != "" {
kr, err := newKeyringFromFlags(clientCtx, keyringBackend)
kr, err := NewKeyringFromFlags(clientCtx, keyringBackend)
if err != nil {
return clientCtx, err
}
Expand Down
112 changes: 112 additions & 0 deletions client/config/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package config

import (
"encoding/json"
"errors"
"fmt"
"path/filepath"

tmcli "github.com/tendermint/tendermint/libs/cli"

"github.com/spf13/cobra"

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

// Cmd returns a CLI command to interactively create an application CLI
// config file.
func Cmd() *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),
}
return cmd
}

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

clientCtx := client.GetClientContextFromCmd(cmd)
configPath := filepath.Join(clientCtx.HomeDir, "config")

/*
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 ensureConfigPath anymore, cause ~/.simapp/config directory will exist in any way due to ReadFromClientConfig logic

if err := ensureConfigPath(configPath); err != nil {
return fmt.Errorf("couldn't make client config: %v", err)
}
*/

cliConfig, err := getClientConfig(configPath, clientCtx.Viper)
if err != nil {
return fmt.Errorf("couldn't get client config: %v", err)
}

switch len(args) {
case 0:
// print all client config fields to sdt out
s, _ := json.MarshalIndent(cliConfig, "", "\t")
cmd.Println(string(s))

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

case 2:
// it's set

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

switch key {
case flags.FlagChainID:
cliConfig.SetChainID(value)
case flags.FlagKeyringBackend:
cliConfig.SetKeyringBackend(value)
case tmcli.OutputFlag:
cliConfig.SetOutput(value)
case flags.FlagNode:
cliConfig.SetNode(value)
case flags.FlagBroadcastMode:
cliConfig.SetBroadcastMode(value)
default:
return errUnknownConfigKey(key)
}

configTemplate, err := initConfigTemplate()
if err != nil {
return fmt.Errorf("could not initiate config template: %v", err)
}

cliConfigFile := filepath.Join(configPath, "client.toml")
if err := writeConfigFile(cliConfigFile, cliConfig, configTemplate); err != nil {
return fmt.Errorf("could not write client config to the file: %v", err)
}

default:
// print error
return errors.New("cound not execute config command")
}

return nil
}

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

import (
"fmt"
"os"
"path/filepath"

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

// Default constants
const (
chainID = ""
keyringBackend = "os"
output = "text"
node = "tcp://localhost:26657"
broadcastMode = "sync"
)

type ClientConfig 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 DefaultClientConfig() *ClientConfig {
return &ClientConfig{chainID, keyringBackend, output, node, broadcastMode}
}

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
func ReadFromClientConfig(ctx client.Context) (client.Context, error) {

configPath := filepath.Join(ctx.HomeDir, "config")
configFilePath := filepath.Join(configPath, "client.toml")

conf := DefaultClientConfig()

switch _, err := os.Stat(configFilePath); {
// config file does not exist
case os.IsNotExist(err):
// we create ~/.simapp/config/client.toml with default values

// create a directority configPath
if err := ensureConfigPath(configPath); err != nil {
return ctx, fmt.Errorf("couldn't make client config: %v", err)
}

configTemplate, err := initConfigTemplate()
if err != nil {
return ctx, fmt.Errorf("could not initiate config template: %v", err)
}

if err := writeConfigFile(configFilePath, conf, configTemplate); err != nil {
return ctx, fmt.Errorf("could not write client config to the file: %v", err)
}
// config file exists and we read config values from client.toml file
default:
conf, err = getClientConfig(configPath, ctx.Viper)
if err != nil {
return ctx, fmt.Errorf("couldn't get client config: %v", err)
}
}

keyring, err := client.NewKeyringFromFlags(ctx, conf.KeyringBackend)
if err != nil {
return ctx, fmt.Errorf("couldn't get key ring: %v", err)
}

ctx = ctx.WithChainID(conf.ChainID).
WithKeyring(keyring).
WithOutputFormat(conf.Output).
WithNodeURI(conf.Node).
WithBroadcastMode(conf.BroadcastMode)

return ctx, nil
}
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 (
"errors"
"io/ioutil"
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

var ErrWrongNumberOfArgs = errors.New("wrong number of arguments")

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

// Init command config
cmd := Cmd()
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
// for now manual testign is ok

}

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

import (
"bytes"
"os"
"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 = "{{ .ChainID }}"
keyring-backend = "{{ .KeyringBackend }}"
output = "{{ .Output }}"
node = "{{ .Node }}"
broadcast-mode = "{{ .BroadcastMode }}"
`

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

return configTemplate, nil
}

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

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

tmos.MustWriteFile(cfgFile, buffer.Bytes(), 0644)
return nil

}

func ensureConfigPath(configPath string) error {
if err := os.MkdirAll(configPath, os.ModePerm); err != nil {
return err
}

return nil
}

func getClientConfig(configPath string, v *viper.Viper) (*ClientConfig, error) {

v.AddConfigPath(configPath)
v.SetConfigName("client")
v.SetConfigType("toml")

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
}
Loading