From facdc089d8f3693b1df566d392e4b9354b1e9630 Mon Sep 17 00:00:00 2001 From: Kelvin Clement Mwinuka Date: Wed, 28 Feb 2024 15:16:11 +0800 Subject: [PATCH] Implemented command handler for COMMAND LIST command --- docker-compose.yaml | 2 +- src/modules/admin/commands.go | 138 +++++++++++++++++++++++++++++----- 2 files changed, 122 insertions(+), 18 deletions(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index 8f53bc0b..07945174 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -18,7 +18,7 @@ services: - PLUGIN_DIR=/usr/local/lib/echovault - DATA_DIR=/var/lib/echovault - IN_MEMORY=false - - TLS=false + - TLS=true - MTLS=false - BOOTSTRAP_CLUSTER=false - ACL_CONFIG=/etc/config/echovault/acl.yml diff --git a/src/modules/admin/commands.go b/src/modules/admin/commands.go index 3f635741..70c017e1 100644 --- a/src/modules/admin/commands.go +++ b/src/modules/admin/commands.go @@ -5,7 +5,10 @@ import ( "errors" "fmt" "github.com/echovault/echovault/src/utils" + "github.com/gobwas/glob" "net" + "slices" + "strings" ) func handleGetAllCommands(ctx context.Context, cmd []string, server utils.Server, _ *net.Conn) ([]byte, error) { @@ -53,30 +56,114 @@ func handleGetAllCommands(ctx context.Context, cmd []string, server utils.Server return []byte(res), nil } -func handleCommandDocs(ctx context.Context, cmd []string, server utils.Server, _ *net.Conn) ([]byte, error) { - return []byte("*0\r\n"), nil -} - func handleCommandCount(ctx context.Context, cmd []string, server utils.Server, _ *net.Conn) ([]byte, error) { - return nil, errors.New("command not yet implemented") -} + var count int -func handleCommandList(ctx context.Context, cmd []string, server utils.Server, _ *net.Conn) ([]byte, error) { - return nil, errors.New("command not yet implemented") -} + commands := server.GetAllCommands(ctx) + for _, command := range commands { + if command.SubCommands != nil && len(command.SubCommands) > 0 { + for _, _ = range command.SubCommands { + count += 1 + } + continue + } + count += 1 + } -func handleConfigGet(ctx context.Context, cmd []string, server utils.Server, _ *net.Conn) ([]byte, error) { - return nil, errors.New("command not yet implemented") + return []byte(fmt.Sprintf(":%d\r\n", count)), nil } -func handleConfigRewrite(ctx context.Context, cmd []string, server *utils.Server, _ *net.Conn) ([]byte, error) { - return nil, errors.New("command not yet implemented") -} +func handleCommandList(ctx context.Context, cmd []string, server utils.Server, _ *net.Conn) ([]byte, error) { + switch len(cmd) { + case 2: + // Command is COMMAND LIST + var count int + var res string + commands := server.GetAllCommands(ctx) + for _, command := range commands { + if command.SubCommands != nil && len(command.SubCommands) > 0 { + for _, subcommand := range command.SubCommands { + comm := fmt.Sprintf("%s %s", command.Command, subcommand.Command) + res += fmt.Sprintf("$%d\r\n%s\r\n", len(comm), comm) + count += 1 + } + continue + } + res += fmt.Sprintf("$%d\r\n%s\r\n", len(command.Command), command.Command) + count += 1 + } + res = fmt.Sprintf("*%d\r\n%s", count, res) + return []byte(res), nil -func handleConfigSet(ctx context.Context, cmd []string, server *utils.Server, _ *net.Conn) ([]byte, error) { - return nil, errors.New("command not yet implemented") + case 5: + var count int + var res string + // Command has filter + if !strings.EqualFold("FILTERBY", cmd[2]) { + return nil, fmt.Errorf("expected FILTERBY, got %s", strings.ToUpper(cmd[2])) + } + if strings.EqualFold("ACLCAT", cmd[3]) { + // ACL Category filter + commands := server.GetAllCommands(ctx) + category := strings.ToLower(cmd[4]) + for _, command := range commands { + if command.SubCommands != nil && len(command.SubCommands) > 0 { + for _, subcommand := range command.SubCommands { + if slices.Contains(subcommand.Categories, category) { + comm := fmt.Sprintf("%s %s", command.Command, subcommand.Command) + res += fmt.Sprintf("$%d\r\n%s\r\n", len(comm), comm) + count += 1 + } + } + continue + } + if slices.Contains(command.Categories, category) { + res += fmt.Sprintf("$%d\r\n%s\r\n", len(command.Command), command.Command) + count += 1 + } + } + } else if strings.EqualFold("PATTERN", cmd[3]) { + // Pattern filter + commands := server.GetAllCommands(ctx) + g := glob.MustCompile(cmd[4]) + for _, command := range commands { + if command.SubCommands != nil && len(command.SubCommands) > 0 { + for _, subcommand := range command.SubCommands { + comm := fmt.Sprintf("%s %s", command.Command, subcommand.Command) + if g.Match(comm) { + res += fmt.Sprintf("$%d\r\n%s\r\n", len(comm), comm) + count += 1 + } + } + continue + } + if g.Match(command.Command) { + res += fmt.Sprintf("$%d\r\n%s\r\n", len(command.Command), command.Command) + count += 1 + } + } + } else { + return nil, fmt.Errorf("expected filter to be ACLCAT or PATTERN, got %s", strings.ToUpper(cmd[3])) + } + res = fmt.Sprintf("*%d\r\n%s", count, res) + return []byte(res), nil + default: + return nil, errors.New(utils.WRONG_ARGS_RESPONSE) + } } +// func handleConfigGet(ctx context.Context, cmd []string, server utils.Server, _ *net.Conn) ([]byte, error) { +// return nil, errors.New("command not yet implemented") +// } +// +// func handleConfigRewrite(ctx context.Context, cmd []string, server *utils.Server, _ *net.Conn) ([]byte, error) { +// return nil, errors.New("command not yet implemented") +// } +// +// func handleConfigSet(ctx context.Context, cmd []string, server *utils.Server, _ *net.Conn) ([]byte, error) { +// return nil, errors.New("command not yet implemented") +// } + func Commands() []utils.Command { return []utils.Command{ { @@ -102,7 +189,24 @@ func Commands() []utils.Command { Description: "Get command documentation", Sync: false, KeyExtractionFunc: func(cmd []string) ([]string, error) { return []string{}, nil }, - HandlerFunc: handleCommandDocs, + HandlerFunc: handleGetAllCommands, + }, + { + Command: "count", + Categories: []string{utils.SlowCategory}, + Description: "Get the dumber of commands in the server", + Sync: false, + KeyExtractionFunc: func(cmd []string) ([]string, error) { return []string{}, nil }, + HandlerFunc: handleCommandCount, + }, + { + Command: "list", + Categories: []string{utils.SlowCategory}, + Description: `(COMMAND LIST [FILTERBY ]) Get the list of command names. +Allows for filtering by ACL category or glob pattern.`, + Sync: false, + KeyExtractionFunc: func(cmd []string) ([]string, error) { return []string{}, nil }, + HandlerFunc: handleCommandList, }, }, },