mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2024-12-12 23:49:18 +08:00
273 lines
7.3 KiB
Go
273 lines
7.3 KiB
Go
package task
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"path"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/1Panel-dev/1Panel/agent/app/model"
|
|
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
|
"github.com/1Panel-dev/1Panel/agent/constant"
|
|
"github.com/1Panel-dev/1Panel/agent/i18n"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
type ActionFunc func(*Task) error
|
|
type RollbackFunc func(*Task)
|
|
|
|
type Task struct {
|
|
Name string
|
|
TaskID string
|
|
Logger *log.Logger
|
|
SubTasks []*SubTask
|
|
Rollbacks []RollbackFunc
|
|
logFile *os.File
|
|
taskRepo repo.ITaskRepo
|
|
Task *model.Task
|
|
ParentID string
|
|
}
|
|
|
|
type SubTask struct {
|
|
RootTask *Task
|
|
Name string
|
|
StepAlias string
|
|
Retry int
|
|
Timeout time.Duration
|
|
Action ActionFunc
|
|
Rollback RollbackFunc
|
|
Error error
|
|
IgnoreErr bool
|
|
}
|
|
|
|
const (
|
|
TaskInstall = "TaskInstall"
|
|
TaskUninstall = "TaskUninstall"
|
|
TaskCreate = "TaskCreate"
|
|
TaskDelete = "TaskDelete"
|
|
TaskUpgrade = "TaskUpgrade"
|
|
TaskUpdate = "TaskUpdate"
|
|
TaskRestart = "TaskRestart"
|
|
TaskBackup = "TaskBackup"
|
|
TaskRecover = "TaskRecover"
|
|
TaskRollback = "TaskRollback"
|
|
TaskSync = "TaskSync"
|
|
TaskBuild = "TaskBuild"
|
|
TaskPull = "TaskPull"
|
|
TaskPush = "TaskPush"
|
|
)
|
|
|
|
const (
|
|
TaskScopeWebsite = "Website"
|
|
TaskScopeApp = "App"
|
|
TaskScopeRuntime = "Runtime"
|
|
TaskScopeDatabase = "Database"
|
|
TaskScopeAppStore = "AppStore"
|
|
TaskScopeSnapshot = "Snapshot"
|
|
TaskScopeContainer = "Container"
|
|
TaskScopeCompose = "Compose"
|
|
TaskScopeImage = "Image"
|
|
TaskScopeRuntimeExtension = "RuntimeExtension"
|
|
)
|
|
|
|
const (
|
|
TaskSuccess = "Success"
|
|
TaskFailed = "Failed"
|
|
)
|
|
|
|
func GetTaskName(resourceName, operate, scope string) string {
|
|
return fmt.Sprintf("%s%s [%s]", i18n.GetMsgByKey(operate), i18n.GetMsgByKey(scope), resourceName)
|
|
}
|
|
|
|
func NewTaskWithOps(resourceName, operate, scope, taskID string, resourceID uint) (*Task, error) {
|
|
return NewTask(GetTaskName(resourceName, operate, scope), operate, scope, taskID, resourceID)
|
|
}
|
|
|
|
func NewTask(name, operate, taskScope, taskID string, resourceID uint) (*Task, error) {
|
|
if taskID == "" {
|
|
taskID = uuid.New().String()
|
|
}
|
|
logDir := path.Join(constant.LogDir, taskScope)
|
|
if _, err := os.Stat(logDir); os.IsNotExist(err) {
|
|
if err = os.MkdirAll(logDir, 0755); err != nil {
|
|
return nil, fmt.Errorf("failed to create log directory: %w", err)
|
|
}
|
|
}
|
|
logPath := path.Join(constant.LogDir, taskScope, taskID+".log")
|
|
file, err := os.OpenFile(logPath, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0666)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to open log file: %w", err)
|
|
}
|
|
logger := log.New(file, "", log.LstdFlags)
|
|
taskModel := &model.Task{
|
|
ID: taskID,
|
|
Name: name,
|
|
Type: taskScope,
|
|
LogFile: logPath,
|
|
Status: constant.StatusRunning,
|
|
ResourceID: resourceID,
|
|
Operate: operate,
|
|
}
|
|
taskRepo := repo.NewITaskRepo()
|
|
task := &Task{Name: name, logFile: file, Logger: logger, taskRepo: taskRepo, Task: taskModel}
|
|
return task, nil
|
|
}
|
|
|
|
func (t *Task) AddSubTask(name string, action ActionFunc, rollback RollbackFunc) {
|
|
subTask := &SubTask{RootTask: t, Name: name, Retry: 0, Timeout: 10 * time.Minute, Action: action, Rollback: rollback}
|
|
t.SubTasks = append(t.SubTasks, subTask)
|
|
}
|
|
|
|
func (t *Task) AddSubTaskWithAlias(key string, action ActionFunc, rollback RollbackFunc) {
|
|
subTask := &SubTask{RootTask: t, Name: i18n.GetMsgByKey(key), StepAlias: key, Retry: 0, Timeout: 10 * time.Minute, Action: action, Rollback: rollback}
|
|
t.SubTasks = append(t.SubTasks, subTask)
|
|
}
|
|
|
|
func (t *Task) AddSubTaskWithOps(name string, action ActionFunc, rollback RollbackFunc, retry int, timeout time.Duration) {
|
|
subTask := &SubTask{RootTask: t, Name: name, Retry: retry, Timeout: timeout, Action: action, Rollback: rollback}
|
|
t.SubTasks = append(t.SubTasks, subTask)
|
|
}
|
|
|
|
func (t *Task) AddSubTaskWithIgnoreErr(name string, action ActionFunc) {
|
|
subTask := &SubTask{RootTask: t, Name: name, Retry: 0, Timeout: 10 * time.Minute, Action: action, Rollback: nil, IgnoreErr: true}
|
|
t.SubTasks = append(t.SubTasks, subTask)
|
|
}
|
|
|
|
func (s *SubTask) Execute() error {
|
|
subTaskName := s.Name
|
|
if s.Name == "" {
|
|
subTaskName = i18n.GetMsgByKey("SubTask")
|
|
}
|
|
var err error
|
|
for i := 0; i < s.Retry+1; i++ {
|
|
if i > 0 {
|
|
s.RootTask.Log(i18n.GetWithName("TaskRetry", strconv.Itoa(i)))
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), s.Timeout)
|
|
defer cancel()
|
|
|
|
done := make(chan error)
|
|
go func() {
|
|
done <- s.Action(s.RootTask)
|
|
}()
|
|
|
|
select {
|
|
case <-ctx.Done():
|
|
s.RootTask.Log(i18n.GetWithName("TaskTimeout", subTaskName))
|
|
case err = <-done:
|
|
if err != nil {
|
|
s.RootTask.Log(i18n.GetWithNameAndErr("SubTaskFailed", subTaskName, err))
|
|
} else {
|
|
s.RootTask.Log(i18n.GetWithName("SubTaskSuccess", subTaskName))
|
|
return nil
|
|
}
|
|
}
|
|
|
|
if i == s.Retry {
|
|
if s.Rollback != nil {
|
|
s.Rollback(s.RootTask)
|
|
}
|
|
}
|
|
time.Sleep(1 * time.Second)
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (t *Task) updateTask(task *model.Task) {
|
|
_ = t.taskRepo.Update(context.Background(), task)
|
|
}
|
|
|
|
func (t *Task) Execute() error {
|
|
if err := t.taskRepo.Save(context.Background(), t.Task); err != nil {
|
|
return err
|
|
}
|
|
var err error
|
|
t.Log(i18n.GetWithName("TaskStart", t.Name))
|
|
for _, subTask := range t.SubTasks {
|
|
t.Task.CurrentStep = subTask.StepAlias
|
|
t.updateTask(t.Task)
|
|
if err = subTask.Execute(); err == nil {
|
|
if subTask.Rollback != nil {
|
|
t.Rollbacks = append(t.Rollbacks, subTask.Rollback)
|
|
}
|
|
} else {
|
|
if subTask.IgnoreErr {
|
|
err = nil
|
|
continue
|
|
}
|
|
t.Task.ErrorMsg = err.Error()
|
|
t.Task.Status = constant.StatusFailed
|
|
for _, rollback := range t.Rollbacks {
|
|
rollback(t)
|
|
}
|
|
t.updateTask(t.Task)
|
|
break
|
|
}
|
|
}
|
|
if t.Task.Status == constant.Running {
|
|
t.Task.Status = constant.StatusSuccess
|
|
t.Log(i18n.GetWithName("TaskSuccess", t.Name))
|
|
} else {
|
|
t.Log(i18n.GetWithName("TaskFailed", t.Name))
|
|
}
|
|
t.Log("[TASK-END]")
|
|
t.Task.EndAt = time.Now()
|
|
t.updateTask(t.Task)
|
|
_ = t.logFile.Close()
|
|
return err
|
|
}
|
|
|
|
func (t *Task) DeleteLogFile() {
|
|
_ = os.Remove(t.Task.LogFile)
|
|
}
|
|
|
|
func (t *Task) LogWithStatus(msg string, err error) {
|
|
if err != nil {
|
|
t.Logger.Printf(i18n.GetWithNameAndErr("FailedStatus", msg, err))
|
|
} else {
|
|
t.Logger.Printf(i18n.GetWithName("SuccessStatus", msg))
|
|
}
|
|
}
|
|
|
|
func (t *Task) Log(msg string) {
|
|
t.Logger.Printf(msg)
|
|
}
|
|
|
|
func (t *Task) Logf(format string, v ...any) {
|
|
t.Logger.Printf(format, v...)
|
|
}
|
|
|
|
func (t *Task) LogFailed(msg string) {
|
|
t.Logger.Printf(msg + i18n.GetMsgByKey("Failed"))
|
|
}
|
|
|
|
func (t *Task) LogFailedWithErr(msg string, err error) {
|
|
t.Logger.Printf(fmt.Sprintf("%s %s : %s", msg, i18n.GetMsgByKey("Failed"), err.Error()))
|
|
}
|
|
|
|
func (t *Task) LogSuccess(msg string) {
|
|
t.Logger.Printf(msg + i18n.GetMsgByKey("Success"))
|
|
}
|
|
func (t *Task) LogSuccessF(format string, v ...any) {
|
|
t.Logger.Printf(fmt.Sprintf(format, v...) + i18n.GetMsgByKey("Success"))
|
|
}
|
|
|
|
func (t *Task) LogStart(msg string) {
|
|
t.Logger.Printf(fmt.Sprintf("%s%s", i18n.GetMsgByKey("Start"), msg))
|
|
}
|
|
|
|
func (t *Task) LogWithOps(operate, msg string) {
|
|
t.Logger.Printf("%s%s", i18n.GetMsgByKey(operate), msg)
|
|
}
|
|
|
|
func (t *Task) LogSuccessWithOps(operate, msg string) {
|
|
t.Logger.Printf("%s%s%s", i18n.GetMsgByKey(operate), msg, i18n.GetMsgByKey("Success"))
|
|
}
|
|
|
|
func (t *Task) LogFailedWithOps(operate, msg string, err error) {
|
|
t.Logger.Printf("%s%s%s : %s ", i18n.GetMsgByKey(operate), msg, i18n.GetMsgByKey("Failed"), err.Error())
|
|
}
|