From 9dcecbc11e1e0d243c39e6347b16a7850b6fb391 Mon Sep 17 00:00:00 2001 From: Trapier Marshall Date: Mon, 23 Sep 2019 16:53:52 -0400 Subject: [PATCH 1/2] swarm-rafttool: add output format flag Add --format output flag which defaults to "text" (current style) and also accepts "json". Signed-off-by: Trapier Marshall --- cmd/swarm-rafttool/dump.go | 107 ++++++++++++++++++++++++++++--------- cmd/swarm-rafttool/main.go | 23 ++++++++ 2 files changed, 104 insertions(+), 26 deletions(-) diff --git a/cmd/swarm-rafttool/dump.go b/cmd/swarm-rafttool/dump.go index 481a0ba6c4..e71e100a88 100644 --- a/cmd/swarm-rafttool/dump.go +++ b/cmd/swarm-rafttool/dump.go @@ -2,6 +2,7 @@ package main import ( "context" + "encoding/json" "errors" "fmt" "os" @@ -84,17 +85,38 @@ func dumpWAL(swarmdir, unlockKey string, start, end uint64, redact bool) error { for _, ent := range walData.Entries { if (start == 0 || ent.Index >= start) && (end == 0 || ent.Index <= end) { - fmt.Printf("Entry Index=%d, Term=%d, Type=%s:\n", ent.Index, ent.Term, ent.Type.String()) + if format == "text" { + fmt.Printf("Entry Index=%d, Term=%d, Type=%s:\n", ent.Index, ent.Term, ent.Type.String()) + } switch ent.Type { case raftpb.EntryConfChange: + cc := &raftpb.ConfChange{} err := proto.Unmarshal(ent.Data, cc) if err != nil { return err } - - fmt.Println("Conf change type:", cc.Type.String()) - fmt.Printf("Node ID: %x\n\n", cc.NodeID) + switch format { + case "text": + fmt.Println("Conf change type:", cc.Type.String()) + fmt.Printf("Node ID: %x\n\n", cc.NodeID) + fmt.Println() + case "json": + err := json.NewEncoder(os.Stdout).Encode(struct { + Term uint64 + Index uint64 + Type raftpb.EntryType + Data *raftpb.ConfChange + }{ + ent.Term, + ent.Index, + ent.Type, + cc, + }) + if err != nil { + return err + } + } case raftpb.EntryNormal: r := &api.InternalRaftRequest{} @@ -135,10 +157,28 @@ func dumpWAL(swarmdir, unlockKey string, start, end uint64, redact bool) error { } } - if err := proto.MarshalText(os.Stdout, r); err != nil { - return err + switch format { + case "text": + if err := proto.MarshalText(os.Stdout, r); err != nil { + return err + } + fmt.Println() + case "json": + err := json.NewEncoder(os.Stdout).Encode(struct { + Term uint64 + Index uint64 + Type raftpb.EntryType + Data *api.InternalRaftRequest + }{ + Term: ent.Term, + Index: ent.Index, + Type: ent.Type, + Data: r, + }) + if err != nil { + return err + } } - fmt.Println() } } } @@ -164,18 +204,6 @@ func dumpSnapshot(swarmdir, unlockKey string, redact bool) error { return fmt.Errorf("unrecognized snapshot version %d", s.Version) } - fmt.Println("Active members:") - for _, member := range s.Membership.Members { - fmt.Printf(" NodeID=%s, RaftID=%x, Addr=%s\n", member.NodeID, member.RaftID, member.Addr) - } - fmt.Println() - - fmt.Println("Removed members:") - for _, member := range s.Membership.Removed { - fmt.Printf(" RaftID=%x\n", member) - } - fmt.Println() - if redact { for _, cluster := range s.Store.Clusters { if cluster != nil { @@ -218,11 +246,30 @@ func dumpSnapshot(swarmdir, unlockKey string, redact bool) error { } } - fmt.Println("Objects:") - if err := proto.MarshalText(os.Stdout, &s.Store); err != nil { - return err + switch format { + case "text": + fmt.Println("Active members:") + for _, member := range s.Membership.Members { + fmt.Printf(" NodeID=%s, RaftID=%x, Addr=%s\n", member.NodeID, member.RaftID, member.Addr) + } + fmt.Println() + + fmt.Println("Removed members:") + for _, member := range s.Membership.Removed { + fmt.Printf(" RaftID=%x\n", member) + } + fmt.Println() + + fmt.Println("Objects:") + if err := proto.MarshalText(os.Stdout, &s.Store); err != nil { + return err + } + fmt.Println() + case "json": + if err := json.NewEncoder(os.Stdout).Encode(&s); err != nil { + return err + } } - fmt.Println() return nil } @@ -452,10 +499,18 @@ func dumpObject(swarmdir, unlockKey, objType string, selector objSelector) error } for _, object := range objects { - if err := proto.MarshalText(os.Stdout, object); err != nil { - return err + switch format { + case "text": + if err := proto.MarshalText(os.Stdout, object); err != nil { + return err + } + fmt.Println() + case "json": + if err := json.NewEncoder(os.Stdout).Encode(object); err != nil { + return err + } + } - fmt.Println() } return nil diff --git a/cmd/swarm-rafttool/main.go b/cmd/swarm-rafttool/main.go index a5d2963c6e..dc27fe604c 100644 --- a/cmd/swarm-rafttool/main.go +++ b/cmd/swarm-rafttool/main.go @@ -10,6 +10,8 @@ import ( ) var ( + format string + mainCmd = &cobra.Command{ Use: os.Args[0], Short: "Tool to translate and decrypt the raft logs of a swarm manager", @@ -45,6 +47,9 @@ var ( dumpWALCmd = &cobra.Command{ Use: "dump-wal", Short: "Display entries from the Raft log", + PreRunE: func(cmd *cobra.Command, args []string) error { + return validateFormatFlag(format) + }, RunE: func(cmd *cobra.Command, args []string) error { stateDir, err := cmd.Flags().GetString("state-dir") if err != nil { @@ -78,6 +83,9 @@ var ( dumpSnapshotCmd = &cobra.Command{ Use: "dump-snapshot", Short: "Display entries from the latest Raft snapshot", + PreRunE: func(cmd *cobra.Command, args []string) error { + return validateFormatFlag(format) + }, RunE: func(cmd *cobra.Command, args []string) error { stateDir, err := cmd.Flags().GetString("state-dir") if err != nil { @@ -101,6 +109,9 @@ var ( dumpObjectCmd = &cobra.Command{ Use: "dump-object [type]", Short: "Display an object from the Raft snapshot/WAL", + PreRunE: func(cmd *cobra.Command, args []string) error { + return validateFormatFlag(format) + }, RunE: func(cmd *cobra.Command, args []string) error { if len(args) != 1 { return errors.New("dump-object subcommand takes exactly 1 argument") @@ -159,6 +170,15 @@ var ( } ) +func validateFormatFlag(format string) error { + switch format { + case "text", "json": + return nil + default: + return fmt.Errorf("invalid 'format' %s", format) + } +} + func init() { mainCmd.PersistentFlags().StringP("state-dir", "d", defaults.StateDir, "State directory") mainCmd.PersistentFlags().String("unlock-key", "", "Unlock key, if raft logs are encrypted") @@ -172,13 +192,16 @@ func init() { ) dumpSnapshotCmd.Flags().Bool("redact", false, "Redact the values of secrets, configs, and environment variables") + dumpSnapshotCmd.Flags().StringVar(&format, "format", "text", `Output format (text|json)`) dumpWALCmd.Flags().Uint64("start", 0, "Start of index range to dump") dumpWALCmd.Flags().Uint64("end", 0, "End of index range to dump") dumpWALCmd.Flags().Bool("redact", false, "Redact the values of secrets, configs, and environment variables") + dumpWALCmd.Flags().StringVar(&format, "format", "text", `Output format (text|json)`) dumpObjectCmd.Flags().String("id", "", "Look up object by ID") dumpObjectCmd.Flags().String("name", "", "Look up object by name") + dumpObjectCmd.Flags().StringVar(&format, "format", "text", `Output format (text|json)`) } func main() { From 7fea0104a7f8badaf860533870fef65b89642b88 Mon Sep 17 00:00:00 2001 From: Trapier Marshall Date: Tue, 24 Sep 2019 15:34:56 -0400 Subject: [PATCH 2/2] swarm-rafttool: pretty print json format Signed-off-by: Trapier Marshall --- cmd/swarm-rafttool/dump.go | 68 ++++++++++++++++++++++++++++++-------- 1 file changed, 55 insertions(+), 13 deletions(-) diff --git a/cmd/swarm-rafttool/dump.go b/cmd/swarm-rafttool/dump.go index e71e100a88..a395e26ad5 100644 --- a/cmd/swarm-rafttool/dump.go +++ b/cmd/swarm-rafttool/dump.go @@ -1,10 +1,12 @@ package main import ( + "bytes" "context" "encoding/json" "errors" "fmt" + "io" "os" "path/filepath" @@ -83,7 +85,13 @@ func dumpWAL(swarmdir, unlockKey string, start, end uint64, redact bool) error { return err } - for _, ent := range walData.Entries { + prefix := "" + if format == "json" && len(walData.Entries) > 1 { + fmt.Println("[") + fmt.Print(" ") + prefix = " " + } + for idx, ent := range walData.Entries { if (start == 0 || ent.Index >= start) && (end == 0 || ent.Index <= end) { if format == "text" { fmt.Printf("Entry Index=%d, Term=%d, Type=%s:\n", ent.Index, ent.Term, ent.Type.String()) @@ -102,20 +110,26 @@ func dumpWAL(swarmdir, unlockKey string, start, end uint64, redact bool) error { fmt.Printf("Node ID: %x\n\n", cc.NodeID) fmt.Println() case "json": - err := json.NewEncoder(os.Stdout).Encode(struct { + bytesBuffer := new(bytes.Buffer) + b, err := json.MarshalIndent(struct { Term uint64 Index uint64 Type raftpb.EntryType Data *raftpb.ConfChange }{ - ent.Term, - ent.Index, - ent.Type, - cc, - }) + Term: ent.Term, + Index: ent.Index, + Type: ent.Type, + Data: cc, + }, prefix, " ") if err != nil { return err } + bytesBuffer.Write(b) + if idx < len(walData.Entries)-1 { + bytesBuffer.WriteString(",\n ") + } + io.Copy(os.Stdout, bytesBuffer) } case raftpb.EntryNormal: @@ -164,7 +178,8 @@ func dumpWAL(swarmdir, unlockKey string, start, end uint64, redact bool) error { } fmt.Println() case "json": - err := json.NewEncoder(os.Stdout).Encode(struct { + bytesBuffer := new(bytes.Buffer) + b, err := json.MarshalIndent(struct { Term uint64 Index uint64 Type raftpb.EntryType @@ -174,14 +189,23 @@ func dumpWAL(swarmdir, unlockKey string, start, end uint64, redact bool) error { Index: ent.Index, Type: ent.Type, Data: r, - }) + }, prefix, " ") if err != nil { return err } + bytesBuffer.Write(b) + if idx < len(walData.Entries)-1 { + bytesBuffer.WriteString(",\n ") + } + io.Copy(os.Stdout, bytesBuffer) } } } } + if format == "json" && len(walData.Entries) > 1 { + fmt.Println() + fmt.Println("]") + } return nil } @@ -266,7 +290,9 @@ func dumpSnapshot(swarmdir, unlockKey string, redact bool) error { } fmt.Println() case "json": - if err := json.NewEncoder(os.Stdout).Encode(&s); err != nil { + enc := json.NewEncoder(os.Stdout) + enc.SetIndent("", " ") + if err := enc.Encode(&s); err != nil { return err } } @@ -498,7 +524,13 @@ func dumpObject(swarmdir, unlockKey, objType string, selector objSelector) error return fmt.Errorf("no matching objects found") } - for _, object := range objects { + prefix := "" + if format == "json" && len(objects) > 1 { + fmt.Println("[") + fmt.Print(" ") + prefix = " " + } + for idx, object := range objects { switch format { case "text": if err := proto.MarshalText(os.Stdout, object); err != nil { @@ -506,12 +538,22 @@ func dumpObject(swarmdir, unlockKey, objType string, selector objSelector) error } fmt.Println() case "json": - if err := json.NewEncoder(os.Stdout).Encode(object); err != nil { + bytesBuffer := new(bytes.Buffer) + b, err := json.MarshalIndent(object, prefix, " ") + if err != nil { return err } - + bytesBuffer.Write(b) + if idx < len(objects)-1 { + bytesBuffer.WriteString(",\n ") + } + io.Copy(os.Stdout, bytesBuffer) } } + if format == "json" && len(objects) > 1 { + fmt.Println() + fmt.Println("]") + } return nil }