mirror of https://github.com/go-redis/redis.git
feat: Add ACL LOG (#2536)
* feat: Add ACL LOG Signed-off-by: monkey92t <golang@88.com> --------- Signed-off-by: monkey92t <golang@88.com> Co-authored-by: Anuragkillswitch <70265851+Anuragkillswitch@users.noreply.github.com> Co-authored-by: ktsivkov <ktsivkov@gmail.com> Co-authored-by: Elena Kolevska <elena@kolevska.com> Co-authored-by: monkey92t <golang@88.com>
This commit is contained in:
parent
fcb3506e79
commit
31ba855dde
109
command.go
109
command.go
|
@ -5057,3 +5057,112 @@ func parseClientInfo(txt string) (info *ClientInfo, err error) {
|
||||||
|
|
||||||
return info, nil
|
return info, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------
|
||||||
|
|
||||||
|
type ACLLogEntry struct {
|
||||||
|
Count int64
|
||||||
|
Reason string
|
||||||
|
Context string
|
||||||
|
Object string
|
||||||
|
Username string
|
||||||
|
AgeSeconds float64
|
||||||
|
ClientInfo *ClientInfo
|
||||||
|
EntryID int64
|
||||||
|
TimestampCreated int64
|
||||||
|
TimestampLastUpdated int64
|
||||||
|
}
|
||||||
|
|
||||||
|
type ACLLogCmd struct {
|
||||||
|
baseCmd
|
||||||
|
|
||||||
|
val []*ACLLogEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Cmder = (*ACLLogCmd)(nil)
|
||||||
|
|
||||||
|
func NewACLLogCmd(ctx context.Context, args ...interface{}) *ACLLogCmd {
|
||||||
|
return &ACLLogCmd{
|
||||||
|
baseCmd: baseCmd{
|
||||||
|
ctx: ctx,
|
||||||
|
args: args,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd *ACLLogCmd) SetVal(val []*ACLLogEntry) {
|
||||||
|
cmd.val = val
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd *ACLLogCmd) Val() []*ACLLogEntry {
|
||||||
|
return cmd.val
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd *ACLLogCmd) Result() ([]*ACLLogEntry, error) {
|
||||||
|
return cmd.Val(), cmd.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd *ACLLogCmd) String() string {
|
||||||
|
return cmdString(cmd, cmd.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd *ACLLogCmd) readReply(rd *proto.Reader) error {
|
||||||
|
n, err := rd.ReadArrayLen()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.val = make([]*ACLLogEntry, n)
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
cmd.val[i] = &ACLLogEntry{}
|
||||||
|
entry := cmd.val[i]
|
||||||
|
respLen, err := rd.ReadMapLen()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for j := 0; j < respLen; j++ {
|
||||||
|
key, err := rd.ReadString()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch key {
|
||||||
|
case "count":
|
||||||
|
entry.Count, err = rd.ReadInt()
|
||||||
|
case "reason":
|
||||||
|
entry.Reason, err = rd.ReadString()
|
||||||
|
case "context":
|
||||||
|
entry.Context, err = rd.ReadString()
|
||||||
|
case "object":
|
||||||
|
entry.Object, err = rd.ReadString()
|
||||||
|
case "username":
|
||||||
|
entry.Username, err = rd.ReadString()
|
||||||
|
case "age-seconds":
|
||||||
|
entry.AgeSeconds, err = rd.ReadFloat()
|
||||||
|
case "client-info":
|
||||||
|
txt, err := rd.ReadString()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
entry.ClientInfo, err = parseClientInfo(strings.TrimSpace(txt))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case "entry-id":
|
||||||
|
entry.EntryID, err = rd.ReadInt()
|
||||||
|
case "timestamp-created":
|
||||||
|
entry.TimestampCreated, err = rd.ReadInt()
|
||||||
|
case "timestamp-last-updated":
|
||||||
|
entry.TimestampLastUpdated, err = rd.ReadInt()
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("redis: unexpected key %q in ACL LOG reply", key)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
19
commands.go
19
commands.go
|
@ -500,6 +500,8 @@ type Cmdable interface {
|
||||||
GeoHash(ctx context.Context, key string, members ...string) *StringSliceCmd
|
GeoHash(ctx context.Context, key string, members ...string) *StringSliceCmd
|
||||||
|
|
||||||
ACLDryRun(ctx context.Context, username string, command ...interface{}) *StringCmd
|
ACLDryRun(ctx context.Context, username string, command ...interface{}) *StringCmd
|
||||||
|
ACLLog(ctx context.Context, count int64) *ACLLogCmd
|
||||||
|
ACLLogReset(ctx context.Context) *StatusCmd
|
||||||
|
|
||||||
ModuleLoadex(ctx context.Context, conf *ModuleLoadexConfig) *StringCmd
|
ModuleLoadex(ctx context.Context, conf *ModuleLoadexConfig) *StringCmd
|
||||||
}
|
}
|
||||||
|
@ -3946,3 +3948,20 @@ func (c cmdable) ModuleLoadex(ctx context.Context, conf *ModuleLoadexConfig) *St
|
||||||
_ = c(ctx, cmd)
|
_ = c(ctx, cmd)
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c cmdable) ACLLog(ctx context.Context, count int64) *ACLLogCmd {
|
||||||
|
args := make([]interface{}, 0, 3)
|
||||||
|
args = append(args, "acl", "log")
|
||||||
|
if count > 0 {
|
||||||
|
args = append(args, count)
|
||||||
|
}
|
||||||
|
cmd := NewACLLogCmd(ctx, args...)
|
||||||
|
_ = c(ctx, cmd)
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c cmdable) ACLLogReset(ctx context.Context) *StatusCmd {
|
||||||
|
cmd := NewStatusCmd(ctx, "acl", "log", "reset")
|
||||||
|
_ = c(ctx, cmd)
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
|
@ -1985,6 +1985,55 @@ var _ = Describe("Commands", func() {
|
||||||
|
|
||||||
Expect(args).To(Equal(expectedArgs))
|
Expect(args).To(Equal(expectedArgs))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
It("should ACL LOG", func() {
|
||||||
|
|
||||||
|
err := client.Do(ctx, "acl", "setuser", "test", ">test", "on", "allkeys", "+get").Err()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
clientAcl := redis.NewClient(redisOptions())
|
||||||
|
clientAcl.Options().Username = "test"
|
||||||
|
clientAcl.Options().Password = "test"
|
||||||
|
clientAcl.Options().DB = 0
|
||||||
|
_ = clientAcl.Set(ctx, "mystring", "foo", 0).Err()
|
||||||
|
_ = clientAcl.HSet(ctx, "myhash", "foo", "bar").Err()
|
||||||
|
_ = clientAcl.SAdd(ctx, "myset", "foo", "bar").Err()
|
||||||
|
|
||||||
|
logEntries, err := client.ACLLog(ctx, 10).Result()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(len(logEntries)).To(Equal(3))
|
||||||
|
|
||||||
|
for _, entry := range logEntries {
|
||||||
|
Expect(entry.Count).To(BeNumerically("==", 1))
|
||||||
|
Expect(entry.Reason).To(Equal("command"))
|
||||||
|
Expect(entry.Context).To(Equal("toplevel"))
|
||||||
|
Expect(entry.Object).NotTo(BeEmpty())
|
||||||
|
Expect(entry.Username).To(Equal("test"))
|
||||||
|
Expect(entry.AgeSeconds).To(BeNumerically(">=", 0))
|
||||||
|
Expect(entry.ClientInfo).NotTo(BeNil())
|
||||||
|
Expect(entry.EntryID).To(BeNumerically(">=", 0))
|
||||||
|
Expect(entry.TimestampCreated).To(BeNumerically(">=", 0))
|
||||||
|
Expect(entry.TimestampLastUpdated).To(BeNumerically(">=", 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
limitedLogEntries, err := client.ACLLog(ctx, 2).Result()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(len(limitedLogEntries)).To(Equal(2))
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should ACL LOG RESET", func() {
|
||||||
|
// Call ACL LOG RESET
|
||||||
|
resetCmd := client.ACLLogReset(ctx)
|
||||||
|
Expect(resetCmd.Err()).NotTo(HaveOccurred())
|
||||||
|
Expect(resetCmd.Val()).To(Equal("OK"))
|
||||||
|
|
||||||
|
// Verify that the log is empty after the reset
|
||||||
|
logEntries, err := client.ACLLog(ctx, 10).Result()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(len(logEntries)).To(Equal(0))
|
||||||
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
Describe("hashes", func() {
|
Describe("hashes", func() {
|
||||||
|
|
Loading…
Reference in New Issue