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

feat: create config fix tool #14342

Merged
merged 32 commits into from
Jan 10, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
bc015b1
feat: create config fix tool
julienrbrt Dec 16, 2022
3760019
Merge branch 'main' into julien/config-fix
julienrbrt Jan 2, 2023
d3909d9
wip
julienrbrt Jan 4, 2023
6822b81
updates
julienrbrt Jan 4, 2023
fbe2f40
wip
julienrbrt Jan 4, 2023
b0fcfa9
Merge branch 'main' into julien/config-fix
julienrbrt Jan 4, 2023
52325e9
updates
julienrbrt Jan 5, 2023
c4130ff
update changelog
julienrbrt Jan 5, 2023
5588b8f
Merge branch 'main' into julien/config-fix
julienrbrt Jan 5, 2023
61924ff
add tests and diff command
julienrbrt Jan 5, 2023
34388a6
updates
julienrbrt Jan 5, 2023
81a8f52
renaming
julienrbrt Jan 5, 2023
25d24fc
updates
julienrbrt Jan 5, 2023
79eb103
wip
julienrbrt Jan 6, 2023
f2cabde
Merge branch 'main' into julien/config-fix
julienrbrt Jan 6, 2023
ee11e8b
naming
julienrbrt Jan 6, 2023
a98c4ee
store value in diff
julienrbrt Jan 6, 2023
77be207
finish migrate (only comments addition missing)
julienrbrt Jan 6, 2023
14210fa
Merge branch 'main' into julien/config-fix
julienrbrt Jan 8, 2023
1dfd460
add section comments
julienrbrt Jan 8, 2023
c425738
parse comments from diff
julienrbrt Jan 8, 2023
1643cc7
remove dep and imp tests
julienrbrt Jan 9, 2023
86fd670
Merge branch 'main' into julien/config-fix
julienrbrt Jan 9, 2023
598817d
chore: add docs
julienrbrt Jan 9, 2023
d6f21ca
fix tests
julienrbrt Jan 9, 2023
942ae4e
fix tests
julienrbrt Jan 9, 2023
de3794d
Merge branch 'main' into julien/config-fix
julienrbrt Jan 9, 2023
bda3028
Merge branch 'main' into julien/config-fix
julienrbrt Jan 9, 2023
a6bac20
use tagged version
julienrbrt Jan 9, 2023
a99937f
Merge branch 'main' into julien/config-fix
julienrbrt Jan 10, 2023
d87310d
go mod tidy all
julienrbrt Jan 10, 2023
4e19b7f
go mod tidy
julienrbrt Jan 10, 2023
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
Prev Previous commit
Next Next commit
wip
  • Loading branch information
julienrbrt committed Jan 6, 2023
commit 79eb103e9c90857a7a8678e75e0d9f19c47fcc50
13 changes: 13 additions & 0 deletions tools/confix/cmd/condiff/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package main

import (
"os"

confixcmd "cosmossdk.io/tools/confix/cmd"
)

func main() {
if err := confixcmd.DiffCommand().Execute(); err != nil {
os.Exit(1)
}
}
2 changes: 0 additions & 2 deletions tools/confix/cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,10 @@ func ConfigCommand() *cobra.Command {
Short: "Utilities for managing application configuration",
}

// add subcommands
cmd.AddCommand(
MigrateCommand(),
GetCommand(),
SetCommand(),
DiffCommand(),
)

return cmd
Expand Down
123 changes: 15 additions & 108 deletions tools/confix/cmd/diff.go
Original file line number Diff line number Diff line change
@@ -1,22 +1,13 @@
package cmd

import (
"fmt"
"io"
"log"
"os"
"sort"
"strings"

"github.com/creachadair/tomledit"
"github.com/creachadair/tomledit/parser"
"github.com/creachadair/tomledit/transform"
"cosmossdk.io/tools/confix"
"github.com/spf13/cobra"
)

