feat: 优化 PostgreSQL 远程数据库备份逻辑 (#3538)

This commit is contained in:
ssongliu 2024-01-09 15:53:33 +08:00 committed by GitHub
parent 9a905750af
commit 47524dc49b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 67 additions and 23 deletions

View File

@ -116,6 +116,7 @@ var (
ErrInUsed = "ErrInUsed"
ErrObjectInUsed = "ErrObjectInUsed"
ErrPortRules = "ErrPortRules"
ErrPgImagePull = "ErrPgImagePull"
)
// runtime

View File

@ -125,6 +125,7 @@ ErrTypeOfRedis: "The recovery file type does not match the current persistence m
ErrInUsed: "{{ .detail }} is in use and cannot be deleted"
ErrObjectInUsed: "This object is in use and cannot be deleted"
ErrPortRules: "The number of ports does not match, please re-enter!"
ErrPgImagePull: "Image pull timeout. Please configure image acceleration or manually pull the postgres:16.0-alpine image and try again"
#runtime
ErrDirNotFound: "The build folder does not exist! Please check file integrity"

View File

@ -126,6 +126,7 @@ ErrTypeOfRedis: "恢復文件類型與當前持久化方式不符,請修改後
ErrInUsed: "{{ .detail }} 正被使用,無法刪除"
ErrObjectInUsed: "該對象正被使用,無法刪除"
ErrPortRules: "端口數目不匹配,請重新輸入!"
ErrPgImagePull: "鏡像拉取超時,請配置鏡像加速或手動拉取 postgres:16.0-alpine 鏡像後重試"
#runtime
ErrDirNotFound: "build 文件夾不存在!請檢查文件完整性!"

View File

@ -125,6 +125,7 @@ ErrTypeOfRedis: "恢复文件类型与当前持久化方式不符,请修改后
ErrInUsed: "{{ .detail }} 正被使用,无法删除"
ErrObjectInUsed: "该对象正被使用,无法删除"
ErrPortRules: "端口数目不匹配,请重新输入!"
ErrPgImagePull: "镜像拉取超时,请配置镜像加速或手动拉取 postgres:16.0-alpine 镜像后重试"
#runtime
ErrDirNotFound: "build 文件夹不存在!请检查文件完整性!"

View File

@ -122,6 +122,10 @@ func (r *Remote) ChangePassword(info PasswordChangeInfo) error {
}
func (r *Remote) Backup(info BackupInfo) error {
imageTag, err := loadImageTag()
if err != nil {
return err
}
fileOp := files.NewFileOp()
if !fileOp.Stat(info.TargetDir) {
if err := os.MkdirAll(info.TargetDir, os.ModePerm); err != nil {
@ -129,7 +133,6 @@ func (r *Remote) Backup(info BackupInfo) error {
}
}
fileNameItem := info.TargetDir + "/" + strings.TrimSuffix(info.FileName, ".gz")
imageTag := loadImageTag()
backupCommand := exec.Command("bash", "-c",
fmt.Sprintf("docker run --rm --net=host -i %s /bin/bash -c 'PGPASSWORD=%s pg_dump -h %s -p %d --no-owner -Fc -U %s %s' > %s",
imageTag, r.Password, r.Address, r.Port, r.User, info.Name, fileNameItem))
@ -156,6 +159,10 @@ func (r *Remote) Backup(info BackupInfo) error {
}
func (r *Remote) Recover(info RecoverInfo) error {
imageTag, err := loadImageTag()
if err != nil {
return err
}
fileName := info.SourceFile
if strings.HasSuffix(info.SourceFile, ".sql.gz") {
fileName = strings.TrimSuffix(info.SourceFile, ".gz")
@ -169,7 +176,6 @@ func (r *Remote) Recover(info RecoverInfo) error {
_, _ = gzipCmd.CombinedOutput()
}()
}
imageTag := loadImageTag()
recoverCommand := exec.Command("bash", "-c",
fmt.Sprintf("docker run --rm --net=host -i %s /bin/bash -c 'PGPASSWORD=%s pg_restore -h %s -p %d --verbose --clean --no-privileges --no-owner -Fc -U %s -d %s --role=%s' < %s",
imageTag, r.Password, r.Address, r.Port, r.User, info.Name, info.Username, fileName))
@ -241,36 +247,64 @@ func (r *Remote) ExecSQL(command string, timeout uint) error {
return nil
}
func loadImageTag() string {
func loadImageTag() (string, error) {
var (
app model.App
appDetails []model.AppDetail
itemTag = "postgres:16.1-alpine"
versions []string
)
if err := global.DB.Where("key = ?", "postgresql").First(&app).Error; err != nil {
return itemTag
}
if err := global.DB.Where("app_id = ?", app.ID).Find(&appDetails).Error; err != nil {
return itemTag
versions = []string{"postgres:16.1-alpine", "postgres:16.0-alpine"}
} else {
if err := global.DB.Where("app_id = ?", app.ID).Find(&appDetails).Error; err != nil {
versions = []string{"postgres:16.1-alpine", "postgres:16.0-alpine"}
} else {
for _, item := range appDetails {
versions = append(versions, "postgres:"+item.Version)
}
}
}
client, err := docker.NewDockerClient()
if err != nil {
return itemTag
return "", err
}
images, err := client.ImageList(context.Background(), types.ImageListOptions{})
if err != nil {
return itemTag
return "", err
}
for _, item := range appDetails {
itemTag := ""
for _, item := range versions {
for _, image := range images {
for _, tag := range image.RepoTags {
if tag == "postgres:"+item.Version {
return tag
if tag == item {
itemTag = tag
break
}
}
if len(itemTag) != 0 {
break
}
}
if len(itemTag) != 0 {
break
}
}
return itemTag
if len(itemTag) != 0 {
return itemTag, nil
}
itemTag = "postgres:16.1-alpine"
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute)
defer cancel()
if _, err := client.ImagePull(ctx, itemTag, types.ImagePullOptions{}); err != nil {
if ctx.Err() == context.DeadlineExceeded {
return itemTag, buserr.New(constant.ErrPgImagePull)
}
global.LOG.Errorf("image %s pull failed, err: %v", itemTag, err)
return itemTag, fmt.Errorf("image %s pull failed, err: %v", itemTag, err)
}
return itemTag, nil
}

View File

@ -159,7 +159,7 @@ const message = {
commonName: 'Support English, Chinese, numbers, .-, and _ length 1-128',
userName: 'Support English, Chinese, numbers and _ length 3-30',
simpleName: 'Supports non-underscore starting, English, numbers, _, length 1-30',
dbName: 'Support English, Chinese, numbers, .-, and _ length 1-64',
dbName: 'Supports non-special character starting, including English, Chinese, numbers, .-_, with a length of 1-64',
imageName: 'Support English, numbers, :/.-_, length 1-150',
volumeName: 'Support English, numbers, .-_, length 2-30',
complexityPassword:

View File

@ -160,7 +160,7 @@ const message = {
commonName: '支持英文中文數字.-和_,長度1-128',
userName: '支持英文中文數字和_,長度3-30',
simpleName: '支持非底線開頭英文數字_,長度1-30',
dbName: '支持英文中文數字.-_,長度1-64',
dbName: '支持非特殊字符開頭英文中文數字.-_長度1-64',
imageName: '支持英文數字:/.-_,長度1-150',
volumeName: '支持英文數字.-和_,長度2-30',
complexityPassword: '請輸入長度為 8-30 並包含字母數字至少兩種特殊字符的密碼組合',

View File

@ -160,7 +160,7 @@ const message = {
commonName: '支持英文中文数字.-和_,长度1-128',
userName: '支持英文中文数字和_,长度3-30',
simpleName: '支持非下划线开头英文数字_,长度3-30',
dbName: '支持英文中文数字.-_,长度1-64',
dbName: '支持非特殊字符开头英文中文数字.-_,长度1-64',
imageName: '支持英文数字:/.-_,长度1-150',
volumeName: '支持英文数字.-和_,长度2-30',
complexityPassword: '请输入长度为 8-30 位且包含字母数字特殊字符至少两项的密码组合',

View File

@ -507,6 +507,9 @@ const onChangePassword = async (row: Database.PostgresqlDBInfo) => {
const buttons = [
{
label: i18n.global.t('database.changePassword'),
disabled: (row: Database.PostgresqlDBInfo) => {
return !row.username;
},
click: (row: Database.PostgresqlDBInfo) => {
onChangePassword(row);
},

View File

@ -4,12 +4,12 @@
<template #header>
<DrawerHeader :header="title" :resource="changeForm.postgresqlName" :back="handleClose" />
</template>
<el-form v-loading="loading" ref="changeFormRef" :model="changeForm" label-position="top">
<el-form v-loading="loading" ref="changeFormRef" :rules="rules" :model="changeForm" label-position="top">
<el-row type="flex" justify="center">
<el-col :span="22">
<div v-if="changeForm.operation === 'password'">
<el-form-item :label="$t('commons.login.username')" prop="userName">
<el-input disabled v-model="changeForm.userName"></el-input>
<el-form-item :label="$t('commons.login.username')" prop="username">
<el-input disabled v-model="changeForm.username"></el-input>
</el-form-item>
<el-form-item :label="$t('commons.login.password')" prop="password">
<el-input
@ -45,6 +45,7 @@ import { ElForm } from 'element-plus';
import { deleteCheckPostgresqlDB, updatePostgresqlPassword } from '@/api/modules/database';
import DrawerHeader from '@/components/drawer-header/index.vue';
import { MsgSuccess } from '@/utils/message';
import { Rules } from '@/global/form-rules';
const loading = ref();
const changeVisible = ref(false);
@ -57,12 +58,15 @@ const changeForm = reactive({
type: '',
database: '',
postgresqlName: '',
userName: '',
username: '',
password: '',
operation: '',
value: '',
});
const confirmDialogRef = ref();
const rules = reactive({
password: [Rules.paramComplexity],
});
interface DialogProps {
id: number;
@ -79,13 +83,12 @@ interface DialogProps {
}
const acceptParams = (params: DialogProps): void => {
title.value = i18n.global.t('database.changePassword');
changeForm.id = params.id;
changeForm.from = params.from;
changeForm.type = params.type;
changeForm.database = params.database;
changeForm.postgresqlName = params.postgresqlName;
changeForm.userName = params.username;
changeForm.username = params.username;
changeForm.password = params.password;
changeForm.operation = params.operation;
changeForm.value = params.value;