48e5a74f21
Fix #25088 This PR adds the support for [`pull_request_target`](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target) workflow trigger. `pull_request_target` is similar to `pull_request`, but the workflow triggered by the `pull_request_target` event runs in the context of the base branch of the pull request rather than the head branch. Since the workflow from the base is considered trusted, it can access the secrets and doesn't need approvals to run.
287 lines
7.8 KiB
Go
287 lines
7.8 KiB
Go
// Copyright 2022 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package actions
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"code.gitea.io/gitea/models/db"
|
|
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/json"
|
|
api "code.gitea.io/gitea/modules/structs"
|
|
"code.gitea.io/gitea/modules/timeutil"
|
|
"code.gitea.io/gitea/modules/util"
|
|
webhook_module "code.gitea.io/gitea/modules/webhook"
|
|
|
|
"github.com/nektos/act/pkg/jobparser"
|
|
"xorm.io/builder"
|
|
)
|
|
|
|
// ActionRun represents a run of a workflow file
|
|
type ActionRun struct {
|
|
ID int64
|
|
Title string
|
|
RepoID int64 `xorm:"index unique(repo_index)"`
|
|
Repo *repo_model.Repository `xorm:"-"`
|
|
OwnerID int64 `xorm:"index"`
|
|
WorkflowID string `xorm:"index"` // the name of workflow file
|
|
Index int64 `xorm:"index unique(repo_index)"` // a unique number for each run of a repository
|
|
TriggerUserID int64 `xorm:"index"`
|
|
TriggerUser *user_model.User `xorm:"-"`
|
|
Ref string
|
|
CommitSHA string
|
|
IsForkPullRequest bool // If this is triggered by a PR from a forked repository or an untrusted user, we need to check if it is approved and limit permissions when running the workflow.
|
|
NeedApproval bool // may need approval if it's a fork pull request
|
|
ApprovedBy int64 `xorm:"index"` // who approved
|
|
Event webhook_module.HookEventType // the webhook event that causes the workflow to run
|
|
EventPayload string `xorm:"LONGTEXT"`
|
|
TriggerEvent string // the trigger event defined in the `on` configuration of the triggered workflow
|
|
Status Status `xorm:"index"`
|
|
Started timeutil.TimeStamp
|
|
Stopped timeutil.TimeStamp
|
|
Created timeutil.TimeStamp `xorm:"created"`
|
|
Updated timeutil.TimeStamp `xorm:"updated"`
|
|
}
|
|
|
|
func init() {
|
|
db.RegisterModel(new(ActionRun))
|
|
db.RegisterModel(new(ActionRunIndex))
|
|
}
|
|
|
|
func (run *ActionRun) HTMLURL() string {
|
|
if run.Repo == nil {
|
|
return ""
|
|
}
|
|
return fmt.Sprintf("%s/actions/runs/%d", run.Repo.HTMLURL(), run.Index)
|
|
}
|
|
|
|
func (run *ActionRun) Link() string {
|
|
if run.Repo == nil {
|
|
return ""
|
|
}
|
|
return fmt.Sprintf("%s/actions/runs/%d", run.Repo.Link(), run.Index)
|
|
}
|
|
|
|
// RefLink return the url of run's ref
|
|
func (run *ActionRun) RefLink() string {
|
|
refName := git.RefName(run.Ref)
|
|
if refName.IsPull() {
|
|
return run.Repo.Link() + "/pulls/" + refName.ShortName()
|
|
}
|
|
return git.RefURL(run.Repo.Link(), run.Ref)
|
|
}
|
|
|
|
// PrettyRef return #id for pull ref or ShortName for others
|
|
func (run *ActionRun) PrettyRef() string {
|
|
refName := git.RefName(run.Ref)
|
|
if refName.IsPull() {
|
|
return "#" + strings.TrimSuffix(strings.TrimPrefix(run.Ref, git.PullPrefix), "/head")
|
|
}
|
|
return refName.ShortName()
|
|
}
|
|
|
|
// LoadAttributes load Repo TriggerUser if not loaded
|
|
func (run *ActionRun) LoadAttributes(ctx context.Context) error {
|
|
if run == nil {
|
|
return nil
|
|
}
|
|
|
|
if run.Repo == nil {
|
|
repo, err := repo_model.GetRepositoryByID(ctx, run.RepoID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
run.Repo = repo
|
|
}
|
|
if err := run.Repo.LoadAttributes(ctx); err != nil {
|
|
return err
|
|
}
|
|
|
|
if run.TriggerUser == nil {
|
|
u, err := user_model.GetPossibleUserByID(ctx, run.TriggerUserID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
run.TriggerUser = u
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (run *ActionRun) Duration() time.Duration {
|
|
return calculateDuration(run.Started, run.Stopped, run.Status)
|
|
}
|
|
|
|
func (run *ActionRun) GetPushEventPayload() (*api.PushPayload, error) {
|
|
if run.Event == webhook_module.HookEventPush {
|
|
var payload api.PushPayload
|
|
if err := json.Unmarshal([]byte(run.EventPayload), &payload); err != nil {
|
|
return nil, err
|
|
}
|
|
return &payload, nil
|
|
}
|
|
return nil, fmt.Errorf("event %s is not a push event", run.Event)
|
|
}
|
|
|
|
func (run *ActionRun) GetPullRequestEventPayload() (*api.PullRequestPayload, error) {
|
|
if run.Event == webhook_module.HookEventPullRequest || run.Event == webhook_module.HookEventPullRequestSync {
|
|
var payload api.PullRequestPayload
|
|
if err := json.Unmarshal([]byte(run.EventPayload), &payload); err != nil {
|
|
return nil, err
|
|
}
|
|
return &payload, nil
|
|
}
|
|
return nil, fmt.Errorf("event %s is not a pull request event", run.Event)
|
|
}
|
|
|
|
func updateRepoRunsNumbers(ctx context.Context, repo *repo_model.Repository) error {
|
|
_, err := db.GetEngine(ctx).ID(repo.ID).
|
|
SetExpr("num_action_runs",
|
|
builder.Select("count(*)").From("action_run").
|
|
Where(builder.Eq{"repo_id": repo.ID}),
|
|
).
|
|
SetExpr("num_closed_action_runs",
|
|
builder.Select("count(*)").From("action_run").
|
|
Where(builder.Eq{
|
|
"repo_id": repo.ID,
|
|
}.And(
|
|
builder.In("status",
|
|
StatusSuccess,
|
|
StatusFailure,
|
|
StatusCancelled,
|
|
StatusSkipped,
|
|
),
|
|
),
|
|
),
|
|
).
|
|
Update(repo)
|
|
return err
|
|
}
|
|
|
|
// InsertRun inserts a run
|
|
func InsertRun(ctx context.Context, run *ActionRun, jobs []*jobparser.SingleWorkflow) error {
|
|
ctx, commiter, err := db.TxContext(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer commiter.Close()
|
|
|
|
index, err := db.GetNextResourceIndex(ctx, "action_run_index", run.RepoID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
run.Index = index
|
|
|
|
if err := db.Insert(ctx, run); err != nil {
|
|
return err
|
|
}
|
|
|
|
if run.Repo == nil {
|
|
repo, err := repo_model.GetRepositoryByID(ctx, run.RepoID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
run.Repo = repo
|
|
}
|
|
|
|
if err := updateRepoRunsNumbers(ctx, run.Repo); err != nil {
|
|
return err
|
|
}
|
|
|
|
runJobs := make([]*ActionRunJob, 0, len(jobs))
|
|
for _, v := range jobs {
|
|
id, job := v.Job()
|
|
needs := job.Needs()
|
|
if err := v.SetJob(id, job.EraseNeeds()); err != nil {
|
|
return err
|
|
}
|
|
payload, _ := v.Marshal()
|
|
status := StatusWaiting
|
|
if len(needs) > 0 || run.NeedApproval {
|
|
status = StatusBlocked
|
|
}
|
|
job.Name, _ = util.SplitStringAtByteN(job.Name, 255)
|
|
runJobs = append(runJobs, &ActionRunJob{
|
|
RunID: run.ID,
|
|
RepoID: run.RepoID,
|
|
OwnerID: run.OwnerID,
|
|
CommitSHA: run.CommitSHA,
|
|
IsForkPullRequest: run.IsForkPullRequest,
|
|
Name: job.Name,
|
|
WorkflowPayload: payload,
|
|
JobID: id,
|
|
Needs: needs,
|
|
RunsOn: job.RunsOn(),
|
|
Status: status,
|
|
})
|
|
}
|
|
if err := db.Insert(ctx, runJobs); err != nil {
|
|
return err
|
|
}
|
|
|
|
return commiter.Commit()
|
|
}
|
|
|
|
func GetRunByID(ctx context.Context, id int64) (*ActionRun, error) {
|
|
var run ActionRun
|
|
has, err := db.GetEngine(ctx).Where("id=?", id).Get(&run)
|
|
if err != nil {
|
|
return nil, err
|
|
} else if !has {
|
|
return nil, fmt.Errorf("run with id %d: %w", id, util.ErrNotExist)
|
|
}
|
|
|
|
return &run, nil
|
|
}
|
|
|
|
func GetRunByIndex(ctx context.Context, repoID, index int64) (*ActionRun, error) {
|
|
run := &ActionRun{
|
|
RepoID: repoID,
|
|
Index: index,
|
|
}
|
|
has, err := db.GetEngine(ctx).Get(run)
|
|
if err != nil {
|
|
return nil, err
|
|
} else if !has {
|
|
return nil, fmt.Errorf("run with index %d %d: %w", repoID, index, util.ErrNotExist)
|
|
}
|
|
|
|
return run, nil
|
|
}
|
|
|
|
func UpdateRun(ctx context.Context, run *ActionRun, cols ...string) error {
|
|
sess := db.GetEngine(ctx).ID(run.ID)
|
|
if len(cols) > 0 {
|
|
sess.Cols(cols...)
|
|
}
|
|
_, err := sess.Update(run)
|
|
|
|
if run.Status != 0 || util.SliceContains(cols, "status") {
|
|
if run.RepoID == 0 {
|
|
run, err = GetRunByID(ctx, run.ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if run.Repo == nil {
|
|
repo, err := repo_model.GetRepositoryByID(ctx, run.RepoID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
run.Repo = repo
|
|
}
|
|
if err := updateRepoRunsNumbers(ctx, run.Repo); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
type ActionRunIndex db.ResourceIndex
|