From 3e088b3b40c54ba3812d0f19ae7ae8ba39c1d04b Mon Sep 17 00:00:00 2001 From: Gyu-Ho Lee Date: Mon, 9 May 2016 15:58:11 -0700 Subject: [PATCH] etcdctl/ctlv3: make 'table' printer configurable Fix https://github.com/coreos/etcd/issues/5296. --- etcdctl/READMEv3.md | 52 +++++--- etcdctl/ctlv3/command/ep_command.go | 5 +- etcdctl/ctlv3/command/member_command.go | 3 + etcdctl/ctlv3/command/printer.go | 145 +++++++++++++++------- etcdctl/ctlv3/command/snapshot_command.go | 5 +- etcdctl/ctlv3/ctl.go | 2 +- 6 files changed, 150 insertions(+), 62 deletions(-) diff --git a/etcdctl/READMEv3.md b/etcdctl/READMEv3.md index 2ea379910..ff220451a 100644 --- a/etcdctl/READMEv3.md +++ b/etcdctl/READMEv3.md @@ -423,6 +423,18 @@ On success, prints a JSON listing of the member IDs, statuses, names, peer addre ```bash ./etcdctl member list +8211f1d0f64f3269, started, infra1, http://127.0.0.1:12380, http://127.0.0.1:2379 +91bc3c398fb3c146, started, infra2, http://127.0.0.1:22380, http://127.0.0.1:22379 +fd422379fda50e48, started, infra3, http://127.0.0.1:32380, http://127.0.0.1:32379 +``` + +```bash +./etcdctl -w json member list +{"header":{"cluster_id":17237436991929493444,"member_id":9372538179322589801,"raft_term":2},"members":[{"ID":9372538179322589801,"name":"infra1","peerURLs":["http://127.0.0.1:12380"],"clientURLs":["http://127.0.0.1:2379"]},{"ID":10501334649042878790,"name":"infra2","peerURLs":["http://127.0.0.1:22380"],"clientURLs":["http://127.0.0.1:22379"]},{"ID":18249187646912138824,"name":"infra3","peerURLs":["http://127.0.0.1:32380"],"clientURLs":["http://127.0.0.1:32379"]}]} +``` + +```bash +./etcdctl -w table member list +------------------+---------+--------+------------------------+------------------------+ | ID | STATUS | NAME | PEER ADDRS | CLIENT ADDRS | +------------------+---------+--------+------------------------+------------------------+ @@ -432,11 +444,6 @@ On success, prints a JSON listing of the member IDs, statuses, names, peer addre +------------------+---------+--------+------------------------+------------------------+ ``` -```bash -./etcdctl -w json member list -{"header":{"cluster_id":17237436991929493444,"member_id":9372538179322589801,"raft_term":2},"members":[{"ID":9372538179322589801,"name":"infra1","peerURLs":["http://127.0.0.1:12380"],"clientURLs":["http://127.0.0.1:2379"]},{"ID":10501334649042878790,"name":"infra2","peerURLs":["http://127.0.0.1:22380"],"clientURLs":["http://127.0.0.1:22379"]},{"ID":18249187646912138824,"name":"infra3","peerURLs":["http://127.0.0.1:32380"],"clientURLs":["http://127.0.0.1:32379"]}]} -``` - ## Utility Commands ### ENDPOINT \ @@ -481,13 +488,9 @@ On success, prints a line of JSON encoding each endpoint URL, ID, version, datab ```bash ./etcdctl endpoint status -+-----------------+------------------+-----------+---------+-----------+-----------+------------+ -| ENDPOINT | ID | VERSION | DB SIZE | IS LEADER | RAFT TERM | RAFT INDEX | -+-----------------+------------------+-----------+---------+-----------+-----------+------------+ -| 127.0.0.1:2379 | 8211f1d0f64f3269 | 2.3.0+git | 25 kB | false | 2 | 31563 | -| 127.0.0.1:22379 | 91bc3c398fb3c146 | 2.3.0+git | 25 kB | false | 2 | 31563 | -| 127.0.0.1:32379 | fd422379fda50e48 | 2.3.0+git | 25 kB | true | 2 | 31563 | -+-----------------+------------------+-----------+---------+-----------+-----------+------------+ +127.0.0.1:2379, 8211f1d0f64f3269, 3.0.0-beta.0+git, 25 kB, false, 2, 63 +127.0.0.1:22379, 91bc3c398fb3c146, 3.0.0-beta.0+git, 25 kB, false, 2, 63 +127.0.0.1:32379, fd422379fda50e48, 3.0.0-beta.0+git, 25 kB, true, 2, 63 ``` ```bash @@ -495,6 +498,16 @@ On success, prints a line of JSON encoding each endpoint URL, ID, version, datab [{"Endpoint":"127.0.0.1:2379","Status":{"header":{"cluster_id":17237436991929493444,"member_id":9372538179322589801,"revision":2,"raft_term":2},"version":"2.3.0+git","dbSize":24576,"leader":18249187646912138824,"raftIndex":32623,"raftTerm":2}},{"Endpoint":"127.0.0.1:22379","Status":{"header":{"cluster_id":17237436991929493444,"member_id":10501334649042878790,"revision":2,"raft_term":2},"version":"2.3.0+git","dbSize":24576,"leader":18249187646912138824,"raftIndex":32623,"raftTerm":2}},{"Endpoint":"127.0.0.1:32379","Status":{"header":{"cluster_id":17237436991929493444,"member_id":18249187646912138824,"revision":2,"raft_term":2},"version":"2.3.0+git","dbSize":24576,"leader":18249187646912138824,"raftIndex":32623,"raftTerm":2}}] ``` +```bash +./etcdctl -w table endpoint status ++-----------------+------------------+------------------+---------+-----------+-----------+------------+ +| ENDPOINT | ID | VERSION | DB SIZE | IS LEADER | RAFT TERM | RAFT INDEX | ++-----------------+------------------+------------------+---------+-----------+-----------+------------+ +| 127.0.0.1:2379 | 8211f1d0f64f3269 | 3.0.0-beta.0+git | 25 kB | false | 2 | 52 | +| 127.0.0.1:22379 | 91bc3c398fb3c146 | 3.0.0-beta.0+git | 25 kB | false | 2 | 52 | +| 127.0.0.1:32379 | fd422379fda50e48 | 3.0.0-beta.0+git | 25 kB | true | 2 | 52 | ++-----------------+------------------+------------------+---------+-----------+-----------+------------+ +``` ### LOCK \ @@ -705,11 +718,7 @@ On success, prints a line of JSON encoding the database hash, revision, total ke #### Examples ```bash ./etcdctl snapshot status file.db -+----------+----------+------------+------------+ -| HASH | REVISION | TOTAL KEYS | TOTAL SIZE | -+----------+----------+------------+------------+ -| cf1550fb | 3 | 3 | 25 kB | -+----------+----------+------------+------------+ +cf1550fb, 3, 3, 25 kB ``` ```bash @@ -717,6 +726,15 @@ On success, prints a line of JSON encoding the database hash, revision, total ke {"hash":3474280699,"revision":3,"totalKey":3,"totalSize":24576} ``` +```bash +./etcdctl -write-out=table snapshot status file.db ++----------+----------+------------+------------+ +| HASH | REVISION | TOTAL KEYS | TOTAL SIZE | ++----------+----------+------------+------------+ +| cf1550fb | 3 | 3 | 25 kB | ++----------+----------+------------+------------+ +``` + ## Notes - JSON encoding for keys and values uses base64 since they are byte strings. diff --git a/etcdctl/ctlv3/command/ep_command.go b/etcdctl/ctlv3/command/ep_command.go index 742f07aae..a3bd4d36a 100644 --- a/etcdctl/ctlv3/command/ep_command.go +++ b/etcdctl/ctlv3/command/ep_command.go @@ -51,7 +51,10 @@ func newEpStatusCommand() *cobra.Command { return &cobra.Command{ Use: "status", Short: "status prints out the status of endpoints specified in `--endpoints` flag", - Run: epStatusCommandFunc, + Long: `When --write-out is set to simple, this command prints out comma-separated status lists for each endpoint. +The items in the lists are endpoint, ID, version, db size, is leader, raft term, raft index. +`, + Run: epStatusCommandFunc, } } diff --git a/etcdctl/ctlv3/command/member_command.go b/etcdctl/ctlv3/command/member_command.go index ded282744..6a40e4d7c 100644 --- a/etcdctl/ctlv3/command/member_command.go +++ b/etcdctl/ctlv3/command/member_command.go @@ -84,6 +84,9 @@ func NewMemberListCommand() *cobra.Command { cc := &cobra.Command{ Use: "list", Short: "list is used to list all members in the cluster", + Long: `When --write-out is set to simple, this command prints out comma-separated member lists for each endpoint. +The items in the lists are ID, Status, Name, Peer Addrs, Client Addrs. +`, Run: memberListCommandFunc, } diff --git a/etcdctl/ctlv3/command/printer.go b/etcdctl/ctlv3/command/printer.go index f177a9c6e..d549c39a1 100644 --- a/etcdctl/ctlv3/command/printer.go +++ b/etcdctl/ctlv3/command/printer.go @@ -51,10 +51,57 @@ func NewPrinter(printerType string, isHex bool) printer { return &jsonPrinter{} case "protobuf": return &pbPrinter{} + case "table": + return &tablePrinter{} } return nil } +func makeMemberListTable(r v3.MemberListResponse) (hdr []string, rows [][]string) { + hdr = []string{"ID", "Status", "Name", "Peer Addrs", "Client Addrs"} + for _, m := range r.Members { + status := "started" + if len(m.Name) == 0 { + status = "unstarted" + } + rows = append(rows, []string{ + fmt.Sprintf("%x", m.ID), + status, + m.Name, + strings.Join(m.PeerURLs, ","), + strings.Join(m.ClientURLs, ","), + }) + } + return +} + +func makeEndpointStatusTable(statusList []epStatus) (hdr []string, rows [][]string) { + hdr = []string{"endpoint", "ID", "version", "db size", "is leader", "raft term", "raft index"} + for _, status := range statusList { + rows = append(rows, []string{ + fmt.Sprint(status.Ep), + fmt.Sprintf("%x", status.Resp.Header.MemberId), + fmt.Sprint(status.Resp.Version), + fmt.Sprint(humanize.Bytes(uint64(status.Resp.DbSize))), + fmt.Sprint(status.Resp.Leader == status.Resp.Header.MemberId), + fmt.Sprint(status.Resp.RaftTerm), + fmt.Sprint(status.Resp.RaftIndex), + }) + } + return +} + +func makeDBStatusTable(ds dbstatus) (hdr []string, rows [][]string) { + hdr = []string{"hash", "revision", "total keys", "total size"} + rows = append(rows, []string{ + fmt.Sprintf("%x", ds.Hash), + fmt.Sprint(ds.Revision), + fmt.Sprint(ds.TotalKey), + humanize.Bytes(uint64(ds.TotalSize)), + }) + return +} + type simplePrinter struct { isHex bool } @@ -107,57 +154,71 @@ func (s *simplePrinter) Alarm(resp v3.AlarmResponse) { } func (s *simplePrinter) MemberList(resp v3.MemberListResponse) { - table := tablewriter.NewWriter(os.Stdout) - table.SetHeader([]string{"ID", "Status", "Name", "Peer Addrs", "Client Addrs"}) - - for _, m := range resp.Members { - status := "started" - if len(m.Name) == 0 { - status = "unstarted" - } - - table.Append([]string{ - fmt.Sprintf("%x", m.ID), - status, - m.Name, - strings.Join(m.PeerURLs, ","), - strings.Join(m.ClientURLs, ","), - }) + _, rows := makeMemberListTable(resp) + for _, row := range rows { + fmt.Println(strings.Join(row, ", ")) } - - table.Render() } func (s *simplePrinter) EndpointStatus(statusList []epStatus) { - table := tablewriter.NewWriter(os.Stdout) - table.SetHeader([]string{"endpoint", "ID", "version", "db size", "is leader", "raft term", "raft index"}) - - for _, status := range statusList { - table.Append([]string{ - fmt.Sprint(status.Ep), - fmt.Sprintf("%x", status.Resp.Header.MemberId), - fmt.Sprint(status.Resp.Version), - fmt.Sprint(humanize.Bytes(uint64(status.Resp.DbSize))), - fmt.Sprint(status.Resp.Leader == status.Resp.Header.MemberId), - fmt.Sprint(status.Resp.RaftTerm), - fmt.Sprint(status.Resp.RaftIndex), - }) + _, rows := makeEndpointStatusTable(statusList) + for _, row := range rows { + fmt.Println(strings.Join(row, ", ")) } - - table.Render() } func (s *simplePrinter) DBStatus(ds dbstatus) { + _, rows := makeDBStatusTable(ds) + for _, row := range rows { + fmt.Println(strings.Join(row, ", ")) + } +} + +type tablePrinter struct{} + +func (tp *tablePrinter) Del(r v3.DeleteResponse) { + ExitWithError(ExitBadFeature, errors.New("table is not supported as output format")) +} +func (tp *tablePrinter) Get(r v3.GetResponse) { + ExitWithError(ExitBadFeature, errors.New("table is not supported as output format")) +} +func (tp *tablePrinter) Put(r v3.PutResponse) { + ExitWithError(ExitBadFeature, errors.New("table is not supported as output format")) +} +func (tp *tablePrinter) Txn(r v3.TxnResponse) { + ExitWithError(ExitBadFeature, errors.New("table is not supported as output format")) +} +func (tp *tablePrinter) Watch(r v3.WatchResponse) { + ExitWithError(ExitBadFeature, errors.New("table is not supported as output format")) +} +func (tp *tablePrinter) Alarm(r v3.AlarmResponse) { + ExitWithError(ExitBadFeature, errors.New("table is not supported as output format")) +} +func (tp *tablePrinter) MemberList(r v3.MemberListResponse) { + hdr, rows := makeMemberListTable(r) table := tablewriter.NewWriter(os.Stdout) - table.SetHeader([]string{"hash", "revision", "total keys", "total size"}) - - table.Append([]string{ - fmt.Sprintf("%x", ds.Hash), - fmt.Sprint(ds.Revision), - fmt.Sprint(ds.TotalKey), - humanize.Bytes(uint64(ds.TotalSize)), - }) - + table.SetHeader(hdr) + for _, row := range rows { + table.Append(row) + } + table.Render() +} +func (tp *tablePrinter) EndpointStatus(r []epStatus) { + hdr, rows := makeEndpointStatusTable(r) + table := tablewriter.NewWriter(os.Stdout) + table.SetHeader(hdr) + for _, row := range rows { + table.Append(row) + } + table.Render() +} +func (tp *tablePrinter) DBStatus(r dbstatus) { + hdr, rows := makeDBStatusTable(r) + table := tablewriter.NewWriter(os.Stdout) + table.SetHeader(hdr) + for _, row := range rows { + table.Append(row) + } table.Render() } diff --git a/etcdctl/ctlv3/command/snapshot_command.go b/etcdctl/ctlv3/command/snapshot_command.go index 67503fdf8..e9d3ce863 100644 --- a/etcdctl/ctlv3/command/snapshot_command.go +++ b/etcdctl/ctlv3/command/snapshot_command.go @@ -76,7 +76,10 @@ func newSnapshotStatusCommand() *cobra.Command { return &cobra.Command{ Use: "status ", Short: "status gets backend snapshot status of a given file.", - Run: snapshotStatusCommandFunc, + Long: `When --write-out is set to simple, this command prints out comma-separated status lists for each endpoint. +The items in the lists are hash, revision, total keys, total size. +`, + Run: snapshotStatusCommandFunc, } } diff --git a/etcdctl/ctlv3/ctl.go b/etcdctl/ctlv3/ctl.go index 017cbc562..c17893094 100644 --- a/etcdctl/ctlv3/ctl.go +++ b/etcdctl/ctlv3/ctl.go @@ -45,7 +45,7 @@ var ( func init() { rootCmd.PersistentFlags().StringSliceVar(&globalFlags.Endpoints, "endpoints", []string{"127.0.0.1:2379", "127.0.0.1:22379", "127.0.0.1:32379"}, "gRPC endpoints") - rootCmd.PersistentFlags().StringVarP(&globalFlags.OutputFormat, "write-out", "w", "simple", "set the output format (simple, json, protobuf)") + rootCmd.PersistentFlags().StringVarP(&globalFlags.OutputFormat, "write-out", "w", "simple", "set the output format (simple, json, protobuf, table)") rootCmd.PersistentFlags().BoolVar(&globalFlags.IsHex, "hex", false, "print byte strings as hex encoded strings") rootCmd.PersistentFlags().DurationVar(&globalFlags.DialTimeout, "dial-timeout", defaultDialTimeout, "dial timeout for client connections")