diff --git a/.changelog/4266.feature.1.md b/.changelog/4266.feature.1.md new file mode 100644 index 00000000000..2d80103b542 --- /dev/null +++ b/.changelog/4266.feature.1.md @@ -0,0 +1 @@ +go/oasis-node/cmd: Use pretty-printed variant when outputting JSON diff --git a/.changelog/4266.feature.2.md b/.changelog/4266.feature.2.md new file mode 100644 index 00000000000..32c708d24ef --- /dev/null +++ b/.changelog/4266.feature.2.md @@ -0,0 +1 @@ +go/common/entity: Use pretty-printed JSON when saving entity to file diff --git a/.changelog/4266.internal.md b/.changelog/4266.internal.md new file mode 100644 index 00000000000..994f815db1c --- /dev/null +++ b/.changelog/4266.internal.md @@ -0,0 +1 @@ +go/oasis-node/cmd/common: Add `PrettyJSONMarshal()` helper diff --git a/go/common/entity/entity.go b/go/common/entity/entity.go index 76657afbc0f..8c4b2688b88 100644 --- a/go/common/entity/entity.go +++ b/go/common/entity/entity.go @@ -129,7 +129,7 @@ func (e *Entity) Save(baseDir string) error { entityPath := filepath.Join(baseDir, entityFilename) // Write to disk. - b, err := json.Marshal(e) + b, err := json.MarshalIndent(e, "", " ") if err != nil { return err } diff --git a/go/oasis-node/cmd/common/consensus/consensus.go b/go/oasis-node/cmd/common/consensus/consensus.go index 75ff4811761..40abdb50ab9 100644 --- a/go/oasis-node/cmd/common/consensus/consensus.go +++ b/go/oasis-node/cmd/common/consensus/consensus.go @@ -3,7 +3,6 @@ package consensus import ( "context" - "encoding/json" "fmt" "io/ioutil" "os" @@ -142,15 +141,15 @@ func SignAndSaveTx(ctx context.Context, tx *transaction.Transaction, signer sign os.Exit(1) } - rawTx, err := json.Marshal(sigTx) + prettySigTx, err := cmdCommon.PrettyJSONMarshal(sigTx) if err != nil { - logger.Error("failed to marshal transaction", + logger.Error("failed to get pretty JSON of signed transaction", "err", err, ) os.Exit(1) } - if err = ioutil.WriteFile(viper.GetString(CfgTxFile), rawTx, 0o600); err != nil { - logger.Error("failed to save transaction", + if err = ioutil.WriteFile(viper.GetString(CfgTxFile), prettySigTx, 0o600); err != nil { + logger.Error("failed to save signed transaction", "err", err, ) os.Exit(1) diff --git a/go/oasis-node/cmd/common/json.go b/go/oasis-node/cmd/common/json.go new file mode 100644 index 00000000000..1a07ad97996 --- /dev/null +++ b/go/oasis-node/cmd/common/json.go @@ -0,0 +1,15 @@ +package common + +import ( + "encoding/json" + "fmt" +) + +// PrettyJSONMarshal returns pretty-printed JSON encoding of v. +func PrettyJSONMarshal(v interface{}) ([]byte, error) { + formatted, err := json.MarshalIndent(v, "", " ") + if err != nil { + return nil, fmt.Errorf("failed to marshal to pretty JSON: %w", err) + } + return formatted, nil +} diff --git a/go/oasis-node/cmd/control/control.go b/go/oasis-node/cmd/control/control.go index c4e8942a5c8..d26e1c345e4 100644 --- a/go/oasis-node/cmd/control/control.go +++ b/go/oasis-node/cmd/control/control.go @@ -220,14 +220,14 @@ func doStatus(cmd *cobra.Command, args []string) { ) os.Exit(128) } - formatted, err := json.MarshalIndent(status, "", " ") + prettyStatus, err := cmdCommon.PrettyJSONMarshal(status) if err != nil { - logger.Error("failed to format status", + logger.Error("failed to get pretty JSON of node status", "err", err, ) os.Exit(1) } - fmt.Println(string(formatted)) + fmt.Println(string(prettyStatus)) } // Register registers the client sub-command and all of it's children. diff --git a/go/oasis-node/cmd/debug/beacon/beacon.go b/go/oasis-node/cmd/debug/beacon/beacon.go index eaecd0a0783..a9c009b17b9 100644 --- a/go/oasis-node/cmd/debug/beacon/beacon.go +++ b/go/oasis-node/cmd/debug/beacon/beacon.go @@ -3,7 +3,6 @@ package beacon import ( "context" - "encoding/json" "fmt" "os" @@ -84,14 +83,14 @@ func doBeaconStatus(cmd *cobra.Command, args []string) { } } - formatted, err := json.MarshalIndent(prettyOut, "", " ") + prettyJSON, err := cmdCommon.PrettyJSONMarshal(prettyOut) if err != nil { - logger.Error("failed to format state", + logger.Error("failed to get pretty JSON of beacon state", "err", err, ) os.Exit(1) } - fmt.Println(string(formatted)) + fmt.Println(string(prettyJSON)) } // Register registers the beacon sub-command and all of it's children. diff --git a/go/oasis-node/cmd/debug/dumpdb/dumpdb.go b/go/oasis-node/cmd/debug/dumpdb/dumpdb.go index 4bf542a9045..f7550f6ca62 100644 --- a/go/oasis-node/cmd/debug/dumpdb/dumpdb.go +++ b/go/oasis-node/cmd/debug/dumpdb/dumpdb.go @@ -3,7 +3,6 @@ package dumpdb import ( "context" - "encoding/json" "fmt" "os" "path/filepath" @@ -254,14 +253,14 @@ func doDumpDB(cmd *cobra.Command, args []string) { if shouldClose { defer w.Close() } - raw, err := json.Marshal(doc) + prettyDoc, err := cmdCommon.PrettyJSONMarshal(doc) if err != nil { logger.Error("failed to marshal state dump into JSON", "err", err, ) return } - if _, err := w.Write(raw); err != nil { + if _, err := w.Write(prettyDoc); err != nil { logger.Error("failed to write state dump file", "err", err, ) diff --git a/go/oasis-node/cmd/governance/governance.go b/go/oasis-node/cmd/governance/governance.go index 3408f305fd8..ec8c18f952a 100644 --- a/go/oasis-node/cmd/governance/governance.go +++ b/go/oasis-node/cmd/governance/governance.go @@ -198,14 +198,20 @@ func doProposalInfo(cmd *cobra.Command, args []string) { defer conn.Close() ctx := context.Background() - p, err := client.Proposal(ctx, &governance.ProposalQuery{Height: consensus.HeightLatest, ProposalID: id}) + proposal, err := client.Proposal(ctx, &governance.ProposalQuery{Height: consensus.HeightLatest, ProposalID: id}) if err != nil { logger.Error("error querying proposal", "err", err) os.Exit(1) } - o, _ := json.Marshal(p) - fmt.Println(string(o)) + prettyProposal, err := cmdCommon.PrettyJSONMarshal(proposal) + if err != nil { + logger.Error("failed to get pretty JSON of proposal", + "err", err, + ) + os.Exit(1) + } + fmt.Println(string(prettyProposal)) } func doProposalVotes(cmd *cobra.Command, args []string) { @@ -229,8 +235,14 @@ func doProposalVotes(cmd *cobra.Command, args []string) { os.Exit(1) } - o, _ := json.Marshal(votes) - fmt.Println(string(o)) + prettyVotes, err := cmdCommon.PrettyJSONMarshal(votes) + if err != nil { + logger.Error("failed to get pretty JSON of votes", + "err", err, + ) + os.Exit(1) + } + fmt.Println(string(prettyVotes)) } func doListProposals(cmd *cobra.Command, args []string) { @@ -256,8 +268,14 @@ func doListProposals(cmd *cobra.Command, args []string) { os.Exit(1) } - o, _ := json.Marshal(proposals) - fmt.Println(string(o)) + prettyProposals, err := cmdCommon.PrettyJSONMarshal(proposals) + if err != nil { + logger.Error("failed to get pretty JSON of proposals", + "err", err, + ) + os.Exit(1) + } + fmt.Println(string(prettyProposals)) } // Register registers the governance sub-command and all of it's children. diff --git a/go/oasis-node/cmd/keymanager/keymanager.go b/go/oasis-node/cmd/keymanager/keymanager.go index 15a3c60ce26..b84038da787 100644 --- a/go/oasis-node/cmd/keymanager/keymanager.go +++ b/go/oasis-node/cmd/keymanager/keymanager.go @@ -4,7 +4,6 @@ package keymanager import ( "bytes" "encoding/hex" - "encoding/json" "errors" "fmt" "io/ioutil" @@ -316,8 +315,14 @@ func verifyPolicyFromFlags() error { // Output policy content in JSON, if verbose switch given. if cmdFlags.Verbose() { - c, _ := json.Marshal(policy) - fmt.Printf("%s\n", string(c)) + prettyPolicy, err := cmdCommon.PrettyJSONMarshal(policy) + if err != nil { + logger.Error("failed to get pretty JSON of policy", + "err", err, + ) + os.Exit(1) + } + fmt.Println(string(prettyPolicy)) } // Check the signatures of the policy. Public key is taken from the PEM @@ -364,7 +369,7 @@ func doInitStatus(cmd *cobra.Command, args []string) { cmdCommon.EarlyLogAndExit(err) } - s, err := statusFromFlags() + status, err := statusFromFlags() if err != nil { logger.Error("failed to generate status", "err", err, @@ -372,8 +377,14 @@ func doInitStatus(cmd *cobra.Command, args []string) { os.Exit(1) } - c, _ := json.Marshal(s) - if err = ioutil.WriteFile(viper.GetString(CfgStatusFile), c, 0o644); err != nil { // nolint: gosec + prettyStatus, err := cmdCommon.PrettyJSONMarshal(status) + if err != nil { + logger.Error("failed to get pretty JSON of key manager status", + "err", err, + ) + os.Exit(1) + } + if err = ioutil.WriteFile(viper.GetString(CfgStatusFile), prettyStatus, 0o644); err != nil { // nolint: gosec logger.Error("failed to write key manager status json file", "err", err, "CfgStatusFile", viper.GetString(CfgStatusFile), @@ -382,7 +393,7 @@ func doInitStatus(cmd *cobra.Command, args []string) { } logger.Info("generated key manager status file", - "Status.ID", s.ID, + "Status.ID", status.ID, ) } diff --git a/go/oasis-node/cmd/registry/entity/entity.go b/go/oasis-node/cmd/registry/entity/entity.go index ffae8280a48..7822a95d496 100644 --- a/go/oasis-node/cmd/registry/entity/entity.go +++ b/go/oasis-node/cmd/registry/entity/entity.go @@ -264,8 +264,14 @@ func signAndWriteEntityGenesis(dataDir string, signer signature.Signer, ent *ent } // Write out the signed entity registration. - b, _ := json.Marshal(signed) - if err = ioutil.WriteFile(filepath.Join(dataDir, entityGenesisFilename), b, 0o600); err != nil { + prettySigned, err := cmdCommon.PrettyJSONMarshal(signed) + if err != nil { + logger.Error("failed to get pretty JSON of signed entity genesis registration", + "err", err, + ) + os.Exit(1) + } + if err = ioutil.WriteFile(filepath.Join(dataDir, entityGenesisFilename), prettySigned, 0o600); err != nil { logger.Error("failed to write signed entity genesis registration", "err", err, ) @@ -337,16 +343,24 @@ func doList(cmd *cobra.Command, args []string) { } for _, ent := range entities { - var s string + var entString string switch cmdFlags.Verbose() { case true: - b, _ := json.Marshal(ent) - s = string(b) + prettyEnt, err := cmdCommon.PrettyJSONMarshal(ent) + if err != nil { + logger.Error("failed to get pretty JSON of entity", + "err", err, + "entity ID", ent.ID.String(), + ) + entString = fmt.Sprintf("[invalid pretty JSON for entity %s]", ent.ID) + } else { + entString = string(prettyEnt) + } default: - s = ent.ID.String() + entString = ent.ID.String() } - fmt.Printf("%v\n", s) + fmt.Println(entString) } } diff --git a/go/oasis-node/cmd/registry/node/node.go b/go/oasis-node/cmd/registry/node/node.go index 52234e1a58e..13a9eb0426f 100644 --- a/go/oasis-node/cmd/registry/node/node.go +++ b/go/oasis-node/cmd/registry/node/node.go @@ -3,7 +3,6 @@ package node import ( "context" - "encoding/json" "fmt" "io/ioutil" "os" @@ -258,8 +257,14 @@ func doInit(cmd *cobra.Command, args []string) { // nolint: gocyclo ) os.Exit(1) } - b, _ := json.Marshal(signed) - if err = ioutil.WriteFile(filepath.Join(dataDir, NodeGenesisFilename), b, 0o600); err != nil { + prettySigned, err := cmdCommon.PrettyJSONMarshal(signed) + if err != nil { + logger.Error("failed to get pretty JSON of signed node genesis registration", + "err", err, + ) + os.Exit(1) + } + if err = ioutil.WriteFile(filepath.Join(dataDir, NodeGenesisFilename), prettySigned, 0o600); err != nil { logger.Error("failed to write signed node genesis registration", "err", err, ) @@ -304,16 +309,24 @@ func doList(cmd *cobra.Command, args []string) { } for _, node := range nodes { - var s string + var nodeString string switch cmdFlags.Verbose() { case true: - b, _ := json.Marshal(node) - s = string(b) + prettyNode, err := cmdCommon.PrettyJSONMarshal(node) + if err != nil { + logger.Error("failed to get pretty JSON of node", + "err", err, + "node ID", node.ID.String(), + ) + nodeString = fmt.Sprintf("[invalid pretty JSON for node %s]", node.ID) + } else { + nodeString = string(prettyNode) + } default: - s = node.ID.String() + nodeString = node.ID.String() } - fmt.Printf("%v\n", s) + fmt.Println(nodeString) } } diff --git a/go/oasis-node/cmd/registry/runtime/runtime.go b/go/oasis-node/cmd/registry/runtime/runtime.go index d4a7d25cbf6..51e228cad50 100644 --- a/go/oasis-node/cmd/registry/runtime/runtime.go +++ b/go/oasis-node/cmd/registry/runtime/runtime.go @@ -133,16 +133,24 @@ func doList(cmd *cobra.Command, args []string) { } for _, rt := range runtimes { - var s string + var rtString string switch cmdFlags.Verbose() { case true: - b, _ := json.Marshal(rt) - s = string(b) + prettyRt, err := cmdCommon.PrettyJSONMarshal(rt) + if err != nil { + logger.Error("failed to get pretty JSON of runtime", + "err", err, + "runtime ID", rt.ID.String(), + ) + rtString = fmt.Sprintf("[invalid pretty JSON for runtime %s]", rt.ID) + } else { + rtString = string(prettyRt) + } default: - s = rt.ID.String() + rtString = rt.ID.String() } - fmt.Printf("%v\n", s) + fmt.Println(rtString) } } diff --git a/go/oasis-node/cmd/stake/stake.go b/go/oasis-node/cmd/stake/stake.go index 2acd80e6bd1..c67016ec323 100644 --- a/go/oasis-node/cmd/stake/stake.go +++ b/go/oasis-node/cmd/stake/stake.go @@ -3,7 +3,6 @@ package stake import ( "context" - "encoding/json" "fmt" "os" @@ -282,20 +281,28 @@ func doList(cmd *cobra.Command, args []string) { } for _, addr := range addresses { - var s string + var acctString string switch cmdFlags.Verbose() { case true: // NOTE: getAccount()'s output doesn't contain an account's address, // so we need to add it manually. - acctMap := make(map[api.Address]*api.Account) - acctMap[addr] = getAccount(ctx, cmd, addr, client) - b, _ := json.Marshal(acctMap) - s = string(b) + acctWithAddr := make(map[api.Address]*api.Account) + acctWithAddr[addr] = getAccount(ctx, cmd, addr, client) + prettyAcct, err := cmdCommon.PrettyJSONMarshal(acctWithAddr) + if err != nil { + logger.Error("failed to get pretty JSON of account", + "err", err, + "address", addr, + ) + acctString = fmt.Sprintf("[invalid pretty JSON for account %s]", addr) + } else { + acctString = string(prettyAcct) + } default: - s = addr.String() + acctString = addr.String() } - fmt.Printf("%v\n", s) + fmt.Println(acctString) } } diff --git a/go/oasis-test-runner/scenario/e2e/registry_cli.go b/go/oasis-test-runner/scenario/e2e/registry_cli.go index ad4d8c384da..eb5e280a1e1 100644 --- a/go/oasis-test-runner/scenario/e2e/registry_cli.go +++ b/go/oasis-test-runner/scenario/e2e/registry_cli.go @@ -6,6 +6,7 @@ import ( "encoding/json" "errors" "fmt" + "io" "io/ioutil" "net" "os" @@ -751,17 +752,14 @@ func (sc *registryCLIImpl) listRuntimes(childEnv *env.Env, includeSuspended bool if err != nil { return nil, fmt.Errorf("failed to list runtimes: error: %w output: %s", err, out.String()) } - runtimesStr := strings.Split(out.String(), "\n") + dec := json.NewDecoder(bytes.NewReader(out.Bytes())) runtimes := map[common.Namespace]registry.Runtime{} - for _, rtStr := range runtimesStr { - // Ignore last newline. - if rtStr == "" { - continue - } - + for { var rt registry.Runtime - if err = json.Unmarshal([]byte(rtStr), &rt); err != nil { + if err = dec.Decode(&rt); err == io.EOF { + break + } else if err != nil { return nil, err } runtimes[rt.ID] = rt