1
0
mirror of https://github.com/1Panel-dev/1Panel.git synced 2025-01-31 14:08:06 +08:00

feat: 完成 redis 备份功能

This commit is contained in:
ssongliu 2022-11-03 23:42:42 +08:00 committed by ssongliu
parent bfe2b95334
commit 917a11457e
14 changed files with 685 additions and 203 deletions

View File

@ -11,6 +11,7 @@ import (
"github.com/1Panel-dev/1Panel/backend/app/dto" "github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/constant" "github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global" "github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/compose"
"github.com/1Panel-dev/1Panel/backend/utils/docker" "github.com/1Panel-dev/1Panel/backend/utils/docker"
"github.com/1Panel-dev/1Panel/backend/utils/terminal" "github.com/1Panel-dev/1Panel/backend/utils/terminal"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
@ -65,6 +66,86 @@ func (b *BaseApi) UpdateRedisConf(c *gin.Context) {
helper.SuccessWithData(c, nil) helper.SuccessWithData(c, nil)
} }
func (b *BaseApi) UpdateRedisPersistenceConf(c *gin.Context) {
var req dto.RedisConfPersistenceUpdate
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := global.VALID.Struct(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := redisService.UpdatePersistenceConf(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
func (b *BaseApi) RedisBackup(c *gin.Context) {
if err := redisService.Backup(); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
func (b *BaseApi) RedisRecover(c *gin.Context) {
var req dto.RedisBackupRecover
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := global.VALID.Struct(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := redisService.Recover(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
func (b *BaseApi) RedisBackupList(c *gin.Context) {
var req dto.PageInfo
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
total, list, err := redisService.SearchBackupListWithPage(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, dto.PageResult{
Items: list,
Total: total,
})
}
func (b *BaseApi) RedisBackupDelete(c *gin.Context) {
var req dto.RedisBackupDelete
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
for _, name := range req.Names {
fullPath := fmt.Sprintf("%s/%s", req.FileDir, name)
if err := os.Remove(fullPath); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
}
helper.SuccessWithData(c, nil)
}
func (b *BaseApi) UpdateRedisConfByFile(c *gin.Context) { func (b *BaseApi) UpdateRedisConfByFile(c *gin.Context) {
var req dto.RedisConfUpdateByFile var req dto.RedisConfUpdateByFile
if err := c.ShouldBindJSON(&req); err != nil { if err := c.ShouldBindJSON(&req); err != nil {
@ -76,7 +157,7 @@ func (b *BaseApi) UpdateRedisConfByFile(c *gin.Context) {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return return
} }
path := fmt.Sprintf("/opt/1Panel/data/apps/redis/%s/conf/redis.conf", redisInfo.Name) path := fmt.Sprintf("%s/redis/%s/conf/redis.conf", constant.AppInstallDir, redisInfo.Name)
file, err := os.OpenFile(path, os.O_WRONLY|os.O_TRUNC, 0640) file, err := os.OpenFile(path, os.O_WRONLY|os.O_TRUNC, 0640)
if err != nil { if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
@ -87,6 +168,14 @@ func (b *BaseApi) UpdateRedisConfByFile(c *gin.Context) {
_, _ = write.WriteString(req.File) _, _ = write.WriteString(req.File)
write.Flush() write.Flush()
if req.RestartNow {
composeDir := fmt.Sprintf("%s/redis/%s/docker-compose.yml", constant.AppInstallDir, redisInfo.Name)
if _, err := compose.Restart(composeDir); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
}
helper.SuccessWithData(c, nil) helper.SuccessWithData(c, nil)
} }

View File

@ -145,12 +145,18 @@ type RecoverDB struct {
type RedisConfUpdate struct { type RedisConfUpdate struct {
Timeout string `json:"timeout"` Timeout string `json:"timeout"`
Maxclients string `json:"maxclients"` Maxclients string `json:"maxclients"`
Databases string `json:"databases"`
Requirepass string `json:"requirepass"` Requirepass string `json:"requirepass"`
Maxmemory string `json:"maxmemory"` Maxmemory string `json:"maxmemory"`
} }
type RedisConfPersistenceUpdate struct {
Type string `json:"type" validate:"required,oneof=aof rbd"`
Appendonly string `json:"appendonly"`
Appendfsync string `json:"appendfsync"`
Save string `json:"save"`
}
type RedisConfUpdateByFile struct { type RedisConfUpdateByFile struct {
File string `json:"file"` File string `json:"file"`
RestartNow bool `json:"restartNow"`
} }
type RedisConf struct { type RedisConf struct {
@ -158,13 +164,11 @@ type RedisConf struct {
ContainerName string `json:"containerName"` ContainerName string `json:"containerName"`
Timeout string `json:"timeout"` Timeout string `json:"timeout"`
Maxclients string `json:"maxclients"` Maxclients string `json:"maxclients"`
Databases string `json:"databases"`
Requirepass string `json:"requirepass"` Requirepass string `json:"requirepass"`
Maxmemory string `json:"maxmemory"` Maxmemory string `json:"maxmemory"`
} }
type RedisPersistence struct { type RedisPersistence struct {
Dir string `json:"dir"`
Appendonly string `json:"appendonly"` Appendonly string `json:"appendonly"`
Appendfsync string `json:"appendfsync"` Appendfsync string `json:"appendfsync"`
Save string `json:"save"` Save string `json:"save"`
@ -185,3 +189,18 @@ type RedisStatus struct {
KeyspaceMisses string `json:"keyspace_misses"` KeyspaceMisses string `json:"keyspace_misses"`
LatestForkUsec string `json:"latest_fork_usec"` LatestForkUsec string `json:"latest_fork_usec"`
} }
type RedisBackupRecords struct {
FileName string `json:"fileName"`
FileDir string `json:"fileDir"`
CreatedAt string `json:"createdAt"`
Size int `json:"size"`
}
type RedisBackupRecover struct {
FileName string `json:"fileName"`
FileDir string `json:"fileDir"`
}
type RedisBackupDelete struct {
FileDir string `json:"fileDir"`
Names []string `json:"names"`
}

View File

@ -22,7 +22,7 @@ type IMysqlRepo interface {
LoadRunningVersion(keys []string) ([]string, error) LoadRunningVersion(keys []string) ([]string, error)
LoadBaseInfoByName(name string) (*RootInfo, error) LoadBaseInfoByName(name string) (*RootInfo, error)
LoadRedisBaseInfo() (*RootInfo, error) LoadRedisBaseInfo() (*RootInfo, error)
UpdateMysqlConf(id uint, vars map[string]interface{}) error UpdateDatabasePassword(id uint, vars map[string]interface{}) error
} }
func NewIMysqlRepo() IMysqlRepo { func NewIMysqlRepo() IMysqlRepo {
@ -181,7 +181,7 @@ func (u *MysqlRepo) Update(id uint, vars map[string]interface{}) error {
return global.DB.Model(&model.DatabaseMysql{}).Where("id = ?", id).Updates(vars).Error return global.DB.Model(&model.DatabaseMysql{}).Where("id = ?", id).Updates(vars).Error
} }
func (u *MysqlRepo) UpdateMysqlConf(id uint, vars map[string]interface{}) error { func (u *MysqlRepo) UpdateDatabasePassword(id uint, vars map[string]interface{}) error {
if err := global.DB.Model(&model.AppInstall{}).Where("id = ?", id).Updates(vars).Error; err != nil { if err := global.DB.Model(&model.AppInstall{}).Where("id = ?", id).Updates(vars).Error; err != nil {
return err return err
} }

View File

@ -221,7 +221,7 @@ func (u *MysqlService) ChangeInfo(info dto.ChangeDBInfo) error {
} }
} }
} }
_ = mysqlRepo.UpdateMysqlConf(app.ID, map[string]interface{}{ _ = mysqlRepo.UpdateDatabasePassword(app.ID, map[string]interface{}{
"param": strings.ReplaceAll(app.Param, app.Password, info.Value), "param": strings.ReplaceAll(app.Param, app.Password, info.Value),
"env": strings.ReplaceAll(app.Env, app.Password, info.Value), "env": strings.ReplaceAll(app.Env, app.Password, info.Value),
}) })
@ -444,8 +444,8 @@ func excuteSql(containerName, password, command string) error {
return nil return nil
} }
func backupMysql(backupType, baseDir, backupDir, mysqkName, dbName, fileName string) error { func backupMysql(backupType, baseDir, backupDir, mysqlName, dbName, fileName string) error {
app, err := mysqlRepo.LoadBaseInfoByName(mysqkName) app, err := mysqlRepo.LoadBaseInfoByName(mysqlName)
if err != nil { if err != nil {
return err return err
} }

View File

@ -1,15 +1,19 @@
package service package service
import ( import (
"bufio"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io" "io/ioutil"
"os" "os"
"os/exec"
"path/filepath"
"strings" "strings"
"time"
"github.com/1Panel-dev/1Panel/backend/app/dto" "github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/go-redis/redis" "github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/utils/compose"
_ "github.com/go-sql-driver/mysql" _ "github.com/go-sql-driver/mysql"
) )
@ -17,84 +21,93 @@ type RedisService struct{}
type IRedisService interface { type IRedisService interface {
UpdateConf(req dto.RedisConfUpdate) error UpdateConf(req dto.RedisConfUpdate) error
UpdatePersistenceConf(req dto.RedisConfPersistenceUpdate) error
LoadStatus() (*dto.RedisStatus, error) LoadStatus() (*dto.RedisStatus, error)
LoadConf() (*dto.RedisConf, error) LoadConf() (*dto.RedisConf, error)
LoadPersistenceConf() (*dto.RedisPersistence, error) LoadPersistenceConf() (*dto.RedisPersistence, error)
// Backup(db dto.BackupDB) error Backup() error
// Recover(db dto.RecoverDB) error SearchBackupListWithPage(req dto.PageInfo) (int64, interface{}, error)
Recover(req dto.RedisBackupRecover) error
} }
func NewIRedisService() IRedisService { func NewIRedisService() IRedisService {
return &RedisService{} return &RedisService{}
} }
func newRedisClient() (*redis.Client, error) {
redisInfo, err := mysqlRepo.LoadRedisBaseInfo()
if err != nil {
return nil, err
}
client := redis.NewClient(&redis.Options{
Addr: fmt.Sprintf("localhost:%v", redisInfo.Port),
Password: redisInfo.Password,
DB: 0,
})
return client, nil
}
func (u *RedisService) UpdateConf(req dto.RedisConfUpdate) error { func (u *RedisService) UpdateConf(req dto.RedisConfUpdate) error {
redisInfo, err := mysqlRepo.LoadRedisBaseInfo() redisInfo, err := mysqlRepo.LoadRedisBaseInfo()
if err != nil { if err != nil {
return err return err
} }
file, err := os.OpenFile(fmt.Sprintf("/opt/1Panel/data/apps/redis/%s/conf/redis.conf", redisInfo.Name), os.O_RDWR, 0666) if err := configSetStr(redisInfo.ContainerName, redisInfo.Password, "timeout", req.Timeout); err != nil {
return err
}
if err := configSetStr(redisInfo.ContainerName, redisInfo.Password, "maxclients", req.Maxclients); err != nil {
return err
}
if err := mysqlRepo.UpdateDatabasePassword(redisInfo.ID, map[string]interface{}{
"param": strings.ReplaceAll(redisInfo.Param, redisInfo.Password, req.Requirepass),
"env": strings.ReplaceAll(redisInfo.Env, redisInfo.Password, req.Requirepass),
}); err != nil {
return err
}
if err := configSetStr(redisInfo.ContainerName, redisInfo.Password, "requirepass", req.Requirepass); err != nil {
return err
}
if err := configSetStr(redisInfo.ContainerName, redisInfo.Password, "maxmemory", req.Maxmemory); err != nil {
return err
}
commands := append(redisExec(redisInfo.ContainerName, redisInfo.Password), []string{"config", "rewrite"}...)
cmd := exec.Command("docker", commands...)
stdout, err := cmd.CombinedOutput()
if err != nil {
return errors.New(string(stdout))
}
return nil
}
func (u *RedisService) UpdatePersistenceConf(req dto.RedisConfPersistenceUpdate) error {
redisInfo, err := mysqlRepo.LoadRedisBaseInfo()
if err != nil { if err != nil {
return err return err
} }
defer file.Close()
reader := bufio.NewReader(file) if req.Type == "rbd" {
pos := int64(0) if err := configSetStr(redisInfo.ContainerName, redisInfo.Password, "save", req.Save); err != nil {
for { return err
line, err := reader.ReadString('\n')
if err != nil {
if err == io.EOF {
break
} else {
return err
}
} }
if bytes := updateConfFile(line, "timeout", req.Timeout); len(bytes) != 0 { } else {
_, _ = file.WriteAt(bytes, pos) if err := configSetStr(redisInfo.ContainerName, redisInfo.Password, "appendonly", req.Appendonly); err != nil {
return err
} }
if bytes := updateConfFile(line, "maxclients", req.Maxclients); len(bytes) != 0 { if err := configSetStr(redisInfo.ContainerName, redisInfo.Password, "appendfsync", req.Appendfsync); err != nil {
_, _ = file.WriteAt(bytes, pos) return err
} }
if bytes := updateConfFile(line, "databases", req.Databases); len(bytes) != 0 { }
_, _ = file.WriteAt(bytes, pos) commands := append(redisExec(redisInfo.ContainerName, redisInfo.Password), []string{"config", "rewrite"}...)
} cmd := exec.Command("docker", commands...)
if bytes := updateConfFile(line, "requirepass", req.Requirepass); len(bytes) != 0 { stdout, err := cmd.CombinedOutput()
_, _ = file.WriteAt(bytes, pos) if err != nil {
} return errors.New(string(stdout))
if bytes := updateConfFile(line, "maxmemory", req.Maxmemory); len(bytes) != 0 {
_, _ = file.WriteAt(bytes, pos)
}
pos += int64(len(line))
} }
return nil return nil
} }
func (u *RedisService) LoadStatus() (*dto.RedisStatus, error) { func (u *RedisService) LoadStatus() (*dto.RedisStatus, error) {
client, err := newRedisClient() redisInfo, err := mysqlRepo.LoadRedisBaseInfo()
if err != nil { if err != nil {
return nil, err return nil, err
} }
stdStr, err := client.Info().Result() commands := append(redisExec(redisInfo.ContainerName, redisInfo.Password), "info")
cmd := exec.Command("docker", commands...)
stdout, err := cmd.CombinedOutput()
if err != nil { if err != nil {
return nil, err return nil, errors.New(string(stdout))
} }
rows := strings.Split(stdStr, "\r\n") rows := strings.Split(string(stdout), "\r\n")
rowMap := make(map[string]string) rowMap := make(map[string]string)
for _, v := range rows { for _, v := range rows {
itemRow := strings.Split(v, ":") itemRow := strings.Split(v, ":")
@ -116,19 +129,22 @@ func (u *RedisService) LoadConf() (*dto.RedisConf, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
client := redis.NewClient(&redis.Options{
Addr: fmt.Sprintf("localhost:%v", redisInfo.Port),
Password: redisInfo.Password,
DB: 0,
})
var item dto.RedisConf var item dto.RedisConf
item.ContainerName = redisInfo.ContainerName item.ContainerName = redisInfo.ContainerName
item.Name = redisInfo.Name item.Name = redisInfo.Name
item.Timeout = configGetStr(client, "timeout") if item.Timeout, err = configGetStr(redisInfo.ContainerName, redisInfo.Password, "timeout"); err != nil {
item.Maxclients = configGetStr(client, "maxclients") return nil, err
item.Databases = configGetStr(client, "databases") }
item.Requirepass = configGetStr(client, "requirepass") if item.Maxclients, err = configGetStr(redisInfo.ContainerName, redisInfo.Password, "maxclients"); err != nil {
item.Maxmemory = configGetStr(client, "maxmemory") return nil, err
}
if item.Requirepass, err = configGetStr(redisInfo.ContainerName, redisInfo.Password, "requirepass"); err != nil {
return nil, err
}
if item.Maxmemory, err = configGetStr(redisInfo.ContainerName, redisInfo.Password, "maxmemory"); err != nil {
return nil, err
}
return &item, nil return &item, nil
} }
@ -137,38 +153,153 @@ func (u *RedisService) LoadPersistenceConf() (*dto.RedisPersistence, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
client := redis.NewClient(&redis.Options{
Addr: fmt.Sprintf("localhost:%v", redisInfo.Port),
Password: redisInfo.Password,
DB: 0,
})
var item dto.RedisPersistence var item dto.RedisPersistence
item.Dir = configGetStr(client, "dir") if item.Appendonly, err = configGetStr(redisInfo.ContainerName, redisInfo.Password, "appendonly"); err != nil {
item.Appendonly = configGetStr(client, "appendonly") return nil, err
item.Appendfsync = configGetStr(client, "appendfsync") }
item.Save = configGetStr(client, "save") if item.Appendfsync, err = configGetStr(redisInfo.ContainerName, redisInfo.Password, "appendfsync"); err != nil {
return nil, err
}
if item.Save, err = configGetStr(redisInfo.ContainerName, redisInfo.Password, "save"); err != nil {
return nil, err
}
return &item, nil return &item, nil
} }
func configGetStr(client *redis.Client, param string) string { func (u *RedisService) Backup() error {
item, _ := client.ConfigGet(param).Result() redisInfo, err := mysqlRepo.LoadRedisBaseInfo()
if len(item) == 2 { if err != nil {
if value, ok := item[1].(string); ok { return err
return value }
commands := append(redisExec(redisInfo.ContainerName, redisInfo.Password), "save")
cmd := exec.Command("docker", commands...)
if stdout, err := cmd.CombinedOutput(); err != nil {
return errors.New(string(stdout))
}
name := fmt.Sprintf("%s.rdb", time.Now().Format("20060102150405"))
backupLocal, err := backupRepo.Get(commonRepo.WithByType("LOCAL"))
if err != nil {
return err
}
localDir, err := loadLocalDir(backupLocal)
if err != nil {
return err
}
backupDir := fmt.Sprintf("database/redis/%s/", redisInfo.Name)
fullDir := fmt.Sprintf("%s/%s", localDir, backupDir)
if _, err := os.Stat(fullDir); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(fullDir, os.ModePerm); err != nil {
if err != nil {
return fmt.Errorf("mkdir %s failed, err: %v", fullDir, err)
}
} }
} }
return "" cmd2 := exec.Command("docker", "cp", fmt.Sprintf("%s:/data/dump.rdb", redisInfo.ContainerName), fmt.Sprintf("%s/%s", fullDir, name))
if stdout, err := cmd2.CombinedOutput(); err != nil {
return errors.New(string(stdout))
}
return nil
} }
func updateConfFile(line, param string, value string) []byte { func (u *RedisService) Recover(req dto.RedisBackupRecover) error {
var bytes []byte redisInfo, err := mysqlRepo.LoadRedisBaseInfo()
if strings.HasPrefix(line, param) || strings.HasPrefix(line, "# "+param) { if err != nil {
if len(value) == 0 || value == "0" { return err
bytes = []byte(fmt.Sprintf("# %s", param))
} else {
bytes = []byte(fmt.Sprintf("%s %v", param, value))
}
return bytes
} }
return bytes composeDir := fmt.Sprintf("%s/redis/%s", constant.AppInstallDir, redisInfo.Name)
if _, err := compose.Down(composeDir + "/docker-compose.yml"); err != nil {
return err
}
fullName := fmt.Sprintf("%s/%s", req.FileDir, req.FileName)
input, err := ioutil.ReadFile(fullName)
if err != nil {
return err
}
if err = ioutil.WriteFile(composeDir+"/data/dump.rdb", input, 0640); err != nil {
return err
}
if _, err := compose.Up(composeDir + "/docker-compose.yml"); err != nil {
return err
}
return nil
}
func (u *RedisService) SearchBackupListWithPage(req dto.PageInfo) (int64, interface{}, error) {
var (
list []dto.RedisBackupRecords
backDatas []dto.RedisBackupRecords
)
redisInfo, err := mysqlRepo.LoadRedisBaseInfo()
if err != nil {
return 0, nil, err
}
backupLocal, err := backupRepo.Get(commonRepo.WithByType("LOCAL"))
if err != nil {
return 0, nil, err
}
localDir, err := loadLocalDir(backupLocal)
if err != nil {
return 0, nil, err
}
backupDir := fmt.Sprintf("%s/database/redis/%s", localDir, redisInfo.Name)
_ = filepath.Walk(backupDir, func(path string, info os.FileInfo, err error) error {
if !info.IsDir() {
list = append(list, dto.RedisBackupRecords{
CreatedAt: info.ModTime().Format("2006-01-02 15:04:05"),
Size: int(info.Size()),
FileDir: backupDir,
FileName: info.Name(),
})
}
return nil
})
total, start, end := len(list), (req.Page-1)*req.PageSize, req.Page*req.PageSize
if start > total {
backDatas = make([]dto.RedisBackupRecords, 0)
} else {
if end >= total {
end = total
}
backDatas = list[start:end]
}
return int64(total), backDatas, nil
}
func configGetStr(containerName, password, param string) (string, error) {
commands := append(redisExec(containerName, password), []string{"config", "get", param}...)
cmd := exec.Command("docker", commands...)
stdout, err := cmd.CombinedOutput()
if err != nil {
return "", errors.New(string(stdout))
}
rows := strings.Split(string(stdout), "\r\n")
for _, v := range rows {
itemRow := strings.Split(v, "\n")
if len(itemRow) == 3 {
return itemRow[1], nil
}
}
return "", nil
}
func configSetStr(containerName, password, param, value string) error {
commands := append(redisExec(containerName, password), []string{"config", "set", param, value}...)
cmd := exec.Command("docker", commands...)
stdout, err := cmd.CombinedOutput()
if err != nil {
return errors.New(string(stdout))
}
return nil
}
func redisExec(containerName, password string) []string {
cmds := []string{"exec", containerName, "redis-cli", "-a", password, "--no-auth-warning"}
if len(password) == 0 {
cmds = []string{"exec", containerName, "redis-cli"}
}
return cmds
} }

View File

@ -1,45 +1,35 @@
package service package service
import ( import (
"encoding/json"
"fmt" "fmt"
"os/exec"
"strings"
"testing" "testing"
"github.com/1Panel-dev/1Panel/backend/app/dto" "github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/go-redis/redis"
) )
func TestMysql(t *testing.T) { func TestMysql(t *testing.T) {
client := redis.NewClient(&redis.Options{ cmd := exec.Command("docker", "exec", "1Panel-redis-7.0.5-zgVH-K859", "redis-cli", "config", "get", "save")
Addr: "172.16.10.143:6379", stdout, err := cmd.CombinedOutput()
Password: "", if err != nil {
DB: 0, fmt.Println(string(stdout))
}) }
fmt.Println(client.Ping().Result())
var item dto.RedisPersistence rows := strings.Split(string(stdout), "\r\n")
dir, _ := client.ConfigGet("dir").Result() rowMap := make(map[string]string)
if len(dir) == 2 { for _, v := range rows {
if value, ok := dir[1].(string); ok { itemRow := strings.Split(v, "\n")
item.Dir = value if len(itemRow) == 3 {
rowMap[itemRow[0]] = itemRow[1]
} }
} }
appendonly, _ := client.ConfigGet("appendonly").Result() var info dto.RedisStatus
if len(appendonly) == 2 { arr, err := json.Marshal(rowMap)
if value, ok := appendonly[1].(string); ok { if err != nil {
item.Appendonly = value fmt.Println(err)
}
} }
appendfsync, _ := client.ConfigGet("appendfsync").Result() _ = json.Unmarshal(arr, &info)
if len(appendfsync) == 2 { fmt.Println(info)
if value, ok := appendfsync[1].(string); ok {
item.Appendfsync = value
}
}
save, _ := client.ConfigGet("save").Result()
if len(save) == 2 {
if value, ok := save[1].(string); ok {
item.Save = value
}
}
fmt.Println(item)
} }

View File

@ -40,7 +40,12 @@ func (s *DatabaseRouter) InitDatabaseRouter(Router *gin.RouterGroup) {
cmdRouter.GET("/redis/status", baseApi.LoadRedisStatus) cmdRouter.GET("/redis/status", baseApi.LoadRedisStatus)
cmdRouter.GET("/redis/conf", baseApi.LoadRedisConf) cmdRouter.GET("/redis/conf", baseApi.LoadRedisConf)
cmdRouter.GET("/redis/exec", baseApi.RedisExec) cmdRouter.GET("/redis/exec", baseApi.RedisExec)
cmdRouter.POST("/redis/backup", baseApi.RedisBackup)
cmdRouter.POST("/redis/recover", baseApi.RedisRecover)
cmdRouter.POST("/redis/backup/records", baseApi.RedisBackupList)
cmdRouter.POST("/redis/backup/del", baseApi.RedisBackupDelete)
cmdRouter.POST("/redis/conf/update", baseApi.UpdateRedisConf) cmdRouter.POST("/redis/conf/update", baseApi.UpdateRedisConf)
cmdRouter.POST("/redis/conf/update/byfile", baseApi.UpdateRedisConfByFile) cmdRouter.POST("/redis/conf/update/byfile", baseApi.UpdateRedisConfByFile)
cmdRouter.POST("/redis/conf/update/persistence", baseApi.UpdateRedisPersistenceConf)
} }
} }

View File

@ -112,12 +112,18 @@ export namespace Database {
export interface RedisConfUpdate { export interface RedisConfUpdate {
timeout: string; timeout: string;
maxclients: string; maxclients: string;
databases: string;
requirepass: string; requirepass: string;
maxmemory: string; maxmemory: string;
} }
export interface RedisConfPersistenceUpdate {
type: string;
appendonly: string;
appendfsync: string;
save: string;
}
export interface RedisConfUpdateByFile { export interface RedisConfUpdateByFile {
file: string; file: string;
restartNow: boolean;
} }
export interface RedisStatus { export interface RedisStatus {
tcp_port: string; tcp_port: string;
@ -138,14 +144,26 @@ export namespace Database {
name: string; name: string;
timeout: number; timeout: number;
maxclients: number; maxclients: number;
databases: number;
requirepass: string; requirepass: string;
maxmemory: number; maxmemory: number;
} }
export interface RedisPersistenceConf { export interface RedisPersistenceConf {
dir: string;
appendonly: string; appendonly: string;
appendfsync: string; appendfsync: string;
save: string; save: string;
} }
export interface RedisBackupRecord {
fileName: string;
fileDir: string;
createdAt: string;
size: string;
}
export interface RedisBackupDelete {
fileDir: string;
names: Array<string>;
}
export interface RedisRecover {
fileName: string;
fileDir: string;
}
} }

View File

@ -1,5 +1,5 @@
import http from '@/api'; import http from '@/api';
import { ResPage } from '../interface'; import { ReqPage, ResPage } from '../interface';
import { Backup } from '../interface/backup'; import { Backup } from '../interface/backup';
import { Database } from '../interface/database'; import { Database } from '../interface/database';
@ -59,9 +59,24 @@ export const loadRedisConf = () => {
export const RedisPersistenceConf = () => { export const RedisPersistenceConf = () => {
return http.get<Database.RedisPersistenceConf>(`/databases/redis/persistence/conf`); return http.get<Database.RedisPersistenceConf>(`/databases/redis/persistence/conf`);
}; };
export const updateRedisPersistenceConf = (params: Database.RedisConfPersistenceUpdate) => {
return http.post(`/databases/redis/conf/update/persistence`, params);
};
export const updateRedisConf = (params: Database.RedisConfUpdate) => { export const updateRedisConf = (params: Database.RedisConfUpdate) => {
return http.post(`/databases/redis/conf/update`, params); return http.post(`/databases/redis/conf/update`, params);
}; };
export const updateRedisConfByFile = (params: Database.RedisConfUpdateByFile) => { export const updateRedisConfByFile = (params: Database.RedisConfUpdateByFile) => {
return http.post(`/databases/redis/conf/update/byfile`, params); return http.post(`/databases/redis/conf/update/byfile`, params);
}; };
export const backupRedis = () => {
return http.post(`/databases/redis/backup`);
};
export const recoverRedis = (param: Database.RedisRecover) => {
return http.post(`/databases/redis/recover`, param);
};
export const redisBackupRedisRecords = (param: ReqPage) => {
return http.post<ResPage<Database.RedisBackupRecord>>(`/databases/redis/backup/records`, param);
};
export const deleteBackupRedis = (param: Database.RedisBackupDelete) => {
return http.post(`/databases/redis/backup/del`, param);
};

View File

@ -252,14 +252,18 @@ export default {
hit: '查找数据库键命中率', hit: '查找数据库键命中率',
latestForkUsec: '最近一次 fork() 操作耗费的微秒数', latestForkUsec: '最近一次 fork() 操作耗费的微秒数',
baseConf: '基础配置',
allConf: '全部配置',
restartNow: '立即重启',
restartNowHelper1: '修改配置后需要',
restartNowHelper2: '重启 redis 生效',
restartNowHelper3: '若您的数据需要持久化请先执行 save 操作',
persistence: '持久化', persistence: '持久化',
persistenceDir: '持久化路径', rdbHelper1: '秒內,插入',
persistenceDirHelper: '设置后将在选择的目录下新建 redis_cache 目录并赋予 redis 权限', rdbHelper2: '条数据',
aofPersistence: 'AOF 持久化', rdbHelper3: '符合任意一个条件将会触发RDB持久化',
rdbPersistence: 'RDB 持久化', rdbInfo: '规则列表存在 0 请确认后重试',
drbHelper1: '秒內,插入',
drbHelper2: '条数据',
drbHelper3: '符合任意一个条件将会触发RDB持久化',
}, },
container: { container: {
operatorHelper: '将对选中容器进行 {0} 操作是否继续', operatorHelper: '将对选中容器进行 {0} 操作是否继续',

View File

@ -1,38 +1,147 @@
<template> <template>
<div v-if="persistenceShow"> <div v-if="persistenceShow">
<el-card style="margin-top: 5px"> <el-row :gutter="20" style="margin-top: 5px" class="row-box">
<el-form :model="form" ref="formRef" :rules="rules" label-width="120px"> <el-col :span="12">
<el-row> <el-card class="el-card">
<el-col :span="1"><br /></el-col> <template #header>
<el-col :span="10"> <div class="card-header">
<el-form> <span>AOF {{ $t('database.persistence') }}</span>
<el-form-item label="appendonly" prop="appendonly"> </div>
<el-switch v-model="form.appendonly"></el-switch> </template>
</el-form-item> <el-form :model="form" ref="formRef" :rules="rules" label-width="120px">
<el-form-item label="appendfsync" prop="appendfsync"> <el-row>
<el-radio-group v-model="form.appendfsync"> <el-col :span="1"><br /></el-col>
<el-radio label="always">always</el-radio> <el-col :span="10">
<el-radio label="everysec">everysec</el-radio> <el-form>
<el-radio label="no">no</el-radio> <el-form-item label="appendonly" prop="appendonly">
</el-radio-group> <el-switch
</el-form-item> active-value="yes"
</el-form> inactive-value="no"
</el-col> v-model="form.appendonly"
</el-row> ></el-switch>
</el-form> </el-form-item>
<el-form-item label="appendfsync" prop="appendfsync">
<el-radio-group v-model="form.appendfsync">
<el-radio label="always">always</el-radio>
<el-radio label="everysec">everysec</el-radio>
<el-radio label="no">no</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<el-button
type="primary"
@click="onSave(formRef, 'aof')"
style="bottom: 10px; width: 90px"
>
{{ $t('commons.button.save') }}
</el-button>
</el-col>
</el-row>
</el-form>
</el-card>
</el-col>
<el-col :span="12">
<el-card class="el-card">
<template #header>
<div class="card-header">
<span>RDB {{ $t('database.persistence') }}</span>
</div>
</template>
<table style="width: 100%" class="tab-table">
<tr v-for="(row, index) in form.saves" :key="index">
<td width="32%">
<el-input type="number" v-model="row.second"></el-input>
</td>
<td width="55px">
<span>{{ $t('database.rdbHelper1') }}</span>
</td>
<td width="32%">
<el-input type="number" v-model="row.count"></el-input>
</td>
<td width="12%">
<span>{{ $t('database.rdbHelper2') }}</span>
</td>
<td>
<el-button link type="primary" style="font-size: 10px" @click="handleDelete(index)">
{{ $t('commons.button.delete') }}
</el-button>
</td>
</tr>
<tr>
<td align="left">
<el-button @click="handleAdd()">{{ $t('commons.button.add') }}</el-button>
</td>
</tr>
</table>
<div>
<span style="margin-left: 2px; margin-top: 5px">{{ $t('database.rdbHelper3') }}</span>
</div>
<el-button type="primary" @click="onSave(undefined, 'rbd')" style="margin-top: 10px; width: 90px">
{{ $t('commons.button.save') }}
</el-button>
</el-card>
</el-col>
</el-row>
<el-card style="margin-top: 20px">
<ComplexTable
:pagination-config="paginationConfig"
v-model:selects="selects"
@search="loadBackupRecords"
:data="data"
>
<template #toolbar>
<el-button type="primary" @click="onBackup">{{ $t('setting.backup') }}</el-button>
<el-button type="danger" plain :disabled="selects.length === 0" @click="onBatchDelete(null)">
{{ $t('commons.button.delete') }}
</el-button>
</template>
<el-table-column type="selection" fix />
<el-table-column :label="$t('commons.table.name')" show-overflow-tooltip prop="fileName" />
<el-table-column :label="$t('file.dir')" show-overflow-tooltip prop="fileDir" />
<el-table-column :label="$t('file.size')" prop="size">
<template #default="{ row }">
{{ computeSize(row.size) }}
</template>
</el-table-column>
<el-table-column :label="$t('commons.table.createdAt')" prop="createdAt" />
<fu-table-operations
width="300px"
:buttons="buttons"
:ellipsis="10"
:label="$t('commons.table.operate')"
fix
/>
</ComplexTable>
</el-card> </el-card>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { RedisPersistenceConf } from '@/api/modules/database'; import ComplexTable from '@/components/complex-table/index.vue';
import { Database } from '@/api/interface/database';
import {
backupRedis,
deleteBackupRedis,
recoverRedis,
redisBackupRedisRecords,
RedisPersistenceConf,
updateRedisPersistenceConf,
} from '@/api/modules/database';
import { Rules } from '@/global/form-rules'; import { Rules } from '@/global/form-rules';
import { FormInstance } from 'element-plus'; import i18n from '@/lang';
import { ElMessage, FormInstance } from 'element-plus';
import { reactive, ref } from 'vue'; import { reactive, ref } from 'vue';
import { useDeleteData } from '@/hooks/use-delete-data';
import { computeSize } from '@/utils/util';
interface saveStruct {
second: number;
count: number;
}
const form = reactive({ const form = reactive({
appendonly: '', appendonly: '',
appendfsync: 'no', appendfsync: 'no',
saves: [] as Array<saveStruct>,
}); });
const rules = reactive({ const rules = reactive({
appendonly: [Rules.requiredSelect], appendonly: [Rules.requiredSelect],
@ -44,36 +153,124 @@ const persistenceShow = ref(false);
const acceptParams = (): void => { const acceptParams = (): void => {
persistenceShow.value = true; persistenceShow.value = true;
loadform(); loadform();
loadBackupRecords();
}; };
const onClose = (): void => { const onClose = (): void => {
persistenceShow.value = false; persistenceShow.value = false;
}; };
// const onSave = async (formEl: FormInstance | undefined, key: string) => { const data = ref();
// if (!formEl) return; const selects = ref<any>([]);
// const result = await formEl.validateField(key, callback); const paginationConfig = reactive({
// if (!result) { currentPage: 1,
// return; pageSize: 10,
// } total: 0,
// // let changeForm = { });
// // paramName: key,
// // value: val + '', const handleAdd = () => {
// // }; let item = {
// // await updateRedisConf(changeForm); second: 0,
// ElMessage.success(i18n.global.t('commons.msg.operationSuccess')); count: 0,
// }; };
// function callback(error: any) { form.saves.push(item);
// if (error) { };
// return error.message; const handleDelete = (index: number) => {
// } else { form.saves.splice(index, 1);
// return; };
// }
// } const loadBackupRecords = async () => {
let params = {
page: paginationConfig.currentPage,
pageSize: paginationConfig.pageSize,
};
const res = await redisBackupRedisRecords(params);
data.value = res.data.items || [];
paginationConfig.total = res.data.total;
};
const onBackup = async () => {
await backupRedis();
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
loadBackupRecords();
};
const onRecover = async (row: Database.RedisBackupRecord) => {
let param = {
fileName: row.fileName,
fileDir: row.fileDir,
};
await recoverRedis(param);
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
};
const onBatchDelete = async (row: Database.RedisBackupRecord | null) => {
let names: Array<string> = [];
let fileDir: string = '';
if (row) {
fileDir = row.fileDir;
names.push(row.fileName);
} else {
selects.value.forEach((item: Database.RedisBackupRecord) => {
fileDir = item.fileDir;
names.push(item.fileName);
});
}
await useDeleteData(deleteBackupRedis, { fileDir: fileDir, names: names }, 'commons.msg.delete', true);
loadBackupRecords();
};
const buttons = [
{
label: i18n.global.t('commons.button.recover'),
click: (row: Database.RedisBackupRecord) => {
onRecover(row);
},
},
{
label: i18n.global.t('commons.button.delete'),
click: (row: Database.RedisBackupRecord) => {
onBatchDelete(row);
},
},
];
const onSave = async (formEl: FormInstance | undefined, type: string) => {
let param = {} as Database.RedisConfPersistenceUpdate;
if (type == 'aof') {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
param.type = type;
param.appendfsync = form.appendfsync;
param.appendonly = form.appendonly;
await updateRedisPersistenceConf(param);
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
return;
});
return;
}
let itemSaves = [] as Array<string>;
for (const item of form.saves) {
if (item.count === 0 || item.second === 0) {
ElMessage.info(i18n.global.t('database.rdbInfo'));
return;
}
itemSaves.push(item.second + '', item.count + '');
}
param.type = type;
param.save = itemSaves.join(' ');
await updateRedisPersistenceConf(param);
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
};
const loadform = async () => { const loadform = async () => {
form.saves = [];
const res = await RedisPersistenceConf(); const res = await RedisPersistenceConf();
form.appendonly = res.data?.appendonly; form.appendonly = res.data?.appendonly;
form.appendfsync = res.data?.appendfsync; form.appendfsync = res.data?.appendfsync;
let itemSaves = res.data?.save.split(' ');
for (let i = 0; i < itemSaves.length; i++) {
if (i % 2 === 1) {
form.saves.push({ second: Number(itemSaves[i - 1]), count: Number(itemSaves[i]) });
}
}
}; };
defineExpose({ defineExpose({

View File

@ -2,8 +2,8 @@
<div v-if="settingShow"> <div v-if="settingShow">
<el-card style="margin-top: 5px"> <el-card style="margin-top: 5px">
<el-radio-group v-model="confShowType"> <el-radio-group v-model="confShowType">
<el-radio-button label="base">基础配置</el-radio-button> <el-radio-button label="base">{{ $t('database.baseConf') }}</el-radio-button>
<el-radio-button label="all">全部配置</el-radio-button> <el-radio-button label="all">{{ $t('database.allConf') }}</el-radio-button>
</el-radio-group> </el-radio-group>
<el-form v-if="confShowType === 'base'" :model="form" ref="formRef" :rules="rules" label-width="120px"> <el-form v-if="confShowType === 'base'" :model="form" ref="formRef" :rules="rules" label-width="120px">
<el-row style="margin-top: 20px"> <el-row style="margin-top: 20px">
@ -23,9 +23,6 @@
<el-form-item :label="$t('database.maxclients')" prop="maxclients"> <el-form-item :label="$t('database.maxclients')" prop="maxclients">
<el-input clearable type="number" v-model.number="form.maxclients" /> <el-input clearable type="number" v-model.number="form.maxclients" />
</el-form-item> </el-form-item>
<el-form-item :label="$t('database.databases')" prop="databases">
<el-input clearable type="number" v-model.number="form.databases" />
</el-form-item>
<el-form-item :label="$t('database.maxmemory')" prop="maxmemory"> <el-form-item :label="$t('database.maxmemory')" prop="maxmemory">
<el-input clearable type="number" v-model.number="form.maxmemory" /> <el-input clearable type="number" v-model.number="form.maxmemory" />
<span class="input-help">{{ $t('database.maxmemoryHelper') }}</span> <span class="input-help">{{ $t('database.maxmemoryHelper') }}</span>
@ -53,11 +50,40 @@
v-model="mysqlConf" v-model="mysqlConf"
:readOnly="true" :readOnly="true"
/> />
<el-button type="primary" size="default" @click="onSaveFile" style="width: 90px; margin-top: 5px"> <el-button
type="primary"
size="default"
@click="saveVisiable = true"
style="width: 90px; margin-top: 5px"
>
{{ $t('commons.button.save') }} {{ $t('commons.button.save') }}
</el-button> </el-button>
</div> </div>
</el-card> </el-card>
<el-dialog v-model="saveVisiable" :destroy-on-close="true" width="30%">
<template #header>
<div class="card-header">
<span>{{ $t('database.confChange') }}</span>
</div>
</template>
<el-checkbox v-model="restartNow" :label="$t('database.restartNow')" />
<div>
<span style="font-size: 12px">{{ $t('database.restartNowHelper1') }}</span>
<span style="font-size: 12px; color: red; font-weight: 500">
{{ $t('database.restartNowHelper2') }}
</span>
<span style="font-size: 12px">{{ $t('database.restartNowHelper3') }}</span>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="saveVisiable = false">{{ $t('commons.button.cancel') }}</el-button>
<el-button @click="onSaveFile()">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
</template>
</el-dialog>
</div> </div>
</template> </template>
@ -75,20 +101,20 @@ import { Rules } from '@/global/form-rules';
const extensions = [javascript(), oneDark]; const extensions = [javascript(), oneDark];
const confShowType = ref('base'); const confShowType = ref('base');
const restartNow = ref(false);
const saveVisiable = ref(false);
const form = reactive({ const form = reactive({
name: '', name: '',
port: 3306, port: 3306,
requirepass: '', requirepass: '',
timeout: 0, timeout: 0,
maxclients: 0, maxclients: 0,
databases: 0,
maxmemory: 0, maxmemory: 0,
}); });
const rules = reactive({ const rules = reactive({
port: [Rules.port], port: [Rules.port],
timeout: [Rules.number], timeout: [Rules.number],
maxclients: [Rules.number], maxclients: [Rules.number],
databases: [Rules.number],
maxmemory: [Rules.number], maxmemory: [Rules.number],
}); });
@ -106,17 +132,20 @@ const onClose = (): void => {
}; };
const onSave = async (formEl: FormInstance | undefined) => { const onSave = async (formEl: FormInstance | undefined) => {
if (confShowType.value === 'all') {
onSaveFile();
}
if (!formEl) return; if (!formEl) return;
formEl.validate(async (valid) => { formEl.validate(async (valid) => {
if (!valid) return; if (!valid) return;
let param = { let param = {
timeout: form.timeout + '', timeout: form.timeout + '',
maxclients: form.maxclients + '', maxclients: form.maxclients + '',
databases: form.databases + '',
requirepass: form.requirepass, requirepass: form.requirepass,
maxmemory: form.maxmemory + '', maxmemory: form.maxmemory + '',
}; };
await updateRedisConf(param); await updateRedisConf(param);
saveVisiable.value = false;
ElMessage.success(i18n.global.t('commons.msg.operationSuccess')); ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
}); });
}; };
@ -124,8 +153,11 @@ const onSave = async (formEl: FormInstance | undefined) => {
const onSaveFile = async () => { const onSaveFile = async () => {
let param = { let param = {
file: mysqlConf.value, file: mysqlConf.value,
restartNow: restartNow.value,
}; };
await updateRedisConfByFile(param); await updateRedisConfByFile(param);
saveVisiable.value = false;
restartNow.value = false;
ElMessage.success(i18n.global.t('commons.msg.operationSuccess')); ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
}; };
@ -134,7 +166,6 @@ const loadform = async () => {
form.name = res.data?.name; form.name = res.data?.name;
form.timeout = Number(res.data?.timeout); form.timeout = Number(res.data?.timeout);
form.maxclients = Number(res.data?.maxclients); form.maxclients = Number(res.data?.maxclients);
form.databases = Number(res.data?.databases);
form.requirepass = res.data?.requirepass; form.requirepass = res.data?.requirepass;
form.maxmemory = Number(res.data?.maxmemory); form.maxmemory = Number(res.data?.maxmemory);
loadMysqlConf(`/opt/1Panel/data/apps/redis/${form.name}/conf/redis.conf`); loadMysqlConf(`/opt/1Panel/data/apps/redis/${form.name}/conf/redis.conf`);

3
go.mod
View File

@ -16,7 +16,6 @@ require (
github.com/gin-gonic/gin v1.8.1 github.com/gin-gonic/gin v1.8.1
github.com/go-gormigrate/gormigrate/v2 v2.0.2 github.com/go-gormigrate/gormigrate/v2 v2.0.2
github.com/go-playground/validator/v10 v10.11.0 github.com/go-playground/validator/v10 v10.11.0
github.com/go-redis/redis v6.15.9+incompatible
github.com/go-sql-driver/mysql v1.6.0 github.com/go-sql-driver/mysql v1.6.0
github.com/gogf/gf v1.16.9 github.com/gogf/gf v1.16.9
github.com/golang-jwt/jwt/v4 v4.4.2 github.com/golang-jwt/jwt/v4 v4.4.2
@ -119,8 +118,6 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/nwaples/rardecode/v2 v2.0.0-beta.2 // indirect github.com/nwaples/rardecode/v2 v2.0.0-beta.2 // indirect
github.com/onsi/ginkgo v1.16.5 // indirect
github.com/onsi/gomega v1.21.1 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/runc v1.1.4 // indirect github.com/opencontainers/runc v1.1.4 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml v1.9.5 // indirect

14
go.sum
View File

@ -401,12 +401,9 @@ github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
github.com/go-playground/validator/v10 v10.11.0 h1:0W+xRM511GY47Yy3bZUbJVitCNg2BOGlCyvTqsp/xIw= github.com/go-playground/validator/v10 v10.11.0 h1:0W+xRM511GY47Yy3bZUbJVitCNg2BOGlCyvTqsp/xIw=
github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM= github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM=
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
@ -718,8 +715,6 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA
github.com/nwaples/rardecode/v2 v2.0.0-beta.2 h1:e3mzJFJs4k83GXBEiTaQ5HgSc/kOK8q0rDaRO0MPaOk= github.com/nwaples/rardecode/v2 v2.0.0-beta.2 h1:e3mzJFJs4k83GXBEiTaQ5HgSc/kOK8q0rDaRO0MPaOk=
github.com/nwaples/rardecode/v2 v2.0.0-beta.2/go.mod h1:yntwv/HfMc/Hbvtq9I19D1n58te3h6KsqCf3GxyfBGY= github.com/nwaples/rardecode/v2 v2.0.0-beta.2/go.mod h1:yntwv/HfMc/Hbvtq9I19D1n58te3h6KsqCf3GxyfBGY=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
@ -733,18 +728,13 @@ github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+
github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
github.com/onsi/gomega v1.21.1 h1:OB/euWYIExnPBohllTicTHmGTrMaqJ67nIu80j0/uEM=
github.com/onsi/gomega v1.21.1/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8luStNc=
github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
@ -1093,7 +1083,6 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
@ -1207,7 +1196,6 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -1310,7 +1298,6 @@ golang.org/x/tools v0.0.0-20200916195026-c9a70fc28ce3/go.mod h1:z6u4i615ZeAfBE4X
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
@ -1453,7 +1440,6 @@ gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=