1Panel/backend/app/service/app_install.go

515 lines
14 KiB
Go
Raw Normal View History

2022-11-18 14:27:40 +08:00
package service
import (
"context"
"encoding/json"
2022-11-18 14:27:40 +08:00
"fmt"
2022-11-18 16:14:23 +08:00
"io/ioutil"
"os"
"path"
2022-11-22 22:47:38 +08:00
"reflect"
2022-11-18 16:14:23 +08:00
"strconv"
"strings"
2022-12-20 20:30:14 +08:00
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
"github.com/1Panel-dev/1Panel/backend/app/dto/response"
2022-11-29 17:39:10 +08:00
"github.com/1Panel-dev/1Panel/backend/app/repo"
2022-11-18 14:27:40 +08:00
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/model"
"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/compose"
"github.com/1Panel-dev/1Panel/backend/utils/docker"
"github.com/1Panel-dev/1Panel/backend/utils/files"
"github.com/pkg/errors"
)
type AppInstallService struct {
}
2022-12-14 15:08:21 +08:00
func (a AppInstallService) Page(req request.AppInstalledSearch) (int64, []response.AppInstalledDTO, error) {
2022-11-28 13:50:53 +08:00
var opts []repo.DBOption
if req.Name != "" {
opts = append(opts, commonRepo.WithLikeName(req.Name))
}
total, installs, err := appInstallRepo.Page(req.Page, req.PageSize, opts...)
2022-11-18 14:27:40 +08:00
if err != nil {
return 0, nil, err
}
2022-11-22 14:22:25 +08:00
installDTOs, err := handleInstalled(installs)
if err != nil {
return 0, nil, err
2022-11-18 14:27:40 +08:00
}
return total, installDTOs, nil
}
2022-12-14 15:08:21 +08:00
func (a AppInstallService) CheckExist(key string) (*response.AppInstalledCheck, error) {
res := &response.AppInstalledCheck{
2022-11-22 22:47:38 +08:00
IsExist: false,
}
2022-11-18 16:14:23 +08:00
app, err := appRepo.GetFirst(appRepo.WithKey(key))
if err != nil {
2022-11-22 22:47:38 +08:00
return res, nil
2022-11-18 16:14:23 +08:00
}
res.App = app.Name
2022-11-18 16:14:23 +08:00
appInstall, _ := appInstallRepo.GetFirst(appInstallRepo.WithAppId(app.ID))
2022-11-22 22:47:38 +08:00
if reflect.DeepEqual(appInstall, model.AppInstall{}) {
return res, nil
}
2022-12-04 17:45:54 +08:00
if err := syncById(appInstall.ID); err != nil {
return nil, err
}
2022-11-23 16:59:06 +08:00
res.ContainerName = appInstall.ContainerName
2022-11-22 22:47:38 +08:00
res.Name = appInstall.Name
res.Version = appInstall.Version
res.CreatedAt = appInstall.CreatedAt
res.Status = appInstall.Status
res.AppInstallID = appInstall.ID
res.IsExist = true
res.InstallPath = path.Join(constant.AppInstallDir, appInstall.Name)
2022-11-22 22:47:38 +08:00
if len(appInstall.Backups) > 0 {
res.LastBackupAt = appInstall.Backups[0].CreatedAt.Format("2006-01-02 15:04:05")
2022-11-18 16:14:23 +08:00
}
2022-11-22 22:47:38 +08:00
return res, nil
2022-11-18 16:14:23 +08:00
}
func (a AppInstallService) LoadPort(key string) (int64, error) {
app, err := appInstallRepo.LoadBaseInfo(key, "")
if err != nil {
return int64(0), nil
}
return app.Port, nil
}
2022-12-20 20:30:14 +08:00
func (a AppInstallService) LoadPassword(key string) (string, error) {
app, err := appInstallRepo.LoadBaseInfo(key, "")
2022-12-20 20:30:14 +08:00
if err != nil {
return "", nil
}
return app.Password, nil
}
2022-12-14 15:08:21 +08:00
func (a AppInstallService) Search(req request.AppInstalledSearch) ([]response.AppInstalledDTO, error) {
2022-11-18 14:27:40 +08:00
var installs []model.AppInstall
var err error
if req.Type != "" {
apps, err := appRepo.GetBy(appRepo.WithType(req.Type))
if err != nil {
return nil, err
}
var ids []uint
for _, app := range apps {
ids = append(ids, app.ID)
}
installs, err = appInstallRepo.GetBy(appInstallRepo.WithAppIdsIn(ids), appInstallRepo.WithIdNotInWebsite())
2022-11-18 14:27:40 +08:00
if err != nil {
return nil, err
}
} else {
installs, err = appInstallRepo.GetBy()
if err != nil {
return nil, err
}
}
2022-11-22 14:22:25 +08:00
return handleInstalled(installs)
2022-11-18 14:27:40 +08:00
}
2022-12-14 15:08:21 +08:00
func (a AppInstallService) Operate(req request.AppInstalledOperate) error {
2022-11-18 14:27:40 +08:00
install, err := appInstallRepo.GetFirst(commonRepo.WithByID(req.InstallId))
if err != nil {
return err
}
dockerComposePath := install.GetComposePath()
switch req.Operate {
2022-12-14 15:08:21 +08:00
case constant.Up:
2022-11-18 14:27:40 +08:00
out, err := compose.Up(dockerComposePath)
if err != nil {
return handleErr(install, err, out)
}
install.Status = constant.Running
2022-12-14 15:08:21 +08:00
case constant.Down:
2022-12-06 15:59:25 +08:00
out, err := compose.Stop(dockerComposePath)
2022-11-18 14:27:40 +08:00
if err != nil {
return handleErr(install, err, out)
}
install.Status = constant.Stopped
2022-12-14 15:08:21 +08:00
case constant.Restart:
2022-11-18 14:27:40 +08:00
out, err := compose.Restart(dockerComposePath)
if err != nil {
return handleErr(install, err, out)
}
install.Status = constant.Running
2022-12-14 15:08:21 +08:00
case constant.Delete:
2022-11-18 14:27:40 +08:00
tx, ctx := getTxAndContext()
if err := deleteAppInstall(ctx, install, req.ForceDelete, req.DeleteBackup); err != nil {
2022-11-18 14:27:40 +08:00
tx.Rollback()
return err
}
tx.Commit()
return nil
2022-12-14 15:08:21 +08:00
case constant.Sync:
2022-11-18 14:27:40 +08:00
return syncById(install.ID)
2022-12-14 15:08:21 +08:00
case constant.Backup:
2022-11-18 14:27:40 +08:00
tx, ctx := getTxAndContext()
if err := backupInstall(ctx, install); err != nil {
tx.Rollback()
return err
}
tx.Commit()
return nil
2022-12-14 15:08:21 +08:00
case constant.Restore:
2022-11-18 14:27:40 +08:00
return restoreInstall(install, req.BackupId)
2022-12-14 15:08:21 +08:00
case constant.Update:
2022-11-18 14:27:40 +08:00
return updateInstall(install.ID, req.DetailId)
default:
return errors.New("operate not support")
}
return appInstallRepo.Save(&install)
}
func (a AppInstallService) SyncAll() error {
allList, err := appInstallRepo.GetBy()
if err != nil {
return err
}
go func() {
for _, i := range allList {
if err := syncById(i.ID); err != nil {
global.LOG.Errorf("sync install app[%s] error,mgs: %s", i.Name, err.Error())
}
}
}()
return nil
}
2022-12-14 15:08:21 +08:00
func (a AppInstallService) PageInstallBackups(req request.AppBackupSearch) (int64, []model.AppInstallBackup, error) {
2022-11-18 14:27:40 +08:00
return appInstallBackupRepo.Page(req.Page, req.PageSize, appInstallBackupRepo.WithAppInstallID(req.AppInstallID))
}
2022-12-14 15:08:21 +08:00
func (a AppInstallService) DeleteBackup(req request.AppBackupDelete) error {
2022-11-18 14:27:40 +08:00
backups, err := appInstallBackupRepo.GetBy(commonRepo.WithIdsIn(req.Ids))
if err != nil {
return err
}
fileOp := files.NewFileOp()
var errStr strings.Builder
for _, backup := range backups {
dst := path.Join(backup.Path, backup.Name)
if err := fileOp.DeleteFile(dst); err != nil {
errStr.WriteString(err.Error())
continue
}
if err := appInstallBackupRepo.Delete(context.TODO(), commonRepo.WithIdsIn(req.Ids)); err != nil {
errStr.WriteString(err.Error())
}
}
if errStr.String() != "" {
return errors.New(errStr.String())
}
return nil
}
2022-12-14 15:08:21 +08:00
func (a AppInstallService) GetServices(key string) ([]response.AppService, error) {
2022-11-18 14:27:40 +08:00
app, err := appRepo.GetFirst(appRepo.WithKey(key))
if err != nil {
return nil, err
}
installs, err := appInstallRepo.GetBy(appInstallRepo.WithAppId(app.ID), appInstallRepo.WithStatus(constant.Running))
if err != nil {
return nil, err
}
2022-12-14 15:08:21 +08:00
var res []response.AppService
2022-11-18 14:27:40 +08:00
for _, install := range installs {
paramMap := make(map[string]string)
if install.Param != "" {
_ = json.Unmarshal([]byte(install.Param), &paramMap)
}
2022-12-14 15:08:21 +08:00
res = append(res, response.AppService{
Label: install.Name,
Value: install.ServiceName,
Config: paramMap,
2022-11-18 14:27:40 +08:00
})
}
return res, nil
}
func (a AppInstallService) GetUpdateVersions(installId uint) ([]dto.AppVersion, error) {
install, err := appInstallRepo.GetFirst(commonRepo.WithByID(installId))
var versions []dto.AppVersion
if err != nil {
return versions, err
}
app, err := appRepo.GetFirst(commonRepo.WithByID(install.AppId))
if err != nil {
return versions, err
}
details, err := appDetailRepo.GetBy(appDetailRepo.WithAppId(app.ID))
if err != nil {
return versions, err
}
for _, detail := range details {
if common.CompareVersion(detail.Version, install.Version) {
versions = append(versions, dto.AppVersion{
Version: detail.Version,
DetailId: detail.ID,
})
}
}
return versions, nil
}
2022-12-14 15:08:21 +08:00
func (a AppInstallService) ChangeAppPort(req request.PortUpdate) error {
return updateInstallInfoInDB(req.Key, "", "port", true, strconv.FormatInt(req.Port, 10))
2022-11-18 14:27:40 +08:00
}
2022-12-01 19:25:02 +08:00
func (a AppInstallService) DeleteCheck(installId uint) ([]dto.AppResource, error) {
var res []dto.AppResource
appInstall, err := appInstallRepo.GetFirst(commonRepo.WithByID(installId))
if err != nil {
return nil, err
}
app, err := appRepo.GetFirst(commonRepo.WithByID(appInstall.AppId))
if err != nil {
return nil, err
}
if app.Type == "website" {
websites, _ := websiteRepo.GetBy(websiteRepo.WithAppInstallId(appInstall.ID))
for _, website := range websites {
res = append(res, dto.AppResource{
Type: "website",
Name: website.PrimaryDomain,
})
}
}
2022-12-13 20:03:54 +08:00
if app.Key == constant.AppNginx {
2022-12-01 19:25:02 +08:00
websites, _ := websiteRepo.GetBy()
for _, website := range websites {
res = append(res, dto.AppResource{
Type: "website",
Name: website.PrimaryDomain,
})
}
}
if app.Type == "runtime" {
resources, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithLinkId(appInstall.ID))
for _, resource := range resources {
linkInstall, _ := appInstallRepo.GetFirst(commonRepo.WithByID(resource.AppInstallId))
res = append(res, dto.AppResource{
Type: "app",
Name: linkInstall.Name,
})
}
}
2022-12-13 20:03:54 +08:00
if app.Key == constant.AppMysql {
2022-12-01 19:25:02 +08:00
databases, _ := mysqlRepo.List()
for _, database := range databases {
res = append(res, dto.AppResource{
Type: "database",
Name: database.Name,
})
}
}
return res, nil
}
2022-12-09 17:16:07 +08:00
func (a AppInstallService) GetDefaultConfigByKey(key string) (string, error) {
appInstall, err := getAppInstallByKey(key)
if err != nil {
return "", err
}
filePath := path.Join(constant.AppResourceDir, appInstall.App.Key, "versions", appInstall.Version, "conf")
2022-12-13 20:03:54 +08:00
if key == constant.AppMysql {
2022-12-09 17:16:07 +08:00
filePath = path.Join(filePath, "my.cnf")
}
2022-12-13 20:03:54 +08:00
if key == constant.AppRedis {
2022-12-09 17:16:07 +08:00
filePath = path.Join(filePath, "redis.conf")
}
2022-12-13 20:03:54 +08:00
if key == constant.AppNginx {
2022-12-09 17:16:07 +08:00
filePath = path.Join(filePath, "nginx.conf")
}
contentByte, err := os.ReadFile(filePath)
if err != nil {
return "", err
}
return string(contentByte), nil
}
2022-11-18 14:27:40 +08:00
func syncById(installId uint) error {
appInstall, err := appInstallRepo.GetFirst(commonRepo.WithByID(installId))
if err != nil {
return err
}
containerNames, err := getContainerNames(appInstall)
if err != nil {
return err
}
cli, err := docker.NewClient()
if err != nil {
return err
}
containers, err := cli.ListContainersByName(containerNames)
if err != nil {
return err
}
var (
errorContainers []string
notFoundContainers []string
runningContainers []string
exitedContainers []string
2022-11-18 14:27:40 +08:00
)
for _, n := range containers {
switch n.State {
case "exited":
exitedContainers = append(exitedContainers, n.Names[0])
case "running":
2022-11-18 14:27:40 +08:00
runningContainers = append(runningContainers, n.Names[0])
default:
errorContainers = append(errorContainers, n.Names[0])
2022-11-18 14:27:40 +08:00
}
}
for _, old := range containerNames {
exist := false
for _, new := range containers {
if common.ExistWithStrArray(old, new.Names) {
exist = true
break
}
}
if !exist {
notFoundContainers = append(notFoundContainers, old)
}
}
containerCount := len(containers)
errCount := len(errorContainers)
notFoundCount := len(notFoundContainers)
existedCount := len(exitedContainers)
2022-11-18 14:27:40 +08:00
normalCount := len(containerNames)
runningCount := len(runningContainers)
if containerCount == 0 {
appInstall.Status = constant.Error
appInstall.Message = "container is not found"
return appInstallRepo.Save(&appInstall)
}
if errCount == 0 && existedCount == 0 {
2022-11-18 14:27:40 +08:00
appInstall.Status = constant.Running
return appInstallRepo.Save(&appInstall)
}
if existedCount == normalCount {
appInstall.Status = constant.Stopped
return appInstallRepo.Save(&appInstall)
}
2022-11-18 14:27:40 +08:00
if errCount == normalCount {
appInstall.Status = constant.Error
}
if runningCount < normalCount {
appInstall.Status = constant.UnHealthy
}
var errMsg strings.Builder
if errCount > 0 {
errMsg.Write([]byte(string(rune(errCount)) + " error containers:"))
for _, e := range errorContainers {
errMsg.Write([]byte(e))
}
errMsg.Write([]byte("\n"))
}
if notFoundCount > 0 {
errMsg.Write([]byte(string(rune(notFoundCount)) + " not found containers:"))
for _, e := range notFoundContainers {
errMsg.Write([]byte(e))
}
errMsg.Write([]byte("\n"))
}
appInstall.Message = errMsg.String()
return appInstallRepo.Save(&appInstall)
}
2022-12-08 16:00:59 +08:00
func updateInstallInfoInDB(appKey, appName, param string, isRestart bool, value interface{}) error {
2022-12-08 16:00:59 +08:00
if param != "password" && param != "port" {
return nil
2022-12-08 16:00:59 +08:00
}
appInstall, err := appInstallRepo.LoadBaseInfo(appKey, appName)
2022-12-12 14:06:39 +08:00
if err != nil {
return nil
2022-12-08 16:00:59 +08:00
}
2022-12-12 17:17:39 +08:00
envPath := fmt.Sprintf("%s/%s/%s/.env", constant.AppInstallDir, appKey, appInstall.Name)
lineBytes, err := ioutil.ReadFile(envPath)
if err != nil {
return err
2022-12-12 17:17:39 +08:00
}
envKey := "PANEL_DB_ROOT_PASSWORD="
if param == "port" {
envKey = "PANEL_APP_PORT_HTTP="
}
files := strings.Split(string(lineBytes), "\n")
var newFiles []string
for _, line := range files {
if strings.HasPrefix(line, envKey) {
newFiles = append(newFiles, fmt.Sprintf("%s%v", envKey, value))
} else {
newFiles = append(newFiles, line)
}
}
file, err := os.OpenFile(envPath, os.O_WRONLY|os.O_TRUNC, 0666)
if err != nil {
return err
2022-12-12 17:17:39 +08:00
}
defer file.Close()
_, err = file.WriteString(strings.Join(newFiles, "\n"))
if err != nil {
return err
2022-12-12 17:17:39 +08:00
}
2022-12-08 16:00:59 +08:00
oldVal, newVal := "", ""
if param == "password" {
oldVal = fmt.Sprintf("\"PANEL_DB_ROOT_PASSWORD\":\"%v\"", appInstall.Password)
newVal = fmt.Sprintf("\"PANEL_DB_ROOT_PASSWORD\":\"%v\"", value)
2022-12-12 14:06:39 +08:00
_ = appInstallRepo.BatchUpdateBy(map[string]interface{}{
"param": strings.ReplaceAll(appInstall.Param, oldVal, newVal),
"env": strings.ReplaceAll(appInstall.Env, oldVal, newVal),
}, commonRepo.WithByID(appInstall.ID))
2022-12-12 17:17:39 +08:00
2022-12-08 16:00:59 +08:00
}
if param == "port" {
oldVal = fmt.Sprintf("\"PANEL_APP_PORT_HTTP\":%v", appInstall.Port)
newVal = fmt.Sprintf("\"PANEL_APP_PORT_HTTP\":%v", value)
2022-12-12 14:06:39 +08:00
_ = appInstallRepo.BatchUpdateBy(map[string]interface{}{
"param": strings.ReplaceAll(appInstall.Param, oldVal, newVal),
"env": strings.ReplaceAll(appInstall.Env, oldVal, newVal),
"http_port": value,
}, commonRepo.WithByID(appInstall.ID))
2022-12-08 16:00:59 +08:00
}
ComposeFile := fmt.Sprintf("%s/%s/%s/docker-compose.yml", constant.AppInstallDir, appKey, appInstall.Name)
stdout, err := compose.Down(ComposeFile)
if err != nil {
return errors.New(stdout)
}
stdout, err = compose.Up(ComposeFile)
if err != nil {
return errors.New(stdout)
}
return nil
2022-12-08 16:00:59 +08:00
}