package service import ( "context" "encoding/json" "github.com/1Panel-dev/1Panel/backend/app/dto" "github.com/1Panel-dev/1Panel/backend/app/dto/request" "github.com/1Panel-dev/1Panel/backend/app/dto/response" "github.com/1Panel-dev/1Panel/backend/app/model" "github.com/1Panel-dev/1Panel/backend/app/repo" "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/compose" "github.com/1Panel-dev/1Panel/backend/utils/docker" "github.com/1Panel-dev/1Panel/backend/utils/files" "github.com/pkg/errors" "github.com/subosito/gotenv" "path" "strconv" "strings" ) type RuntimeService struct { } type IRuntimeService interface { Page(req request.RuntimeSearch) (int64, []response.RuntimeDTO, error) Create(create request.RuntimeCreate) error Delete(delete request.RuntimeDelete) error Update(req request.RuntimeUpdate) error Get(id uint) (res *response.RuntimeDTO, err error) GetNodePackageRunScript(req request.NodePackageReq) ([]response.PackageScripts, error) OperateRuntime(req request.RuntimeOperate) error } func NewRuntimeService() IRuntimeService { return &RuntimeService{} } func (r *RuntimeService) Create(create request.RuntimeCreate) (err error) { var ( opts []repo.DBOption ) if create.Name != "" { opts = append(opts, commonRepo.WithLikeName(create.Name)) } if create.Type != "" { opts = append(opts, commonRepo.WithByType(create.Type)) } exist, _ := runtimeRepo.GetFirst(opts...) if exist != nil { return buserr.New(constant.ErrNameIsExist) } fileOp := files.NewFileOp() switch create.Type { case constant.RuntimePHP: if create.Resource == constant.ResourceLocal { runtime := &model.Runtime{ Name: create.Name, Resource: create.Resource, Type: create.Type, Version: create.Version, Status: constant.RuntimeNormal, } return runtimeRepo.Create(context.Background(), runtime) } exist, _ = runtimeRepo.GetFirst(runtimeRepo.WithImage(create.Image)) if exist != nil { return buserr.New(constant.ErrImageExist) } case constant.RuntimeNode: if !fileOp.Stat(create.CodeDir) { return buserr.New(constant.ErrPathNotFound) } create.Install = true if err = checkPortExist(create.Port); err != nil { return err } if containerName, ok := create.Params["CONTAINER_NAME"]; ok { if err := checkContainerName(containerName.(string)); err != nil { return err } } } appDetail, err := appDetailRepo.GetFirst(commonRepo.WithByID(create.AppDetailID)) if err != nil { return err } app, err := appRepo.GetFirst(commonRepo.WithByID(appDetail.AppId)) if err != nil { return err } appVersionDir := path.Join(constant.AppResourceDir, app.Resource, app.Key, appDetail.Version) if !fileOp.Stat(appVersionDir) || appDetail.Update { if err := downloadApp(app, appDetail, nil); err != nil { return err } } runtime := &model.Runtime{ Name: create.Name, AppDetailID: create.AppDetailID, Type: create.Type, Image: create.Image, Resource: create.Resource, Version: create.Version, } switch create.Type { case constant.RuntimePHP: if err = handlePHP(create, runtime, fileOp, appVersionDir); err != nil { return } case constant.RuntimeNode: runtime.Port = create.Port if err = handleNode(create, runtime, fileOp, appVersionDir); err != nil { return } } return runtimeRepo.Create(context.Background(), runtime) } func (r *RuntimeService) Page(req request.RuntimeSearch) (int64, []response.RuntimeDTO, error) { var ( opts []repo.DBOption res []response.RuntimeDTO ) if req.Name != "" { opts = append(opts, commonRepo.WithLikeName(req.Name)) } if req.Status != "" { opts = append(opts, runtimeRepo.WithStatus(req.Status)) } if req.Type != "" { opts = append(opts, commonRepo.WithByType(req.Type)) } total, runtimes, err := runtimeRepo.Page(req.Page, req.PageSize, opts...) if err != nil { return 0, nil, err } for _, runtime := range runtimes { runtimeDTO := response.NewRuntimeDTO(runtime) runtimeDTO.Params = make(map[string]interface{}) envs, err := gotenv.Unmarshal(runtime.Env) if err != nil { return 0, nil, err } for k, v := range envs { runtimeDTO.Params[k] = v } res = append(res, runtimeDTO) } return total, res, nil } func (r *RuntimeService) Delete(runtimeDelete request.RuntimeDelete) error { runtime, err := runtimeRepo.GetFirst(commonRepo.WithByID(runtimeDelete.ID)) if err != nil { return err } website, _ := websiteRepo.GetFirst(websiteRepo.WithRuntimeID(runtimeDelete.ID)) if website.ID > 0 { return buserr.New(constant.ErrDelWithWebsite) } if runtime.Resource == constant.ResourceAppstore { projectDir := runtime.GetPath() switch runtime.Type { case constant.RuntimePHP: client, err := docker.NewClient() if err != nil { return err } imageID, err := client.GetImageIDByName(runtime.Image) if err != nil { return err } if imageID != "" { if err := client.DeleteImage(imageID); err != nil { global.LOG.Errorf("delete image id [%s] error %v", imageID, err) } } case constant.RuntimeNode: if out, err := compose.Down(runtime.GetComposePath()); err != nil && !runtimeDelete.ForceDelete { if out != "" { return errors.New(out) } return err } } if err := files.NewFileOp().DeleteDir(projectDir); err != nil && !runtimeDelete.ForceDelete { return err } } return runtimeRepo.DeleteBy(commonRepo.WithByID(runtimeDelete.ID)) } func (r *RuntimeService) Get(id uint) (*response.RuntimeDTO, error) { runtime, err := runtimeRepo.GetFirst(commonRepo.WithByID(id)) if err != nil { return nil, err } res := response.NewRuntimeDTO(*runtime) if runtime.Resource == constant.ResourceLocal { return &res, nil } appDetail, err := appDetailRepo.GetFirst(commonRepo.WithByID(runtime.AppDetailID)) if err != nil { return nil, err } res.AppID = appDetail.AppId switch runtime.Type { case constant.RuntimePHP: var ( appForm dto.AppForm appParams []response.AppParam ) if err := json.Unmarshal([]byte(runtime.Params), &appForm); err != nil { return nil, err } envs, err := gotenv.Unmarshal(runtime.Env) if err != nil { return nil, err } if v, ok := envs["CONTAINER_PACKAGE_URL"]; ok { res.Source = v } for _, form := range appForm.FormFields { if v, ok := envs[form.EnvKey]; ok { appParam := response.AppParam{ Edit: false, Key: form.EnvKey, Rule: form.Rule, Type: form.Type, Required: form.Required, } if form.Edit { appParam.Edit = true } appParam.LabelZh = form.LabelZh appParam.LabelEn = form.LabelEn appParam.Multiple = form.Multiple appParam.Value = v if form.Type == "select" { if form.Multiple { if v == "" { appParam.Value = []string{} } else { appParam.Value = strings.Split(v, ",") } } else { for _, fv := range form.Values { if fv.Value == v { appParam.ShowValue = fv.Label break } } } appParam.Values = form.Values } appParams = append(appParams, appParam) } } res.AppParams = appParams case constant.RuntimeNode: res.Params = make(map[string]interface{}) envs, err := gotenv.Unmarshal(runtime.Env) if err != nil { return nil, err } for k, v := range envs { switch k { case "NODE_APP_PORT", "PANEL_APP_PORT_HTTP": port, err := strconv.Atoi(v) if err != nil { return nil, err } res.Params[k] = port default: res.Params[k] = v } } if v, ok := envs["CONTAINER_PACKAGE_URL"]; ok { res.Source = v } } return &res, nil } func (r *RuntimeService) Update(req request.RuntimeUpdate) error { runtime, err := runtimeRepo.GetFirst(commonRepo.WithByID(req.ID)) if err != nil { return err } if runtime.Resource == constant.ResourceLocal { runtime.Version = req.Version return runtimeRepo.Save(runtime) } oldImage := runtime.Image switch runtime.Type { case constant.RuntimePHP: exist, _ := runtimeRepo.GetFirst(runtimeRepo.WithImage(req.Name), runtimeRepo.WithNotId(req.ID)) if exist != nil { return buserr.New(constant.ErrImageExist) } case constant.RuntimeNode: if runtime.Port != req.Port { if err = checkPortExist(req.Port); err != nil { return err } runtime.Port = req.Port } if containerName, ok := req.Params["CONTAINER_NAME"]; ok { envs, err := gotenv.Unmarshal(runtime.Env) if err != nil { return err } oldContainerName := envs["CONTAINER_NAME"] if containerName != oldContainerName { if err := checkContainerName(containerName.(string)); err != nil { return err } } } } projectDir := path.Join(constant.RuntimeDir, runtime.Type, runtime.Name) create := request.RuntimeCreate{ Image: req.Image, Type: runtime.Type, Source: req.Source, Params: req.Params, CodeDir: req.CodeDir, Version: req.Version, NodeConfig: request.NodeConfig{ Port: req.Port, Install: true, }, } composeContent, envContent, _, err := handleParams(create, projectDir) if err != nil { return err } runtime.Env = string(envContent) runtime.DockerCompose = string(composeContent) switch runtime.Type { case constant.RuntimePHP: runtime.Image = req.Image runtime.Status = constant.RuntimeBuildIng _ = runtimeRepo.Save(runtime) client, err := docker.NewClient() if err != nil { return err } imageID, err := client.GetImageIDByName(oldImage) if err != nil { return err } go buildRuntime(runtime, imageID, req.Rebuild) case constant.RuntimeNode: runtime.Version = req.Version runtime.CodeDir = req.CodeDir runtime.Port = req.Port runtime.Status = constant.RuntimeReCreating _ = runtimeRepo.Save(runtime) go reCreateRuntime(runtime) } return nil } func (r *RuntimeService) GetNodePackageRunScript(req request.NodePackageReq) ([]response.PackageScripts, error) { fileOp := files.NewFileOp() if !fileOp.Stat(req.CodeDir) { return nil, buserr.New(constant.ErrPathNotFound) } if !fileOp.Stat(path.Join(req.CodeDir, "package.json")) { return nil, buserr.New(constant.ErrPackageJsonNotFound) } content, err := fileOp.GetContent(path.Join(req.CodeDir, "package.json")) if err != nil { return nil, err } var packageMap map[string]interface{} err = json.Unmarshal(content, &packageMap) if err != nil { return nil, err } scripts, ok := packageMap["scripts"] if !ok { return nil, buserr.New(constant.ErrScriptsNotFound) } var packageScripts []response.PackageScripts for k, v := range scripts.(map[string]interface{}) { packageScripts = append(packageScripts, response.PackageScripts{ Name: k, Script: v.(string), }) } return packageScripts, nil } func (r *RuntimeService) OperateRuntime(req request.RuntimeOperate) error { runtime, err := runtimeRepo.GetFirst(commonRepo.WithByID(req.ID)) if err != nil { return err } defer func() { if err != nil { runtime.Status = constant.RuntimeError runtime.Message = err.Error() _ = runtimeRepo.Save(runtime) } }() switch req.Operate { case constant.RuntimeUp: if err = runComposeCmdWithLog(req.Operate, runtime.GetComposePath(), runtime.GetLogPath()); err != nil { return err } if err = SyncRuntimeContainerStatus(runtime); err != nil { return err } case constant.RuntimeDown: if err = runComposeCmdWithLog(req.Operate, runtime.GetComposePath(), runtime.GetLogPath()); err != nil { return err } runtime.Status = constant.RuntimeStopped case constant.RuntimeRestart: if err = runComposeCmdWithLog(constant.RuntimeDown, runtime.GetComposePath(), runtime.GetLogPath()); err != nil { return err } if err = runComposeCmdWithLog(constant.RuntimeUp, runtime.GetComposePath(), runtime.GetLogPath()); err != nil { return err } if err = SyncRuntimeContainerStatus(runtime); err != nil { return err } } return runtimeRepo.Save(runtime) }