Browse Source

etcdctl: tighten up output, reorganize README.md

Documentation was far too repetitive, making it a chore to read and
make changes. All commands are now organized by functionality and all
repetitive bits about return values and output are in a generalized
subsections.

etcdctl's output handling was missing a lot of commands. Similarly,
in many cases an output format could be given but fail to report
an error as expected.
Anthony Romano 9 years ago
parent
commit
780d2f2a59

File diff suppressed because it is too large
+ 244 - 348
etcdctl/README.md


+ 3 - 4
etcdctl/ctlv3/command/member_command.go

@@ -112,7 +112,7 @@ func memberAddCommandFunc(cmd *cobra.Command, args []string) {
 		ExitWithError(ExitError, err)
 		ExitWithError(ExitError, err)
 	}
 	}
 
 
-	fmt.Printf("Member %16x added to cluster %16x\n", resp.Member.ID, resp.Header.ClusterId)
+	display.MemberAdd(*resp)
 }
 }
 
 
 // memberRemoveCommandFunc executes the "member remove" command.
 // memberRemoveCommandFunc executes the "member remove" command.
@@ -132,8 +132,7 @@ func memberRemoveCommandFunc(cmd *cobra.Command, args []string) {
 	if err != nil {
 	if err != nil {
 		ExitWithError(ExitError, err)
 		ExitWithError(ExitError, err)
 	}
 	}
-
-	fmt.Printf("Member %16x removed from cluster %16x\n", id, resp.Header.ClusterId)
+	display.MemberRemove(id, *resp)
 }
 }
 
 
 // memberUpdateCommandFunc executes the "member update" command.
 // memberUpdateCommandFunc executes the "member update" command.
@@ -160,7 +159,7 @@ func memberUpdateCommandFunc(cmd *cobra.Command, args []string) {
 		ExitWithError(ExitError, err)
 		ExitWithError(ExitError, err)
 	}
 	}
 
 
-	fmt.Printf("Member %16x updated in cluster %16x\n", id, resp.Header.ClusterId)
+	display.MemberUpdate(id, *resp)
 }
 }
 
 
 // memberListCommandFunc executes the "member list" command.
 // memberListCommandFunc executes the "member list" command.

+ 85 - 359
etcdctl/ctlv3/command/printer.go

@@ -15,17 +15,14 @@
 package command
 package command
 
 
 import (
 import (
-	"encoding/json"
 	"errors"
 	"errors"
 	"fmt"
 	"fmt"
-	"os"
 	"strings"
 	"strings"
 
 
 	v3 "github.com/coreos/etcd/clientv3"
 	v3 "github.com/coreos/etcd/clientv3"
-	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
-	spb "github.com/coreos/etcd/mvcc/mvccpb"
 	"github.com/dustin/go-humanize"
 	"github.com/dustin/go-humanize"
-	"github.com/olekukonko/tablewriter"
+
+	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
 )
 )
 
 
 type printer interface {
 type printer interface {
@@ -37,12 +34,30 @@ type printer interface {
 
 
 	TimeToLive(r v3.LeaseTimeToLiveResponse, keys bool)
 	TimeToLive(r v3.LeaseTimeToLiveResponse, keys bool)
 
 
+	MemberAdd(v3.MemberAddResponse)
+	MemberRemove(id uint64, r v3.MemberRemoveResponse)
+	MemberUpdate(id uint64, r v3.MemberUpdateResponse)
 	MemberList(v3.MemberListResponse)
 	MemberList(v3.MemberListResponse)
 
 
 	EndpointStatus([]epStatus)
 	EndpointStatus([]epStatus)
 
 
 	Alarm(v3.AlarmResponse)
 	Alarm(v3.AlarmResponse)
 	DBStatus(dbstatus)
 	DBStatus(dbstatus)
+
+	RoleAdd(role string, r v3.AuthRoleAddResponse)
+	RoleGet(role string, r v3.AuthRoleGetResponse)
+	RoleDelete(role string, r v3.AuthRoleDeleteResponse)
+	RoleList(v3.AuthRoleListResponse)
+	RoleGrantPermission(role string, r v3.AuthRoleGrantPermissionResponse)
+	RoleRevokePermission(role string, key string, end string, r v3.AuthRoleRevokePermissionResponse)
+
+	UserAdd(user string, r v3.AuthUserAddResponse)
+	UserGet(user string, r v3.AuthUserGetResponse)
+	UserList(r v3.AuthUserListResponse)
+	UserChangePassword(v3.AuthUserChangePasswordResponse)
+	UserGrantRole(user string, role string, r v3.AuthUserGrantRoleResponse)
+	UserRevokeRole(user string, role string, r v3.AuthUserRevokeRoleResponse)
+	UserDelete(user string, r v3.AuthUserDeleteResponse)
 }
 }
 
 
 func NewPrinter(printerType string, isHex bool) printer {
 func NewPrinter(printerType string, isHex bool) printer {
@@ -50,17 +65,78 @@ func NewPrinter(printerType string, isHex bool) printer {
 	case "simple":
 	case "simple":
 		return &simplePrinter{isHex: isHex}
 		return &simplePrinter{isHex: isHex}
 	case "fields":
 	case "fields":
-		return &fieldsPrinter{}
+		return &fieldsPrinter{newPrinterUnsupported("fields")}
 	case "json":
 	case "json":
-		return &jsonPrinter{}
+		return newJSONPrinter()
 	case "protobuf":
 	case "protobuf":
-		return &pbPrinter{}
+		return newPBPrinter()
 	case "table":
 	case "table":
-		return &tablePrinter{}
+		return &tablePrinter{newPrinterUnsupported("table")}
 	}
 	}
 	return nil
 	return nil
 }
 }
 
 
