mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2024-11-24 02:59:16 +08:00
feat: 优化 PostgreSQL 远程数据库备份逻辑 (#3538)
This commit is contained in:
parent
9a905750af
commit
47524dc49b
@ -116,6 +116,7 @@ var (
|
||||
ErrInUsed = "ErrInUsed"
|
||||
ErrObjectInUsed = "ErrObjectInUsed"
|
||||
ErrPortRules = "ErrPortRules"
|
||||
ErrPgImagePull = "ErrPgImagePull"
|
||||
)
|
||||
|
||||
// runtime
|
||||
|
@ -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!"
|
||||
|
@ -126,6 +126,7 @@ ErrTypeOfRedis: "恢復文件類型與當前持久化方式不符,請修改後
|
||||
ErrInUsed: "{{ .detail }} 正被使用,無法刪除"
|
||||
ErrObjectInUsed: "該對象正被使用,無法刪除"
|
||||
ErrPortRules: "端口數目不匹配,請重新輸入!"
|
||||
ErrPgImagePull: "鏡像拉取超時,請配置鏡像加速或手動拉取 postgres:16.0-alpine 鏡像後重試"
|
||||
|
||||
#runtime
|
||||
ErrDirNotFound: "build 文件夾不存在!請檢查文件完整性!"
|
||||
|
@ -125,6 +125,7 @@ ErrTypeOfRedis: "恢复文件类型与当前持久化方式不符,请修改后
|
||||
ErrInUsed: "{{ .detail }} 正被使用,无法删除"
|
||||
ErrObjectInUsed: "该对象正被使用,无法删除"
|
||||
ErrPortRules: "端口数目不匹配,请重新输入!"
|
||||
ErrPgImagePull: "镜像拉取超时,请配置镜像加速或手动拉取 postgres:16.0-alpine 镜像后重试"
|
||||
|
||||
#runtime
|
||||
ErrDirNotFound: "build 文件夹不存在!请检查文件完整性!"
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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 位,並包含字母、數字、至少兩種特殊字符的密碼組合',
|
||||
|
@ -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 位且包含字母、数字、特殊字符至少两项的密码组合',
|
||||
|
@ -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);
|
||||
},
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user