1
0
mirror of https://github.com/go-gitea/gitea.git synced 2025-04-18 00:47:48 -04:00
gitea/models/bots/run.go
2022-11-25 17:48:48 +08:00

281 lines
6.4 KiB
Go

// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package bots
import (
"context"
"encoding/json"
"fmt"
"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/models/webhook"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
"github.com/nektos/act/pkg/jobparser"
"golang.org/x/exp/slices"
"xorm.io/builder"
)
// Run represents a run of a workflow file
type Run 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
TriggerUser *user_model.User `xorm:"-"`
Ref string
CommitSHA string
IsForkPullRequest bool
Event webhook.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(Run))
db.RegisterModel(new(RunIndex))
}
func (Run) TableName() string {
return "bots_run"
}
func (run *Run) HTMLURL() string {
return fmt.Sprintf("%s/bots/runs/%d", run.Repo.HTMLURL(), run.Index)
}
// LoadAttributes load Repo TriggerUser if not loaded
func (run *Run) LoadAttributes(ctx context.Context) error {
if run == nil {
return nil
}
if run.Repo == nil {
repo, err := repo_model.GetRepositoryByIDCtx(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.GetPossbileUserByID(ctx, run.TriggerUserID)
if err != nil {
return err
}
run.TriggerUser = u
}
return nil
}
func (run *Run) TakeTime() time.Duration {
if run.Started == 0 {
return 0
}
started := run.Started.AsTime()
if run.Status.IsDone() {
return run.Stopped.AsTime().Sub(started)
}
run.Stopped.AsTime().Sub(started)
return time.Since(started).Truncate(time.Second)
}
func (run *Run) GetPushEventPayload() (*api.PushPayload, error) {
if run.Event == webhook.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_runs",
builder.Select("count(*)").From("bots_run").
Where(builder.Eq{"repo_id": repo.ID}),
).
SetExpr("num_closed_runs",
builder.Select("count(*)").From("bots_run").
Where(builder.Eq{
"repo_id": repo.ID,
}.And(
builder.In("status",
StatusSuccess,
StatusFailure,
StatusCancelled,
StatusSkipped,
),
),
),
).
Update(repo)
return err
}
// InsertRun inserts a bot run
func InsertRun(run *Run, jobs []*jobparser.SingleWorkflow) error {
ctx, commiter, err := db.TxContext(db.DefaultContext)
if err != nil {
return err
}
defer commiter.Close()
index, err := db.GetNextResourceIndex(ctx, "bots_run_index", run.RepoID)
if err != nil {
return err
}
run.Index = index
if run.Status.IsUnknown() {
run.Status = StatusWaiting
}
if err := db.Insert(ctx, run); err != nil {
return err
}
if run.Repo == nil {
repo, err := repo_model.GetRepositoryByIDCtx(ctx, run.RepoID)
if err != nil {
return err
}
run.Repo = repo
}
if err := updateRepoRunsNumbers(ctx, run.Repo); err != nil {
return err
}
runJobs := make([]*RunJob, 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 {
status = StatusBlocked
}
runJobs = append(runJobs, &RunJob{
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()
}
// ErrRunNotExist represents an error for bot run not exist
type ErrRunNotExist struct {
ID int64
RepoID int64
Index int64
}
func (err ErrRunNotExist) Error() string {
if err.RepoID > 0 {
return fmt.Sprintf("run repe_id [%d] index [%d] is not exist", err.RepoID, err.Index)
}
return fmt.Sprintf("run [%d] is not exist", err.ID)
}
func GetRunByID(ctx context.Context, id int64) (*Run, error) {
var run Run
has, err := db.GetEngine(ctx).Where("id=?", id).Get(&run)
if err != nil {
return nil, err
} else if !has {
return nil, ErrRunNotExist{
ID: id,
}
}
return &run, nil
}
func GetRunByIndex(ctx context.Context, repoID, index int64) (*Run, error) {
run := &Run{
RepoID: repoID,
Index: index,
}
has, err := db.GetEngine(ctx).Get(run)
if err != nil {
return nil, err
} else if !has {
return nil, ErrRunNotExist{
RepoID: repoID,
Index: index,
}
}
return run, nil
}
func UpdateRun(ctx context.Context, run *Run, 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 || slices.Contains(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.GetRepositoryByIDCtx(ctx, run.RepoID)
if err != nil {
return err
}
run.Repo = repo
}
if err := updateRepoRunsNumbers(ctx, run.Repo); err != nil {
return err
}
}
return err
}
type RunIndex db.ResourceIndex
func (RunIndex) TableName() string {
return "bots_run_index"
}