Files
devstar/services/actions/debug_workflow.go
2025-11-19 09:08:04 +08:00

147 lines
4.3 KiB
Go

// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package actions
import (
"fmt"
"strings"
actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/perm"
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/reqctx"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/services/convert"
"github.com/nektos/act/pkg/jobparser"
)
// DebugWorkflowOptions 调试工作流的选项
type DebugWorkflowOptions struct {
WorkflowContent string `json:"workflow_content"`
Ref string `json:"ref"`
Inputs map[string]string `json:"inputs"`
}
// DebugActionWorkflow 执行调试工作流
func DebugActionWorkflow(ctx reqctx.RequestContext, doer *user_model.User, repo *repo_model.Repository, gitRepo *git.Repository, opts *DebugWorkflowOptions) (*actions_model.ActionRun, error) {
if opts == nil || opts.WorkflowContent == "" {
return nil, fmt.Errorf("workflow content is empty")
}
if opts.Ref == "" {
opts.Ref = repo.DefaultBranch
}
// 验证工作流内容
if err := validateWorkflowContent(opts.WorkflowContent); err != nil {
return nil, fmt.Errorf("invalid workflow content: %w", err)
}
// 获取目标提交
refName := git.RefName(opts.Ref)
var runTargetCommit *git.Commit
var err error
if refName.IsTag() {
runTargetCommit, err = gitRepo.GetTagCommit(refName.TagName())
} else if refName.IsBranch() {
runTargetCommit, err = gitRepo.GetBranchCommit(refName.BranchName())
} else {
runTargetCommit, err = gitRepo.GetCommit(opts.Ref)
}
if err != nil {
return nil, fmt.Errorf("get target commit: %w", err)
}
// 创建临时工作流运行记录
run := &actions_model.ActionRun{
Title: "[DEBUG] " + strings.SplitN(runTargetCommit.CommitMessage, "\n", 2)[0],
RepoID: repo.ID,
Repo: repo,
OwnerID: repo.OwnerID,
WorkflowID: "debug-workflow.yml",
TriggerUserID: doer.ID,
TriggerUser: doer,
Ref: string(refName),
CommitSHA: runTargetCommit.ID.String(),
IsForkPullRequest: false,
Event: "workflow_dispatch",
TriggerEvent: "workflow_dispatch",
Status: actions_model.StatusWaiting,
}
// 验证工作流内容并获取任务信息
giteaCtx := GenerateGiteaContext(run, nil)
workflows, err := jobparser.Parse([]byte(opts.WorkflowContent), jobparser.WithGitContext(giteaCtx.ToGitHubContext()))
if err != nil {
return nil, fmt.Errorf("parse workflow: %w", err)
}
if len(workflows) == 0 {
return nil, fmt.Errorf("no jobs found in workflow")
}
// 如果工作流定义了名称,使用它
if len(workflows) > 0 && workflows[0].RunName != "" {
run.Title = "[DEBUG] " + workflows[0].RunName
}
// 创建事件负载
inputsAny := make(map[string]any)
for k, v := range opts.Inputs {
inputsAny[k] = v
}
workflowDispatchPayload := &api.WorkflowDispatchPayload{
Workflow: run.WorkflowID,
Ref: opts.Ref,
Repository: convert.ToRepo(ctx, repo, access_model.Permission{AccessMode: perm.AccessModeNone}),
Inputs: inputsAny,
Sender: convert.ToUserWithAccessMode(ctx, doer, perm.AccessModeNone),
}
eventPayload, err := workflowDispatchPayload.JSONPayload()
if err != nil {
return nil, fmt.Errorf("marshal event payload: %w", err)
}
run.EventPayload = string(eventPayload)
// 插入数据库
if err := db.Insert(ctx, run); err != nil {
return nil, fmt.Errorf("insert action run: %w", err)
}
log.Trace("Debug workflow created for run %d", run.ID)
return run, nil
}
// validateWorkflowContent 验证工作流内容
func validateWorkflowContent(content string) error {
_, err := jobparser.Parse([]byte(content))
return err
}
// GetDebugWorkflowRun 获取调试工作流运行详情
func GetDebugWorkflowRun(ctx reqctx.RequestContext, repoID, runID int64) (*actions_model.ActionRun, error) {
run, err := actions_model.GetRunByRepoAndID(ctx, repoID, runID)
if err != nil {
return nil, fmt.Errorf("get run: %w", err)
}
// 检查这是否是调试工作流
if run.WorkflowID != "debug-workflow.yml" {
return nil, fmt.Errorf("not a debug workflow")
}
return run, nil
}