// 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 }