From dbb62c4cd45a04f6aa3e346fb20d50cef40e1c88 Mon Sep 17 00:00:00 2001 From: Will Winder Date: Thu, 9 Nov 2023 09:58:20 -0500 Subject: [PATCH 1/7] Add print option to algocfg. --- cmd/algocfg/profileCommand.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/cmd/algocfg/profileCommand.go b/cmd/algocfg/profileCommand.go index e629f2578a..d7f7086ec6 100644 --- a/cmd/algocfg/profileCommand.go +++ b/cmd/algocfg/profileCommand.go @@ -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) } @@ -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) + } + stdoutName := os.Stdout.Name() + err = codecs.SaveNonDefaultValuesToFile(stdoutName, cfg, config.GetDefaultLocal(), nil, true) + if err != nil { + reportErrorf("Error saving updated config file '%s' - %s", stdoutName, err) + } + }, +} + var setProfileCmd = &cobra.Command{ Use: "set", Short: "Set config.json file from a profile.", From 170c25f0499f746e3859f83e2d76ac69df9ddfac Mon Sep 17 00:00:00 2001 From: Will Winder Date: Thu, 9 Nov 2023 10:02:12 -0500 Subject: [PATCH 2/7] Add trailing newline. --- cmd/algocfg/profileCommand.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/algocfg/profileCommand.go b/cmd/algocfg/profileCommand.go index d7f7086ec6..328b33c2ff 100644 --- a/cmd/algocfg/profileCommand.go +++ b/cmd/algocfg/profileCommand.go @@ -148,6 +148,7 @@ var printProfileCmd = &cobra.Command{ if err != nil { reportErrorf("Error saving updated config file '%s' - %s", stdoutName, err) } + fmt.Fprintf(os.Stdout, "\n") }, } From c389f27b1d267b84237ded47a1c7714bc75cb9bc Mon Sep 17 00:00:00 2001 From: Will Winder Date: Thu, 9 Nov 2023 13:00:06 -0500 Subject: [PATCH 3/7] Avoid stdout hack. --- cmd/algocfg/profileCommand.go | 5 ++- util/codecs/json.go | 60 ++++++++++++++++------------------- 2 files changed, 30 insertions(+), 35 deletions(-) diff --git a/cmd/algocfg/profileCommand.go b/cmd/algocfg/profileCommand.go index 328b33c2ff..5991b7c1f9 100644 --- a/cmd/algocfg/profileCommand.go +++ b/cmd/algocfg/profileCommand.go @@ -143,10 +143,9 @@ var printProfileCmd = &cobra.Command{ if err != nil { reportErrorf("%v", err) } - stdoutName := os.Stdout.Name() - err = codecs.SaveNonDefaultValuesToFile(stdoutName, cfg, config.GetDefaultLocal(), nil, true) + err = codecs.WriteNonDefaultValues(os.Stdout, cfg, config.GetDefaultLocal(), nil, true) if err != nil { - reportErrorf("Error saving updated config file '%s' - %s", stdoutName, err) + reportErrorf("Error writing config file to stdout: %s", err) } fmt.Fprintf(os.Stdout, "\n") }, diff --git a/util/codecs/json.go b/util/codecs/json.go index e283ef0624..c23df6a911 100644 --- a/util/codecs/json.go +++ b/util/codecs/json.go @@ -18,6 +18,7 @@ package codecs import ( "bufio" + "bytes" "encoding/json" "fmt" "io" @@ -48,6 +49,16 @@ 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) @@ -55,22 +66,10 @@ func SaveObjectToFile(filename string, object interface{}, prettyFormat bool) er 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 -// 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, prettyFormat bool) 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 @@ -78,25 +77,13 @@ func SaveNonDefaultValuesToFile(filename string, object, defaultObject interface // 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 @@ -155,15 +142,24 @@ 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, prettyFormat bool) 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) + + WriteNonDefaultValues(writer, object, defaultObject, ignore, prettyFormat) if err == nil { writer.Flush() } From 222173ecedfc52723a0484522ad7ec0d951d61b8 Mon Sep 17 00:00:00 2001 From: Will Winder Date: Thu, 9 Nov 2023 13:10:26 -0500 Subject: [PATCH 4/7] Add godoc, remove unused parameter. --- cmd/algocfg/profileCommand.go | 4 ++-- cmd/algocfg/resetCommand.go | 2 +- cmd/algocfg/setCommand.go | 2 +- config/localTemplate.go | 2 +- util/codecs/json.go | 9 ++++++--- 5 files changed, 11 insertions(+), 8 deletions(-) diff --git a/cmd/algocfg/profileCommand.go b/cmd/algocfg/profileCommand.go index 5991b7c1f9..0a368fc975 100644 --- a/cmd/algocfg/profileCommand.go +++ b/cmd/algocfg/profileCommand.go @@ -143,7 +143,7 @@ var printProfileCmd = &cobra.Command{ if err != nil { reportErrorf("%v", err) } - err = codecs.WriteNonDefaultValues(os.Stdout, cfg, config.GetDefaultLocal(), nil, true) + err = codecs.WriteNonDefaultValues(os.Stdout, cfg, config.GetDefaultLocal(), nil) if err != nil { reportErrorf("Error writing config file to stdout: %s", err) } @@ -175,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) } diff --git a/cmd/algocfg/resetCommand.go b/cmd/algocfg/resetCommand.go index 24f9cf1dad..2ec8c55aad 100644 --- a/cmd/algocfg/resetCommand.go +++ b/cmd/algocfg/resetCommand.go @@ -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 diff --git a/cmd/algocfg/setCommand.go b/cmd/algocfg/setCommand.go index 8367857592..58f7ee796d 100644 --- a/cmd/algocfg/setCommand.go +++ b/cmd/algocfg/setCommand.go @@ -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 diff --git a/config/localTemplate.go b/config/localTemplate.go index 74c0b2e08a..c1fd87201b 100644 --- a/config/localTemplate.go +++ b/config/localTemplate.go @@ -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 diff --git a/util/codecs/json.go b/util/codecs/json.go index c23df6a911..f92556372a 100644 --- a/util/codecs/json.go +++ b/util/codecs/json.go @@ -69,7 +69,10 @@ func SaveObjectToFile(filename string, object interface{}, prettyFormat bool) er return writeBytes(f, object, prettyFormat) } -func WriteNonDefaultValues(writer io.Writer, object, defaultObject interface{}, ignore []string, prettyFormat bool) error { +// 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 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 @@ -151,7 +154,7 @@ func WriteNonDefaultValues(writer io.Writer, object, defaultObject interface{}, // 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, prettyFormat bool) error { +func SaveNonDefaultValuesToFile(filename string, object, defaultObject interface{}, ignore []string) error { outFile, err := os.Create(filename) if err != nil { return err @@ -159,7 +162,7 @@ func SaveNonDefaultValuesToFile(filename string, object, defaultObject interface defer outFile.Close() writer := bufio.NewWriter(outFile) - WriteNonDefaultValues(writer, object, defaultObject, ignore, prettyFormat) + WriteNonDefaultValues(writer, object, defaultObject, ignore) if err == nil { writer.Flush() } From e1d52ab1a0f8c466d89eab5fa17e08b8b200d531 Mon Sep 17 00:00:00 2001 From: Will Winder Date: Thu, 9 Nov 2023 13:48:12 -0500 Subject: [PATCH 5/7] Lint error --- util/codecs/json.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/util/codecs/json.go b/util/codecs/json.go index f92556372a..8c2cebf087 100644 --- a/util/codecs/json.go +++ b/util/codecs/json.go @@ -162,11 +162,13 @@ func SaveNonDefaultValuesToFile(filename string, object, defaultObject interface defer outFile.Close() writer := bufio.NewWriter(outFile) - WriteNonDefaultValues(writer, object, defaultObject, ignore) - 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) { From 231494e93d7202640793724fa48bd2fe2db0d46b Mon Sep 17 00:00:00 2001 From: Will Winder Date: Thu, 9 Nov 2023 17:55:18 -0500 Subject: [PATCH 6/7] Add some tests. --- util/codecs/json_test.go | 81 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 79 insertions(+), 2 deletions(-) diff --git a/util/codecs/json_test.go b/util/codecs/json_test.go index 1f56531971..10d813e043 100644 --- a/util/codecs/json_test.go +++ b/util/codecs/json_test.go @@ -17,9 +17,13 @@ package codecs import ( - "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" + "bytes" "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) type testValue struct { @@ -30,6 +34,7 @@ type testValue struct { func TestIsDefaultValue(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() a := require.New(t) @@ -52,3 +57,75 @@ func TestIsDefaultValue(t *testing.T) { a.False(isDefaultValue("Int", objectValues, defaultValues)) a.True(isDefaultValue("Missing", objectValues, defaultValues)) } + +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()) + }) + } +} From 6a7af221bb24fe3960a4663973f4ee84d8f851b5 Mon Sep 17 00:00:00 2001 From: Will Winder Date: Mon, 13 Nov 2023 13:40:10 -0500 Subject: [PATCH 7/7] Test SaveObjectToFile. --- util/codecs/json_test.go | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/util/codecs/json_test.go b/util/codecs/json_test.go index 10d813e043..6bd4d53cd0 100644 --- a/util/codecs/json_test.go +++ b/util/codecs/json_test.go @@ -18,6 +18,8 @@ package codecs import ( "bytes" + "os" + "path" "testing" "time" @@ -58,6 +60,44 @@ func TestIsDefaultValue(t *testing.T) { 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()