You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1Panel/backend/utils/mysql/client/remote.go

377 lines
10 KiB

package client
import (
"context"
"database/sql"
"fmt"
"os"
"os/exec"
"strings"
"time"
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/1Panel-dev/1Panel/backend/utils/files"
"github.com/1Panel-dev/1Panel/backend/utils/mysql/helper"
)
type Remote struct {
Client *sql.DB
From string
User string
Password string
Address string
Port uint
}
func NewRemote(db Remote) *Remote {
return &db
}
func (r *Remote) Create(info CreateInfo) error {
createSql := fmt.Sprintf("create database `%s` default character set %s collate %s", info.Name, info.Format, formatMap[info.Format])
if err := r.ExecSQL(createSql, info.Timeout); err != nil {
if strings.Contains(err.Error(), "ERROR 1007") {
return buserr.New(constant.ErrDatabaseIsExist)
}
return err
}
if err := r.CreateUser(info); err != nil {
return err
}
return nil
}
func (r *Remote) CreateUser(info CreateInfo) error {
var userlist []string
if strings.Contains(info.Permission, ",") {
ips := strings.Split(info.Permission, ",")
for _, ip := range ips {
if len(ip) != 0 {
userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", info.Username, ip))
}
}
} else {
userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", info.Username, info.Permission))
}
for _, user := range userlist {
if err := r.ExecSQL(fmt.Sprintf("create user %s identified by '%s';", user, info.Password), info.Timeout); err != nil {
_ = r.Delete(DeleteInfo{
Name: info.Name,
Version: info.Version,
Username: info.Username,
Permission: info.Permission,
ForceDelete: true,
Timeout: 300})
if strings.Contains(err.Error(), "ERROR 1396") {
return buserr.New(constant.ErrUserIsExist)
}
return err
}
grantStr := fmt.Sprintf("grant all privileges on `%s`.* to %s", info.Name, user)
if info.Name == "*" {
grantStr = fmt.Sprintf("grant all privileges on *.* to %s", user)
}
if strings.HasPrefix(info.Version, "5.7") || strings.HasPrefix(info.Version, "5.6") {
grantStr = fmt.Sprintf("%s identified by '%s' with grant option;", grantStr, info.Password)
}
if err := r.ExecSQL(grantStr, info.Timeout); err != nil {
_ = r.Delete(DeleteInfo{
Name: info.Name,
Version: info.Version,
Username: info.Username,
Permission: info.Permission,
ForceDelete: true,
Timeout: 300})
return err
}
}
return nil
}
func (r *Remote) Delete(info DeleteInfo) error {
var userlist []string
if strings.Contains(info.Permission, ",") {
ips := strings.Split(info.Permission, ",")
for _, ip := range ips {
if len(ip) != 0 {
userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", info.Username, ip))
}
}
} else {
userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", info.Username, info.Permission))
}
for _, user := range userlist {
if strings.HasPrefix(info.Version, "5.6") {
if err := r.ExecSQL(fmt.Sprintf("drop user %s", user), info.Timeout); err != nil && !info.ForceDelete {
return err
}
} else {
if err := r.ExecSQL(fmt.Sprintf("drop user if exists %s", user), info.Timeout); err != nil && !info.ForceDelete {
return err
}
}
}
if len(info.Name) != 0 {
if err := r.ExecSQL(fmt.Sprintf("drop database if exists `%s`", info.Name), info.Timeout); err != nil && !info.ForceDelete {
return err
}
}
if !info.ForceDelete {
global.LOG.Info("execute delete database sql successful, now start to drop uploads and records")
}
return nil
}
func (r *Remote) ChangePassword(info PasswordChangeInfo) error {
if info.Username != "root" {
var userlist []string
if strings.Contains(info.Permission, ",") {
ips := strings.Split(info.Permission, ",")
for _, ip := range ips {
if len(ip) != 0 {
userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", info.Username, ip))
}
}
} else {
userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", info.Username, info.Permission))
}
for _, user := range userlist {
passwordChangeSql := fmt.Sprintf("set password for %s = password('%s')", user, info.Password)
if !strings.HasPrefix(info.Version, "5.7") && !strings.HasPrefix(info.Version, "5.6") {
passwordChangeSql = fmt.Sprintf("ALTER USER %s IDENTIFIED WITH mysql_native_password BY '%s';", user, info.Password)
}
if err := r.ExecSQL(passwordChangeSql, info.Timeout); err != nil {
return err
}
}
return nil
}
hosts, err := r.ExecSQLForHosts(info.Timeout)
if err != nil {
return err
}
for _, host := range hosts {
if host == "%" || host == "localhost" {
passwordRootChangeCMD := fmt.Sprintf("set password for 'root'@'%s' = password('%s')", host, info.Password)
if !strings.HasPrefix(info.Version, "5.7") && !strings.HasPrefix(info.Version, "5.6") {
passwordRootChangeCMD = fmt.Sprintf("alter user 'root'@'%s' identified with mysql_native_password BY '%s';", host, info.Password)
}
if err := r.ExecSQL(passwordRootChangeCMD, info.Timeout); err != nil {
return err
}
}
}
return nil
}
func (r *Remote) ChangeAccess(info AccessChangeInfo) error {
if info.Username == "root" {
info.OldPermission = "%"
info.Name = "*"
}
if info.Permission != info.OldPermission {
if err := r.Delete(DeleteInfo{
Version: info.Version,
Username: info.Username,
Permission: info.OldPermission,
ForceDelete: true,
Timeout: 300}); err != nil {
return err
}
if info.Username == "root" {
return nil
}
}
if err := r.CreateUser(CreateInfo{
Name: info.Name,
Version: info.Version,
Username: info.Username,
Password: info.Password,
Permission: info.Permission,
Timeout: info.Timeout,
}); err != nil {
return err
}
if err := r.ExecSQL("flush privileges", 300); err != nil {
return err
}
return nil
}
func (r *Remote) Backup(info BackupInfo) error {
fileOp := files.NewFileOp()
if !fileOp.Stat(info.TargetDir) {
if err := os.MkdirAll(info.TargetDir, os.ModePerm); err != nil {
return fmt.Errorf("mkdir %s failed, err: %v", info.TargetDir, err)
}
}
fileNameItem := info.TargetDir + "/" + strings.TrimSuffix(info.FileName, ".gz")
dns := fmt.Sprintf("%s:%s@tcp(%s:%v)/%s?charset=%s&parseTime=true&loc=Asia%sShanghai", r.User, r.Password, r.Address, r.Port, info.Name, info.Format, "%2F")
f, _ := os.OpenFile(fileNameItem, os.O_RDWR|os.O_CREATE, 0755)
defer f.Close()
if err := helper.Dump(dns, helper.WithData(), helper.WithDropTable(), helper.WithWriter(f)); err != nil {
return err
}
gzipCmd := exec.Command("gzip", fileNameItem)
stdout, err := gzipCmd.CombinedOutput()
if err != nil {
return fmt.Errorf("gzip file %s failed, stdout: %v, err: %v", strings.TrimSuffix(info.FileName, ".gz"), string(stdout), err)
}
return nil
}
func (r *Remote) Recover(info RecoverInfo) error {
fileName := info.SourceFile
if strings.HasSuffix(info.SourceFile, ".sql.gz") {
fileName = strings.TrimSuffix(info.SourceFile, ".gz")
gzipCmd := exec.Command("gunzip", info.SourceFile)
stdout, err := gzipCmd.CombinedOutput()
if err != nil {
return fmt.Errorf("gunzip file %s failed, stdout: %v, err: %v", info.SourceFile, string(stdout), err)
}
}
dns := fmt.Sprintf("%s:%s@tcp(%s:%v)/%s?charset=%s&parseTime=true&loc=Asia%sShanghai", r.User, r.Password, r.Address, r.Port, info.Name, info.Format, "%2F")
f, err := os.Open(fileName)
if err != nil {
return err
}
defer f.Close()
if err := helper.Source(dns, f, helper.WithMergeInsert(1000)); err != nil {
return err
}
return nil
}
func (r *Remote) SyncDB(version string) ([]SyncDBInfo, error) {
var datas []SyncDBInfo
rows, err := r.Client.Query("SELECT SCHEMA_NAME, DEFAULT_CHARACTER_SET_NAME FROM information_schema.SCHEMATA")
if err != nil {
return datas, err
}
defer rows.Close()
for rows.Next() {
var dbName, charsetName string
if err = rows.Scan(&dbName, &charsetName); err != nil {
return datas, err
}
if dbName == "information_schema" || dbName == "mysql" || dbName == "performance_schema" || dbName == "sys" {
continue
}
dataItem := SyncDBInfo{
Name: dbName,
From: r.From,
MysqlName: r.From,
Format: charsetName,
}
userRows, err := r.Client.Query("SELECT USER,HOST FROM mysql.DB WHERE DB = ?", dbName)
if err != nil {
return datas, err
}
var permissionItem []string
isLocal := true
i := 0
for userRows.Next() {
var user, host string
if err = userRows.Scan(&user, &host); err != nil {
return datas, err
}
if user == "root" {
continue
}
if i == 0 {
dataItem.Username = user
}
if dataItem.Username == user && host == "%" {
isLocal = false
dataItem.Permission = "%"
} else if dataItem.Username == user && host != "localhost" {
isLocal = false
permissionItem = append(permissionItem, host)
}
i++
}
if len(dataItem.Username) == 0 {
if err := r.CreateUser(CreateInfo{
Name: dbName,
Format: charsetName,
Version: version,
Username: dbName,
Password: common.RandStr(16),
Permission: "%",
Timeout: 300,
}); err != nil {
global.LOG.Errorf("sync from remote server failed, err: create user failed %v", err)
}
dataItem.Username = dbName
dataItem.Permission = "%"
} else {
if isLocal {
dataItem.Permission = "localhost"
}
if len(dataItem.Permission) == 0 {
dataItem.Permission = strings.Join(permissionItem, ",")
}
}
datas = append(datas, dataItem)
}
if err = rows.Err(); err != nil {
return datas, err
}
return datas, nil
}
func (r *Remote) Close() {
_ = r.Client.Close()
}
func (r *Remote) ExecSQL(command string, timeout uint) error {
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
defer cancel()
if _, err := r.Client.ExecContext(ctx, command); err != nil {
return err
}
if ctx.Err() == context.DeadlineExceeded {
return buserr.New(constant.ErrExecTimeOut)
}
return nil
}
func (r *Remote) ExecSQLForHosts(timeout uint) ([]string, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
defer cancel()
results, err := r.Client.QueryContext(ctx, "select host from mysql.user where user='root';")
if err != nil {
return nil, err
}
if ctx.Err() == context.DeadlineExceeded {
return nil, buserr.New(constant.ErrExecTimeOut)
}
var rows []string
for results.Next() {
var host string
if err := results.Scan(&host); err != nil {
continue
}
rows = append(rows, host)
}
return rows, nil
}