Skip to content

Commit

Permalink
algocfg: Add print option to algocfg. (#5824)
Browse files Browse the repository at this point in the history
  • Loading branch information
winder authored Nov 13, 2023
1 parent 1bb78de commit 90b10d2
Show file tree
Hide file tree
Showing 6 changed files with 175 additions and 39 deletions.
20 changes: 19 additions & 1 deletion cmd/algocfg/profileCommand.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ func init() {
rootCmd.AddCommand(profileCmd)
profileCmd.AddCommand(setProfileCmd)
setProfileCmd.Flags().BoolVarP(&forceUpdate, "yes", "y", false, "Force updates to be written")
profileCmd.AddCommand(printProfileCmd)
profileCmd.AddCommand(listProfileCmd)
}

Expand Down Expand Up @@ -133,6 +134,23 @@ var listProfileCmd = &cobra.Command{
},
}

var printProfileCmd = &cobra.Command{
Use: "print",
Short: "Print config.json to stdout.",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
cfg, err := getConfigForArg(args[0])
if err != nil {
reportErrorf("%v", err)
}
err = codecs.WriteNonDefaultValues(os.Stdout, cfg, config.GetDefaultLocal(), nil)
if err != nil {
reportErrorf("Error writing config file to stdout: %s", err)
}
fmt.Fprintf(os.Stdout, "\n")
},
}