+type printerRPC struct {
+	printer
+	p func(interface{})
+}
+
+func (p *printerRPC) Del(r v3.DeleteResponse)                            { p.p((*pb.DeleteRangeResponse)(&r)) }
+func (p *printerRPC) Get(r v3.GetResponse)                               { p.p((*pb.RangeResponse)(&r)) }
+func (p *printerRPC) Put(r v3.PutResponse)                               { p.p((*pb.PutResponse)(&r)) }
+func (p *printerRPC) Txn(r v3.TxnResponse)                               { p.p((*pb.TxnResponse)(&r)) }
+func (p *printerRPC) Watch(r v3.WatchResponse)                           { p.p(&r) }
+func (p *printerRPC) TimeToLive(r v3.LeaseTimeToLiveResponse, keys bool) { p.p(&r) }
+func (p *printerRPC) MemberAdd(r v3.MemberAddResponse)                   { p.p((*pb.MemberAddResponse)(&r)) }
+func (p *printerRPC) MemberRemove(id uint64, r v3.MemberRemoveResponse) {
+	p.p((*pb.MemberRemoveResponse)(&r))
+}
+func (p *printerRPC) MemberUpdate(id uint64, r v3.MemberUpdateResponse) {
+	p.p((*pb.MemberUpdateResponse)(&r))
+}
+func (p *printerRPC) MemberList(r v3.MemberListResponse) { p.p((*pb.MemberListResponse)(&r)) }
+func (p *printerRPC) Alarm(r v3.AlarmResponse)           { p.p((*pb.AlarmResponse)(&r)) }
+
+func (p *printerRPC) RoleAdd(_ string, r v3.AuthRoleAddResponse) { p.p((*pb.AuthRoleAddResponse)(&r)) }
+func (p *printerRPC) RoleGet(_ string, r v3.AuthRoleGetResponse) { p.p((*pb.AuthRoleGetResponse)(&r)) }
+func (p *printerRPC) RoleDelete(_ string, r v3.AuthRoleDeleteResponse) {
+	p.p((*pb.AuthRoleDeleteResponse)(&r))
+}
+func (p *printerRPC) RoleList(r v3.AuthRoleListResponse) { p.p((*pb.AuthRoleListResponse)(&r)) }
+func (p *printerRPC) RoleGrantPermission(_ string, r v3.AuthRoleGrantPermissionResponse) {
+	p.p((*pb.AuthRoleGrantPermissionResponse)(&r))
+}
+func (p *printerRPC) RoleRevokePermission(_ string, _ string, _ string, r v3.AuthRoleRevokePermissionResponse) {
+	p.p((*pb.AuthRoleRevokePermissionResponse)(&r))
+}
+func (p *printerRPC) UserAdd(_ string, r v3.AuthUserAddResponse) { p.p((*pb.AuthUserAddResponse)(&r)) }
+func (p *printerRPC) UserGet(_ string, r v3.AuthUserGetResponse) { p.p((*pb.AuthUserGetResponse)(&r)) }
+func (p *printerRPC) UserList(r v3.AuthUserListResponse)         { p.p((*pb.AuthUserListResponse)(&r)) }
+func (p *printerRPC) UserChangePassword(r v3.AuthUserChangePasswordResponse) {
+	p.p((*pb.AuthUserChangePasswordResponse)(&r))
+}
+func (p *printerRPC) UserGrantRole(_ string, _ string, r v3.AuthUserGrantRoleResponse) {
+	p.p((*pb.AuthUserGrantRoleResponse)(&r))
+}
+func (p *printerRPC) UserRevokeRole(_ string, _ string, r v3.AuthUserRevokeRoleResponse) {
+	p.p((*pb.AuthUserRevokeRoleResponse)(&r))
+}
+func (p *printerRPC) UserDelete(_ string, r v3.AuthUserDeleteResponse) {
+	p.p((*pb.AuthUserDeleteResponse)(&r))
+}
+
+type printerUnsupported struct{ printerRPC }
+
+func newPrinterUnsupported(n string) printer {
+	f := func(interface{}) {
+		ExitWithError(ExitBadFeature, errors.New(n+" not supported as output format"))
+	}
+	return &printerUnsupported{printerRPC{nil, f}}
+}
+
+func (p *printerUnsupported) EndpointStatus([]epStatus) { p.p(nil) }
+func (p *printerUnsupported) DBStatus(dbstatus)         { p.p(nil) }
+
 func makeMemberListTable(r v3.MemberListResponse) (hdr []string, rows [][]string) {
 func makeMemberListTable(r v3.MemberListResponse) (hdr []string, rows [][]string) {
 	hdr = []string{"ID", "Status", "Name", "Peer Addrs", "Client Addrs"}
 	hdr = []string{"ID", "Status", "Name", "Peer Addrs", "Client Addrs"}
 	for _, m := range r.Members {
 	for _, m := range r.Members {
@@ -105,353 +181,3 @@ func makeDBStatusTable(ds dbstatus) (hdr []string, rows [][]string) {
 	})
 	})
 	return
 	return
 }
 }
