diff --git a/CHANGELOG-3.5.md b/CHANGELOG-3.5.md index b4a559d8a..74b5a9229 100644 --- a/CHANGELOG-3.5.md +++ b/CHANGELOG-3.5.md @@ -142,6 +142,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change. - Add [`etcdctl watch --progress-notify`](https://github.com/etcd-io/etcd/pull/11462) flag. - Add [`etcdctl auth status`](https://github.com/etcd-io/etcd/pull/11536) command to check if authentication is enabled - Add [`etcdctl get --count-only`](https://github.com/etcd-io/etcd/pull/11743) flag for output type `fields`. +- Add [`etcdctl member list -w=json --hex`](https://github.com/etcd-io/etcd/pull/11812) flag to print memberListResponse in hex format json. ### gRPC gateway diff --git a/etcdctl/ctlv3/command/printer.go b/etcdctl/ctlv3/command/printer.go index 686c49975..2bb6086c8 100644 --- a/etcdctl/ctlv3/command/printer.go +++ b/etcdctl/ctlv3/command/printer.go @@ -78,7 +78,7 @@ func NewPrinter(printerType string, isHex bool) printer { case "fields": return &fieldsPrinter{newPrinterUnsupported("fields")} case "json": - return newJSONPrinter() + return newJSONPrinter(isHex) case "protobuf": return newPBPrinter() case "table": diff --git a/etcdctl/ctlv3/command/printer_json.go b/etcdctl/ctlv3/command/printer_json.go index 2a6ea9f21..a368d3c84 100644 --- a/etcdctl/ctlv3/command/printer_json.go +++ b/etcdctl/ctlv3/command/printer_json.go @@ -15,18 +15,25 @@ package command import ( + "bytes" "encoding/json" "fmt" "os" + "strconv" + "go.etcd.io/etcd/v3/clientv3" "go.etcd.io/etcd/v3/clientv3/snapshot" ) -type jsonPrinter struct{ printer } +type jsonPrinter struct { + isHex bool + printer +} -func newJSONPrinter() printer { +func newJSONPrinter(isHex bool) printer { return &jsonPrinter{ - &printerRPC{newPrinterUnsupported("json"), printJSON}, + isHex: isHex, + printer: &printerRPC{newPrinterUnsupported("json"), printJSON}, } } @@ -35,6 +42,14 @@ func (p *jsonPrinter) EndpointStatus(r []epStatus) { printJSON(r) } func (p *jsonPrinter) EndpointHashKV(r []epHashKV) { printJSON(r) } func (p *jsonPrinter) DBStatus(r snapshot.Status) { printJSON(r) } +func (p *jsonPrinter) MemberList(r clientv3.MemberListResponse) { + if p.isHex { + printMemberListWithHexJSON(r) + } else { + printJSON(r) + } +} + func printJSON(v interface{}) { b, err := json.Marshal(v) if err != nil { @@ -43,3 +58,46 @@ func printJSON(v interface{}) { } fmt.Println(string(b)) } + +func printMemberListWithHexJSON(r clientv3.MemberListResponse) { + var buffer bytes.Buffer + var b []byte + buffer.WriteString("{\"header\":{\"cluster_id\":\"") + b = strconv.AppendUint(nil, r.Header.ClusterId, 16) + buffer.Write(b) + buffer.WriteString("\",\"member_id\":\"") + b = strconv.AppendUint(nil, r.Header.MemberId, 16) + buffer.Write(b) + buffer.WriteString("\",\"raft_term\":") + b = strconv.AppendUint(nil, r.Header.RaftTerm, 16) + buffer.Write(b) + buffer.WriteByte('}') + for i := 0; i < len(r.Members); i++ { + if i == 0 { + buffer.WriteString(",\"members\":[{\"ID\":\"") + } else { + buffer.WriteString(",{\"ID\":\"") + } + b = strconv.AppendUint(nil, r.Members[i].ID, 16) + buffer.Write(b) + buffer.WriteString("\",\"name\":\"" + r.Members[i].Name + "\"," + "\"peerURLs\":") + b, err := json.Marshal(r.Members[i].PeerURLs) + if err != nil { + return + } + buffer.Write(b) + buffer.WriteString(",\"clientURLS\":") + b, err = json.Marshal(r.Members[i].ClientURLs) + if err != nil { + return + } + buffer.Write(b) + buffer.WriteByte('}') + if i == len(r.Members)-1 { + buffer.WriteString("]") + } + } + buffer.WriteString("}") + fmt.Println(string(buffer.Bytes())) + +} diff --git a/tests/e2e/ctl_v3_member_test.go b/tests/e2e/ctl_v3_member_test.go index 06da3b0f7..b324160fd 100644 --- a/tests/e2e/ctl_v3_member_test.go +++ b/tests/e2e/ctl_v3_member_test.go @@ -18,6 +18,7 @@ import ( "encoding/json" "fmt" "io" + "reflect" "strings" "testing" @@ -25,6 +26,7 @@ import ( ) func TestCtlV3MemberList(t *testing.T) { testCtl(t, memberListTest) } +func TestCtlV3MemberListWithHex(t *testing.T) { testCtl(t, memberListWithHexTest) } func TestCtlV3MemberListNoTLS(t *testing.T) { testCtl(t, memberListTest, withCfg(configNoTLS)) } func TestCtlV3MemberListClientTLS(t *testing.T) { testCtl(t, memberListTest, withCfg(configClientTLS)) } func TestCtlV3MemberListClientAutoTLS(t *testing.T) { @@ -110,6 +112,52 @@ func getMemberList(cx ctlCtx) (etcdserverpb.MemberListResponse, error) { return resp, nil } +func memberListWithHexTest(cx ctlCtx) { + resp, err := getMemberList(cx) + if err != nil { + cx.t.Fatalf("getMemberList error (%v)", err) + } + + cmdArgs := append(cx.PrefixArgs(), "--write-out", "json", "--hex", "member", "list") + + proc, err := spawnCmd(cmdArgs) + if err != nil { + cx.t.Fatalf("memberListWithHexTest error (%v)", err) + } + var txt string + txt, err = proc.Expect("members") + if err != nil { + cx.t.Fatalf("memberListWithHexTest error (%v)", err) + } + if err = proc.Close(); err != nil { + cx.t.Fatalf("memberListWithHexTest error (%v)", err) + } + hexResp := etcdserverpb.MemberListResponse{} + dec := json.NewDecoder(strings.NewReader(txt)) + if err := dec.Decode(&hexResp); err == io.EOF { + cx.t.Fatalf("memberListWithHexTest error (%v)", err) + } + num := len(resp.Members) + hexNum := len(hexResp.Members) + if num != hexNum { + cx.t.Fatalf("member number,expected %d,got %d", num, hexNum) + } + if num == 0 { + cx.t.Fatal("member number is 0") + } + for i := 0; i < num; i++ { + if resp.Members[i].Name != hexResp.Members[i].Name { + cx.t.Fatalf("member name,expected %v,got %v", resp.Members[i].Name, hexResp.Members[i].Name) + } + if !reflect.DeepEqual(resp.Members[i].PeerURLs, hexResp.Members[i].PeerURLs) { + cx.t.Fatalf("member peerURLs,expected %v,got %v", resp.Members[i].PeerURLs, hexResp.Members[i].PeerURLs) + } + if !reflect.DeepEqual(resp.Members[i].ClientURLs, hexResp.Members[i].ClientURLs) { + cx.t.Fatalf("member clientURLS,expected %v,got %v", resp.Members[i].ClientURLs, hexResp.Members[i].ClientURLs) + } + } +} + func memberRemoveTest(cx ctlCtx) { ep, memIDToRemove, clusterID := cx.memberToRemove() if err := ctlV3MemberRemove(cx, ep, memIDToRemove, clusterID); err != nil {