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 subcommand to CLI #8953

Merged
merged 9 commits into from
Mar 31, 2021
Merged
Show file tree
Hide file tree
Changes from all 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 .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
- uses: golangci/golangci-lint-action@master
with:
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
version: v1.37
version: v1.39
args: --timeout 10m
github-token: ${{ secrets.github_token }}
if: env.GIT_DIFF
25 changes: 17 additions & 8 deletions client/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/tendermint/tendermint/libs/cli"
rpchttp "github.com/tendermint/tendermint/rpc/client/http"

"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
Expand Down Expand Up @@ -94,11 +93,6 @@ func ReadPersistentCommandFlags(clientCtx Context, flagSet *pflag.FlagSet) (Cont
clientCtx = clientCtx.WithOutputFormat(output)
}

if clientCtx.HomeDir == "" || flagSet.Changed(flags.FlagHome) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

From my perspective, the code L96-L99 is redundant. We reuse the same code in ReadHomeFlag. That's why I have decided to remove it.

homeDir, _ := flagSet.GetString(flags.FlagHome)
clientCtx = clientCtx.WithHomeDir(homeDir)
}

if !clientCtx.Simulate || flagSet.Changed(flags.FlagDryRun) {
dryRun, _ := flagSet.GetBool(flags.FlagDryRun)
clientCtx = clientCtx.WithSimulation(dryRun)
Expand All @@ -125,7 +119,7 @@ func ReadPersistentCommandFlags(clientCtx Context, flagSet *pflag.FlagSet) (Cont
keyringBackend, _ := flagSet.GetString(flags.FlagKeyringBackend)

if keyringBackend != "" {
kr, err := newKeyringFromFlags(clientCtx, keyringBackend)
kr, err := NewKeyringFromBackend(clientCtx, keyringBackend)
if err != nil {
return clientCtx, err
}
Expand All @@ -139,7 +133,7 @@ func ReadPersistentCommandFlags(clientCtx Context, flagSet *pflag.FlagSet) (Cont
if rpcURI != "" {
clientCtx = clientCtx.WithNodeURI(rpcURI)

client, err := rpchttp.New(rpcURI, "/websocket")
client, err := NewClientFromNode(rpcURI)
if err != nil {
return clientCtx, err
}
Expand Down Expand Up @@ -255,6 +249,21 @@ func readTxCommandFlags(clientCtx Context, flagSet *pflag.FlagSet) (Context, err
return clientCtx, nil
}

// ReadHomeFlag checks if home flag is changed.
// If this is a case, we update HomeDir field of Client Context
/* Discovered a bug with Cory
./build/simd init andrei --home ./test
cd test/config there is no client.toml configuration file
*/
func ReadHomeFlag(clientCtx Context, cmd *cobra.Command) Context {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can L96-L99 call this function too?

if cmd.Flags().Changed(flags.FlagHome) {
rootDir, _ := cmd.Flags().GetString(flags.FlagHome)
clientCtx = clientCtx.WithHomeDir(rootDir)
}

return clientCtx
}

// GetClientQueryContext returns a Context from a command with fields set based on flags
// defined in AddQueryFlagsToCmd. An error is returned if any flag query fails.
//
Expand Down
96 changes: 96 additions & 0 deletions client/config/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package config

import (
"encoding/json"
"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")

conf, 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(conf, "", "\t")
cmd.Println(string(s))

case 1:
// it's a get
key := args[0]

switch key {
case flags.FlagChainID:
cmd.Println(conf.ChainID)
case flags.FlagKeyringBackend:
cmd.Println(conf.KeyringBackend)
case tmcli.OutputFlag:
cmd.Println(conf.Output)
case flags.FlagNode:
cmd.Println(conf.Node)
case flags.FlagBroadcastMode:
cmd.Println(conf.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:
conf.SetChainID(value)
case flags.FlagKeyringBackend:
conf.SetKeyringBackend(value)
case tmcli.OutputFlag:
conf.SetOutput(value)
case flags.FlagNode:
conf.SetNode(value)
case flags.FlagBroadcastMode:
conf.SetBroadcastMode(value)
default:
return errUnknownConfigKey(key)
}

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

default:
panic("cound not execute config command")
}

return nil
}

func errUnknownConfigKey(key string) error {
return fmt.Errorf("unknown configuration key: %q", key)
}
96 changes: 96 additions & 0 deletions client/config/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
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"`
}

// defaultClientConfig returns the reference to ClientConfig with default values.
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()

// if config.toml file does not exist we create it and write default ClientConfig values into it.
if _, err := os.Stat(configFilePath); os.IsNotExist(err) {
if err := ensureConfigPath(configPath); err != nil {
return ctx, fmt.Errorf("couldn't make client config: %v", err)
}

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

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
ctx = ctx.WithOutputFormat(conf.Output).
WithChainID(conf.ChainID)

keyring, err := client.NewKeyringFromBackend(ctx, conf.KeyringBackend)
if err != nil {
return ctx, fmt.Errorf("couldn't get key ring: %v", 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).
WithClient(client).
WithBroadcastMode(conf.BroadcastMode)

return ctx, nil
}
70 changes: 70 additions & 0 deletions client/config/toml.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package config

import (
"bytes"
"io/ioutil"
"os"
"text/template"

"github.com/spf13/viper"
)

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

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

# The network chain ID
chain-id = "{{ .ChainID }}"
# The keyring's backend, where the keys are stored (os|file|kwallet|pass|test|memory)
keyring-backend = "{{ .KeyringBackend }}"
# CLI output format (text|json)
output = "{{ .Output }}"
# <host>:<port> to Tendermint RPC interface for this chain
node = "{{ .Node }}"
# Transaction broadcasting mode (sync|async|block)
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

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

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

return ioutil.WriteFile(configFilePath, buffer.Bytes(), 0600)
alessio marked this conversation as resolved.
Show resolved Hide resolved
}

// ensureConfigPath creates a directory configPath if it does not exist
func ensureConfigPath(configPath string) error {
return os.MkdirAll(configPath, os.ModePerm)
}

// getClientConfig reads values from client.toml file and unmarshalls them into ClientConfig
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
}
18 changes: 16 additions & 2 deletions client/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"io"
"os"

"github.com/spf13/viper"

"gopkg.in/yaml.v2"

"github.com/gogo/protobuf/proto"
Expand Down Expand Up @@ -46,6 +48,7 @@ type Context struct {
AccountRetriever AccountRetriever
NodeURI string
FeeGranter sdk.AccAddress
Viper *viper.Viper

// TODO: Deprecated (remove).
LegacyAmino *codec.LegacyAmino
Expand Down Expand Up @@ -133,7 +136,9 @@ func (ctx Context) WithChainID(chainID string) Context {

// WithHomeDir returns a copy of the Context with HomeDir set.
func (ctx Context) WithHomeDir(dir string) Context {
ctx.HomeDir = dir
if dir != "" {
ctx.HomeDir = dir
}
return ctx
}

Expand Down Expand Up @@ -220,6 +225,14 @@ func (ctx Context) WithInterfaceRegistry(interfaceRegistry codectypes.InterfaceR
return ctx
}

// WithViper returns the context with Viper field. This Viper instance is used to read
// client-side config from the config file.
func (ctx Context) WithViper() Context {
v := viper.New()
ctx.Viper = v
return ctx
}

// PrintString prints the raw string to ctx.Output if it's defined, otherwise to os.Stdout
func (ctx Context) PrintString(str string) error {
return ctx.PrintBytes([]byte(str))
Expand Down Expand Up @@ -330,7 +343,8 @@ func GetFromFields(kr keyring.Keyring, from string, genOnly bool) (sdk.AccAddres
return info.GetAddress(), info.GetName(), info.GetType(), nil
}

func newKeyringFromFlags(ctx Context, backend string) (keyring.Keyring, error) {
// NewKeyringFromBackend gets a Keyring object from a backend
func NewKeyringFromBackend(ctx Context, backend string) (keyring.Keyring, error) {
amaury1093 marked this conversation as resolved.
Show resolved Hide resolved
if ctx.GenerateOnly || ctx.Simulate {
return keyring.New(sdk.KeyringServiceName(), keyring.BackendMemory, ctx.KeyringDir, ctx.Input, ctx.KeyringOptions...)
}
Expand Down
Loading