-
-type simplePrinter struct {
-	isHex     bool
-	valueOnly bool
-}
-
-func (s *simplePrinter) Del(resp v3.DeleteResponse) {
-	fmt.Println(resp.Deleted)
-	for _, kv := range resp.PrevKvs {
-		printKV(s.isHex, s.valueOnly, kv)
-	}
-}
-
-func (s *simplePrinter) Get(resp v3.GetResponse) {
-	for _, kv := range resp.Kvs {
-		printKV(s.isHex, s.valueOnly, kv)
-	}
-}
-
-func (s *simplePrinter) Put(r v3.PutResponse) {
-	fmt.Println("OK")
-	if r.PrevKv != nil {
-		printKV(s.isHex, s.valueOnly, r.PrevKv)
-	}
-}
-
-func (s *simplePrinter) Txn(resp v3.TxnResponse) {
-	if resp.Succeeded {
-		fmt.Println("SUCCESS")
-	} else {
-		fmt.Println("FAILURE")
-	}
-
-	for _, r := range resp.Responses {
-		fmt.Println("")
-		switch v := r.Response.(type) {
-		case *pb.ResponseOp_ResponseDeleteRange:
-			s.Del((v3.DeleteResponse)(*v.ResponseDeleteRange))
-		case *pb.ResponseOp_ResponsePut:
-			s.Put((v3.PutResponse)(*v.ResponsePut))
-		case *pb.ResponseOp_ResponseRange:
-			s.Get(((v3.GetResponse)(*v.ResponseRange)))
-		default:
-			fmt.Printf("unexpected response %+v\n", r)
-		}
-	}
-}
-
-func (s *simplePrinter) Watch(resp v3.WatchResponse) {
-	for _, e := range resp.Events {
-		fmt.Println(e.Type)
-		if e.PrevKv != nil {
-			printKV(s.isHex, s.valueOnly, e.PrevKv)
-		}
-		printKV(s.isHex, s.valueOnly, e.Kv)
-	}
-}
-
-func (s *simplePrinter) TimeToLive(resp v3.LeaseTimeToLiveResponse, keys bool) {
-	txt := fmt.Sprintf("lease %016x granted with TTL(%ds), remaining(%ds)", resp.ID, resp.GrantedTTL, resp.TTL)
-	if keys {
-		ks := make([]string, len(resp.Keys))
-		for i := range resp.Keys {
-			ks[i] = string(resp.Keys[i])
-		}
-		txt += fmt.Sprintf(", attached keys(%v)", ks)
-	}
-	fmt.Println(txt)
-}
-
-func (s *simplePrinter) Alarm(resp v3.AlarmResponse) {
-	for _, e := range resp.Alarms {
-		fmt.Printf("%+v\n", e)
-	}
-}
-
-func (s *simplePrinter) MemberList(resp v3.MemberListResponse) {
-	_, rows := makeMemberListTable(resp)
-	for _, row := range rows {
-		fmt.Println(strings.Join(row, ", "))
-	}
-}
-
-func (s *simplePrinter) EndpointStatus(statusList []epStatus) {
-	_, rows := makeEndpointStatusTable(statusList)
-	for _, row := range rows {
-		fmt.Println(strings.Join(row, ", "))
-	}
-}
-
-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) TimeToLive(r v3.LeaseTimeToLiveResponse, keys bool) {
-	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(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()
-}
-
-type jsonPrinter struct{}
-
-func (p *jsonPrinter) Del(r v3.DeleteResponse)                            { printJSON(r) }
-func (p *jsonPrinter) Get(r v3.GetResponse)                               { printJSON(r) }
-func (p *jsonPrinter) Put(r v3.PutResponse)                               { printJSON(r) }
-func (p *jsonPrinter) Txn(r v3.TxnResponse)                               { printJSON(r) }
-func (p *jsonPrinter) Watch(r v3.WatchResponse)                           { printJSON(r) }
-func (p *jsonPrinter) TimeToLive(r v3.LeaseTimeToLiveResponse, keys bool) { printJSON(r) }
-func (p *jsonPrinter) Alarm(r v3.AlarmResponse)                           { printJSON(r) }
-func (p *jsonPrinter) MemberList(r v3.MemberListResponse)                 { printJSON(r) }
-func (p *jsonPrinter) EndpointStatus(r []epStatus)                        { printJSON(r) }
-func (p *jsonPrinter) DBStatus(r dbstatus)                                { printJSON(r) }
-
-func printJSON(v interface{}) {
-	b, err := json.Marshal(v)
-	if err != nil {
-		fmt.Fprintf(os.Stderr, "%v\n", err)
-		return
-	}
-	fmt.Println(string(b))
-}
-
-type pbPrinter struct{}
-
-type pbMarshal interface {
-	Marshal() ([]byte, error)
-}
-
-func (p *pbPrinter) Del(r v3.DeleteResponse) {
-	printPB((*pb.DeleteRangeResponse)(&r))
-}
-
-func (p *pbPrinter) Get(r v3.GetResponse) {
-	printPB((*pb.RangeResponse)(&r))
-}
-
-func (p *pbPrinter) Put(r v3.PutResponse) {
-	printPB((*pb.PutResponse)(&r))
-}
-
-func (p *pbPrinter) Txn(r v3.TxnResponse) {
-	printPB((*pb.TxnResponse)(&r))
-}
-
-func (p *pbPrinter) Watch(r v3.WatchResponse) {
-	for _, ev := range r.Events {
-		printPB((*spb.Event)(ev))
-	}
-}
-
-func (p *pbPrinter) TimeToLive(r v3.LeaseTimeToLiveResponse, keys bool) {
-	ExitWithError(ExitBadFeature, errors.New("only support simple or json as output format"))
-}
-
-func (p *pbPrinter) Alarm(r v3.AlarmResponse) {
-	printPB((*pb.AlarmResponse)(&r))
-}
-
-func (p *pbPrinter) MemberList(r v3.MemberListResponse) {
-	printPB((*pb.MemberListResponse)(&r))
-}
-
-func (p *pbPrinter) EndpointStatus(statusList []epStatus) {
-	ExitWithError(ExitBadFeature, errors.New("only support simple or json as output format"))
-}
-
-func (p *pbPrinter) DBStatus(r dbstatus) {
-	ExitWithError(ExitBadFeature, errors.New("only support simple or json as output format"))
-}
-
-func printPB(m pbMarshal) {
-	b, err := m.Marshal()
-	if err != nil {
-		fmt.Fprintf(os.Stderr, "%v\n", err)
-		return
-	}
-	fmt.Printf(string(b))
-}
-
-type fieldsPrinter struct{}
-
-func (p *fieldsPrinter) kv(pfx string, kv *spb.KeyValue) {
-	fmt.Printf("\"%sKey\" : %q\n", pfx, string(kv.Key))
-	fmt.Printf("\"%sCreateRevision\" : %d\n", pfx, kv.CreateRevision)
-	fmt.Printf("\"%sModRevision\" : %d\n", pfx, kv.ModRevision)
-	fmt.Printf("\"%sVersion\" : %d\n", pfx, kv.Version)
-	fmt.Printf("\"%sValue\" : %q\n", pfx, string(kv.Value))
-	fmt.Printf("\"%sLease\" : %d\n", pfx, string(kv.Lease))
-}
-
-func (p *fieldsPrinter) hdr(h *pb.ResponseHeader) {
-	fmt.Println(`"ClusterID" :`, h.ClusterId)
-	fmt.Println(`"MemberID" :`, h.MemberId)
-	fmt.Println(`"Revision" :`, h.Revision)
-	fmt.Println(`"RaftTerm" :`, h.RaftTerm)
-}
-
-func (p *fieldsPrinter) Del(r v3.DeleteResponse) {
-	p.hdr(r.Header)
-	fmt.Println(`"Deleted" :`, r.Deleted)
-	for _, kv := range r.PrevKvs {
-		p.kv("Prev", kv)
-	}
-}
-
-func (p *fieldsPrinter) Get(r v3.GetResponse) {
-	p.hdr(r.Header)
-	for _, kv := range r.Kvs {
-		p.kv("", kv)
-	}
-	fmt.Println(`"More" :`, r.More)
-	fmt.Println(`"Count" :`, r.Count)
-}
-
-func (p *fieldsPrinter) Put(r v3.PutResponse) {
-	p.hdr(r.Header)
-	if r.PrevKv != nil {
-		p.kv("Prev", r.PrevKv)
-	}
-}
-
-func (p *fieldsPrinter) Txn(r v3.TxnResponse) {
-	p.hdr(r.Header)
-	fmt.Println(`"Succeeded" :`, r.Succeeded)
-	for _, resp := range r.Responses {
-		switch v := resp.Response.(type) {
-		case *pb.ResponseOp_ResponseDeleteRange:
-			p.Del((v3.DeleteResponse)(*v.ResponseDeleteRange))
-		case *pb.ResponseOp_ResponsePut:
-			p.Put((v3.PutResponse)(*v.ResponsePut))
-		case *pb.ResponseOp_ResponseRange:
-			p.Get((v3.GetResponse)(*v.ResponseRange))
-		default:
-			fmt.Printf("\"Unknown\" : %q\n", fmt.Sprintf("%+v", v))
-		}
-	}
-}
-
-func (p *fieldsPrinter) Watch(resp v3.WatchResponse) {
-	p.hdr(&resp.Header)
-	for _, e := range resp.Events {
-		fmt.Println(`"Type" : `, e.Type)
-		if e.PrevKv != nil {
-			p.kv("Prev", e.PrevKv)
-		}
-		p.kv("", e.Kv)
-	}
-}
-
-func (p *fieldsPrinter) TimeToLive(r v3.LeaseTimeToLiveResponse, keys bool) {
-	p.hdr(r.ResponseHeader)
-	fmt.Println(`"ID" :`, r.ID)
-	fmt.Println(`"TTL" :`, r.TTL)
-	fmt.Println(`"GrantedTTL" :`, r.GrantedTTL)
-	for _, k := range r.Keys {
-		fmt.Printf("\"Key\" : %q\n", string(k))
-	}
-}
-
-func (p *fieldsPrinter) MemberList(r v3.MemberListResponse) {
-	p.hdr(r.Header)
-	for _, m := range r.Members {
-		fmt.Println(`"ID" :`, m.ID)
-		fmt.Printf("\"Name\" : %q\n", m.Name)
-		for _, u := range m.PeerURLs {
-			fmt.Printf("\"PeerURL\" : %q\n", u)
-		}
-		for _, u := range m.ClientURLs {
-			fmt.Printf("\"ClientURL\" : %q\n", u)
-		}
-		fmt.Println()
-	}
-}
-
-func (p *fieldsPrinter) EndpointStatus(eps []epStatus) {
-	for _, ep := range eps {
-		p.hdr(ep.Resp.Header)
-		fmt.Printf("\"Version\" : %q\n", ep.Resp.Version)
-		fmt.Println(`"DBSize" :"`, ep.Resp.DbSize)
-		fmt.Println(`"Leader" :"`, ep.Resp.Leader)
-		fmt.Println(`"RaftIndex" :"`, ep.Resp.RaftIndex)
-		fmt.Println(`"RaftTerm" :"`, ep.Resp.RaftTerm)
-		fmt.Printf("\"Endpoint\" : %q\n", ep.Ep)
-		fmt.Println()
-	}
-}
-
-func (p *fieldsPrinter) Alarm(r v3.AlarmResponse) {
-	p.hdr(r.Header)
-	for _, a := range r.Alarms {
-		fmt.Println(`"MemberID" :`, a.MemberID)
-		fmt.Println(`"AlarmType" :`, a.Alarm)
-		fmt.Println()
-	}
-}
-
-func (p *fieldsPrinter) DBStatus(r dbstatus) {
-	fmt.Println(`"Hash" :`, r.Hash)
-	fmt.Println(`"Revision" :`, r.Revision)
-	fmt.Println(`"Keys" :`, r.TotalKey)
-	fmt.Println(`"Size" :`, r.TotalSize)
-}

+ 167 - 0
etcdctl/ctlv3/command/printer_fields.go

@@ -0,0 +1,167 @@
+// Copyright 2016 The etcd Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package command
+
+import (
+	"fmt"
+
+	v3 "github.com/coreos/etcd/clientv3"
+	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
+	spb "github.com/coreos/etcd/mvcc/mvccpb"
+)
+
+type fieldsPrinter struct{ printer }
+
+func (p *fieldsPrinter) kv(pfx string, kv *spb.KeyValue) {
+	fmt.Printf("\"%sKey\" : %q\n", pfx, string(kv.Key))
+	fmt.Printf("\"%sCreateRevision\" : %d\n", pfx, kv.CreateRevision)
+	fmt.Printf("\"%sModRevision\" : %d\n", pfx, kv.ModRevision)
+	fmt.Printf("\"%sVersion\" : %d\n", pfx, kv.Version)
+	fmt.Printf("\"%sValue\" : %q\n", pfx, string(kv.Value))
+	fmt.Printf("\"%sLease\" : %d\n", pfx, string(kv.Lease))
+}
+
+func (p *fieldsPrinter) hdr(h *pb.ResponseHeader) {
+	fmt.Println(`"ClusterID" :`, h.ClusterId)
+	fmt.Println(`"MemberID" :`, h.MemberId)
+	fmt.Println(`"Revision" :`, h.Revision)
+	fmt.Println(`"RaftTerm" :`, h.RaftTerm)
+}
+
+func (p *fieldsPrinter) Del(r v3.DeleteResponse) {
+	p.hdr(r.Header)
+	fmt.Println(`"Deleted" :`, r.Deleted)
+	for _, kv := range r.PrevKvs {
+		p.kv("Prev", kv)
+	}
+}
+
+func (p *fieldsPrinter) Get(r v3.GetResponse) {
+	p.hdr(r.Header)
+	for _, kv := range r.Kvs {
+		p.kv("", kv)
+	}
+	fmt.Println(`"More" :`, r.More)
+	fmt.Println(`"Count" :`, r.Count)
+}
+
+func (p *fieldsPrinter) Put(r v3.PutResponse) {
+	p.hdr(r.Header)
+	if r.PrevKv != nil {
+		p.kv("Prev", r.PrevKv)
+	}
+}
+
+func (p *fieldsPrinter) Txn(r v3.TxnResponse) {
+	p.hdr(r.Header)
+	fmt.Println(`"Succeeded" :`, r.Succeeded)
+	for _, resp := range r.Responses {
+		switch v := resp.Response.(type) {
+		case *pb.ResponseOp_ResponseDeleteRange:
+			p.Del((v3.DeleteResponse)(*v.ResponseDeleteRange))
+		case *pb.ResponseOp_ResponsePut:
+			p.Put((v3.PutResponse)(*v.ResponsePut))
+		case *pb.ResponseOp_ResponseRange:
+			p.Get((v3.GetResponse)(*v.ResponseRange))
+		default:
+			fmt.Printf("\"Unknown\" : %q\n", fmt.Sprintf("%+v", v))
+		}
+	}
+}
+
+func (p *fieldsPrinter) Watch(resp v3.WatchResponse) {
+	p.hdr(&resp.Header)
+	for _, e := range resp.Events {
+		fmt.Println(`"Type" :`, e.Type)
+		if e.PrevKv != nil {
+			p.kv("Prev", e.PrevKv)
+		}
+		p.kv("", e.Kv)
+	}
+}
+
+func (p *fieldsPrinter) TimeToLive(r v3.LeaseTimeToLiveResponse, keys bool) {
+	p.hdr(r.ResponseHeader)
+	fmt.Println(`"ID" :`, r.ID)
+	fmt.Println(`"TTL" :`, r.TTL)
+	fmt.Println(`"GrantedTTL" :`, r.GrantedTTL)
+	for _, k := range r.Keys {
+		fmt.Printf("\"Key\" : %q\n", string(k))
+	}
+}
+
+func (p *fieldsPrinter) MemberList(r v3.MemberListResponse) {
+	p.hdr(r.Header)
+	for _, m := range r.Members {
+		fmt.Println(`"ID" :`, m.ID)
+		fmt.Printf("\"Name\" : %q\n", m.Name)
+		for _, u := range m.PeerURLs {
+			fmt.Printf("\"PeerURL\" : %q\n", u)
+		}
+		for _, u := range m.ClientURLs {
+			fmt.Printf("\"ClientURL\" : %q\n", u)
+		}
+		fmt.Println()
+	}
+}
+
+func (p *fieldsPrinter) EndpointStatus(eps []epStatus) {
+	for _, ep := range eps {
+		p.hdr(ep.Resp.Header)
+		fmt.Printf("\"Version\" : %q\n", ep.Resp.Version)
+		fmt.Println(`"DBSize" :"`, ep.Resp.DbSize)
+		fmt.Println(`"Leader" :"`, ep.Resp.Leader)
+		fmt.Println(`"RaftIndex" :"`, ep.Resp.RaftIndex)
+		fmt.Println(`"RaftTerm" :"`, ep.Resp.RaftTerm)
+		fmt.Printf("\"Endpoint\" : %q\n", ep.Ep)
+		fmt.Println()
+	}
+}
+
+func (p *fieldsPrinter) Alarm(r v3.AlarmResponse) {
+	p.hdr(r.Header)
+	for _, a := range r.Alarms {
+		fmt.Println(`"MemberID" :`, a.MemberID)
+		fmt.Println(`"AlarmType" :`, a.Alarm)
+		fmt.Println()
+	}
+}
+
+func (p *fieldsPrinter) DBStatus(r dbstatus) {
+	fmt.Println(`"Hash" :`, r.Hash)
+	fmt.Println(`"Revision" :`, r.Revision)
+	fmt.Println(`"Keys" :`, r.TotalKey)
+	fmt.Println(`"Size" :`, r.TotalSize)
+}
+
+func (p *fieldsPrinter) RoleAdd(role string, r v3.AuthRoleAddResponse)       { p.hdr(r.Header) }
+func (p *fieldsPrinter) RoleGet(role string, r v3.AuthRoleGetResponse)       { p.hdr(r.Header) }
+func (p *fieldsPrinter) RoleDelete(role string, r v3.AuthRoleDeleteResponse) { p.hdr(r.Header) }
+func (p *fieldsPrinter) RoleList(r v3.AuthRoleListResponse)                  { p.hdr(r.Header) }
+func (p *fieldsPrinter) RoleGrantPermission(role string, r v3.AuthRoleGrantPermissionResponse) {
+	p.hdr(r.Header)
+}
+func (p *fieldsPrinter) RoleRevokePermission(role string, key string, end string, r v3.AuthRoleRevokePermissionResponse) {
+	p.hdr(r.Header)
+}
+func (p *fieldsPrinter) UserAdd(user string, r v3.AuthUserAddResponse)          { p.hdr(r.Header) }
+func (p *fieldsPrinter) UserChangePassword(r v3.AuthUserChangePasswordResponse) { p.hdr(r.Header) }
+func (p *fieldsPrinter) UserGrantRole(user string, role string, r v3.AuthUserGrantRoleResponse) {
+	p.hdr(r.Header)
+}
+func (p *fieldsPrinter) UserRevokeRole(user string, role string, r v3.AuthUserRevokeRoleResponse) {
+	p.hdr(r.Header)
+}
+func (p *fieldsPrinter) UserDelete(user string, r v3.AuthUserDeleteResponse) { p.hdr(r.Header) }

+ 41 - 0
etcdctl/ctlv3/command/printer_json.go

@@ -0,0 +1,41 @@
+// Copyright 2016 The etcd Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package command
+
+import (
+	"encoding/json"
+	"fmt"
+	"os"
+)
+
+type jsonPrinter struct{ printer }
+
+func newJSONPrinter() printer {
+	return &jsonPrinter{
+		&printerRPC{newPrinterUnsupported("json"), printJSON},
+	}
+}
+
+func (p *jsonPrinter) EndpointStatus(r []epStatus) { printJSON(r) }
+func (p *jsonPrinter) DBStatus(r dbstatus)         { printJSON(r) }
+
+func printJSON(v interface{}) {
+	b, err := json.Marshal(v)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "%v\n", err)
+		return
+	}
+	fmt.Println(string(b))
+}

