d92909fa8b
There is no fork concept in agit flow, anyone with read permission can push `refs/for/<target-branch>/<topic-branch>` to the repo. So we should treat it as a fork pull request because it may be from an untrusted user.
286 lines
7.5 KiB
Go
286 lines
7.5 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
|
|
EventPayload string `xorm:"LONGTEXT"`
|
|
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.RefGroup() == "pull" {
|
|
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.RefGroup() == "pull" {
|
|
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
|