func DiffCommand() *cobra.Command {
return &cobra.Command{
Use: "diff f1 f2",
Use: "condiff f1 f2",
Short: "Diff the keyspaces of the TOML documents in files f1 and f2",
Long: `Diff the keyspaces of the TOML documents in files f1 and f2.
The output prints one line per key that differs:
Expand All @@ -28,103 +19,19 @@ The output prints one line per key that differs:

Comments, order, and values are ignored for comparison purposes.`,
Args: cobra.ExactArgs(2),
Run: func(cmd *cobra.Command, args []string) {
lhs := mustParse(args[0])
rhs := mustParse(args[1])
diffDocs(os.Stdout, lhs, rhs)
RunE: func(cmd *cobra.Command, args []string) error {
lhs, err := confix.LoadConfig(args[0])
if err != nil {
return err
}

rhs, err := confix.LoadConfig(args[1])
if err != nil {
return err
}

confix.PrintDiff(cmd.OutOrStdout(), confix.DiffDocs(lhs, rhs))
return nil
},
}
}

func mustParse(path string) *tomledit.Document {
f, err := os.Open(path)
if err != nil {
log.Fatalf("Opening TOML input: %v", err)
}
defer f.Close()
doc, err := tomledit.Parse(f)
if err != nil {
log.Fatalf("Parsing %q: %v", path, err)
}
return doc
}

func allKeys(s *tomledit.Section) []string {
var keys []string
s.Scan(func(key parser.Key, _ *tomledit.Entry) bool {
keys = append(keys, key.String())
return true
})
return keys
}

const (
delSection = "-S"
delMapping = "-M"
addSection = "+S"
addMapping = "+M"

delMapSep = "\n" + delMapping + " "
addMapSep = "\n" + addMapping + " "
)

func diffDocs(w io.Writer, lhs, rhs *tomledit.Document) {
diffSections(w, lhs.Global, rhs.Global)
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) {
fmt.Fprintln(w, delSection, lsec[i].Name)
fmt.Fprintln(w, delMapping, strings.Join(allKeys(lsec[i]), delMapSep))
i++
} else if rsec[j].Name.Before(lsec[i].Name) {
fmt.Fprintln(w, addSection, rsec[j].Name)
fmt.Fprintln(w, addMapping, strings.Join(allKeys(rsec[j]), addMapSep))
j++
} else {
diffSections(w, lsec[i], rsec[j])
i++
j++
}
}
for ; i < len(lsec); i++ {
fmt.Fprintln(w, delSection, lsec[i].Name)
fmt.Fprintln(w, delMapping, strings.Join(allKeys(lsec[i]), delMapSep))
}
for ; j < len(rsec); j++ {
fmt.Fprintln(w, addSection, rsec[j].Name)
fmt.Fprintln(w, addMapping, strings.Join(allKeys(rsec[j]), addMapSep))
}
}

func diffSections(w io.Writer, lhs, rhs *tomledit.Section) {
diffKeys(w, allKeys(lhs), allKeys(rhs))
}