+ 64 - 0
etcdctl/ctlv3/command/printer_protobuf.go

@@ -0,0 +1,64 @@
+// Copyright 2016 The etcd Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package command
+
+import (
+	"fmt"
+	"os"
+
+	v3 "github.com/coreos/etcd/clientv3"
+	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
+	mvccpb "github.com/coreos/etcd/mvcc/mvccpb"
+)
+
+type pbPrinter struct{ printer }
+
+type pbMarshal interface {
+	Marshal() ([]byte, error)
+}
+
+func newPBPrinter() printer {
+	return &pbPrinter{
+		&printerRPC{newPrinterUnsupported("protobuf"), printPB},
+	}
+}
+
+func (p *pbPrinter) Watch(r v3.WatchResponse) {
+	evs := make([]*mvccpb.Event, len(r.Events))
+	for i, ev := range r.Events {
+		evs[i] = (*mvccpb.Event)(ev)
+	}
+	wr := pb.WatchResponse{
+		Header:          &r.Header,
+		Events:          evs,
+		CompactRevision: r.CompactRevision,
+		Canceled:        r.Canceled,
+		Created:         r.Created,
+	}
+	printPB(&wr)
+}
+
+func printPB(v interface{}) {
+	m, ok := v.(pbMarshal)
+	if !ok {
+		ExitWithError(ExitBadFeature, fmt.Errorf("marshal unsupported for type %T (%v)", v, v))
+	}
+	b, err := m.Marshal()
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "%v\n", err)
+		return
+	}
+	fmt.Printf(string(b))
+}

