From 9c7552d037eb340e772644287eb6b6ee902826f2 Mon Sep 17 00:00:00 2001 From: zhengkunwang223 Date: Mon, 10 Oct 2022 15:10:53 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=88=A0=E9=99=A4wordpress=E7=BA=A7?= =?UTF-8?q?=E8=81=94=E5=88=A0=E9=99=A4datastore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/model/database.go | 1 + backend/app/repo/app_install.go | 4 +- backend/app/repo/database.go | 67 +++++++----- backend/app/service/app.go | 178 +++++++++---------------------- backend/app/service/app_utils.go | 154 ++++++++++++++++++++++++++ 5 files changed, 244 insertions(+), 160 deletions(-) create mode 100644 backend/app/service/app_utils.go diff --git a/backend/app/model/database.go b/backend/app/model/database.go index 1838f3fae..0f7fa5360 100644 --- a/backend/app/model/database.go +++ b/backend/app/model/database.go @@ -3,6 +3,7 @@ package model type Database struct { BaseModel AppContainerId uint `json:"appContainerId" gorm:"type:integer;not null"` + AppInstallId uint `json:"appInstallId" gorm:"type:integer;not null"` Key string `json:"key" gorm:"type:varchar(64);not null"` Dbname string `json:"dbname" gorm:"type:varchar(256);not null"` Username string `json:"username" gorm:"type:varchar(256);not null"` diff --git a/backend/app/repo/app_install.go b/backend/app/repo/app_install.go index c848552fc..55dc4e7cb 100644 --- a/backend/app/repo/app_install.go +++ b/backend/app/repo/app_install.go @@ -63,8 +63,8 @@ func (a AppInstallRepo) DeleteBy(opts ...DBOption) error { return db.Delete(&model.AppInstall{}).Error } -func (a AppInstallRepo) Delete(install model.AppInstall) error { - db := global.DB +func (a AppInstallRepo) Delete(ctx context.Context, install model.AppInstall) error { + db := ctx.Value("db").(*gorm.DB).Model(&model.AppInstall{}) return db.Delete(&install).Error } diff --git a/backend/app/repo/database.go b/backend/app/repo/database.go index d53ccb81e..2ac3c6eaa 100644 --- a/backend/app/repo/database.go +++ b/backend/app/repo/database.go @@ -3,45 +3,54 @@ package repo import ( "context" "github.com/1Panel-dev/1Panel/app/model" + "github.com/1Panel-dev/1Panel/global" "gorm.io/gorm" ) type DatabaseRepo struct { } +func (d DatabaseRepo) ByAppInstallId(installId uint) DBOption { + return func(db *gorm.DB) *gorm.DB { + return db.Where("app_install_id = ?", installId) + } +} + func (d DatabaseRepo) Create(ctx context.Context, database *model.Database) error { db := ctx.Value("db").(*gorm.DB).Model(&model.Database{}) return db.Create(&database).Error } -//func (a DatabaseRepo) BatchCreate(ctx context.Context, tags []*model.AppTag) error { -// db := ctx.Value("db").(*gorm.DB) -// return db.Create(&tags).Error -//} +func (d DatabaseRepo) DeleteBy(ctx context.Context, opts ...DBOption) error { + db := ctx.Value("db").(*gorm.DB).Model(&model.Database{}) + for _, opt := range opts { + db = opt(db) + } + return db.Delete(&model.Database{}).Error +} -//func (d DatabaseRepo) DeleteBy(ctx context.Context, appIds []uint) error { -// db := ctx.Value("db").(*gorm.DB) -// return db.Where("app_id in (?)", appIds).Delete(&model.AppTag{}).Error -//} +func (d DatabaseRepo) GetBy(opts ...DBOption) ([]model.Database, error) { + db := global.DB.Model(model.Database{}) + var databases []model.Database + for _, opt := range opts { + db = opt(db) + } + err := db.Find(&databases).Error + if err != nil { + return nil, err + } + return databases, nil +} -// -//func (a AppTagRepo) DeleteAll(ctx context.Context) error { -// db := ctx.Value("db").(*gorm.DB) -// return db.Where("1 = 1").Delete(&model.AppTag{}).Error -//} -// -//func (a AppTagRepo) GetByAppId(appId uint) ([]model.AppTag, error) { -// var appTags []model.AppTag -// if err := global.DB.Where("app_id = ?", appId).Find(&appTags).Error; err != nil { -// return nil, err -// } -// return appTags, nil -//} -// -//func (a AppTagRepo) GetByTagIds(tagIds []uint) ([]model.AppTag, error) { -// var appTags []model.AppTag -// if err := global.DB.Where("tag_id in (?)", tagIds).Find(&appTags).Error; err != nil { -// return nil, err -// } -// return appTags, nil -//} +func (d DatabaseRepo) GetFirst(opts ...DBOption) (model.Database, error) { + db := global.DB.Model(model.Database{}) + var database model.Database + for _, opt := range opts { + db = opt(db) + } + err := db.Find(&database).Error + if err != nil { + return database, err + } + return database, nil +} diff --git a/backend/app/service/app.go b/backend/app/service/app.go index 4288309b9..210ebf994 100644 --- a/backend/app/service/app.go +++ b/backend/app/service/app.go @@ -10,14 +10,13 @@ import ( "github.com/1Panel-dev/1Panel/app/repo" "github.com/1Panel-dev/1Panel/constant" "github.com/1Panel-dev/1Panel/global" - "github.com/1Panel-dev/1Panel/utils/cmd" "github.com/1Panel-dev/1Panel/utils/common" "github.com/1Panel-dev/1Panel/utils/compose" "github.com/1Panel-dev/1Panel/utils/docker" "github.com/1Panel-dev/1Panel/utils/files" - "github.com/joho/godotenv" "golang.org/x/net/context" "gopkg.in/yaml.v3" + "math" "os" "path" "reflect" @@ -180,17 +179,51 @@ func (a AppService) Operate(req dto.AppInstallOperate) error { op := files.NewFileOp() appDir := install.GetPath() dir, _ := os.Stat(appDir) + + tx := global.DB.Begin() + ctx := context.WithValue(context.Background(), "db", tx) if dir == nil { - return appInstallRepo.Delete(install) + if err := appInstallRepo.Delete(ctx, install); err != nil { + return err + } + tx.Commit() + return nil } out, err := compose.Down(dockerComposePath) if err != nil { + tx.Rollback() return handleErr(install, err, out) } if err := op.DeleteDir(appDir); err != nil { + tx.Rollback() return err } - return appInstallRepo.Delete(install) + if err := appInstallRepo.Delete(ctx, install); err != nil { + tx.Rollback() + return err + } + + database, _ := dataBaseRepo.GetFirst(dataBaseRepo.ByAppInstallId(install.ID)) + if reflect.DeepEqual(database, model.Database{}) { + tx.Commit() + return nil + } + if err := dataBaseRepo.DeleteBy(ctx, dataBaseRepo.ByAppInstallId(install.ID)); err != nil { + tx.Rollback() + return err + } + container, err := appContainerRepo.GetFirst(commonRepo.WithByID(database.AppContainerId)) + if err != nil { + tx.Commit() + return nil + } + + if err := execDockerCommand(database, container, Delete); err != nil { + tx.Rollback() + return err + } + tx.Commit() + return nil case dto.Sync: if err := a.SyncInstalled(install.ID); err != nil { return err @@ -275,32 +308,8 @@ func (a AppService) Install(name string, appDetailId uint, params map[string]int Params: string(paramByte), } - resourceDir := path.Join(global.CONF.System.ResourceDir, "apps", app.Key, appDetail.Version) - installDir := path.Join(global.CONF.System.AppDir, app.Key) - installVersionDir := path.Join(installDir, appDetail.Version) - fileOp := files.NewFileOp() - if err := fileOp.Copy(resourceDir, installVersionDir); err != nil { - return err - } - appDir := path.Join(installDir, name) - if err := fileOp.Rename(installVersionDir, appDir); err != nil { - return err - } - composeFilePath := path.Join(appDir, "docker-compose.yml") - envPath := path.Join(appDir, ".env") - - envParams := make(map[string]string, len(params)) - for k, v := range params { - switch t := v.(type) { - case string: - envParams[k] = t - case float64: - envParams[k] = strconv.FormatFloat(t, 'f', -1, 32) - default: - envParams[k] = t.(string) - } - } - if err := godotenv.Write(envParams, envPath); err != nil { + composeFilePath, err := copyAppData(app.Key, appDetail.Version, name, params) + if err != nil { return err } @@ -353,20 +362,11 @@ func (a AppService) Install(name string, appDetailId uint, params map[string]int containerName := constant.ContainerPrefix + k + "-" + common.RandStr(4) value["container_name"] = containerName servicePort := 0 - if portArray, ok := value["ports"].([]interface{}); ok { - for _, p := range portArray { - if pStr, ok := p.(string); ok { - start := strings.Index(pStr, "{") - end := strings.Index(pStr, "}") - if start > -1 && end > -1 { - portS := pStr[start+1 : end] - if v, ok := envParams[portS]; ok { - portN, _ := strconv.Atoi(v) - servicePort = portN - } - } - } - } + + port, ok := params["PANEL_APP_PORT"] + if ok { + portN := int(math.Ceil(port.(float64))) + servicePort = portN } appContainers = append(appContainers, &model.AppContainer{ @@ -384,6 +384,7 @@ func (a AppService) Install(name string, appDetailId uint, params map[string]int if err != nil { return err } + fileOp := files.NewFileOp() if err := fileOp.WriteFile(composeFilePath, strings.NewReader(string(composeByte)), 0775); err != nil { return err } @@ -423,19 +424,14 @@ func (a AppService) Install(name string, appDetailId uint, params map[string]int database.Dbname = dbConfig.DbName database.Username = dbConfig.DbUser database.Password = dbConfig.Password + database.AppInstallId = appInstall.ID + database.Key = app.Key if err := dataBaseRepo.Create(ctx, &database); err != nil { tx.Rollback() return err } - var auth dto.AuthParam - json.Unmarshal([]byte(container.Auth), &auth) - execConfig := dto.ContainerExec{ - ContainerName: container.ContainerName, - Auth: auth, - DbParam: dbConfig, - } - _, err = cmd.Exec(getSqlStr(app.Key, execConfig)) - if err != nil { + + if err := execDockerCommand(database, container, Add); err != nil { tx.Rollback() return err } @@ -752,7 +748,6 @@ func (a AppService) SyncAppList() error { } func syncCanUpdate() { - apps, err := appRepo.GetBy() if err != nil { global.LOG.Errorf("sync update app error: %s", err.Error()) @@ -787,78 +782,3 @@ func syncCanUpdate() { } } } - -func getApps(oldApps []model.App, items []dto.AppDefine) map[string]model.App { - apps := make(map[string]model.App, len(oldApps)) - for _, old := range oldApps { - old.Status = constant.AppTakeDown - apps[old.Key] = old - } - for _, item := range items { - app, ok := apps[item.Key] - if !ok { - app = model.App{} - } - app.Name = item.Name - app.Key = item.Key - app.ShortDesc = item.ShortDesc - app.Author = item.Author - app.Source = item.Source - app.Type = item.Type - app.CrossVersionUpdate = item.CrossVersionUpdate - app.Required = item.GetRequired() - app.Status = constant.AppNormal - apps[item.Key] = app - } - return apps -} - -func getAppDetails(details []model.AppDetail, versions []string) map[string]model.AppDetail { - appDetails := make(map[string]model.AppDetail, len(details)) - for _, old := range details { - old.Status = constant.AppTakeDown - appDetails[old.Version] = old - } - - for _, v := range versions { - detail, ok := appDetails[v] - if ok { - detail.Status = constant.AppNormal - appDetails[v] = detail - } else { - appDetails[v] = model.AppDetail{ - Version: v, - Status: constant.AppNormal, - } - } - } - return appDetails -} - -func upApp(composeFilePath string, appInstall model.AppInstall) { - out, err := compose.Up(composeFilePath) - if err != nil { - if out != "" { - appInstall.Message = out - } else { - appInstall.Message = err.Error() - } - appInstall.Status = constant.Error - _ = appInstallRepo.Save(appInstall) - } else { - appInstall.Status = constant.Running - _ = appInstallRepo.Save(appInstall) - } -} - -func getSqlStr(key string, exec dto.ContainerExec) string { - var str string - param := exec.DbParam - switch key { - case "mysql": - str = fmt.Sprintf("docker exec -i %s mysql -uroot -p%s -e \"CREATE USER '%s'@'%%' IDENTIFIED BY '%s';\" -e \"create database %s;\" -e \"GRANT ALL ON %s.* TO '%s'@'%%';\"", - exec.ContainerName, exec.Auth.RootPassword, param.DbUser, param.Password, param.DbName, param.DbName, param.DbUser) - } - fmt.Println(str) - return str -} diff --git a/backend/app/service/app_utils.go b/backend/app/service/app_utils.go new file mode 100644 index 000000000..50324366e --- /dev/null +++ b/backend/app/service/app_utils.go @@ -0,0 +1,154 @@ +package service + +import ( + "encoding/json" + "fmt" + "github.com/1Panel-dev/1Panel/app/dto" + "github.com/1Panel-dev/1Panel/app/model" + "github.com/1Panel-dev/1Panel/constant" + "github.com/1Panel-dev/1Panel/global" + "github.com/1Panel-dev/1Panel/utils/cmd" + "github.com/1Panel-dev/1Panel/utils/compose" + "github.com/1Panel-dev/1Panel/utils/files" + "github.com/joho/godotenv" + "path" + "strconv" +) + +type DatabaseOp string + +var ( + Add DatabaseOp = "add" + Delete DatabaseOp = "delete" +) + +func execDockerCommand(database model.Database, container model.AppContainer, op DatabaseOp) error { + var auth dto.AuthParam + var dbConfig dto.AppDatabase + dbConfig.Password = database.Password + dbConfig.DbUser = database.Username + dbConfig.DbName = database.Dbname + json.Unmarshal([]byte(container.Auth), &auth) + execConfig := dto.ContainerExec{ + ContainerName: container.ContainerName, + Auth: auth, + DbParam: dbConfig, + } + _, err := cmd.Exec(getSqlStr(database.Key, op, execConfig)) + if err != nil { + return err + } + return nil +} + +func getSqlStr(key string, operate DatabaseOp, exec dto.ContainerExec) string { + var str string + param := exec.DbParam + switch key { + case "mysql": + if operate == Add { + str = fmt.Sprintf("docker exec -i %s mysql -uroot -p%s -e \"CREATE USER '%s'@'%%' IDENTIFIED BY '%s';\" -e \"create database %s;\" -e \"GRANT ALL ON %s.* TO '%s'@'%%';\"", + exec.ContainerName, exec.Auth.RootPassword, param.DbUser, param.Password, param.DbName, param.DbName, param.DbUser) + } + if operate == Delete { + str = fmt.Sprintf("docker exec -i %s mysql -uroot -p%s -e \"drop database %s;\" -e \"drop user %s;\" ", + exec.ContainerName, exec.Auth.RootPassword, param.DbName, param.DbUser) + } + } + return str +} + +func copyAppData(key, version, installName string, params map[string]interface{}) (composeFilePath string, err error) { + resourceDir := path.Join(global.CONF.System.ResourceDir, "apps", key, version) + installDir := path.Join(global.CONF.System.AppDir, key) + installVersionDir := path.Join(installDir, version) + fileOp := files.NewFileOp() + if err = fileOp.Copy(resourceDir, installVersionDir); err != nil { + return + } + appDir := path.Join(installDir, installName) + if err = fileOp.Rename(installVersionDir, appDir); err != nil { + return + } + composeFilePath = path.Join(appDir, "docker-compose.yml") + envPath := path.Join(appDir, ".env") + + envParams := make(map[string]string, len(params)) + for k, v := range params { + switch t := v.(type) { + case string: + envParams[k] = t + case float64: + envParams[k] = strconv.FormatFloat(t, 'f', -1, 32) + default: + envParams[k] = t.(string) + } + } + if err = godotenv.Write(envParams, envPath); err != nil { + return + } + return +} + +func upApp(composeFilePath string, appInstall model.AppInstall) { + out, err := compose.Up(composeFilePath) + if err != nil { + if out != "" { + appInstall.Message = out + } else { + appInstall.Message = err.Error() + } + appInstall.Status = constant.Error + _ = appInstallRepo.Save(appInstall) + } else { + appInstall.Status = constant.Running + _ = appInstallRepo.Save(appInstall) + } +} + +func getAppDetails(details []model.AppDetail, versions []string) map[string]model.AppDetail { + appDetails := make(map[string]model.AppDetail, len(details)) + for _, old := range details { + old.Status = constant.AppTakeDown + appDetails[old.Version] = old + } + + for _, v := range versions { + detail, ok := appDetails[v] + if ok { + detail.Status = constant.AppNormal + appDetails[v] = detail + } else { + appDetails[v] = model.AppDetail{ + Version: v, + Status: constant.AppNormal, + } + } + } + return appDetails +} + +func getApps(oldApps []model.App, items []dto.AppDefine) map[string]model.App { + apps := make(map[string]model.App, len(oldApps)) + for _, old := range oldApps { + old.Status = constant.AppTakeDown + apps[old.Key] = old + } + for _, item := range items { + app, ok := apps[item.Key] + if !ok { + app = model.App{} + } + app.Name = item.Name + app.Key = item.Key + app.ShortDesc = item.ShortDesc + app.Author = item.Author + app.Source = item.Source + app.Type = item.Type + app.CrossVersionUpdate = item.CrossVersionUpdate + app.Required = item.GetRequired() + app.Status = constant.AppNormal + apps[item.Key] = app + } + return apps +}