// 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 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 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() job.EraseNeeds() payload, _ := v.Marshal() status := StatusWaiting if len(needs) > 0 || run.NeedApproval { status = StatusBlocked } 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