+ 227 - 0
etcdctl/ctlv3/command/printer_simple.go

@@ -0,0 +1,227 @@
+// Copyright 2016 The etcd Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package command
+
+import (
+	"fmt"
+	"strings"
+
+	v3 "github.com/coreos/etcd/clientv3"
+	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
+)
+
+type simplePrinter struct {
+	isHex     bool
+	valueOnly bool
+}
+
+func (s *simplePrinter) Del(resp v3.DeleteResponse) {
+	fmt.Println(resp.Deleted)
+	for _, kv := range resp.PrevKvs {
+		printKV(s.isHex, s.valueOnly, kv)
+	}
+}
+
+func (s *simplePrinter) Get(resp v3.GetResponse) {
+	for _, kv := range resp.Kvs {
+		printKV(s.isHex, s.valueOnly, kv)
+	}
+}
+
+func (s *simplePrinter) Put(r v3.PutResponse) {
+	fmt.Println("OK")
+	if r.PrevKv != nil {
+		printKV(s.isHex, s.valueOnly, r.PrevKv)
+	}
+}
+
+func (s *simplePrinter) Txn(resp v3.TxnResponse) {
+	if resp.Succeeded {
+		fmt.Println("SUCCESS")
+	} else {
+		fmt.Println("FAILURE")
+	}
+
+	for _, r := range resp.Responses {
+		fmt.Println("")
+		switch v := r.Response.(type) {
+		case *pb.ResponseOp_ResponseDeleteRange:
+			s.Del((v3.DeleteResponse)(*v.ResponseDeleteRange))
+		case *pb.ResponseOp_ResponsePut:
+			s.Put((v3.PutResponse)(*v.ResponsePut))
+		case *pb.ResponseOp_ResponseRange:
+			s.Get(((v3.GetResponse)(*v.ResponseRange)))
+		default:
+			fmt.Printf("unexpected response %+v\n", r)
+		}
+	}
+}
+
+func (s *simplePrinter) Watch(resp v3.WatchResponse) {
+	for _, e := range resp.Events {
+		fmt.Println(e.Type)
+		if e.PrevKv != nil {
+			printKV(s.isHex, s.valueOnly, e.PrevKv)
+		}
+		printKV(s.isHex, s.valueOnly, e.Kv)
+	}
+}
+
+func (s *simplePrinter) TimeToLive(resp v3.LeaseTimeToLiveResponse, keys bool) {
+	txt := fmt.Sprintf("lease %016x granted with TTL(%ds), remaining(%ds)", resp.ID, resp.GrantedTTL, resp.TTL)
+	if keys {
+		ks := make([]string, len(resp.Keys))
+		for i := range resp.Keys {
+			ks[i] = string(resp.Keys[i])
+		}
+		txt += fmt.Sprintf(", attached keys(%v)", ks)
+	}
+	fmt.Println(txt)
+}
+
+func (s *simplePrinter) Alarm(resp v3.AlarmResponse) {
+	for _, e := range resp.Alarms {
+		fmt.Printf("%+v\n", e)
+	}
+}
+
+func (s *simplePrinter) MemberAdd(r v3.MemberAddResponse) {
+	fmt.Printf("Member %16x added to cluster %16x\n", r.Member.ID, r.Header.ClusterId)
+}
+
+func (s *simplePrinter) MemberRemove(id uint64, r v3.MemberRemoveResponse) {
+	fmt.Printf("Member %16x removed from cluster %16x\n", id, r.Header.ClusterId)
+}
+
+func (s *simplePrinter) MemberUpdate(id uint64, r v3.MemberUpdateResponse) {
+	fmt.Printf("Member %16x updated in cluster %16x\n", id, r.Header.ClusterId)
+}
+
+func (s *simplePrinter) MemberList(resp v3.MemberListResponse) {
+	_, rows := makeMemberListTable(resp)
+	for _, row := range rows {
+		fmt.Println(strings.Join(row, ", "))
+	}
+}
+
+func (s *simplePrinter) EndpointStatus(statusList []epStatus) {
+	_, rows := makeEndpointStatusTable(statusList)
+	for _, row := range rows {
+		fmt.Println(strings.Join(row, ", "))
+	}
+}
+
+func (s *simplePrinter) DBStatus(ds dbstatus) {
+	_, rows := makeDBStatusTable(ds)
+	for _, row := range rows {
+		fmt.Println(strings.Join(row, ", "))
+	}
+}
+
+func (s *simplePrinter) RoleAdd(role string, r v3.AuthRoleAddResponse) {
+	fmt.Printf("Role %s created\n", role)
+}
+
+func (s *simplePrinter) RoleGet(role string, r v3.AuthRoleGetResponse) {
+	fmt.Printf("Role %s\n", role)
+	fmt.Println("KV Read:")
+
+	printRange := func(perm *v3.Permission) {
+		sKey := string(perm.Key)
+		sRangeEnd := string(perm.RangeEnd)
+		fmt.Printf("\t[%s, %s)", sKey, sRangeEnd)
+		if strings.Compare(v3.GetPrefixRangeEnd(sKey), sRangeEnd) == 0 {
+			fmt.Printf(" (prefix %s)", sKey)
+		}
+		fmt.Printf("\n")
+	}
+
+	for _, perm := range r.Perm {
+		if perm.PermType == v3.PermRead || perm.PermType == v3.PermReadWrite {
+			if len(perm.RangeEnd) == 0 {
+				fmt.Printf("\t%s\n", string(perm.Key))
+			} else {
+				printRange((*v3.Permission)(perm))
+			}
+		}
+	}
+	fmt.Println("KV Write:")
+	for _, perm := range r.Perm {
+		if perm.PermType == v3.PermWrite || perm.PermType == v3.PermReadWrite {
+			if len(perm.RangeEnd) == 0 {
+				fmt.Printf("\t%s\n", string(perm.Key))
+			} else {
+				printRange((*v3.Permission)(perm))
+			}
+		}
+	}
+}
+
+func (s *simplePrinter) RoleList(r v3.AuthRoleListResponse) {
+	for _, role := range r.Roles {
+		fmt.Printf("%s\n", role)
+	}
+}
+
+func (s *simplePrinter) RoleDelete(role string, r v3.AuthRoleDeleteResponse) {
+	fmt.Printf("Role %s deleted\n", role)
+}
+
+func (s *simplePrinter) RoleGrantPermission(role string, r v3.AuthRoleGrantPermissionResponse) {
+	fmt.Printf("Role %s updated\n", role)
+}
+
+func (s *simplePrinter) RoleRevokePermission(role string, key string, end string, r v3.AuthRoleRevokePermissionResponse) {
+	if len(end) == 0 {
+		fmt.Printf("Permission of key %s is revoked from role %s\n", key, role)
+		return
+	}
+	fmt.Printf("Permission of range [%s, %s) is revoked from role %s\n", key, end, role)
+}
+
+func (s *simplePrinter) UserAdd(name string, r v3.AuthUserAddResponse) {
+	fmt.Printf("User %s created\n", name)
+}
+
+func (s *simplePrinter) UserGet(name string, r v3.AuthUserGetResponse) {
+	fmt.Printf("User: %s\n", name)
+	fmt.Printf("Roles:")
+	for _, role := range r.Roles {
+		fmt.Printf(" %s", role)
+	}
+	fmt.Printf("\n")
+}
+
+func (s *simplePrinter) UserChangePassword(v3.AuthUserChangePasswordResponse) {
+	fmt.Println("Password updated")
+}
+
+func (s *simplePrinter) UserGrantRole(user string, role string, r v3.AuthUserGrantRoleResponse) {
+	fmt.Printf("Role %s is granted to user %s\n", role, user)
+}
+
+func (s *simplePrinter) UserRevokeRole(user string, role string, r v3.AuthUserRevokeRoleResponse) {
+	fmt.Printf("Role %s is revoked from user %s\n", role, user)
+}
+
+func (s *simplePrinter) UserDelete(user string, r v3.AuthUserDeleteResponse) {
+	fmt.Printf("User %s deleted\n", user)
+}
+
+func (s *simplePrinter) UserList(r v3.AuthUserListResponse) {
+	for _, user := range r.Users {
+		fmt.Printf("%s\n", user)
+	}
+}

