mirror of https://github.com/1Panel-dev/1Panel
fix: 解决安装应用锁库的问题 (#576)
parent
e746a959af
commit
550872a564
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -91,6 +91,7 @@ var (
|
|||
var (
|
||||
ErrUserIsExist = "ErrUserIsExist"
|
||||
ErrDatabaseIsExist = "ErrDatabaseIsExist"
|
||||
ErrExecTimeOut = "ErrExecTimeOut"
|
||||
)
|
||||
|
||||
// redis
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -52,6 +52,7 @@ ErrEmailIsExist: '邮箱已存在'
|
|||
#mysql
|
||||
ErrUserIsExist: "当前用户已存在,请重新输入"
|
||||
ErrDatabaseIsExist: "当前数据库已存在,请重新输入"
|
||||
ErrExecTimeOut: "SQL 执行超时,请检查{{ .detail }}容器"
|
||||
|
||||
#redis
|
||||
ErrTypeOfRedis: "恢复文件类型与当前持久化方式不符,请修改后重试"
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
<span class="input-help">
|
||||
{{ $t('website.deleteAppHelper') }}
|
||||
</span>
|
||||
<span class="input-help" style="color: red">
|
||||
<span class="input-help" style="color: red" v-if="runtimeApp">
|
||||
{{ $t('website.deleteRuntimeHelper') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
|
|
Loading…
Reference in New Issue