From 27948344f6cc16e6962f5b653748234441ed3470 Mon Sep 17 00:00:00 2001 From: congqixia Date: Tue, 17 Oct 2023 11:29:58 +0800 Subject: [PATCH] Add `show user` command (#207) Signed-off-by: Congqi Xia --- models/user.go | 25 ++++++++++++++++++++ states/etcd/common/list.go | 28 +++++++++++++++++++++++ states/etcd/common/user.go | 29 +++++++++++++++++++++++ states/etcd/show/user.go | 47 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 129 insertions(+) create mode 100644 models/user.go create mode 100644 states/etcd/common/user.go create mode 100644 states/etcd/show/user.go diff --git a/models/user.go b/models/user.go new file mode 100644 index 0000000..631583f --- /dev/null +++ b/models/user.go @@ -0,0 +1,25 @@ +package models + +import ( + "path" + + internalpbv2 "github.com/milvus-io/birdwatcher/proto/v2.2/internalpb" +) + +type UserInfo struct { + Username string + EncryptedPassword string + Tenant string + IsSuper bool + Sha256Password string +} + +func NewUserInfo(info *internalpbv2.CredentialInfo, key string) *UserInfo { + return &UserInfo{ + Username: path.Base(key), + EncryptedPassword: info.GetEncryptedPassword(), + Tenant: info.GetTenant(), + IsSuper: info.GetIsSuper(), + Sha256Password: info.GetSha256Password(), + } +} diff --git a/states/etcd/common/list.go b/states/etcd/common/list.go index 0099612..9b40a80 100644 --- a/states/etcd/common/list.go +++ b/states/etcd/common/list.go @@ -2,6 +2,7 @@ package common import ( "context" + "encoding/json" "fmt" "github.com/golang/protobuf/proto" @@ -9,6 +10,33 @@ import ( "google.golang.org/protobuf/runtime/protoiface" ) +func ListJSONObjects[T any, P interface{ *T }](ctx context.Context, cli clientv3.KV, prefix string, filters ...func(t P) bool) ([]P, []string, error) { + resp, err := cli.Get(ctx, prefix, clientv3.WithPrefix()) + if err != nil { + return nil, nil, err + } + result := make([]P, 0, len(resp.Kvs)) + keys := make([]string, 0, len(resp.Kvs)) +LOOP: + for _, kv := range resp.Kvs { + var elem T + err = json.Unmarshal(kv.Value, &elem) + if err != nil { + fmt.Println(err.Error()) + continue + } + + for _, filter := range filters { + if !filter(&elem) { + continue LOOP + } + } + result = append(result, &elem) + keys = append(keys, string(kv.Key)) + } + return result, keys, nil +} + // ListProtoObjects returns proto objects with specified prefix. func ListProtoObjects[T any, P interface { *T diff --git a/states/etcd/common/user.go b/states/etcd/common/user.go new file mode 100644 index 0000000..9990850 --- /dev/null +++ b/states/etcd/common/user.go @@ -0,0 +1,29 @@ +package common + +import ( + "context" + "path" + + "github.com/milvus-io/birdwatcher/models" + internalpbv2 "github.com/milvus-io/birdwatcher/proto/v2.2/internalpb" + "github.com/samber/lo" + clientv3 "go.etcd.io/etcd/client/v3" +) + +const ( + userPrefix = `root-coord/credential/users` +) + +func ListUsers(ctx context.Context, cli clientv3.KV, basePath string) ([]*models.UserInfo, error) { + prefix := path.Join(basePath, userPrefix) + + infos, keys, err := ListJSONObjects[internalpbv2.CredentialInfo](ctx, cli, prefix) + + if err != nil { + return nil, err + } + + return lo.Map(infos, func(info *internalpbv2.CredentialInfo, idx int) *models.UserInfo { + return models.NewUserInfo(info, keys[idx]) + }), nil +} diff --git a/states/etcd/show/user.go b/states/etcd/show/user.go new file mode 100644 index 0000000..2a0e972 --- /dev/null +++ b/states/etcd/show/user.go @@ -0,0 +1,47 @@ +package show + +import ( + "context" + "fmt" + "strings" + + "github.com/cockroachdb/errors" + "github.com/milvus-io/birdwatcher/framework" + "github.com/milvus-io/birdwatcher/models" + "github.com/milvus-io/birdwatcher/states/etcd/common" +) + +type UserParam struct { + framework.ParamBase `use:"show user" desc:"display user info from rootcoord meta"` + // DatabaseName string `name:"name" default:"" desc:"database name to filter with"` +} + +// DatabaseCommand returns show database comand. +func (c *ComponentShow) UserCommand(ctx context.Context, p *UserParam) (*Users, error) { + users, err := common.ListUsers(ctx, c.client, c.basePath) + if err != nil { + fmt.Println("failed to list database info", err.Error()) + return nil, errors.Wrap(err, "failed to list database info") + } + + return framework.NewListResult[Users](users), nil +} + +type Users struct { + framework.ListResultSet[*models.UserInfo] +} + +func (rs *Users) PrintAs(format framework.Format) string { + switch format { + case framework.FormatDefault, framework.FormatPlain: + sb := &strings.Builder{} + for _, user := range rs.Data { + //rs.printDatabaseInfo(sb, database) + sb.WriteString(fmt.Sprintf("Username: %s Tenant:%s\n", user.Username, user.Tenant)) + } + fmt.Fprintf(sb, "--- Total Users(s): %d\n", len(rs.Data)) + return sb.String() + default: + } + return "" +}