+ 53 - 0
etcdctl/ctlv3/command/printer_table.go

@@ -0,0 +1,53 @@
+// Copyright 2016 The etcd Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package command
+
+import (
+	"os"
+
+	"github.com/olekukonko/tablewriter"
+
+	v3 "github.com/coreos/etcd/clientv3"
+)
+
+type tablePrinter struct{ printer }
+
+func (tp *tablePrinter) MemberList(r v3.MemberListResponse) {
+	hdr, rows := makeMemberListTable(r)
+	table := tablewriter.NewWriter(os.Stdout)
+	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()
+}

+ 10 - 53
etcdctl/ctlv3/command/role_command.go

@@ -16,7 +16,6 @@ package command
 
 
 import (
 import (
 	"fmt"
 	"fmt"
-	"strings"
 
 
 	"github.com/coreos/etcd/clientv3"
 	"github.com/coreos/etcd/clientv3"
 	"github.com/spf13/cobra"
 	"github.com/spf13/cobra"
@@ -102,12 +101,12 @@ func roleAddCommandFunc(cmd *cobra.Command, args []string) {
 		ExitWithError(ExitBadArgs, fmt.Errorf("role add command requires role name as its argument."))
 		ExitWithError(ExitBadArgs, fmt.Errorf("role add command requires role name as its argument."))
 	}
 	}
 
 
-	_, err := mustClientFromCmd(cmd).Auth.RoleAdd(context.TODO(), args[0])
+	resp, err := mustClientFromCmd(cmd).Auth.RoleAdd(context.TODO(), args[0])
 	if err != nil {
 	if err != nil {
 		ExitWithError(ExitError, err)
 		ExitWithError(ExitError, err)
 	}
 	}
 
 
-	fmt.Printf("Role %s created\n", args[0])
+	display.RoleAdd(args[0], *resp)
 }
 }
 
 
 // roleDeleteCommandFunc executes the "role delete" command.
 // roleDeleteCommandFunc executes the "role delete" command.
