From 550872a564d5935330e09d8469f2428d9bde4a98 Mon Sep 17 00:00:00 2001 From: zhengkunwang223 <31820853+zhengkunwang223@users.noreply.github.com> Date: Tue, 11 Apr 2023 14:38:27 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E8=A7=A3=E5=86=B3=E5=AE=89=E8=A3=85?= =?UTF-8?q?=E5=BA=94=E7=94=A8=E9=94=81=E5=BA=93=E7=9A=84=E9=97=AE=E9=A2=98?= =?UTF-8?q?=20(#576)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/repo/app_install.go | 5 +- backend/app/service/app.go | 102 +++++++++++------- backend/app/service/app_utils.go | 30 ++++-- backend/app/service/database_mysql.go | 27 +++-- backend/app/service/website.go | 27 +++-- backend/app/service/website_utils.go | 2 +- backend/constant/errs.go | 1 + backend/i18n/lang/en.yaml | 1 + backend/i18n/lang/zh.yaml | 1 + .../views/website/website/delete/index.vue | 2 +- 10 files changed, 134 insertions(+), 64 deletions(-) diff --git a/backend/app/repo/app_install.go b/backend/app/repo/app_install.go index 5c6d44d28..e863eebd7 100644 --- a/backend/app/repo/app_install.go +++ b/backend/app/repo/app_install.go @@ -3,6 +3,7 @@ package repo import ( "context" "encoding/json" + "gorm.io/gorm/clause" "github.com/1Panel-dev/1Panel/backend/app/model" "github.com/1Panel-dev/1Panel/backend/global" @@ -107,11 +108,11 @@ func (a *AppInstallRepo) GetFirstByCtx(ctx context.Context, opts ...DBOption) (m func (a *AppInstallRepo) Create(ctx context.Context, install *model.AppInstall) error { db := getTx(ctx).Model(&model.AppInstall{}) - return db.Create(&install).Error + return db.Omit(clause.Associations).Create(&install).Error } func (a *AppInstallRepo) Save(ctx context.Context, install *model.AppInstall) error { - return getTx(ctx).Save(&install).Error + return getTx(ctx).Omit(clause.Associations).Save(&install).Error } func (a *AppInstallRepo) DeleteBy(opts ...DBOption) error { diff --git a/backend/app/service/app.go b/backend/app/service/app.go index 2b2facbdc..eb617b2f2 100644 --- a/backend/app/service/app.go +++ b/backend/app/service/app.go @@ -224,34 +224,42 @@ func (a AppService) GetAppDetailByID(id uint) (*response.AppDetailDTO, error) { return res, nil } -func (a AppService) Install(ctx context.Context, req request.AppInstallCreate) (*model.AppInstall, error) { - if err := docker.CreateDefaultDockerNetwork(); err != nil { - return nil, buserr.WithDetail(constant.Err1PanelNetworkFailed, err.Error(), nil) +func (a AppService) Install(ctx context.Context, req request.AppInstallCreate) (appInstall *model.AppInstall, err error) { + if err = docker.CreateDefaultDockerNetwork(); err != nil { + err = buserr.WithDetail(constant.Err1PanelNetworkFailed, err.Error(), nil) + return } if list, _ := appInstallRepo.ListBy(commonRepo.WithByName(req.Name)); len(list) > 0 { - return nil, buserr.New(constant.ErrNameIsExist) + err = buserr.New(constant.ErrNameIsExist) + return } - httpPort, err := checkPort("PANEL_APP_PORT_HTTP", req.Params) + var ( + httpPort int + httpsPort int + appDetail model.AppDetail + app model.App + ) + httpPort, err = checkPort("PANEL_APP_PORT_HTTP", req.Params) + if err != nil { + return + } + httpsPort, err = checkPort("PANEL_APP_PORT_HTTPS", req.Params) + if err != nil { + return + } + appDetail, err = appDetailRepo.GetFirst(commonRepo.WithByID(req.AppDetailId)) + if err != nil { + return + } + app, err = appRepo.GetFirst(commonRepo.WithByID(appDetail.AppId)) if err != nil { return nil, err } - httpsPort, err := checkPort("PANEL_APP_PORT_HTTPS", req.Params) - if err != nil { - return nil, err - } - appDetail, err := appDetailRepo.GetFirst(commonRepo.WithByID(req.AppDetailId)) - if err != nil { - return nil, err - } - app, err := appRepo.GetFirst(commonRepo.WithByID(appDetail.AppId)) - if err != nil { - return nil, err - } - if err := checkRequiredAndLimit(app); err != nil { - return nil, err + if err = checkRequiredAndLimit(app); err != nil { + return } - appInstall := model.AppInstall{ + appInstall = &model.AppInstall{ Name: req.Name, AppId: appDetail.AppId, AppDetailId: appDetail.ID, @@ -262,13 +270,14 @@ func (a AppService) Install(ctx context.Context, req request.AppInstallCreate) ( App: app, } composeMap := make(map[string]interface{}) - if err := yaml.Unmarshal([]byte(appDetail.DockerCompose), &composeMap); err != nil { - return nil, err + if err = yaml.Unmarshal([]byte(appDetail.DockerCompose), &composeMap); err != nil { + return } value, ok := composeMap["services"] if !ok { - return nil, buserr.New("") + err = buserr.New("") + return } servicesMap := value.(map[string]interface{}) changeKeys := make(map[string]string, len(servicesMap)) @@ -289,35 +298,50 @@ func (a AppService) Install(ctx context.Context, req request.AppInstallCreate) ( servicesMap[v] = servicesMap[k] delete(servicesMap, k) } - composeByte, err := yaml.Marshal(composeMap) + + var ( + composeByte []byte + paramByte []byte + ) + + composeByte, err = yaml.Marshal(composeMap) if err != nil { - return nil, err + return } appInstall.DockerCompose = string(composeByte) - if err := copyAppData(app.Key, appDetail.Version, req.Name, req.Params, app.Resource == constant.AppResourceLocal); err != nil { - return nil, err + defer func() { + if err != nil { + hErr := handleAppInstallErr(ctx, appInstall) + if hErr != nil { + global.LOG.Errorf("delete app dir error %s", hErr.Error()) + } + } + }() + + if err = copyAppData(app.Key, appDetail.Version, req.Name, req.Params, app.Resource == constant.AppResourceLocal); err != nil { + return } fileOp := files.NewFileOp() - if err := fileOp.WriteFile(appInstall.GetComposePath(), strings.NewReader(string(composeByte)), 0775); err != nil { - return nil, err + if err = fileOp.WriteFile(appInstall.GetComposePath(), strings.NewReader(string(composeByte)), 0775); err != nil { + return } - paramByte, err := json.Marshal(req.Params) + paramByte, err = json.Marshal(req.Params) if err != nil { - return nil, err + return } appInstall.Env = string(paramByte) - if err := appInstallRepo.Create(ctx, &appInstall); err != nil { - return nil, err + if err = appInstallRepo.Create(ctx, appInstall); err != nil { + return } - if err := createLink(ctx, app, &appInstall, req.Params); err != nil { - return nil, err + if err = createLink(ctx, app, appInstall, req.Params); err != nil { + return } - if err := upAppPre(app, appInstall); err != nil { - return nil, err + if err = upAppPre(app, appInstall); err != nil { + return } - go upApp(ctx, appInstall) + go upApp(appInstall) go updateToolApp(appInstall) ports := []int{appInstall.HttpPort} if appInstall.HttpsPort > 0 { @@ -326,7 +350,7 @@ func (a AppService) Install(ctx context.Context, req request.AppInstallCreate) ( go func() { _ = OperateFirewallPort(nil, ports) }() - return &appInstall, nil + return } func (a AppService) GetAppUpdate() (*response.AppUpdateRes, error) { diff --git a/backend/app/service/app_utils.go b/backend/app/service/app_utils.go index 007f9421f..287613666 100644 --- a/backend/app/service/app_utils.go +++ b/backend/app/service/app_utils.go @@ -129,6 +129,22 @@ func createLink(ctx context.Context, app model.App, appInstall *model.AppInstall return nil } +func handleAppInstallErr(ctx context.Context, install *model.AppInstall) error { + op := files.NewFileOp() + appDir := install.GetPath() + dir, _ := os.Stat(appDir) + if dir != nil { + _, _ = compose.Down(install.GetComposePath()) + if err := op.DeleteDir(appDir); err != nil { + return err + } + } + if err := deleteLink(ctx, install, true, true); err != nil { + return err + } + return nil +} + func deleteAppInstall(ctx context.Context, install model.AppInstall, deleteBackup bool, forceDelete bool, deleteDB bool) error { op := files.NewFileOp() appDir := install.GetPath() @@ -381,7 +397,7 @@ func copyAppData(key, version, installName string, params map[string]interface{} } // 处理文件夹权限等问题 -func upAppPre(app model.App, appInstall model.AppInstall) error { +func upAppPre(app model.App, appInstall *model.AppInstall) error { if app.Key == "nexus" { dataPath := path.Join(appInstall.GetPath(), "data") if err := files.NewFileOp().Chown(dataPath, 200, 0); err != nil { @@ -391,7 +407,7 @@ func upAppPre(app model.App, appInstall model.AppInstall) error { return nil } -func getServiceFromInstall(appInstall model.AppInstall) (service *composeV2.ComposeService, err error) { +func getServiceFromInstall(appInstall *model.AppInstall) (service *composeV2.ComposeService, err error) { var ( project *types.Project envStr string @@ -412,8 +428,8 @@ func getServiceFromInstall(appInstall model.AppInstall) (service *composeV2.Comp return } -func upApp(ctx context.Context, appInstall model.AppInstall) { - upProject := func(appInstall model.AppInstall) (err error) { +func upApp(appInstall *model.AppInstall) { + upProject := func(appInstall *model.AppInstall) (err error) { if err == nil { var composeService *composeV2.ComposeService composeService, err = getServiceFromInstall(appInstall) @@ -437,9 +453,7 @@ func upApp(ctx context.Context, appInstall model.AppInstall) { } exist, _ := appInstallRepo.GetFirst(commonRepo.WithByID(appInstall.ID)) if exist.ID > 0 { - _ = appInstallRepo.Save(context.Background(), &appInstall) - } else { - _ = appInstallRepo.Save(ctx, &appInstall) + _ = appInstallRepo.Save(context.Background(), appInstall) } } @@ -598,7 +612,7 @@ func getAppInstallByKey(key string) (model.AppInstall, error) { return appInstall, nil } -func updateToolApp(installed model.AppInstall) { +func updateToolApp(installed *model.AppInstall) { tooKey, ok := dto.AppToolMap[installed.App.Key] if !ok { return diff --git a/backend/app/service/database_mysql.go b/backend/app/service/database_mysql.go index bf3c500bc..5984628b3 100644 --- a/backend/app/service/database_mysql.go +++ b/backend/app/service/database_mysql.go @@ -93,15 +93,15 @@ func (u *MysqlService) Create(ctx context.Context, req dto.MysqlDBCreate) (*mode } createSql := fmt.Sprintf("create database `%s` default character set %s collate %s", req.Name, req.Format, formatMap[req.Format]) - if err := excuteSql(app.ContainerName, app.Password, createSql); err != nil { + if err := excSQL(app.ContainerName, app.Password, createSql); err != nil { if strings.Contains(err.Error(), "ERROR 1007") { return nil, buserr.New(constant.ErrDatabaseIsExist) } return nil, err } tmpPermission := req.Permission - if err := excuteSql(app.ContainerName, app.Password, fmt.Sprintf("create user '%s'@'%s' identified by '%s';", req.Username, tmpPermission, req.Password)); err != nil { - _ = excuteSql(app.ContainerName, app.Password, fmt.Sprintf("drop database `%s`", req.Name)) + if err := excSQL(app.ContainerName, app.Password, fmt.Sprintf("create user '%s'@'%s' identified by '%s';", req.Username, tmpPermission, req.Password)); err != nil { + _ = excSQL(app.ContainerName, app.Password, fmt.Sprintf("drop database `%s`", req.Name)) if strings.Contains(err.Error(), "ERROR 1396") { return nil, buserr.New(constant.ErrUserIsExist) } @@ -114,7 +114,7 @@ func (u *MysqlService) Create(ctx context.Context, req dto.MysqlDBCreate) (*mode if app.Version == "5.7.39" { grantStr = fmt.Sprintf("%s identified by '%s' with grant option;", grantStr, req.Password) } - if err := excuteSql(app.ContainerName, app.Password, grantStr); err != nil { + if err := excSQL(app.ContainerName, app.Password, grantStr); err != nil { return nil, err } @@ -163,10 +163,10 @@ func (u *MysqlService) Delete(ctx context.Context, req dto.MysqlDBDelete) error return err } - if err := excuteSql(app.ContainerName, app.Password, fmt.Sprintf("drop user if exists '%s'@'%s'", db.Username, db.Permission)); err != nil && !req.ForceDelete { + if err := excSQL(app.ContainerName, app.Password, fmt.Sprintf("drop user if exists '%s'@'%s'", db.Username, db.Permission)); err != nil && !req.ForceDelete { return err } - if err := excuteSql(app.ContainerName, app.Password, fmt.Sprintf("drop database if exists `%s`", db.Name)); err != nil && !req.ForceDelete { + if err := excSQL(app.ContainerName, app.Password, fmt.Sprintf("drop database if exists `%s`", db.Name)); err != nil && !req.ForceDelete { return err } global.LOG.Info("execute delete database sql successful, now start to drop uploads and records") @@ -514,6 +514,21 @@ func excuteSql(containerName, password, command string) error { return nil } +func excSQL(containerName, password, command string) error { + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + cmd := exec.CommandContext(ctx, "docker", "exec", containerName, "mysql", "-uroot", "-p"+password, "-e", command) + err := cmd.Run() + if ctx.Err() == context.DeadlineExceeded { + return buserr.WithDetail(constant.ErrExecTimeOut, containerName, nil) + } + if err != nil { + stdStr := strings.ReplaceAll(err.Error(), "mysql: [Warning] Using a password on the command line interface can be insecure.\n", "") + return errors.New(stdStr) + } + return nil +} + func updateMyCnf(oldFiles []string, group string, param string, value interface{}) []string { isOn := false hasGroup := false diff --git a/backend/app/service/website.go b/backend/app/service/website.go index c63452677..da93acf1a 100644 --- a/backend/app/service/website.go +++ b/backend/app/service/website.go @@ -168,18 +168,25 @@ func (w WebsiteService) CreateWebsite(ctx context.Context, create request.Websit switch create.Type { case constant.Deployment: if create.AppType == constant.NewApp { - var req request.AppInstallCreate + var ( + req request.AppInstallCreate + install *model.AppInstall + ) req.Name = create.AppInstall.Name req.AppDetailId = create.AppInstall.AppDetailId req.Params = create.AppInstall.Params - install, err := NewIAppService().Install(ctx, req) + tx, installCtx := getTxAndContext() + install, err = NewIAppService().Install(installCtx, req) if err != nil { + tx.Rollback() return err } + tx.Commit() website.AppInstallID = install.ID appInstall = install } else { - install, err := appInstallRepo.GetFirst(commonRepo.WithByID(create.AppInstallID)) + var install model.AppInstall + install, err = appInstallRepo.GetFirst(commonRepo.WithByID(create.AppInstallID)) if err != nil { return err } @@ -187,28 +194,34 @@ func (w WebsiteService) CreateWebsite(ctx context.Context, create request.Websit website.AppInstallID = appInstall.ID } case constant.Runtime: - var err error runtime, err = runtimeRepo.GetFirst(commonRepo.WithByID(create.RuntimeID)) if err != nil { return err } website.RuntimeID = runtime.ID if runtime.Resource == constant.ResourceAppstore { - var req request.AppInstallCreate + var ( + req request.AppInstallCreate + nginxInstall model.AppInstall + install *model.AppInstall + ) reg, _ := regexp.Compile(`[^a-z0-9_-]+`) req.Name = reg.ReplaceAllString(strings.ToLower(create.PrimaryDomain), "") req.AppDetailId = create.AppInstall.AppDetailId req.Params = create.AppInstall.Params req.Params["IMAGE_NAME"] = runtime.Image - nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) + nginxInstall, err = getAppInstallByKey(constant.AppOpenresty) if err != nil { return err } req.Params["PANEL_WEBSITE_DIR"] = path.Join(nginxInstall.GetPath(), "/www") - install, err := NewIAppService().Install(ctx, req) + tx, installCtx := getTxAndContext() + install, err = NewIAppService().Install(installCtx, req) if err != nil { + tx.Rollback() return err } + tx.Commit() website.AppInstallID = install.ID appInstall = install } diff --git a/backend/app/service/website_utils.go b/backend/app/service/website_utils.go index e37cebcdd..3fdb4e00b 100644 --- a/backend/app/service/website_utils.go +++ b/backend/app/service/website_utils.go @@ -105,7 +105,7 @@ func createWebsiteFolder(nginxInstall model.AppInstall, website *model.Website, if err := fileOp.CreateDir(path.Join(siteFolder, "ssl"), 0755); err != nil { return err } - if runtime.Type == constant.RuntimePHP && runtime.Resource == constant.ResourceLocal { + if website.Type == constant.Runtime && runtime.Type == constant.RuntimePHP && runtime.Resource == constant.ResourceLocal { phpPoolDir := path.Join(siteFolder, "php-pool") if err := fileOp.CreateDir(phpPoolDir, 0755); err != nil { return err diff --git a/backend/constant/errs.go b/backend/constant/errs.go index dc8670b30..8ec738afc 100644 --- a/backend/constant/errs.go +++ b/backend/constant/errs.go @@ -91,6 +91,7 @@ var ( var ( ErrUserIsExist = "ErrUserIsExist" ErrDatabaseIsExist = "ErrDatabaseIsExist" + ErrExecTimeOut = "ErrExecTimeOut" ) // redis diff --git a/backend/i18n/lang/en.yaml b/backend/i18n/lang/en.yaml index 0c93a9b12..6db5d9728 100644 --- a/backend/i18n/lang/en.yaml +++ b/backend/i18n/lang/en.yaml @@ -52,6 +52,7 @@ ErrEmailIsExist: 'Email is already exist' #mysql ErrUserIsExist: "The current user already exists. Please enter a new user" ErrDatabaseIsExist: "The current database already exists. Please enter a new database" +ErrExecTimeOut: "SQL execution timed out, please check the {{ .detail }} container" #redis ErrTypeOfRedis: "The recovery file type does not match the current persistence mode. Modify the file type and try again" diff --git a/backend/i18n/lang/zh.yaml b/backend/i18n/lang/zh.yaml index 85f49cb76..108b46871 100644 --- a/backend/i18n/lang/zh.yaml +++ b/backend/i18n/lang/zh.yaml @@ -52,6 +52,7 @@ ErrEmailIsExist: '邮箱已存在' #mysql ErrUserIsExist: "当前用户已存在,请重新输入" ErrDatabaseIsExist: "当前数据库已存在,请重新输入" +ErrExecTimeOut: "SQL 执行超时,请检查{{ .detail }}容器" #redis ErrTypeOfRedis: "恢复文件类型与当前持久化方式不符,请修改后重试" diff --git a/frontend/src/views/website/website/delete/index.vue b/frontend/src/views/website/website/delete/index.vue index 79c86cda7..a4fda5d2c 100644 --- a/frontend/src/views/website/website/delete/index.vue +++ b/frontend/src/views/website/website/delete/index.vue @@ -23,7 +23,7 @@ {{ $t('website.deleteAppHelper') }} - + {{ $t('website.deleteRuntimeHelper') }}