Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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 @@ -652,7 +652,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 {
Comment thread
gmalouf marked this conversation as resolved.
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 {
Comment thread
winder marked this conversation as resolved.
// 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()
Comment thread
winder marked this conversation as resolved.

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 {
Comment thread
gmalouf marked this conversation as resolved.
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())
})
}
}