@@ -116,47 +115,12 @@ func roleDeleteCommandFunc(cmd *cobra.Command, args []string) {
 		ExitWithError(ExitBadArgs, fmt.Errorf("role delete command requires role name as its argument."))
 		ExitWithError(ExitBadArgs, fmt.Errorf("role delete command requires role name as its argument."))
 	}
 	}
 
 
-	_, err := mustClientFromCmd(cmd).Auth.RoleDelete(context.TODO(), args[0])
+	resp, err := mustClientFromCmd(cmd).Auth.RoleDelete(context.TODO(), args[0])
 	if err != nil {
 	if err != nil {
 		ExitWithError(ExitError, err)
 		ExitWithError(ExitError, err)
 	}
 	}
 
 
-	fmt.Printf("Role %s deleted\n", args[0])
-}
-
-func printRolePermissions(name string, resp *clientv3.AuthRoleGetResponse) {
-	fmt.Printf("Role %s\n", name)
-	fmt.Println("KV Read:")
-
-	printRange := func(perm *clientv3.Permission) {
-		sKey := string(perm.Key)
-		sRangeEnd := string(perm.RangeEnd)
-		fmt.Printf("\t[%s, %s)", sKey, sRangeEnd)
-		if strings.Compare(clientv3.GetPrefixRangeEnd(sKey), sRangeEnd) == 0 {
-			fmt.Printf(" (prefix %s)", sKey)
-		}
-		fmt.Printf("\n")
-	}
-
-	for _, perm := range resp.Perm {
-		if perm.PermType == clientv3.PermRead || perm.PermType == clientv3.PermReadWrite {
-			if len(perm.RangeEnd) == 0 {
-				fmt.Printf("\t%s\n", string(perm.Key))
-			} else {
-				printRange((*clientv3.Permission)(perm))
-			}
-		}
-	}
-	fmt.Println("KV Write:")
-	for _, perm := range resp.Perm {
-		if perm.PermType == clientv3.PermWrite || perm.PermType == clientv3.PermReadWrite {
-			if len(perm.RangeEnd) == 0 {
-				fmt.Printf("\t%s\n", string(perm.Key))
-			} else {
-				printRange((*clientv3.Permission)(perm))
-			}
-		}
-	}
+	display.RoleDelete(args[0], *resp)
 }
 }
 
 
 // roleGetCommandFunc executes the "role get" command.
 // roleGetCommandFunc executes the "role get" command.
@@ -171,7 +135,7 @@ func roleGetCommandFunc(cmd *cobra.Command, args []string) {
 		ExitWithError(ExitError, err)
 		ExitWithError(ExitError, err)
 	}
 	}
 
 
-	printRolePermissions(name, resp)
+	display.RoleGet(name, *resp)
 }
 }
 
 
 // roleListCommandFunc executes the "role list" command.
 // roleListCommandFunc executes the "role list" command.
@@ -185,9 +149,7 @@ func roleListCommandFunc(cmd *cobra.Command, args []string) {
 		ExitWithError(ExitError, err)
 		ExitWithError(ExitError, err)
 	}
 	}
 
 
-	for _, role := range resp.Roles {
-		fmt.Printf("%s\n", role)
-	}
+	display.RoleList(*resp)
 }
 }
 
 
 // roleGrantPermissionCommandFunc executes the "role grant-permission" command.
 // roleGrantPermissionCommandFunc executes the "role grant-permission" command.
@@ -211,12 +173,12 @@ func roleGrantPermissionCommandFunc(cmd *cobra.Command, args []string) {
 		rangeEnd = clientv3.GetPrefixRangeEnd(args[2])
 		rangeEnd = clientv3.GetPrefixRangeEnd(args[2])
 	}
 	}
 
 
-	_, err = mustClientFromCmd(cmd).Auth.RoleGrantPermission(context.TODO(), args[0], args[2], rangeEnd, perm)
+	resp, err := mustClientFromCmd(cmd).Auth.RoleGrantPermission(context.TODO(), args[0], args[2], rangeEnd, perm)
 	if err != nil {
 	if err != nil {
 		ExitWithError(ExitError, err)
 		ExitWithError(ExitError, err)
 	}
 	}
 
 
-	fmt.Printf("Role %s updated\n", args[0])
+	display.RoleGrantPermission(args[0], *resp)
 }
 }
 
 
 // roleRevokePermissionCommandFunc executes the "role revoke-permission" command.
 // roleRevokePermissionCommandFunc executes the "role revoke-permission" command.
@@ -230,14 +192,9 @@ func roleRevokePermissionCommandFunc(cmd *cobra.Command, args []string) {
 		rangeEnd = args[2]
 		rangeEnd = args[2]
 	}
 	}
 
 
-	_, err := mustClientFromCmd(cmd).Auth.RoleRevokePermission(context.TODO(), args[0], args[1], rangeEnd)
+	resp, err := mustClientFromCmd(cmd).Auth.RoleRevokePermission(context.TODO(), args[0], args[1], rangeEnd)
 	if err != nil {
 	if err != nil {
 		ExitWithError(ExitError, err)
 		ExitWithError(ExitError, err)
 	}
 	}
