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 文件内容 // 添加 offset,limit 参数,支持分页读取日志内容 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()) }