1// Copyright 2016 The etcd Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package command
16
17import (
18	"errors"
19	"fmt"
20	"strings"
21
22	v3 "go.etcd.io/etcd/clientv3"
23	"go.etcd.io/etcd/clientv3/snapshot"
24	pb "go.etcd.io/etcd/etcdserver/etcdserverpb"
25
26	"github.com/dustin/go-humanize"
27)
28
29type printer interface {
30	Del(v3.DeleteResponse)
31	Get(v3.GetResponse)
32	Put(v3.PutResponse)
33	Txn(v3.TxnResponse)
34	Watch(v3.WatchResponse)
35
36	Grant(r v3.LeaseGrantResponse)
37	Revoke(id v3.LeaseID, r v3.LeaseRevokeResponse)
38	KeepAlive(r v3.LeaseKeepAliveResponse)
39	TimeToLive(r v3.LeaseTimeToLiveResponse, keys bool)
40	Leases(r v3.LeaseLeasesResponse)
41
42	MemberAdd(v3.MemberAddResponse)
43	MemberRemove(id uint64, r v3.MemberRemoveResponse)
44	MemberUpdate(id uint64, r v3.MemberUpdateResponse)
45	MemberPromote(id uint64, r v3.MemberPromoteResponse)
46	MemberList(v3.MemberListResponse)
47
48	EndpointHealth([]epHealth)
49	EndpointStatus([]epStatus)
50	EndpointHashKV([]epHashKV)
51	MoveLeader(leader, target uint64, r v3.MoveLeaderResponse)
52
53	Alarm(v3.AlarmResponse)
54	DBStatus(snapshot.Status)
55
56	RoleAdd(role string, r v3.AuthRoleAddResponse)
57	RoleGet(role string, r v3.AuthRoleGetResponse)
58	RoleDelete(role string, r v3.AuthRoleDeleteResponse)
59	RoleList(v3.AuthRoleListResponse)
60	RoleGrantPermission(role string, r v3.AuthRoleGrantPermissionResponse)
61	RoleRevokePermission(role string, key string, end string, r v3.AuthRoleRevokePermissionResponse)
62
63	UserAdd(user string, r v3.AuthUserAddResponse)
64	UserGet(user string, r v3.AuthUserGetResponse)
65	UserList(r v3.AuthUserListResponse)
66	UserChangePassword(v3.AuthUserChangePasswordResponse)
67	UserGrantRole(user string, role string, r v3.AuthUserGrantRoleResponse)
68	UserRevokeRole(user string, role string, r v3.AuthUserRevokeRoleResponse)
69	UserDelete(user string, r v3.AuthUserDeleteResponse)
70}
71
72func NewPrinter(printerType string, isHex bool) printer {
73	switch printerType {
74	case "simple":
75		return &simplePrinter{isHex: isHex}
76	case "fields":
77		return &fieldsPrinter{newPrinterUnsupported("fields")}
78	case "json":
79		return newJSONPrinter()
80	case "protobuf":
81		return newPBPrinter()
82	case "table":
83		return &tablePrinter{newPrinterUnsupported("table")}
84	}
85	return nil
86}
87
88type printerRPC struct {
89	printer
90	p func(interface{})
91}
92
93func (p *printerRPC) Del(r v3.DeleteResponse)  { p.p((*pb.DeleteRangeResponse)(&r)) }
94func (p *printerRPC) Get(r v3.GetResponse)     { p.p((*pb.RangeResponse)(&r)) }
95func (p *printerRPC) Put(r v3.PutResponse)     { p.p((*pb.PutResponse)(&r)) }
96func (p *printerRPC) Txn(r v3.TxnResponse)     { p.p((*pb.TxnResponse)(&r)) }
97func (p *printerRPC) Watch(r v3.WatchResponse) { p.p(&r) }
98
99func (p *printerRPC) Grant(r v3.LeaseGrantResponse)                      { p.p(r) }
100func (p *printerRPC) Revoke(id v3.LeaseID, r v3.LeaseRevokeResponse)     { p.p(r) }
101func (p *printerRPC) KeepAlive(r v3.LeaseKeepAliveResponse)              { p.p(r) }
102func (p *printerRPC) TimeToLive(r v3.LeaseTimeToLiveResponse, keys bool) { p.p(&r) }
103func (p *printerRPC) Leases(r v3.LeaseLeasesResponse)                    { p.p(&r) }
104
105func (p *printerRPC) MemberAdd(r v3.MemberAddResponse) { p.p((*pb.MemberAddResponse)(&r)) }
106func (p *printerRPC) MemberRemove(id uint64, r v3.MemberRemoveResponse) {
107	p.p((*pb.MemberRemoveResponse)(&r))
108}
109func (p *printerRPC) MemberUpdate(id uint64, r v3.MemberUpdateResponse) {
110	p.p((*pb.MemberUpdateResponse)(&r))
111}
112func (p *printerRPC) MemberList(r v3.MemberListResponse) { p.p((*pb.MemberListResponse)(&r)) }
113func (p *printerRPC) Alarm(r v3.AlarmResponse)           { p.p((*pb.AlarmResponse)(&r)) }
114func (p *printerRPC) MoveLeader(leader, target uint64, r v3.MoveLeaderResponse) {
115	p.p((*pb.MoveLeaderResponse)(&r))
116}
117func (p *printerRPC) RoleAdd(_ string, r v3.AuthRoleAddResponse) { p.p((*pb.AuthRoleAddResponse)(&r)) }
118func (p *printerRPC) RoleGet(_ string, r v3.AuthRoleGetResponse) { p.p((*pb.AuthRoleGetResponse)(&r)) }
119func (p *printerRPC) RoleDelete(_ string, r v3.AuthRoleDeleteResponse) {
120	p.p((*pb.AuthRoleDeleteResponse)(&r))
121}
122func (p *printerRPC) RoleList(r v3.AuthRoleListResponse) { p.p((*pb.AuthRoleListResponse)(&r)) }
123func (p *printerRPC) RoleGrantPermission(_ string, r v3.AuthRoleGrantPermissionResponse) {
124	p.p((*pb.AuthRoleGrantPermissionResponse)(&r))
125}
126func (p *printerRPC) RoleRevokePermission(_ string, _ string, _ string, r v3.AuthRoleRevokePermissionResponse) {
127	p.p((*pb.AuthRoleRevokePermissionResponse)(&r))
128}
129func (p *printerRPC) UserAdd(_ string, r v3.AuthUserAddResponse) { p.p((*pb.AuthUserAddResponse)(&r)) }
130func (p *printerRPC) UserGet(_ string, r v3.AuthUserGetResponse) { p.p((*pb.AuthUserGetResponse)(&r)) }
131func (p *printerRPC) UserList(r v3.AuthUserListResponse)         { p.p((*pb.AuthUserListResponse)(&r)) }
132func (p *printerRPC) UserChangePassword(r v3.AuthUserChangePasswordResponse) {
133	p.p((*pb.AuthUserChangePasswordResponse)(&r))
134}
135func (p *printerRPC) UserGrantRole(_ string, _ string, r v3.AuthUserGrantRoleResponse) {
136	p.p((*pb.AuthUserGrantRoleResponse)(&r))
137}
138func (p *printerRPC) UserRevokeRole(_ string, _ string, r v3.AuthUserRevokeRoleResponse) {
139	p.p((*pb.AuthUserRevokeRoleResponse)(&r))
140}
141func (p *printerRPC) UserDelete(_ string, r v3.AuthUserDeleteResponse) {
142	p.p((*pb.AuthUserDeleteResponse)(&r))
143}
144
145type printerUnsupported struct{ printerRPC }
146
147func newPrinterUnsupported(n string) printer {
148	f := func(interface{}) {
149		ExitWithError(ExitBadFeature, errors.New(n+" not supported as output format"))
150	}
151	return &printerUnsupported{printerRPC{nil, f}}
152}
153
154func (p *printerUnsupported) EndpointHealth([]epHealth) { p.p(nil) }
155func (p *printerUnsupported) EndpointStatus([]epStatus) { p.p(nil) }
156func (p *printerUnsupported) EndpointHashKV([]epHashKV) { p.p(nil) }
157func (p *printerUnsupported) DBStatus(snapshot.Status)  { p.p(nil) }
158
159func (p *printerUnsupported) MoveLeader(leader, target uint64, r v3.MoveLeaderResponse) { p.p(nil) }
160
161func makeMemberListTable(r v3.MemberListResponse) (hdr []string, rows [][]string) {
162	hdr = []string{"ID", "Status", "Name", "Peer Addrs", "Client Addrs", "Is Learner"}
163	for _, m := range r.Members {
164		status := "started"
165		if len(m.Name) == 0 {
166			status = "unstarted"
167		}
168		isLearner := "false"
169		if m.IsLearner {
170			isLearner = "true"
171		}
172		rows = append(rows, []string{
173			fmt.Sprintf("%x", m.ID),
174			status,
175			m.Name,
176			strings.Join(m.PeerURLs, ","),
177			strings.Join(m.ClientURLs, ","),
178			isLearner,
179		})
180	}
181	return hdr, rows
182}
183
184func makeEndpointHealthTable(healthList []epHealth) (hdr []string, rows [][]string) {
185	hdr = []string{"endpoint", "health", "took", "error"}
186	for _, h := range healthList {
187		rows = append(rows, []string{
188			h.Ep,
189			fmt.Sprintf("%v", h.Health),
190			h.Took,
191			h.Error,
192		})
193	}
194	return hdr, rows
195}
196
197func makeEndpointStatusTable(statusList []epStatus) (hdr []string, rows [][]string) {
198	hdr = []string{"endpoint", "ID", "version", "db size", "is leader", "is learner", "raft term",
199		"raft index", "raft applied index", "errors"}
200	for _, status := range statusList {
201		rows = append(rows, []string{
202			status.Ep,
203			fmt.Sprintf("%x", status.Resp.Header.MemberId),
204			status.Resp.Version,
205			humanize.Bytes(uint64(status.Resp.DbSize)),
206			fmt.Sprint(status.Resp.Leader == status.Resp.Header.MemberId),
207			fmt.Sprint(status.Resp.IsLearner),
208			fmt.Sprint(status.Resp.RaftTerm),
209			fmt.Sprint(status.Resp.RaftIndex),
210			fmt.Sprint(status.Resp.RaftAppliedIndex),
211			fmt.Sprint(strings.Join(status.Resp.Errors, ", ")),
212		})
213	}
214	return hdr, rows
215}
216
217func makeEndpointHashKVTable(hashList []epHashKV) (hdr []string, rows [][]string) {
218	hdr = []string{"endpoint", "hash"}
219	for _, h := range hashList {
220		rows = append(rows, []string{
221			h.Ep,
222			fmt.Sprint(h.Resp.Hash),
223		})
224	}
225	return hdr, rows
226}
227
228func makeDBStatusTable(ds snapshot.Status) (hdr []string, rows [][]string) {
229	hdr = []string{"hash", "revision", "total keys", "total size"}
230	rows = append(rows, []string{
231		fmt.Sprintf("%x", ds.Hash),
232		fmt.Sprint(ds.Revision),
233		fmt.Sprint(ds.TotalKey),
234		humanize.Bytes(uint64(ds.TotalSize)),
235	})
236	return hdr, rows
237}
238