func diffKeys(w io.Writer, lhs, rhs []string) {
sort.Strings(lhs)
sort.Strings(rhs)

i, j := 0, 0
for i < len(lhs) && j < len(rhs) {
if lhs[i] < rhs[j] {
fmt.Fprintln(w, delMapping, lhs[i])
i++
} else if lhs[i] > rhs[j] {
fmt.Fprintln(w, addMapping, rhs[j])
j++
} else {
i++
j++
}
}
for ; i < len(lhs); i++ {
fmt.Fprintln(w, delMapping, lhs[i])
}
for ; j < len(rhs); j++ {
fmt.Fprintln(w, addMapping, rhs[j])
}
}
20 changes: 12 additions & 8 deletions tools/confix/cmd/migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package cmd
import (
"context"
"fmt"
"log"
"os"

"cosmossdk.io/tools/confix"
Expand All @@ -20,7 +19,7 @@ var (

func MigrateCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "migrate [target-version] [app-toml-path] (options)",
Use: "migrate [target-version] <app-toml-path> (options)",
Short: "Migrate Cosmos SDK app configuration file to the specified version",
Long: `Migrate the contents of the Cosmos SDK app configuration (app.toml) to the specified version.
The output is written in-place unless --stdout is provided.
Expand All @@ -31,10 +30,10 @@ In case of any error in updating the file, no output is written.`,
targetVersion := args[0]

clientCtx := client.GetClientContextFromCmd(cmd)
if clientCtx.HomeDir != "" {
filename = fmt.Sprintf("%s/config/app.toml", clientCtx.HomeDir)
} else if len(args) > 1 {
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")
}
Expand All @@ -44,6 +43,11 @@ In case of any error in updating the file, no output is written.`,
return fmt.Errorf("unknown version %q, supported versions are: %q", targetVersion, maps.Keys(confix.Migrations))
}

rawFile, err := confix.LoadConfig(filename)
if err != nil {
return fmt.Errorf("failed to load config: %v", err)
}

ctx := context.Background()
if FlagVerbose {
ctx = confix.WithLogWriter(ctx, os.Stderr)
Expand All @@ -54,8 +58,8 @@ In case of any error in updating the file, no output is written.`,
outputPath = ""
}

if err := confix.Upgrade(ctx, plan(filename, targetVersion), filename, outputPath, FlagSkipValidate); err != nil {
log.Fatalf("Failed to migrate config: %v", err)
if err := confix.Upgrade(ctx, plan(rawFile, targetVersion), filename, outputPath, FlagSkipValidate); err != nil {
return fmt.Errorf("failed to migrate config: %w", err)
}

return nil
Expand All @@ -64,7 +68,7 @@ In case of any error in updating the file, no output is written.`,

cmd.Flags().BoolVar(&FlagStdOut, "stdout", false, "print the updated config to stdout")
cmd.Flags().BoolVar(&FlagVerbose, "verbose", false, "log changes to stderr")
cmd.Flags().BoolVar(&FlagSkipValidate, "skip-validate", false, "skip configuration validation (allows to mutate unknown configurations)")
cmd.Flags().BoolVar(&FlagSkipValidate, "skip-validate", false, "skip configuration validation (allows to migrate unknown configurations)")

return cmd
}
30 changes: 29 additions & 1 deletion tools/confix/cmd/migrate_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,35 @@
package cmd_test

import "testing"
import (
"testing"

"cosmossdk.io/tools/confix/cmd"
"github.com/cosmos/cosmos-sdk/client"
clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli"
"gotest.tools/v3/assert"
)

func TestMigradeCmd(t *testing.T) {
clientCtx, cleanup := initClientContext(t)
defer func() {
cleanup()
}()

_, err := clitestutil.ExecTestCLICmd(client.Context{}, cmd.MigrateCommand(), []string{"v0.0"})
assert.ErrorContains(t, err, "must provide a path to the app.toml file")

_, err = clitestutil.ExecTestCLICmd(clientCtx, cmd.MigrateCommand(), []string{"v0.0"})
assert.ErrorContains(t, err, "unknown version")

// clientCtx does not create app.toml, so this should fail
_, err = clitestutil.ExecTestCLICmd(clientCtx, cmd.MigrateCommand(), []string{"v0.45"})
assert.ErrorContains(t, err, "no such file or directory")

// // try to migrate from client.toml it should fail without --skip-validate
// _, err = clitestutil.ExecTestCLICmd(clientCtx, cmd.MigrateCommand(), []string{"v0.45", fmt.Sprintf("%s/config/client.toml", t.TempDir())})
// assert.ErrorContains(t, err, "failed to migrate config")

// // try to migrate from client.toml - it should work and give us a big diff
// _, err = clitestutil.ExecTestCLICmd(clientCtx, cmd.MigrateCommand(), []string{"v0.45", fmt.Sprintf("%s/config/client.toml", t.TempDir()), "--skip-validate"})
// assert.NilError(t, err)
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
127 changes: 127 additions & 0 deletions tools/confix/diff.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package confix

import (
"fmt"
"io"
"sort"

"github.com/creachadair/tomledit"
"github.com/creachadair/tomledit/parser"
"github.com/creachadair/tomledit/transform"
)

const (
Section = "S"
Mapping = "M"
)

type Diff struct {
Type string // "section" or "mapping"
Deleted bool

Key string
// TODO store value change as well
Value string
}

// DiffDocs diffs the keyspaces of the TOML documents in files lhs and rhs.
// Comments, order, and values are ignored for comparison purposes.
func DiffDocs(lhs, rhs *tomledit.Document) []Diff {
diff := diffSections(lhs.Global, rhs.Global)

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) {
diff = append(diff, Diff{Type: Section, Deleted: true, Key: lsec[i].Name.String()})
for _, key := range allKeys(lsec[i]) {
diff = append(diff, Diff{Type: Mapping, Deleted: true, Key: key})
}
i++
} else if rsec[j].Name.Before(lsec[i].Name) {
diff = append(diff, Diff{Type: Section, Key: rsec[j].Name.String()})
for _, key := range allKeys(rsec[j]) {
diff = append(diff, Diff{Type: Mapping, Key: key})
}
j++
} else {
diff = append(diff, diffSections(lsec[i], rsec[j])...)
i++
j++
}
}
for ; i < len(lsec); i++ {
diff = append(diff, Diff{Type: Section, Deleted: true, Key: lsec[i].Name.String()})
for _, key := range allKeys(lsec[i]) {
diff = append(diff, Diff{Type: Mapping, Deleted: true, Key: key})
}
}
for ; j < len(rsec); j++ {
diff = append(diff, Diff{Type: Section, Key: rsec[j].Name.String()})
for _, key := range allKeys(rsec[j]) {
diff = append(diff, Diff{Type: Mapping, Key: key})
}
}

return diff
}

func allKeys(s *tomledit.Section) []string {
var keys []string
s.Scan(func(key parser.Key, _ *tomledit.Entry) bool {
keys = append(keys, key.String())
return true
})
return keys
}

func diffSections(lhs, rhs *tomledit.Section) []Diff {
return diffKeys(allKeys(lhs), allKeys(rhs))
}

func diffKeys(lhs, rhs []string) []Diff {
diff := []Diff{}

sort.Strings(lhs)
sort.Strings(rhs)

i, j := 0, 0
for i < len(lhs) && j < len(rhs) {
if lhs[i] < rhs[j] {
diff = append(diff, Diff{Type: Mapping, Deleted: true, Key: lhs[i]})
i++
} else if lhs[i] > rhs[j] {
diff = append(diff, Diff{Type: Mapping, Key: rhs[j]})
j++
} else {
i++
j++
}
}
for ; i < len(lhs); i++ {
diff = append(diff, Diff{Type: Mapping, Deleted: true, Key: lhs[i]})
}
for ; j < len(rhs); j++ {
diff = append(diff, Diff{Type: Mapping, Key: rhs[j]})
}

return diff
}

// PrintDiff output prints one line per key that differs:
// -S name -- section exists in lhs but not rhs
// +S name -- section exists in rhs but not lhs
// -M name -- mapping exists in lhs but not f2
// +M name -- mapping exists in rhs but not lhs
func PrintDiff(w io.Writer, diffs []Diff) {
for _, diff := range diffs {
if diff.Deleted {
fmt.Fprintln(w, fmt.Sprintf("-%s", diff.Type), diff.Key)
} else {
fmt.Fprintln(w, fmt.Sprintf("+%s", diff.Type), diff.Key)
}
}
}
Loading