-
-	if len(rangeEnd) == 0 {
-		fmt.Printf("Permission of key %s is revoked from role %s\n", args[1], args[0])
-	} else {
-		fmt.Printf("Permission of range [%s, %s) is revoked from role %s\n", args[1], rangeEnd, args[0])
-	}
+	display.RoleRevokePermission(args[0], args[1], rangeEnd, *resp)
 }
 }

+ 16 - 24
etcdctl/ctlv3/command/user_command.go

@@ -142,12 +142,12 @@ func userAddCommandFunc(cmd *cobra.Command, args []string) {
 		}
 		}
 	}
 	}
 
 
-	_, err := mustClientFromCmd(cmd).Auth.UserAdd(context.TODO(), user, password)
+	resp, err := mustClientFromCmd(cmd).Auth.UserAdd(context.TODO(), user, password)
 	if err != nil {
 	if err != nil {
 		ExitWithError(ExitError, err)
 		ExitWithError(ExitError, err)
 	}
 	}
 
 
-	fmt.Printf("User %s created\n", user)
+	display.UserAdd(user, *resp)
 }
 }
 
 
 // userDeleteCommandFunc executes the "user delete" command.
 // userDeleteCommandFunc executes the "user delete" command.
@@ -156,12 +156,11 @@ func userDeleteCommandFunc(cmd *cobra.Command, args []string) {
 		ExitWithError(ExitBadArgs, fmt.Errorf("user delete command requires user name as its argument."))
 		ExitWithError(ExitBadArgs, fmt.Errorf("user delete command requires user name as its argument."))
 	}
 	}
 
 
-	_, err := mustClientFromCmd(cmd).Auth.UserDelete(context.TODO(), args[0])
+	resp, err := mustClientFromCmd(cmd).Auth.UserDelete(context.TODO(), args[0])
 	if err != nil {
 	if err != nil {
 		ExitWithError(ExitError, err)
 		ExitWithError(ExitError, err)
 	}
 	}
-
-	fmt.Printf("User %s deleted\n", args[0])
+	display.UserDelete(args[0], *resp)
 }
 }
 
 
 // userGetCommandFunc executes the "user get" command.
 // userGetCommandFunc executes the "user get" command.
@@ -177,23 +176,18 @@ func userGetCommandFunc(cmd *cobra.Command, args []string) {
 		ExitWithError(ExitError, err)
 		ExitWithError(ExitError, err)
 	}
 	}
 
 
-	fmt.Printf("User: %s\n", name)
-	if !userShowDetail {
-		fmt.Printf("Roles:")
-		for _, role := range resp.Roles {
-			fmt.Printf(" %s", role)
-		}
-		fmt.Printf("\n")
-	} else {
+	if userShowDetail {
+		fmt.Printf("User: %s\n", name)
 		for _, role := range resp.Roles {
 		for _, role := range resp.Roles {
 			fmt.Printf("\n")
 			fmt.Printf("\n")
 			roleResp, err := client.Auth.RoleGet(context.TODO(), role)
 			roleResp, err := client.Auth.RoleGet(context.TODO(), role)
 			if err != nil {
 			if err != nil {
 				ExitWithError(ExitError, err)
 				ExitWithError(ExitError, err)
 			}
 			}
-
-			printRolePermissions(role, roleResp)
+			display.RoleGet(role, *roleResp)
 		}
 		}
+	} else {
+		display.UserGet(name, *resp)
 	}
 	}
 }
 }
 
 
@@ -208,9 +202,7 @@ func userListCommandFunc(cmd *cobra.Command, args []string) {
 		ExitWithError(ExitError, err)
 		ExitWithError(ExitError, err)
 	}
 	}
 
 
-	for _, user := range resp.Users {
-		fmt.Printf("%s\n", user)
-	}
+	display.UserList(*resp)
 }
 }
 
 
 // userChangePasswordCommandFunc executes the "user passwd" command.
 // userChangePasswordCommandFunc executes the "user passwd" command.
@@ -227,12 +219,12 @@ func userChangePasswordCommandFunc(cmd *cobra.Command, args []string) {
 		password = readPasswordInteractive(args[0])
 		password = readPasswordInteractive(args[0])
 	}
 	}
 
 
-	_, err := mustClientFromCmd(cmd).Auth.UserChangePassword(context.TODO(), args[0], password)
+	resp, err := mustClientFromCmd(cmd).Auth.UserChangePassword(context.TODO(), args[0], password)
 	if err != nil {
 	if err != nil {
 		ExitWithError(ExitError, err)
 		ExitWithError(ExitError, err)
 	}
 	}
 
 
-	fmt.Println("Password updated")
+	display.UserChangePassword(*resp)
 }
 }
 
 
 // userGrantRoleCommandFunc executes the "user grant-role" command.
 // userGrantRoleCommandFunc executes the "user grant-role" command.
@@ -241,12 +233,12 @@ func userGrantRoleCommandFunc(cmd *cobra.Command, args []string) {
 		ExitWithError(ExitBadArgs, fmt.Errorf("user grant command requires user name and role name as its argument."))
 		ExitWithError(ExitBadArgs, fmt.Errorf("user grant command requires user name and role name as its argument."))
 	}
 	}
 
 
-	_, err := mustClientFromCmd(cmd).Auth.UserGrantRole(context.TODO(), args[0], args[1])
+	resp, err := mustClientFromCmd(cmd).Auth.UserGrantRole(context.TODO(), args[0], args[1])
 	if err != nil {
 	if err != nil {
 		ExitWithError(ExitError, err)
 		ExitWithError(ExitError, err)
 	}
 	}
 
 
-	fmt.Printf("Role %s is granted to user %s\n", args[1], args[0])
+	display.UserGrantRole(args[0], args[1], *resp)
 }
 }
 
 
 // userRevokeRoleCommandFunc executes the "user revoke-role" command.
 // userRevokeRoleCommandFunc executes the "user revoke-role" command.
@@ -255,12 +247,12 @@ func userRevokeRoleCommandFunc(cmd *cobra.Command, args []string) {
 		ExitWithError(ExitBadArgs, fmt.Errorf("user revoke-role requires user name and role name as its argument."))
 		ExitWithError(ExitBadArgs, fmt.Errorf("user revoke-role requires user name and role name as its argument."))
 	}
 	}
 
 
-	_, err := mustClientFromCmd(cmd).Auth.UserRevokeRole(context.TODO(), args[0], args[1])
+	resp, err := mustClientFromCmd(cmd).Auth.UserRevokeRole(context.TODO(), args[0], args[1])
 	if err != nil {
 	if err != nil {
 		ExitWithError(ExitError, err)
 		ExitWithError(ExitError, err)
 	}
 	}
 
 
-	fmt.Printf("Role %s is revoked from user %s\n", args[1], args[0])
+	display.UserRevokeRole(args[0], args[1], *resp)
 }
 }
 
 
 func readPasswordInteractive(name string) string {
 func readPasswordInteractive(name string) string {

Some files were not shown because too many files changed in this diff