Skip to content

Commit

Permalink
feat: add gnoland config command suite (#1605)
Browse files Browse the repository at this point in the history
## Description

Based on discussions in #1593, this PR introduces the CLI suite for
manipulating the `config.toml`. Using this suite, we can build better
workflows for power users.

This PR is a series of lego blocks that are required for us to get a
normal user chain connection going:
- #1252 - solved `genesis.json` management and manipulation
- #1593 - solved node secrets management and manipulation
- this PR - solves `config.toml` management and manipulation

All of these PRs get us to a point where the user can run:
- `gnoland init`
- `gnoland start`

to get up and running with any Gno network. The added middle step is
fine-tuning the configuration and genesis, but it's worth noting this
step is optional.

New commands:
```shell
gnoland config --help             
USAGE
  config <subcommand> [flags]

Gno config manipulation suite, for editing base and module configurations

SUBCOMMANDS
  init  Initializes the Gno node configuration
  set   Edits the Gno node configuration
  get   Shows the Gno node configuration
```

In short, the `gnoland config init` command initializes a default
`config.toml`, while other subcommands allow editing
 viewing any field in the specific configuration.

<details><summary>Contributors' checklist...</summary>

- [x] Added new tests, or not needed, or not feasible
- [x] Provided an example (e.g. screenshot) to aid review or the PR is
self-explanatory
- [x] Updated the official documentation or not needed
- [x] No breaking changes were made, or a `BREAKING CHANGE: xxx` message
was included in the description
- [x] Added references to related issues and PRs
- [ ] Provided any useful hints for running manual tests
- [ ] Added new benchmarks to [generated
graphs](https://gnoland.github.io/benchmarks), if any. More info
[here](https://github.com/gnolang/gno/blob/master/.benchmarks/README.md).
</details>
  • Loading branch information
zivkovicmilos committed Mar 12, 2024
1 parent 05b8afc commit bc77c2b
Show file tree
Hide file tree
Showing 13 changed files with 2,027 additions and 23 deletions.
6 changes: 3 additions & 3 deletions gno.land/cmd/genesis/balances_add_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,7 @@ func TestBalances_GetBalancesFromTransactions(t *testing.T) {
marshalledTxs = append(marshalledTxs, string(marshalledTx))
}

mockErr := bytes.NewBufferString("")
mockErr := new(bytes.Buffer)
io := commands.NewTestIO()
io.SetErr(commands.WriteNopCloser(mockErr))

Expand Down Expand Up @@ -638,7 +638,7 @@ func TestBalances_GetBalancesFromTransactions(t *testing.T) {
marshalledTxs = append(marshalledTxs, string(marshalledTx))
}

mockErr := bytes.NewBufferString("")
mockErr := new(bytes.Buffer)
io := commands.NewTestIO()
io.SetErr(commands.WriteNopCloser(mockErr))

Expand Down Expand Up @@ -690,7 +690,7 @@ func TestBalances_GetBalancesFromTransactions(t *testing.T) {
marshalledTxs = append(marshalledTxs, string(marshalledTx))
}

mockErr := bytes.NewBufferString("")
mockErr := new(bytes.Buffer)
io := commands.NewTestIO()
io.SetErr(commands.WriteNopCloser(mockErr))

Expand Down
94 changes: 94 additions & 0 deletions gno.land/cmd/gnoland/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package main

import (
"flag"
"fmt"
"reflect"

"github.com/gnolang/gno/tm2/pkg/commands"
)

type configCfg struct {
configPath string
}

// newConfigCmd creates the config root command
func newConfigCmd(io commands.IO) *commands.Command {
cmd := commands.NewCommand(
commands.Metadata{
Name: "config",
ShortUsage: "config <subcommand> [flags]",
ShortHelp: "gno config manipulation suite",
LongHelp: "Gno config manipulation suite, for editing base and module configurations",
},
commands.NewEmptyConfig(),
commands.HelpExec,
)

cmd.AddSubCommands(
newConfigInitCmd(io),
newConfigSetCmd(io),
newConfigGetCmd(io),
)

return cmd
}

func (c *configCfg) RegisterFlags(fs *flag.FlagSet) {
fs.StringVar(
&c.configPath,
"config-path",
"./config.toml",
"the path for the config.toml",
)
}

// getFieldAtPath fetches the given field from the given path
func getFieldAtPath(currentValue reflect.Value, path []string) (*reflect.Value, error) {
// Look at the current section, and figure out if
// it's a part of the current struct
field := currentValue.FieldByName(path[0])
if !field.IsValid() || !field.CanSet() {
return nil, newInvalidFieldError(path[0], currentValue)
}

// Dereference the field if needed
if field.Kind() == reflect.Ptr {
field = field.Elem()
}

// Check if this is not the end of the path
// ex: x.y.field
if len(path) > 1 {
// Recursively try to traverse the path and return the given field
return getFieldAtPath(field, path[1:])
}

return &field, nil
}

// newInvalidFieldError creates an error for non-existent struct fields
// being passed as arguments to [getFieldAtPath]
func newInvalidFieldError(field string, value reflect.Value) error {
var (
valueType = value.Type()
numFields = value.NumField()
)

fields := make([]string, 0, numFields)

for i := 0; i < numFields; i++ {
valueField := valueType.Field(i)
if !valueField.IsExported() {
continue
}

fields = append(fields, valueField.Name)
}

return fmt.Errorf(
"field %q, is not a valid configuration key, available keys: %s",
field,
fields,
)
}
73 changes: 73 additions & 0 deletions gno.land/cmd/gnoland/config_get.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package main

import (
"context"
"errors"
"fmt"
"reflect"
"strings"

"github.com/gnolang/gno/tm2/pkg/bft/config"
"github.com/gnolang/gno/tm2/pkg/commands"
)

var errInvalidConfigGetArgs = errors.New("invalid number of config get arguments provided")

// newConfigGetCmd creates the config get command
func newConfigGetCmd(io commands.IO) *commands.Command {
cfg := &configCfg{}

cmd := commands.NewCommand(
commands.Metadata{
Name: "get",
ShortUsage: "config get <key>",
ShortHelp: "shows the Gno node configuration",
LongHelp: "Shows the Gno node configuration at the given path " +
"by fetching the option specified at <key>",
},
cfg,
func(_ context.Context, args []string) error {
return execConfigGet(cfg, io, args)
},
)

return cmd
}

func execConfigGet(cfg *configCfg, io commands.IO, args []string) error {
// Load the config
loadedCfg, err := config.LoadConfigFile(cfg.configPath)
if err != nil {
return fmt.Errorf("unable to load config, %w", err)
}

// Make sure the edit arguments are valid
if len(args) != 1 {
return errInvalidConfigGetArgs
}

// Find and print the config field, if any
if err := printConfigField(loadedCfg, args[0], io); err != nil {
return fmt.Errorf("unable to update config field, %w", err)
}

return nil
}

// printConfigField prints the value of the field at the given path
func printConfigField(config *config.Config, key string, io commands.IO) error {
// Get the config value using reflect
configValue := reflect.ValueOf(config).Elem()

// Get the value path, with sections separated out by a period
path := strings.Split(key, ".")

field, err := getFieldAtPath(configValue, path)
if err != nil {
return err
}

io.Printf("%v", field.Interface())

return nil
}
Loading

0 comments on commit bc77c2b

Please sign in to comment.