go20/devops/agent/api/task.go
2026-03-29 11:41:32 +08:00

181 lines
5.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package api
import (
"bufio"
"bytes"
"devops/agent/script"
"devops/agent/tasks"
"devops/server/apps/task"
"encoding/json"
"os"
"path/filepath"
"strconv"
restfulspec "github.com/emicklei/go-restful-openapi/v2"
"github.com/emicklei/go-restful/v3"
"github.com/google/uuid"
"github.com/infraboard/mcube/v2/exception"
"github.com/infraboard/mcube/v2/http/restful/response"
"github.com/infraboard/mcube/v2/ioc"
"github.com/infraboard/mcube/v2/ioc/config/gorestful"
)
func init() {
ioc.Api().Registry(&TaskApiHandler{})
}
// 一个API, 提供给外部调用的接口来触发一个Task的执行
// 参数 TaskSpec, 返回 Task
type TaskApiHandler struct {
ioc.ObjectImpl
}
func (h *TaskApiHandler) Name() string {
return "tasks"
}
func (i *TaskApiHandler) Version() string {
return "v1"
}
func (h *TaskApiHandler) Init() error {
ws := gorestful.ObjectRouter(h)
// restfulspec "github.com/emicklei/go-restful-openapi/v2"
// 这个库是 go-restful 的一个扩展库,用于生成 OpenAPI 规范的文档
tags := []string{"Task管理"}
ws.Route(ws.POST("").To(h.RunTask).
Doc("运行任务").
Metadata(restfulspec.KeyOpenAPITags, tags).
Reads(task.TaskSpec{}).
Writes(task.Task{}).
Returns(200, "OK", task.Task{}))
ws.Route(ws.GET("/{taskId}/result").To(h.GetTaskResult).
Doc("获取任务执行结果").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(ws.PathParameter("taskId", "任务ID").DataType("string")).
Writes(script.ExecutionResult{}).
Returns(200, "OK", script.ExecutionResult{}))
ws.Route(ws.GET("/{taskId}/log").To(h.GetTaskLog).
Doc("获取任务执行日志").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(ws.PathParameter("taskId", "任务ID").DataType("string")))
return nil
}
// RunTask 处理运行任务的API请求
// Body 参数是 TaskSpec, 返回 Task
func (h *TaskApiHandler) RunTask(r *restful.Request, w *restful.Response) {
// 解析请求参数,构造 TaskSpec
req := tasks.NewRunTaskRequest("", "")
if err := r.ReadEntity(req); err != nil {
// "github.com/infraboard/mcube/v2/http/restful/response"
// Failed 封装的 GoRestful框架的 异常返回
response.Failed(w, err)
return
}
// 补充TaskId
if req.WorkDir == "" {
req.WorkDir = uuid.NewString()
}
// 找到对应的 TaskRunner, 这里我们通过 TaskSpec 中的 Name 字段来找到对应的 TaskRunner
taskDebugRunner := tasks.GetTaskRunner(req.Name)
if taskDebugRunner == nil {
response.Failed(w, exception.NewInternalServerError("%s not found", req.Name))
return
}
// 处理请求
taskResp, err := taskDebugRunner.Run(r.Request.Context(), req)
if err != nil {
response.Failed(w, err)
return
}
response.Success(w, taskResp)
}
// 通过 taskid 获取 Task 的执行结果, 就是Workspace目录下的 result.json 文件内容
func (h *TaskApiHandler) GetTaskResult(r *restful.Request, w *restful.Response) {
// 1. 从请求路径中获取 taskId
taskId := r.PathParameter("taskId")
// 2. 通过 taskId 构建工作目录路径, 工作目录结构是固定的, 比如 workspace/{taskId}
wordirPath, err := script.GetWorkDirAbsPath(taskId)
if err != nil {
response.Failed(w, err)
return
}
// 3. 从工作目录下读取 result.json 文件, 这个文件是脚本执行完成后生成的, 包含了执行结果
resultFilePath := filepath.Join(wordirPath, "result.json")
resultFile, err := os.ReadFile(resultFilePath)
if err != nil {
response.Failed(w, err)
return
}
var result script.ExecutionResult
if err := json.Unmarshal(resultFile, &result); err != nil {
response.Failed(w, err)
return
}
response.Success(w, &result)
}
// 通过 taskid 获取 Task 的执行日志, 就是Workspace目录下的 stdout.log 文件内容
// 添加 offsetlimit 参数,支持分页读取日志内容
func (h *TaskApiHandler) GetTaskLog(r *restful.Request, w *restful.Response) {
// 1. 从请求路径中获取 taskId
taskId := r.PathParameter("taskId")
// 2. 从查询参数中获取 offset 和 limit(行数)默认值分别是0和100, ?offset=0&limit=100
offsetStr := r.QueryParameter("offset")
limitStr := r.QueryParameter("limit")
offset, err := strconv.Atoi(offsetStr)
if err != nil {
offset = 0
}
limit, err := strconv.Atoi(limitStr)
if err != nil {
limit = 1000
}
// 2. 通过 taskId 构建工作目录路径, 工作目录结构是固定的, 比如 workspace/{taskId}
wordirPath, err := script.GetWorkDirAbsPath(taskId)
if err != nil {
response.Failed(w, err)
return
}
// 3. 从工作目录下读取 stdout.log 文件, 这个文件是脚本执行过程中生成的, 包含了执行日志
logFilePath := filepath.Join(wordirPath, "stdout.txt")
// 4. 读取日志文件内容,并根据 offset 和 limit 返回对应的日志片段
logFile, err := os.Open(logFilePath)
if err != nil {
response.Failed(w, err)
return
}
defer logFile.Close()
// offset和limit(行数) 读取日志文件内容
logs := bytes.NewBuffer([]byte{})
scanner := bufio.NewScanner(logFile)
for i := 0; scanner.Scan() && i < offset+limit; i++ {
if i < offset {
continue
}
logs.Write(scanner.Bytes())
logs.Write([]byte("\n"))
}
w.Header().Set("Content-Type", "text/plain")
w.Write(logs.Bytes())
}