Skip to content

Commit 410d8ed

Browse files
cyberbono3Alessio Tregliaamaury1093
authored
Add client config subcommand to CLI (#8953)
* add client config * addressed reviewers comments * refactored,ready for review * fixed linter issues and addressed reviewers comments * Bump golangci-lint * fix linter warnings * fix some tests Co-authored-by: Alessio Treglia <[email protected]> Co-authored-by: Amaury <[email protected]>
1 parent 413938c commit 410d8ed

File tree

15 files changed

+332
-21
lines changed

15 files changed

+332
-21
lines changed

.github/workflows/lint.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ jobs:
2323
- uses: golangci/golangci-lint-action@master
2424
with:
2525
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
26-
version: v1.37
26+
version: v1.39
2727
args: --timeout 10m
2828
github-token: ${{ secrets.github_token }}
2929
if: env.GIT_DIFF

client/cmd.go

+17-8
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"github.com/spf13/cobra"
99
"github.com/spf13/pflag"
1010
"github.com/tendermint/tendermint/libs/cli"
11-
rpchttp "github.com/tendermint/tendermint/rpc/client/http"
1211

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

97-
if clientCtx.HomeDir == "" || flagSet.Changed(flags.FlagHome) {
98-
homeDir, _ := flagSet.GetString(flags.FlagHome)
99-
clientCtx = clientCtx.WithHomeDir(homeDir)
100-
}
101-
10296
if !clientCtx.Simulate || flagSet.Changed(flags.FlagDryRun) {
10397
dryRun, _ := flagSet.GetBool(flags.FlagDryRun)
10498
clientCtx = clientCtx.WithSimulation(dryRun)
@@ -125,7 +119,7 @@ func ReadPersistentCommandFlags(clientCtx Context, flagSet *pflag.FlagSet) (Cont
125119
keyringBackend, _ := flagSet.GetString(flags.FlagKeyringBackend)
126120

127121
if keyringBackend != "" {
128-
kr, err := newKeyringFromFlags(clientCtx, keyringBackend)
122+
kr, err := NewKeyringFromBackend(clientCtx, keyringBackend)
129123
if err != nil {
130124
return clientCtx, err
131125
}
@@ -139,7 +133,7 @@ func ReadPersistentCommandFlags(clientCtx Context, flagSet *pflag.FlagSet) (Cont
139133
if rpcURI != "" {
140134
clientCtx = clientCtx.WithNodeURI(rpcURI)
141135

142-
client, err := rpchttp.New(rpcURI, "/websocket")
136+
client, err := NewClientFromNode(rpcURI)
143137
if err != nil {
144138
return clientCtx, err
145139
}
@@ -255,6 +249,21 @@ func readTxCommandFlags(clientCtx Context, flagSet *pflag.FlagSet) (Context, err
255249
return clientCtx, nil
256250
}
257251

252+
// ReadHomeFlag checks if home flag is changed.
253+
// If this is a case, we update HomeDir field of Client Context
254+
/* Discovered a bug with Cory
255+
./build/simd init andrei --home ./test
256+
cd test/config there is no client.toml configuration file
257+
*/
258+
func ReadHomeFlag(clientCtx Context, cmd *cobra.Command) Context {
259+
if cmd.Flags().Changed(flags.FlagHome) {
260+
rootDir, _ := cmd.Flags().GetString(flags.FlagHome)
261+
clientCtx = clientCtx.WithHomeDir(rootDir)
262+
}
263+
264+
return clientCtx
265+
}
266+
258267
// GetClientQueryContext returns a Context from a command with fields set based on flags
259268
// defined in AddQueryFlagsToCmd. An error is returned if any flag query fails.
260269
//

client/config/cmd.go

+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package config
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"path/filepath"
7+
8+
tmcli "github.com/tendermint/tendermint/libs/cli"
9+
10+
"github.com/spf13/cobra"
11+
12+
"github.com/cosmos/cosmos-sdk/client"
13+
"github.com/cosmos/cosmos-sdk/client/flags"
14+
)
15+
16+
// Cmd returns a CLI command to interactively create an application CLI
17+
// config file.
18+
func Cmd() *cobra.Command {
19+
cmd := &cobra.Command{
20+
Use: "config <key> [value]",
21+
Short: "Create or query an application CLI configuration file",
22+
RunE: runConfigCmd,
23+
Args: cobra.RangeArgs(0, 2),
24+
}
25+
return cmd
26+
}
27+
28+
func runConfigCmd(cmd *cobra.Command, args []string) error {
29+
clientCtx := client.GetClientContextFromCmd(cmd)
30+
configPath := filepath.Join(clientCtx.HomeDir, "config")
31+
32+
conf, err := getClientConfig(configPath, clientCtx.Viper)
33+
if err != nil {
34+
return fmt.Errorf("couldn't get client config: %v", err)
35+
}
36+
37+
switch len(args) {
38+
case 0:
39+
// print all client config fields to sdt out
40+
s, _ := json.MarshalIndent(conf, "", "\t")
41+
cmd.Println(string(s))
42+
43+
case 1:
44+
// it's a get
45+
key := args[0]
46+
47+
switch key {
48+
case flags.FlagChainID:
49+
cmd.Println(conf.ChainID)
50+
case flags.FlagKeyringBackend:
51+
cmd.Println(conf.KeyringBackend)
52+
case tmcli.OutputFlag:
53+
cmd.Println(conf.Output)
54+
case flags.FlagNode:
55+
cmd.Println(conf.Node)
56+
case flags.FlagBroadcastMode:
57+
cmd.Println(conf.BroadcastMode)
58+
default:
59+
err := errUnknownConfigKey(key)
60+
return fmt.Errorf("couldn't get the value for the key: %v, error: %v", key, err)
61+
}
62+
63+
case 2:
64+
// it's set
65+
key, value := args[0], args[1]
66+
67+
switch key {
68+
case flags.FlagChainID:
69+
conf.SetChainID(value)
70+
case flags.FlagKeyringBackend:
71+
conf.SetKeyringBackend(value)
72+
case tmcli.OutputFlag:
73+
conf.SetOutput(value)
74+
case flags.FlagNode:
75+
conf.SetNode(value)
76+
case flags.FlagBroadcastMode:
77+
conf.SetBroadcastMode(value)
78+
default:
79+
return errUnknownConfigKey(key)
80+
}
81+
82+
confFile := filepath.Join(configPath, "client.toml")
83+
if err := writeConfigToFile(confFile, conf); err != nil {
84+
return fmt.Errorf("could not write client config to the file: %v", err)
85+
}
86+
87+
default:
88+
panic("cound not execute config command")
89+
}
90+
91+
return nil
92+
}
93+
94+
func errUnknownConfigKey(key string) error {
95+
return fmt.Errorf("unknown configuration key: %q", key)
96+
}

client/config/config.go

+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package config
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"path/filepath"
7+
8+
"github.com/cosmos/cosmos-sdk/client"
9+
)
10+
11+
// Default constants
12+
const (
13+
chainID = ""
14+
keyringBackend = "os"
15+
output = "text"
16+
node = "tcp://localhost:26657"
17+
broadcastMode = "sync"
18+
)
19+
20+
type ClientConfig struct {
21+
ChainID string `mapstructure:"chain-id" json:"chain-id"`
22+
KeyringBackend string `mapstructure:"keyring-backend" json:"keyring-backend"`
23+
Output string `mapstructure:"output" json:"output"`
24+
Node string `mapstructure:"node" json:"node"`
25+
BroadcastMode string `mapstructure:"broadcast-mode" json:"broadcast-mode"`
26+
}
27+
28+
// defaultClientConfig returns the reference to ClientConfig with default values.
29+
func defaultClientConfig() *ClientConfig {
30+
return &ClientConfig{chainID, keyringBackend, output, node, broadcastMode}
31+
}
32+
33+
func (c *ClientConfig) SetChainID(chainID string) {
34+
c.ChainID = chainID
35+
}
36+
37+
func (c *ClientConfig) SetKeyringBackend(keyringBackend string) {
38+
c.KeyringBackend = keyringBackend
39+
}
40+
41+
func (c *ClientConfig) SetOutput(output string) {
42+
c.Output = output
43+
}
44+
45+
func (c *ClientConfig) SetNode(node string) {
46+
c.Node = node
47+
}
48+
49+
func (c *ClientConfig) SetBroadcastMode(broadcastMode string) {
50+
c.BroadcastMode = broadcastMode
51+
}
52+
53+
// ReadFromClientConfig reads values from client.toml file and updates them in client Context
54+
func ReadFromClientConfig(ctx client.Context) (client.Context, error) {
55+
configPath := filepath.Join(ctx.HomeDir, "config")
56+
configFilePath := filepath.Join(configPath, "client.toml")
57+
conf := defaultClientConfig()
58+
59+
// if config.toml file does not exist we create it and write default ClientConfig values into it.
60+
if _, err := os.Stat(configFilePath); os.IsNotExist(err) {
61+
if err := ensureConfigPath(configPath); err != nil {
62+
return ctx, fmt.Errorf("couldn't make client config: %v", err)
63+
}
64+
65+
if err := writeConfigToFile(configFilePath, conf); err != nil {
66+
return ctx, fmt.Errorf("could not write client config to the file: %v", err)
67+
}
68+
}
69+
70+
conf, err := getClientConfig(configPath, ctx.Viper)
71+
if err != nil {
72+
return ctx, fmt.Errorf("couldn't get client config: %v", err)
73+
}
74+
// we need to update KeyringDir field on Client Context first cause it is used in NewKeyringFromBackend
75+
ctx = ctx.WithOutputFormat(conf.Output).
76+
WithChainID(conf.ChainID)
77+
78+
keyring, err := client.NewKeyringFromBackend(ctx, conf.KeyringBackend)
79+
if err != nil {
80+
return ctx, fmt.Errorf("couldn't get key ring: %v", err)
81+
}
82+
83+
ctx = ctx.WithKeyring(keyring)
84+
85+
// https://github.com/cosmos/cosmos-sdk/issues/8986
86+
client, err := client.NewClientFromNode(conf.Node)
87+
if err != nil {
88+
return ctx, fmt.Errorf("couldn't get client from nodeURI: %v", err)
89+
}
90+
91+
ctx = ctx.WithNodeURI(conf.Node).
92+
WithClient(client).
93+
WithBroadcastMode(conf.BroadcastMode)
94+
95+
return ctx, nil
96+
}

client/config/toml.go

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package config
2+
3+
import (
4+
"bytes"
5+
"io/ioutil"
6+
"os"
7+
"text/template"
8+
9+
"github.com/spf13/viper"
10+
)
11+
12+
const defaultConfigTemplate = `# This is a TOML config file.
13+
# For more information, see https://github.com/toml-lang/toml
14+
15+
###############################################################################
16+
### Client Configuration ###
17+
###############################################################################
18+
19+
# The network chain ID
20+
chain-id = "{{ .ChainID }}"
21+
# The keyring's backend, where the keys are stored (os|file|kwallet|pass|test|memory)
22+
keyring-backend = "{{ .KeyringBackend }}"
23+
# CLI output format (text|json)
24+
output = "{{ .Output }}"
25+
# <host>:<port> to Tendermint RPC interface for this chain
26+
node = "{{ .Node }}"
27+
# Transaction broadcasting mode (sync|async|block)
28+
broadcast-mode = "{{ .BroadcastMode }}"
29+
`
30+
31+
// writeConfigToFile parses defaultConfigTemplate, renders config using the template and writes it to
32+
// configFilePath.
33+
func writeConfigToFile(configFilePath string, config *ClientConfig) error {
34+
var buffer bytes.Buffer
35+
36+
tmpl := template.New("clientConfigFileTemplate")
37+
configTemplate, err := tmpl.Parse(defaultConfigTemplate)
38+
if err != nil {
39+
return err
40+
}
41+
42+
if err := configTemplate.Execute(&buffer, config); err != nil {
43+
return err
44+
}
45+
46+
return ioutil.WriteFile(configFilePath, buffer.Bytes(), 0600)
47+
}
48+
49+
// ensureConfigPath creates a directory configPath if it does not exist
50+
func ensureConfigPath(configPath string) error {
51+
return os.MkdirAll(configPath, os.ModePerm)
52+
}
53+
54+
// getClientConfig reads values from client.toml file and unmarshalls them into ClientConfig
55+
func getClientConfig(configPath string, v *viper.Viper) (*ClientConfig, error) {
56+
v.AddConfigPath(configPath)
57+
v.SetConfigName("client")
58+
v.SetConfigType("toml")
59+
60+
if err := v.ReadInConfig(); err != nil {
61+
return nil, err
62+
}
63+
64+
conf := new(ClientConfig)
65+
if err := v.Unmarshal(conf); err != nil {
66+
return nil, err
67+
}
68+
69+
return conf, nil
70+
}

client/context.go

+16-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55
"io"
66
"os"
77

8+
"github.com/spf13/viper"
9+
810
"gopkg.in/yaml.v2"
911

1012
"github.com/gogo/protobuf/proto"
@@ -46,6 +48,7 @@ type Context struct {
4648
AccountRetriever AccountRetriever
4749
NodeURI string
4850
FeeGranter sdk.AccAddress
51+
Viper *viper.Viper
4952

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

134137
// WithHomeDir returns a copy of the Context with HomeDir set.
135138
func (ctx Context) WithHomeDir(dir string) Context {
136-
ctx.HomeDir = dir
139+
if dir != "" {
140+
ctx.HomeDir = dir
141+
}
137142
return ctx
138143
}
139144

@@ -220,6 +225,14 @@ func (ctx Context) WithInterfaceRegistry(interfaceRegistry codectypes.InterfaceR
220225
return ctx
221226
}
222227

228+
// WithViper returns the context with Viper field. This Viper instance is used to read
229+
// client-side config from the config file.
230+
func (ctx Context) WithViper() Context {
231+
v := viper.New()
232+
ctx.Viper = v
233+
return ctx
234+
}
235+
223236
// PrintString prints the raw string to ctx.Output if it's defined, otherwise to os.Stdout
224237
func (ctx Context) PrintString(str string) error {
225238
return ctx.PrintBytes([]byte(str))
@@ -330,7 +343,8 @@ func GetFromFields(kr keyring.Keyring, from string, genOnly bool) (sdk.AccAddres
330343
return info.GetAddress(), info.GetName(), info.GetType(), nil
331344
}
332345

333-
func newKeyringFromFlags(ctx Context, backend string) (keyring.Keyring, error) {
346+
// NewKeyringFromBackend gets a Keyring object from a backend
347+
func NewKeyringFromBackend(ctx Context, backend string) (keyring.Keyring, error) {
334348
if ctx.GenerateOnly || ctx.Simulate {
335349
return keyring.New(sdk.KeyringServiceName(), keyring.BackendMemory, ctx.KeyringDir, ctx.Input, ctx.KeyringOptions...)
336350
}

0 commit comments

Comments
 (0)