feat: add diff and home command in confix (#14568)

This commit is contained in:
Julien Robert 2023-01-16 14:27:41 +01:00 committed by GitHub
parent f2b6013cee
commit e88a084840
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 209 additions and 41 deletions

View File

@ -185,6 +185,8 @@ require (
)
replace (
// TODO delete after release of confix
cosmossdk.io/tools/confix => ../tools/confix
github.com/99designs/keyring => github.com/cosmos/keyring v1.2.0
// Simapp always use the latest version of the cosmos-sdk
github.com/cosmos/cosmos-sdk => ../.

View File

@ -31,4 +31,5 @@ Ref: https://keepachangelog.com/en/1.0.0/
## [Unreleased]
* [#14568](https://github.com/cosmos/cosmos-sdk/pull/14568) Add `diff` and `home` commands.
* [#14342](https://github.com/cosmos/cosmos-sdk/pull/14342) Add `confix` tool to manage configuration files.

View File

@ -10,6 +10,50 @@ It is based on the [Tendermint RFC 019](https://github.com/tendermint/tendermint
## Installation
### Add Config Command
To add the confix tool, it's required to add the `ConfigCommand` to your application's root command file (e.g. `simd/cmd/root.go`).
Import the `confixCmd` package:
```go
import "cosmossdk.io/tools/confix/cmd"
```
Find the following line:
```go
initRootCmd(rootCmd, encodingConfig)
```
After that line, add the following:
```go
rootCmd.AddCommand(
confixcmd.ConfigCommand(),
)
```
The `ConfixCommand` function builds the `config` root command and is defined in the `confixCmd` package (`cosmossdk.io/tools/confix/cmd`).
An implementation example can be found in `simapp`.
The command will be available as `simd config`.
### Using Confix Standalone
To use Confix standalone, without having to add it in your application, install it with the following command:
```bash
go install cosmossdk.io/tools/confix/cmd/confix@latest
```
:::warning
Currently, due to the replace directive in the Confix go.mod, it is not possible to use `go install`.
Building from source or importing in an application is required until that replace directive is removed.
:::
Alternatively, for building from source, simply run `make confix`. The binary will be located in `tools/confix`.
## Usage
Use standalone:
@ -64,6 +108,18 @@ simd config migrate v0.47 # migrates defaultHome/config/app.toml to the latest v
confix migrate v0.47 ~/.simapp/config/app.toml # migrate ~/.simapp/config/app.toml to the latest v0.47 config
```
### Diff
Get the diff between a given configuration file and the default configuration file, e.g.:
```shell
simd config diff v0.47 # gets the diff between defaultHome/config/app.toml and the latest v0.47 config
```
```shell
confix diff v0.47 ~/.simapp/config/app.toml # gets the diff between ~/.simapp/config/app.toml and the latest v0.47 config
```
### Maintainer
At each SDK modification of the default configuration, add the default SDK config under `data/v0.XX-app.toml`.

View File

@ -17,6 +17,7 @@ func ConfigCommand() *cobra.Command {
DiffCommand(),
GetCommand(),
SetCommand(),
HomeCommand(),
)
return cmd

View File

@ -1,16 +1,55 @@
package cmd
import (
"fmt"
"cosmossdk.io/tools/confix"
"github.com/cosmos/cosmos-sdk/client"
"github.com/spf13/cobra"
"golang.org/x/exp/maps"
)
func DiffCommand() *cobra.Command {
return &cobra.Command{
Use: "diff [config]",
Short: "Display the diff between the current config and the SDK default config",
Args: cobra.ExactArgs(2),
Use: "diff [target-version] <app-toml-path>",
Short: "Outputs all config values that are different from the app.toml defaults.",
Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
// TODO to implement in the next PR
var filename string
clientCtx := client.GetClientContextFromCmd(cmd)
if len(args) > 1 {
filename = args[1]
} else if clientCtx.HomeDir != "" {
filename = fmt.Sprintf("%s/config/app.toml", clientCtx.HomeDir)
} else {
return fmt.Errorf("must provide a path to the app.toml file")
}
targetVersion := args[0]
if _, ok := confix.Migrations[targetVersion]; !ok {
return fmt.Errorf("unknown version %q, supported versions are: %q", targetVersion, maps.Keys(confix.Migrations))
}
targetVersionFile, err := confix.LoadLocalConfig(targetVersion)
if err != nil {
panic(fmt.Errorf("failed to load internal config: %w", err))
}
rawFile, err := confix.LoadConfig(filename)
if err != nil {
return fmt.Errorf("failed to load config: %v", err)
}
diff := confix.DiffValues(rawFile, targetVersionFile)
if len(diff) == 0 {
return clientCtx.PrintString("All config values are the same as the defaults.\n")
}
if err := clientCtx.PrintString("The following config values are different from the defaults:\n"); err != nil {
return err
}
confix.PrintDiff(cmd.OutOrStdout(), diff)
return nil
},
}

22
tools/confix/cmd/home.go Normal file
View File

@ -0,0 +1,22 @@
package cmd
import (
"github.com/cosmos/cosmos-sdk/client"
"github.com/spf13/cobra"
)
func HomeCommand() *cobra.Command {
return &cobra.Command{
Use: "home",
Short: "Outputs the string being used as the home path. No home directory is set when using the tool standalone.",
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
clientCtx := client.GetClientContextFromCmd(cmd)
if clientCtx.HomeDir == "" {
cmd.Println("No home directory set.")
} else {
cmd.Println(clientCtx.HomeDir)
}
},
}
}

View File

@ -26,8 +26,6 @@ In case of any error in updating the file, no output is written.`,
Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
var filename string
targetVersion := args[0]
clientCtx := client.GetClientContextFromCmd(cmd)
if len(args) > 1 {
filename = args[1]
@ -37,6 +35,7 @@ In case of any error in updating the file, no output is written.`,
return fmt.Errorf("must provide a path to the app.toml file")
}
targetVersion := args[0]
plan, ok := confix.Migrations[targetVersion]
if !ok {
return fmt.Errorf("unknown version %q, supported versions are: %q", targetVersion, maps.Keys(confix.Migrations))

View File

@ -20,7 +20,7 @@ func SetCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "set [config] [key] [value]",
Short: "Set an application config value",
Long: "Set an application config value. The [config] argument must be the path of the file when using the too standalone, otherwise it must be the name of the config file without the .toml extension.",
Long: "Set an application config value. The [config] argument must be the path of the file when using the tool standalone, otherwise it must be the name of the config file without the .toml extension.",
Args: cobra.ExactArgs(3),
RunE: func(cmd *cobra.Command, args []string) error {
filename, inputValue := args[0], args[2]

View File

@ -31,7 +31,8 @@ type Diff struct {
// DiffKeys diffs the keyspaces of the TOML documents in files lhs and rhs.
// Comments, order, and values are ignored for comparison purposes.
func DiffKeys(lhs, rhs *tomledit.Document) []Diff {
diff := diffSections(lhs.Global, rhs.Global)
// diff sections
diff := diffDocs(allKVs(lhs.Global), allKVs(rhs.Global), false)
lsec, rsec := lhs.Sections, rhs.Sections
transform.SortSectionsByName(lsec)
@ -52,7 +53,7 @@ func DiffKeys(lhs, rhs *tomledit.Document) []Diff {
}
j++
} else {
diff = append(diff, diffSections(lsec[i], rsec[j])...)
diff = append(diff, diffDocs(allKVs(lsec[i]), allKVs(rsec[j]), false)...)
i++
j++
}
@ -73,11 +74,42 @@ func DiffKeys(lhs, rhs *tomledit.Document) []Diff {
return diff
}
// DiffKeys diffs the keyspaces with different values of the TOML documents in files lhs and rhs.
func DiffValues(lhs, rhs *tomledit.Document) []Diff {
diff := diffDocs(allKVs(lhs.Global), allKVs(rhs.Global), true)
lsec, rsec := lhs.Sections, rhs.Sections
transform.SortSectionsByName(lsec)
transform.SortSectionsByName(rsec)
i, j := 0, 0
for i < len(lsec) && j < len(rsec) {
if lsec[i].Name.Before(rsec[j].Name) {
// skip keys present in lhs but not in rhs
i++
} else if rsec[j].Name.Before(lsec[i].Name) {
// skip keys present in rhs but not in lhs
j++
} else {
for _, d := range diffDocs(allKVs(lsec[i]), allKVs(rsec[j]), true) {
if !d.Deleted {
diff = append(diff, d)
}
}
i++
j++
}
}
return diff
}
func allKVs(s *tomledit.Section) []KV {
keys := []KV{}
s.Scan(func(key parser.Key, entry *tomledit.Entry) bool {
keys = append(keys, KV{
Key: key.String(),
Key: key.String(),
// we get the value of the current configuration (i.e the one we want to compare/migrate)
Value: entry.Value.String(),
Block: entry.Block,
})
@ -87,11 +119,9 @@ func allKVs(s *tomledit.Section) []KV {
return keys
}
func diffSections(lhs, rhs *tomledit.Section) []Diff {
return diffKeys(allKVs(lhs), allKVs(rhs))
}
func diffKeys(lhs, rhs []KV) []Diff {
// diffDocs get the diff between all keys in lhs and rhs.
// when a key is in both lhs and rhs, it is ignored, unless value is true in which case the value is as well compared.
func diffDocs(lhs, rhs []KV, value bool) []Diff {
diff := []Diff{}
sort.Slice(lhs, func(i, j int) bool {
@ -110,6 +140,11 @@ func diffKeys(lhs, rhs []KV) []Diff {
diff = append(diff, Diff{Type: Mapping, KV: rhs[j]})
j++
} else {
// key exists in both lhs and rhs
// if value is true, compare the values
if value && lhs[i].Value != rhs[j].Value {
diff = append(diff, Diff{Type: Mapping, KV: lhs[i]})
}
i++
j++
}

34
tools/confix/file.go Normal file
View File

@ -0,0 +1,34 @@
package confix
import (
"embed"
"fmt"
"os"
"github.com/creachadair/tomledit"
)
//go:embed data
var data embed.FS
// LoadConfig loads and parses the TOML document from confix data
func LoadLocalConfig(name string) (*tomledit.Document, error) {
f, err := data.Open(fmt.Sprintf("data/%s-app.toml", name))
if err != nil {
panic(fmt.Errorf("failed to read file: %w. This file should have been included in confix", err))
}
defer f.Close()
return tomledit.Parse(f)
}
// LoadConfig loads and parses the TOML document from path.
func LoadConfig(path string) (*tomledit.Document, error) {
f, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("failed to open %q: %v", path, err)
}
defer f.Close()
return tomledit.Parse(f)
}

View File

@ -2,7 +2,6 @@ package confix
import (
"context"
"embed"
"fmt"
"strings"
@ -28,27 +27,18 @@ var (
// "v0.47.x": PlanBuilder, // add specific migration in case of configuration changes in minor versions
// "v0.48": PlanBuilder,
}
//go:embed data
data embed.FS
)
// PlanBuilder is a function that returns a transformation plan for a given diff between two files.
func PlanBuilder(from *tomledit.Document, to string) transform.Plan {
plan := transform.Plan{}
deletedSections := map[string]bool{}
file, err := data.Open(fmt.Sprintf("data/%s-app.toml", to))
if err != nil {
panic(fmt.Errorf("failed to read file: %w. This file should have been included in confix", err))
}
target, err := tomledit.Parse(file)
target, err := LoadLocalConfig(to)
if err != nil {
panic(fmt.Errorf("failed to parse file: %w. This file should have been valid", err))
}
deletedSections := map[string]bool{}
diffs := DiffKeys(from, target)
for _, diff := range diffs {
diff := diff

View File

@ -65,17 +65,6 @@ func Upgrade(ctx context.Context, plan transform.Plan, configPath, outputPath st
return err
}
// LoadConfig loads and parses the TOML document from path.
func LoadConfig(path string) (*tomledit.Document, error) {
f, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("failed to open %q: %v", path, err)
}
defer f.Close()
return tomledit.Parse(f)
}
// CheckValid checks whether the specified config appears to be a valid Cosmos SDK config file.
// It tries to unmarshal the config into both the server and client config structs.
func CheckValid(fileName string, data []byte) error {

View File

@ -266,9 +266,9 @@ Clean `~/.simapp` (never do this in a production environment):
Set up app config:
```shell
./build/simd config chain-id test
./build/simd config keyring-backend test
./build/simd config broadcast-mode sync
./build/simd config set client chain-id test
./build/simd config set client keyring-backend test
./build/simd config set client broadcast-mode sync
```
Initialize the node and overwrite any previous genesis file (never do this in a production environment):