var setProfileCmd = &cobra.Command{
Use: "set",
Short: "Set config.json file from a profile.",
Expand All @@ -157,7 +175,7 @@ var setProfileCmd = &cobra.Command{
return
}
}
err = codecs.SaveNonDefaultValuesToFile(file, cfg, config.GetDefaultLocal(), nil, true)
err = codecs.SaveNonDefaultValuesToFile(file, cfg, config.GetDefaultLocal(), nil)
if err != nil {
reportErrorf("Error saving updated config file '%s' - %s", file, err)
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/algocfg/resetCommand.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ var resetCmd = &cobra.Command{
}

file := filepath.Join(dataDir, config.ConfigFilename)
err = codecs.SaveNonDefaultValuesToFile(file, cfg, defaults, nil, true)
err = codecs.SaveNonDefaultValuesToFile(file, cfg, defaults, nil)
if err != nil {
reportWarnf("Error saving updated config file '%s' - %s", file, err)
anyError = true
Expand Down
2 changes: 1 addition & 1 deletion cmd/algocfg/setCommand.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ var setCmd = &cobra.Command{
}

file := filepath.Join(dataDir, config.ConfigFilename)
err = codecs.SaveNonDefaultValuesToFile(file, cfg, config.GetDefaultLocal(), nil, true)
err = codecs.SaveNonDefaultValuesToFile(file, cfg, config.GetDefaultLocal(), nil)
if err != nil {
reportWarnf("Error saving updated config file '%s' - %s", file, err)
anyError = true
Expand Down
2 changes: 1 addition & 1 deletion config/localTemplate.go
Original file line number Diff line number Diff line change
Expand Up @@ -666,7 +666,7 @@ func (cfg Local) SaveAllToDisk(root string) error {
func (cfg Local) SaveToFile(filename string) error {
var alwaysInclude []string
alwaysInclude = append(alwaysInclude, "Version")
return codecs.SaveNonDefaultValuesToFile(filename, cfg, defaultLocal, alwaysInclude, true)
return codecs.SaveNonDefaultValuesToFile(filename, cfg, defaultLocal, alwaysInclude)
}

// DNSSecuritySRVEnforced returns true if SRV response verification enforced
Expand Down
67 changes: 34 additions & 33 deletions util/codecs/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package codecs

import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"io"
Expand Down Expand Up @@ -48,55 +49,44 @@ func LoadObjectFromFile(filename string, object interface{}) (err error) {
return
}

func writeBytes(writer io.Writer, object interface{}, prettyFormat bool) error {
var enc *json.Encoder
if prettyFormat {
enc = NewFormattedJSONEncoder(writer)
} else {
enc = json.NewEncoder(writer)
}
return enc.Encode(object)
}

// SaveObjectToFile implements the common pattern for saving an object to a file as json
func SaveObjectToFile(filename string, object interface{}, prettyFormat bool) error {
f, err := os.Create(filename)
if err != nil {
return err
}
defer f.Close()
var enc *json.Encoder
if prettyFormat {
enc = NewFormattedJSONEncoder(f)
} else {
enc = json.NewEncoder(f)
}
err = enc.Encode(object)
return err
return writeBytes(f, object, prettyFormat)
}

// SaveNonDefaultValuesToFile saves an object to a file as json, but only fields that are not
// WriteNonDefaultValues writes object to a writer as json, but only fields that are not
// currently set to be the default value.
// Optionally, you can specify an array of field names to always include.
func SaveNonDefaultValuesToFile(filename string, object, defaultObject interface{}, ignore []string, prettyFormat bool) error {
// Serialize object to temporary file.
// Read file into string array
func WriteNonDefaultValues(writer io.Writer, object, defaultObject interface{}, ignore []string) error {
// Iterate one line at a time, parse Name
// If ignore contains Name, don't delete
// Use reflection to compare object[Name].value == defaultObject[Name].value
// If same, delete line from array
// When done, ensure last value line doesn't include comma
// Write string array to file.

file, err := os.CreateTemp("", "encsndv")
if err != nil {
return err
}
name := file.Name()
file.Close()

defer os.Remove(name)
// Save object to file pretty-formatted so we can read one value-per-line
err = SaveObjectToFile(name, object, true)
var buf bytes.Buffer
err := writeBytes(&buf, object, true)
if err != nil {
return err
}
content := buf.Bytes()

// Read lines from encoded file into string array
content, err := os.ReadFile(name)
if err != nil {
return err
}
valueLines := strings.Split(string(content), "\n")

// Create maps of the name->value pairs for the object and the defaults
Expand Down Expand Up @@ -155,19 +145,30 @@ func SaveNonDefaultValuesToFile(filename string, object, defaultObject interface
}
}

combined := strings.Join(newFile, "\n")
combined = strings.TrimRight(combined, "\r\n ")
_, err = writer.Write([]byte(combined))
return err
}

// SaveNonDefaultValuesToFile saves an object to a file as json, but only fields that are not
// currently set to be the default value.
// Optionally, you can specify an array of field names to always include.
func SaveNonDefaultValuesToFile(filename string, object, defaultObject interface{}, ignore []string) error {
outFile, err := os.Create(filename)
if err != nil {
return err
}
defer outFile.Close()
writer := bufio.NewWriter(outFile)
combined := strings.Join(newFile, "\n")
combined = strings.TrimRight(combined, "\r\n ")
_, err = writer.WriteString(combined)
if err == nil {
writer.Flush()

err = WriteNonDefaultValues(writer, object, defaultObject, ignore)
if err != nil {
return err
}
return err

writer.Flush()
return nil
}

func extractValueName(line string) (name string) {
Expand Down
121 changes: 119 additions & 2 deletions util/codecs/json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,15 @@
package codecs

import (
"github.com/algorand/go-algorand/test/partitiontest"
"github.com/stretchr/testify/require"
"bytes"
"os"
"path"
"testing"
"time"

"github.com/stretchr/testify/require"

"github.com/algorand/go-algorand/test/partitiontest"
)

type testValue struct {
Expand All @@ -30,6 +36,7 @@ type testValue struct {

func TestIsDefaultValue(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()

a := require.New(t)

Expand All @@ -52,3 +59,113 @@ func TestIsDefaultValue(t *testing.T) {
a.False(isDefaultValue("Int", objectValues, defaultValues))
a.True(isDefaultValue("Missing", objectValues, defaultValues))
}

func TestSaveObjectToFile(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()

type TestType struct {
A uint64
B string
}

obj := TestType{1024, "test"}

// prettyFormat = false
{
filename := path.Join(t.TempDir(), "test.json")
SaveObjectToFile(filename, obj, false)
data, err := os.ReadFile(filename)
require.NoError(t, err)
expected := `{"A":1024,"B":"test"}
`
require.Equal(t, expected, string(data))
}

// prettyFormat = true
{
filename := path.Join(t.TempDir(), "test.json")
SaveObjectToFile(filename, obj, true)
data, err := os.ReadFile(filename)
require.NoError(t, err)
expected := `{
"A": 1024,
"B": "test"
}
`
require.Equal(t, expected, string(data))
}

}

func TestWriteNonDefaultValue(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()

type TestType struct {
Version uint32
Archival bool
GossipFanout int
NetAddress string
ReconnectTime time.Duration
}

defaultObject := TestType{
Version: 1,
Archival: true,
GossipFanout: 50,
NetAddress: "Denver",
ReconnectTime: 60 * time.Second,
}

testcases := []struct {
name string
in TestType
out string
ignore []string
}{
{
name: "all defaults",
in: defaultObject,
out: `{
}`,
}, {
name: "some defaults",
in: TestType{
Version: 1,
Archival: false,
GossipFanout: 25,
NetAddress: "Denver",
ReconnectTime: 60 * time.Nanosecond,
},
out: `{
"Archival": false,
"GossipFanout": 25,
"ReconnectTime": 60
}`,
}, {
name: "ignore",
in: defaultObject,
ignore: []string{"Version", "Archival", "GossipFanout", "NetAddress", "ReconnectTime"},
out: `{
"Version": 1,
"Archival": true,
"GossipFanout": 50,
"NetAddress": "Denver",
"ReconnectTime": 60000000000
}`,
},
}

for _, tc := range testcases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
a := require.New(t)
var writer bytes.Buffer
err := WriteNonDefaultValues(&writer, tc.in, defaultObject, tc.ignore)
a.NoError(err)
a.Equal(tc.out, writer.String())
})
}
}

0 comments on commit 90b10d2

Please sign in to comment.