mirror of
https://github.com/EchoVault/SugarDB.git
synced 2025-09-27 20:32:16 +08:00

Implemented UnloadModules method to remove modules at runtime. Implemented ListModules method to list the current loaded modules. Implemented "MODULE LOAD", "MODULE UNLOAD", and "MODULE LIST" commands.
397 lines
14 KiB
Go
397 lines
14 KiB
Go
// Copyright 2024 Kelvin Clement Mwinuka
|
|
//
|
|
// 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 admin
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"github.com/echovault/echovault/internal"
|
|
"github.com/echovault/echovault/internal/constants"
|
|
"github.com/gobwas/glob"
|
|
"slices"
|
|
"strings"
|
|
)
|
|
|
|
func handleGetAllCommands(params internal.HandlerFuncParams) ([]byte, error) {
|
|
commands := params.GetAllCommands()
|
|
|
|
res := ""
|
|
commandCount := 0
|
|
|
|
for _, c := range commands {
|
|
if c.SubCommands == nil || len(c.SubCommands) <= 0 {
|
|
res += "*6\r\n"
|
|
// Command name
|
|
res += fmt.Sprintf("+command\r\n*1\r\n$%d\r\n%s\r\n", len(c.Command), c.Command)
|
|
// Command categories
|
|
res += fmt.Sprintf("+categories\r\n*%d\r\n", len(c.Categories))
|
|
for _, category := range c.Categories {
|
|
res += fmt.Sprintf("$%d\r\n%s\r\n", len(category), category)
|
|
}
|
|
// Description
|
|
res += fmt.Sprintf("+description\r\n*1\r\n$%d\r\n%s\r\n", len(c.Description), c.Description)
|
|
|
|
commandCount += 1
|
|
continue
|
|
}
|
|
// There are sub-commands
|
|
for _, sc := range c.SubCommands {
|
|
res += "*6\r\n"
|
|
// Command name
|
|
command := fmt.Sprintf("%s %s", c.Command, sc.Command)
|
|
res += fmt.Sprintf("+command\r\n*1\r\n$%d\r\n%s\r\n", len(command), command)
|
|
// Command categories
|
|
res += fmt.Sprintf("+categories\r\n*%d\r\n", len(sc.Categories))
|
|
for _, category := range sc.Categories {
|
|
res += fmt.Sprintf("$%d\r\n%s\r\n", len(category), category)
|
|
}
|
|
// Description
|
|
res += fmt.Sprintf("+description\r\n*1\r\n$%d\r\n%s\r\n", len(sc.Description), sc.Description)
|
|
|
|
commandCount += 1
|
|
}
|
|
}
|
|
|
|
res = fmt.Sprintf("*%d\r\n%s", commandCount, res)
|
|
|
|
return []byte(res), nil
|
|
}
|
|
|
|
func handleCommandCount(params internal.HandlerFuncParams) ([]byte, error) {
|
|
var count int
|
|
|
|
commands := params.GetAllCommands()
|
|
for _, command := range commands {
|
|
if command.SubCommands != nil && len(command.SubCommands) > 0 {
|
|
for _, _ = range command.SubCommands {
|
|
count += 1
|
|
}
|
|
continue
|
|
}
|
|
count += 1
|
|
}
|
|
|
|
return []byte(fmt.Sprintf(":%d\r\n", count)), nil
|
|
}
|
|
|
|
func handleCommandList(params internal.HandlerFuncParams) ([]byte, error) {
|
|
switch len(params.Command) {
|
|
case 2:
|
|
// Command is COMMAND LIST
|
|
var count int
|
|
var res string
|
|
commands := params.GetAllCommands()
|
|
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
|
|
|
|
case 5:
|
|
var count int
|
|
var res string
|
|
// Command has filter
|
|
if !strings.EqualFold("FILTERBY", params.Command[2]) {
|
|
return nil, fmt.Errorf("expected FILTERBY, got %s", strings.ToUpper(params.Command[2]))
|
|
}
|
|
if strings.EqualFold("ACLCAT", params.Command[3]) {
|
|
// ACL Category filter
|
|
commands := params.GetAllCommands()
|
|
category := strings.ToLower(params.Command[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", params.Command[3]) {
|
|
// Pattern filter
|
|
commands := params.GetAllCommands()
|
|
g := glob.MustCompile(params.Command[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 if strings.EqualFold("MODULE", params.Command[3]) {
|
|
// Module filter
|
|
commands := params.GetAllCommands()
|
|
module := strings.ToLower(params.Command[4])
|
|
for _, command := range commands {
|
|
if command.SubCommands != nil && len(command.SubCommands) > 0 {
|
|
for _, subcommand := range command.SubCommands {
|
|
if strings.EqualFold(subcommand.Module, module) {
|
|
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 strings.EqualFold(command.Module, module) {
|
|
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(params.Command[3]))
|
|
}
|
|
res = fmt.Sprintf("*%d\r\n%s", count, res)
|
|
return []byte(res), nil
|
|
default:
|
|
return nil, errors.New(constants.WrongArgsResponse)
|
|
}
|
|
}
|
|
|
|
func handleCommandDocs(params internal.HandlerFuncParams) ([]byte, error) {
|
|
return []byte("*0\r\n"), nil
|
|
}
|
|
|
|
func Commands() []internal.Command {
|
|
return []internal.Command{
|
|
{
|
|
Command: "commands",
|
|
Module: constants.AdminModule,
|
|
Categories: []string{constants.AdminCategory, constants.SlowCategory},
|
|
Description: "Get a list of all the commands in available on the echovault with categories and descriptions",
|
|
Sync: false,
|
|
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
|
return internal.KeyExtractionFuncResult{
|
|
Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0),
|
|
}, nil
|
|
},
|
|
HandlerFunc: handleGetAllCommands,
|
|
},
|
|
{
|
|
Command: "command",
|
|
Module: constants.AdminModule,
|
|
Categories: []string{},
|
|
Description: "Commands pertaining to echovault commands",
|
|
Sync: false,
|
|
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
|
return internal.KeyExtractionFuncResult{
|
|
Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0),
|
|
}, nil
|
|
},
|
|
SubCommands: []internal.SubCommand{
|
|
{
|
|
Command: "docs",
|
|
Module: constants.AdminModule,
|
|
Categories: []string{constants.SlowCategory, constants.ConnectionCategory},
|
|
Description: "Get command documentation",
|
|
Sync: false,
|
|
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
|
return internal.KeyExtractionFuncResult{
|
|
Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0),
|
|
}, nil
|
|
},
|
|
HandlerFunc: handleCommandDocs,
|
|
},
|
|
{
|
|
Command: "count",
|
|
Module: constants.AdminModule,
|
|
Categories: []string{constants.SlowCategory},
|
|
Description: "Get the dumber of commands in the echovault",
|
|
Sync: false,
|
|
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
|
return internal.KeyExtractionFuncResult{
|
|
Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0),
|
|
}, nil
|
|
},
|
|
HandlerFunc: handleCommandCount,
|
|
},
|
|
{
|
|
Command: "list",
|
|
Module: constants.AdminModule,
|
|
Categories: []string{constants.SlowCategory},
|
|
Description: `(COMMAND LIST [FILTERBY <ACLCAT category | PATTERN pattern | MODULE module>]) Get the list of command names.
|
|
Allows for filtering by ACL category or glob pattern.`,
|
|
Sync: false,
|
|
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
|
return internal.KeyExtractionFuncResult{
|
|
Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0),
|
|
}, nil
|
|
},
|
|
HandlerFunc: handleCommandList,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Command: "save",
|
|
Module: constants.AdminModule,
|
|
Categories: []string{constants.AdminCategory, constants.SlowCategory, constants.DangerousCategory},
|
|
Description: "(SAVE) Trigger a snapshot save",
|
|
Sync: true,
|
|
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
|
return internal.KeyExtractionFuncResult{
|
|
Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0),
|
|
}, nil
|
|
},
|
|
HandlerFunc: func(params internal.HandlerFuncParams) ([]byte, error) {
|
|
if err := params.TakeSnapshot(); err != nil {
|
|
return nil, err
|
|
}
|
|
return []byte(constants.OkResponse), nil
|
|
},
|
|
},
|
|
{
|
|
Command: "lastsave",
|
|
Module: constants.AdminModule,
|
|
Categories: []string{constants.AdminCategory, constants.FastCategory, constants.DangerousCategory},
|
|
Description: "(LASTSAVE) Get unix timestamp for the latest snapshot in milliseconds.",
|
|
Sync: false,
|
|
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
|
return internal.KeyExtractionFuncResult{
|
|
Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0),
|
|
}, nil
|
|
},
|
|
HandlerFunc: func(params internal.HandlerFuncParams) ([]byte, error) {
|
|
msec := params.GetLatestSnapshotTime()
|
|
if msec == 0 {
|
|
return nil, errors.New("no snapshot")
|
|
}
|
|
return []byte(fmt.Sprintf(":%d\r\n", msec)), nil
|
|
},
|
|
},
|
|
{
|
|
Command: "rewriteaof",
|
|
Module: constants.AdminModule,
|
|
Categories: []string{constants.AdminCategory, constants.SlowCategory, constants.DangerousCategory},
|
|
Description: "(REWRITEAOF) Trigger re-writing of append process",
|
|
Sync: false,
|
|
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
|
return internal.KeyExtractionFuncResult{
|
|
Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0),
|
|
}, nil
|
|
},
|
|
HandlerFunc: func(params internal.HandlerFuncParams) ([]byte, error) {
|
|
if err := params.RewriteAOF(); err != nil {
|
|
return nil, err
|
|
}
|
|
return []byte(constants.OkResponse), nil
|
|
},
|
|
},
|
|
{
|
|
Command: "module",
|
|
Module: constants.AdminModule,
|
|
Categories: []string{},
|
|
Description: "Module commands",
|
|
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
|
return internal.KeyExtractionFuncResult{
|
|
Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0),
|
|
}, nil
|
|
},
|
|
SubCommands: []internal.SubCommand{
|
|
{
|
|
Command: "load",
|
|
Module: constants.AdminModule,
|
|
Categories: []string{constants.AdminCategory, constants.SlowCategory, constants.DangerousCategory},
|
|
Description: `(MODULE LOAD path [arg [arg ...]]) Load a module from a dynamic library at runtime.
|
|
The path should be the full path to the module, including the .so filename. Any args will be be passed unmodified to the
|
|
module's key extraction and handler functions.`,
|
|
Sync: true,
|
|
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
|
return internal.KeyExtractionFuncResult{
|
|
Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0),
|
|
}, nil
|
|
},
|
|
HandlerFunc: func(params internal.HandlerFuncParams) ([]byte, error) {
|
|
if len(params.Command) < 3 {
|
|
return nil, errors.New(constants.WrongArgsResponse)
|
|
}
|
|
var args []string
|
|
if len(params.Command) > 3 {
|
|
args = params.Command[3:]
|
|
}
|
|
if err := params.LoadModule(params.Command[2], args...); err != nil {
|
|
return nil, err
|
|
}
|
|
return []byte(constants.OkResponse), nil
|
|
},
|
|
},
|
|
{
|
|
Command: "unload",
|
|
Module: constants.AdminModule,
|
|
Categories: []string{constants.AdminCategory, constants.SlowCategory, constants.DangerousCategory},
|
|
Description: `(MODULE UNLOAD name) Unloads a module based on the its name as displayed by the MODULE LIST command.`,
|
|
Sync: true,
|
|
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
|
return internal.KeyExtractionFuncResult{
|
|
Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0),
|
|
}, nil
|
|
},
|
|
HandlerFunc: func(params internal.HandlerFuncParams) ([]byte, error) {
|
|
if len(params.Command) != 3 {
|
|
return nil, errors.New(constants.WrongArgsResponse)
|
|
}
|
|
params.UnloadModule(params.Command[2])
|
|
return []byte(constants.OkResponse), nil
|
|
},
|
|
},
|
|
{
|
|
Command: "list",
|
|
Module: constants.AdminModule,
|
|
Categories: []string{constants.AdminModule, constants.SlowCategory, constants.DangerousCategory},
|
|
Description: `(MODULE LIST) List all the modules that are currently loaded in the server.`,
|
|
Sync: false,
|
|
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
|
return internal.KeyExtractionFuncResult{
|
|
Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0),
|
|
}, nil
|
|
},
|
|
HandlerFunc: func(params internal.HandlerFuncParams) ([]byte, error) {
|
|
modules := params.ListModules()
|
|
res := fmt.Sprintf("*%d\r\n", len(modules))
|
|
for _, module := range modules {
|
|
res += fmt.Sprintf("$%d\r\n%s\r\n", len(module), module)
|
|
}
|
|
return []byte(res), nil
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|