refactor: rename packages
This commit is contained in:
271
models/actions/run.go
Normal file
271
models/actions/run.go
Normal file
@@ -0,0 +1,271 @@
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package actions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
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"
|
||||
)
|
||||
|
||||
// BotRun represents a run of a workflow file
|
||||
type BotRun 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(BotRun))
|
||||
db.RegisterModel(new(BotRunIndex))
|
||||
}
|
||||
|
||||
func (run *BotRun) HTMLURL() string {
|
||||
return fmt.Sprintf("%s/bots/runs/%d", run.Repo.HTMLURL(), run.Index)
|
||||
}
|
||||
|
||||
// LoadAttributes load Repo TriggerUser if not loaded
|
||||
func (run *BotRun) 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.GetPossbileUserByID(ctx, run.TriggerUserID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
run.TriggerUser = u
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (run *BotRun) 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 *BotRun) 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("bot_run").
|
||||
Where(builder.Eq{"repo_id": repo.ID}),
|
||||
).
|
||||
SetExpr("num_closed_runs",
|
||||
builder.Select("count(*)").From("bot_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 *BotRun, jobs []*jobparser.SingleWorkflow) error {
|
||||
ctx, commiter, err := db.TxContext(db.DefaultContext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer commiter.Close()
|
||||
|
||||
index, err := db.GetNextResourceIndex(ctx, "bot_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.GetRepositoryByID(ctx, run.RepoID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
run.Repo = repo
|
||||
}
|
||||
|
||||
if err := updateRepoRunsNumbers(ctx, run.Repo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
runJobs := make([]*BotRunJob, 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, &BotRunJob{
|
||||
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) (*BotRun, error) {
|
||||
var run BotRun
|
||||
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) (*BotRun, error) {
|
||||
run := &BotRun{
|
||||
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 *BotRun, 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.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 BotRunIndex db.ResourceIndex
|
||||
182
models/actions/run_job.go
Normal file
182
models/actions/run_job.go
Normal file
@@ -0,0 +1,182 @@
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package actions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
// BotRunJob represents a job of a run
|
||||
type BotRunJob struct {
|
||||
ID int64
|
||||
RunID int64 `xorm:"index"`
|
||||
Run *BotRun `xorm:"-"`
|
||||
RepoID int64 `xorm:"index"`
|
||||
OwnerID int64 `xorm:"index"`
|
||||
CommitSHA string `xorm:"index"`
|
||||
IsForkPullRequest bool
|
||||
Name string
|
||||
Attempt int64
|
||||
WorkflowPayload []byte
|
||||
JobID string // job id in workflow, not job's id
|
||||
Needs []string `xorm:"JSON TEXT"`
|
||||
RunsOn []string `xorm:"JSON TEXT"`
|
||||
TaskID int64 // the latest task of the job
|
||||
Status Status `xorm:"index"`
|
||||
Started timeutil.TimeStamp
|
||||
Stopped timeutil.TimeStamp
|
||||
Created timeutil.TimeStamp `xorm:"created"`
|
||||
Updated timeutil.TimeStamp `xorm:"updated index"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
db.RegisterModel(new(BotRunJob))
|
||||
}
|
||||
|
||||
func (job *BotRunJob) TakeTime() time.Duration {
|
||||
if job.Started == 0 {
|
||||
return 0
|
||||
}
|
||||
started := job.Started.AsTime()
|
||||
if job.Status.IsDone() {
|
||||
return job.Stopped.AsTime().Sub(started)
|
||||
}
|
||||
job.Stopped.AsTime().Sub(started)
|
||||
return time.Since(started).Truncate(time.Second)
|
||||
}
|
||||
|
||||
func (job *BotRunJob) LoadRun(ctx context.Context) error {
|
||||
if job.Run == nil {
|
||||
run, err := GetRunByID(ctx, job.RunID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
job.Run = run
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadAttributes load Run if not loaded
|
||||
func (job *BotRunJob) LoadAttributes(ctx context.Context) error {
|
||||
if job == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := job.LoadRun(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return job.Run.LoadAttributes(ctx)
|
||||
}
|
||||
|
||||
// ErrRunJobNotExist represents an error for bot run job not exist
|
||||
type ErrRunJobNotExist struct {
|
||||
ID int64
|
||||
}
|
||||
|
||||
func (err ErrRunJobNotExist) Error() string {
|
||||
return fmt.Sprintf("run job [%d] is not exist", err.ID)
|
||||
}
|
||||
|
||||
func GetRunJobByID(ctx context.Context, id int64) (*BotRunJob, error) {
|
||||
var job BotRunJob
|
||||
has, err := db.GetEngine(ctx).Where("id=?", id).Get(&job)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrRunJobNotExist{
|
||||
ID: id,
|
||||
}
|
||||
}
|
||||
|
||||
return &job, nil
|
||||
}
|
||||
|
||||
func GetRunJobsByRunID(ctx context.Context, runID int64) ([]*BotRunJob, error) {
|
||||
var jobs []*BotRunJob
|
||||
if err := db.GetEngine(ctx).Where("run_id=?", runID).OrderBy("id").Find(&jobs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return jobs, nil
|
||||
}
|
||||
|
||||
func UpdateRunJob(ctx context.Context, job *BotRunJob, cond builder.Cond, cols ...string) (int64, error) {
|
||||
e := db.GetEngine(ctx)
|
||||
|
||||
sess := e.ID(job.ID)
|
||||
if len(cols) > 0 {
|
||||
sess.Cols(cols...)
|
||||
}
|
||||
|
||||
if cond != nil {
|
||||
sess.Where(cond)
|
||||
}
|
||||
|
||||
affected, err := sess.Update(job)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if affected == 0 || (!slices.Contains(cols, "status") && job.Status == 0) {
|
||||
return affected, nil
|
||||
}
|
||||
|
||||
if job.RunID == 0 {
|
||||
var err error
|
||||
if job, err = GetRunJobByID(ctx, job.ID); err != nil {
|
||||
return affected, err
|
||||
}
|
||||
}
|
||||
|
||||
jobs, err := GetRunJobsByRunID(ctx, job.RunID)
|
||||
if err != nil {
|
||||
return affected, err
|
||||
}
|
||||
|
||||
runStatus := aggregateJobStatus(jobs)
|
||||
|
||||
run := &BotRun{
|
||||
ID: job.RunID,
|
||||
Status: runStatus,
|
||||
}
|
||||
if runStatus.IsDone() {
|
||||
run.Stopped = timeutil.TimeStampNow()
|
||||
}
|
||||
return affected, UpdateRun(ctx, run)
|
||||
}
|
||||
|
||||
func aggregateJobStatus(jobs []*BotRunJob) Status {
|
||||
allDone := true
|
||||
allWaiting := true
|
||||
hasFailure := false
|
||||
for _, job := range jobs {
|
||||
if !job.Status.IsDone() {
|
||||
allDone = false
|
||||
}
|
||||
if job.Status != StatusWaiting {
|
||||
allWaiting = false
|
||||
}
|
||||
if job.Status == StatusFailure || job.Status == StatusCancelled {
|
||||
hasFailure = true
|
||||
}
|
||||
}
|
||||
if allDone {
|
||||
if hasFailure {
|
||||
return StatusFailure
|
||||
}
|
||||
return StatusSuccess
|
||||
}
|
||||
if allWaiting {
|
||||
return StatusWaiting
|
||||
}
|
||||
return StatusRunning
|
||||
}
|
||||
102
models/actions/run_job_list.go
Normal file
102
models/actions/run_job_list.go
Normal file
@@ -0,0 +1,102 @@
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package actions
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
type RunJobList []*BotRunJob
|
||||
|
||||
func (jobs RunJobList) GetRunIDs() []int64 {
|
||||
runIDsMap := make(map[int64]struct{})
|
||||
for _, j := range jobs {
|
||||
if j.RunID == 0 {
|
||||
continue
|
||||
}
|
||||
runIDsMap[j.RunID] = struct{}{}
|
||||
}
|
||||
runIDs := make([]int64, 0, len(runIDsMap))
|
||||
for runID := range runIDsMap {
|
||||
runIDs = append(runIDs, runID)
|
||||
}
|
||||
return runIDs
|
||||
}
|
||||
|
||||
func (jobs RunJobList) LoadRuns(ctx context.Context, withRepo bool) error {
|
||||
runIDs := jobs.GetRunIDs()
|
||||
runs := make(map[int64]*BotRun, len(runIDs))
|
||||
if err := db.GetEngine(ctx).In("id", runIDs).Find(&runs); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, j := range jobs {
|
||||
if j.RunID > 0 && j.Run == nil {
|
||||
j.Run = runs[j.RunID]
|
||||
}
|
||||
}
|
||||
if withRepo {
|
||||
var runsList RunList = make([]*BotRun, 0, len(runs))
|
||||
for _, r := range runs {
|
||||
runsList = append(runsList, r)
|
||||
}
|
||||
return runsList.LoadRepos()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (jobs RunJobList) LoadAttributes(ctx context.Context, withRepo bool) error {
|
||||
return jobs.LoadRuns(ctx, withRepo)
|
||||
}
|
||||
|
||||
type FindRunJobOptions struct {
|
||||
db.ListOptions
|
||||
RunID int64
|
||||
RepoID int64
|
||||
OwnerID int64
|
||||
CommitSHA string
|
||||
Statuses []Status
|
||||
UpdatedBefore timeutil.TimeStamp
|
||||
}
|
||||
|
||||
func (opts FindRunJobOptions) toConds() builder.Cond {
|
||||
cond := builder.NewCond()
|
||||
if opts.RunID > 0 {
|
||||
cond = cond.And(builder.Eq{"run_id": opts.RunID})
|
||||
}
|
||||
if opts.RepoID > 0 {
|
||||
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
|
||||
}
|
||||
if opts.OwnerID > 0 {
|
||||
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
|
||||
}
|
||||
if opts.CommitSHA != "" {
|
||||
cond = cond.And(builder.Eq{"commit_sha": opts.CommitSHA})
|
||||
}
|
||||
if len(opts.Statuses) > 0 {
|
||||
cond = cond.And(builder.In("status", opts.Statuses))
|
||||
}
|
||||
if opts.UpdatedBefore > 0 {
|
||||
cond = cond.And(builder.Lt{"updated": opts.UpdatedBefore})
|
||||
}
|
||||
return cond
|
||||
}
|
||||
|
||||
func FindRunJobs(ctx context.Context, opts FindRunJobOptions) (RunJobList, int64, error) {
|
||||
e := db.GetEngine(ctx).Where(opts.toConds())
|
||||
if opts.PageSize > 0 && opts.Page >= 1 {
|
||||
e.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize)
|
||||
}
|
||||
var tasks RunJobList
|
||||
total, err := e.FindAndCount(&tasks)
|
||||
return tasks, total, err
|
||||
}
|
||||
|
||||
func CountRunJobs(ctx context.Context, opts FindRunJobOptions) (int64, error) {
|
||||
return db.GetEngine(ctx).Where(opts.toConds()).Count(new(BotRunJob))
|
||||
}
|
||||
114
models/actions/run_list.go
Normal file
114
models/actions/run_list.go
Normal file
@@ -0,0 +1,114 @@
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package actions
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"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/util"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
type RunList []*BotRun
|
||||
|
||||
// GetUserIDs returns a slice of user's id
|
||||
func (runs RunList) GetUserIDs() []int64 {
|
||||
userIDsMap := make(map[int64]struct{})
|
||||
for _, run := range runs {
|
||||
userIDsMap[run.TriggerUserID] = struct{}{}
|
||||
}
|
||||
userIDs := make([]int64, 0, len(userIDsMap))
|
||||
for userID := range userIDsMap {
|
||||
userIDs = append(userIDs, userID)
|
||||
}
|
||||
return userIDs
|
||||
}
|
||||
|
||||
func (runs RunList) GetRepoIDs() []int64 {
|
||||
repoIDsMap := make(map[int64]struct{})
|
||||
for _, run := range runs {
|
||||
repoIDsMap[run.RepoID] = struct{}{}
|
||||
}
|
||||
repoIDs := make([]int64, 0, len(repoIDsMap))
|
||||
for repoID := range repoIDsMap {
|
||||
repoIDs = append(repoIDs, repoID)
|
||||
}
|
||||
return repoIDs
|
||||
}
|
||||
|
||||
func (runs RunList) LoadTriggerUser() error {
|
||||
userIDs := runs.GetUserIDs()
|
||||
users := make(map[int64]*user_model.User, len(userIDs))
|
||||
if err := db.GetEngine(db.DefaultContext).In("id", userIDs).Find(&users); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, run := range runs {
|
||||
if run.TriggerUserID == user_model.BotUserID {
|
||||
run.TriggerUser = user_model.NewBotUser()
|
||||
} else {
|
||||
run.TriggerUser = users[run.TriggerUserID]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (runs RunList) LoadRepos() error {
|
||||
repoIDs := runs.GetRepoIDs()
|
||||
repos, err := repo_model.GetRepositoriesMapByIDs(repoIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, run := range runs {
|
||||
run.Repo = repos[run.RepoID]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type FindRunOptions struct {
|
||||
db.ListOptions
|
||||
RepoID int64
|
||||
OwnerID int64
|
||||
IsClosed util.OptionalBool
|
||||
WorkflowFileName string
|
||||
}
|
||||
|
||||
func (opts FindRunOptions) toConds() builder.Cond {
|
||||
cond := builder.NewCond()
|
||||
if opts.RepoID > 0 {
|
||||
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
|
||||
}
|
||||
if opts.OwnerID > 0 {
|
||||
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
|
||||
}
|
||||
if opts.IsClosed.IsFalse() {
|
||||
cond = cond.And(builder.Eq{"status": StatusWaiting}.Or(
|
||||
builder.Eq{"status": StatusRunning}))
|
||||
} else if opts.IsClosed.IsTrue() {
|
||||
cond = cond.And(
|
||||
builder.Neq{"status": StatusWaiting}.And(
|
||||
builder.Neq{"status": StatusRunning}))
|
||||
}
|
||||
if opts.WorkflowFileName != "" {
|
||||
cond = cond.And(builder.Eq{"workflow_id": opts.WorkflowFileName})
|
||||
}
|
||||
return cond
|
||||
}
|
||||
|
||||
func FindRuns(ctx context.Context, opts FindRunOptions) (RunList, int64, error) {
|
||||
e := db.GetEngine(ctx).Where(opts.toConds())
|
||||
if opts.PageSize > 0 && opts.Page >= 1 {
|
||||
e.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize)
|
||||
}
|
||||
var runs RunList
|
||||
total, err := e.Desc("id").FindAndCount(&runs)
|
||||
return runs, total, err
|
||||
}
|
||||
|
||||
func CountRuns(ctx context.Context, opts FindRunOptions) (int64, error) {
|
||||
return db.GetEngine(ctx).Where(opts.toConds()).Count(new(BotRun))
|
||||
}
|
||||
294
models/actions/runner.go
Normal file
294
models/actions/runner.go
Normal file
@@ -0,0 +1,294 @@
|
||||
// Copyright 2021 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/timeutil"
|
||||
|
||||
runnerv1 "code.gitea.io/bots-proto-go/runner/v1"
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
// ErrRunnerNotExist represents an error for bot runner not exist
|
||||
type ErrRunnerNotExist struct {
|
||||
ID int64
|
||||
UUID string
|
||||
Token string
|
||||
}
|
||||
|
||||
func (err ErrRunnerNotExist) Error() string {
|
||||
if err.UUID != "" {
|
||||
return fmt.Sprintf("Bot runner ID [%s] is not exist", err.UUID)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("Bot runner token [%s] is not exist", err.Token)
|
||||
}
|
||||
|
||||
// BotRunner represents runner machines
|
||||
type BotRunner struct {
|
||||
ID int64
|
||||
UUID string `xorm:"CHAR(36) UNIQUE"`
|
||||
Name string `xorm:"VARCHAR(32)"`
|
||||
OwnerID int64 `xorm:"index"` // org level runner, 0 means system
|
||||
Owner *user_model.User `xorm:"-"`
|
||||
RepoID int64 `xorm:"index"` // repo level runner, if orgid also is zero, then it's a global
|
||||
Repo *repo_model.Repository `xorm:"-"`
|
||||
Description string `xorm:"TEXT"`
|
||||
Base int // 0 native 1 docker 2 virtual machine
|
||||
RepoRange string // glob match which repositories could use this runner
|
||||
|
||||
Token string `xorm:"-"`
|
||||
TokenHash string `xorm:"UNIQUE"` // sha256 of token
|
||||
TokenSalt string
|
||||
// TokenLastEight string `xorm:"token_last_eight"` // it's unnecessary because we don't find runners by token
|
||||
|
||||
LastOnline timeutil.TimeStamp `xorm:"index"`
|
||||
LastActive timeutil.TimeStamp `xorm:"index"`
|
||||
|
||||
// Store OS and Artch.
|
||||
AgentLabels []string
|
||||
// Store custom labes use defined.
|
||||
CustomLabels []string
|
||||
|
||||
Created timeutil.TimeStamp `xorm:"created"`
|
||||
Updated timeutil.TimeStamp `xorm:"updated"`
|
||||
Deleted timeutil.TimeStamp `xorm:"deleted"`
|
||||
}
|
||||
|
||||
func (r *BotRunner) OwnType() string {
|
||||
if r.OwnerID == 0 {
|
||||
return "Global"
|
||||
}
|
||||
if r.RepoID == 0 {
|
||||
return r.Owner.Name
|
||||
}
|
||||
|
||||
return r.Repo.FullName()
|
||||
}
|
||||
|
||||
func (r *BotRunner) Status() runnerv1.RunnerStatus {
|
||||
if time.Since(r.LastOnline.AsTime()) > time.Minute {
|
||||
return runnerv1.RunnerStatus_RUNNER_STATUS_OFFLINE
|
||||
}
|
||||
if time.Since(r.LastActive.AsTime()) > 10*time.Second {
|
||||
return runnerv1.RunnerStatus_RUNNER_STATUS_IDLE
|
||||
}
|
||||
return runnerv1.RunnerStatus_RUNNER_STATUS_ACTIVE
|
||||
}
|
||||
|
||||
func (r *BotRunner) StatusName() string {
|
||||
return strings.ToLower(strings.TrimPrefix(r.Status().String(), "RUNNER_STATUS_"))
|
||||
}
|
||||
|
||||
func (r *BotRunner) IsOnline() bool {
|
||||
status := r.Status()
|
||||
if status == runnerv1.RunnerStatus_RUNNER_STATUS_IDLE || status == runnerv1.RunnerStatus_RUNNER_STATUS_ACTIVE {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// AllLabels returns agent and custom labels
|
||||
func (r *BotRunner) AllLabels() []string {
|
||||
return append(r.AgentLabels, r.CustomLabels...)
|
||||
}
|
||||
|
||||
// Editable checks if the runner is editable by the user
|
||||
func (r *BotRunner) Editable(ownerID, repoID int64) bool {
|
||||
if ownerID == 0 && repoID == 0 {
|
||||
return true
|
||||
}
|
||||
if ownerID > 0 && r.OwnerID == ownerID {
|
||||
return true
|
||||
}
|
||||
return repoID > 0 && r.RepoID == repoID
|
||||
}
|
||||
|
||||
// LoadAttributes loads the attributes of the runner
|
||||
func (r *BotRunner) LoadAttributes(ctx context.Context) error {
|
||||
if r.OwnerID > 0 {
|
||||
var user user_model.User
|
||||
has, err := db.GetEngine(ctx).ID(r.OwnerID).Get(&user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if has {
|
||||
r.Owner = &user
|
||||
}
|
||||
}
|
||||
if r.RepoID > 0 {
|
||||
var repo repo_model.Repository
|
||||
has, err := db.GetEngine(ctx).ID(r.RepoID).Get(&repo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if has {
|
||||
r.Repo = &repo
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *BotRunner) GenerateToken() (err error) {
|
||||
r.Token, r.TokenSalt, r.TokenHash, _, err = generateSaltedToken()
|
||||
return err
|
||||
}
|
||||
|
||||
func init() {
|
||||
db.RegisterModel(&BotRunner{})
|
||||
}
|
||||
|
||||
type FindRunnerOptions struct {
|
||||
db.ListOptions
|
||||
RepoID int64
|
||||
OwnerID int64
|
||||
Sort string
|
||||
Filter string
|
||||
WithDeleted bool
|
||||
}
|
||||
|
||||
func (opts FindRunnerOptions) toCond() builder.Cond {
|
||||
cond := builder.NewCond()
|
||||
|
||||
withGlobal := false
|
||||
if opts.RepoID > 0 {
|
||||
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
|
||||
withGlobal = true
|
||||
}
|
||||
if opts.OwnerID > 0 {
|
||||
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
|
||||
withGlobal = true
|
||||
}
|
||||
if withGlobal {
|
||||
cond = cond.Or(builder.Eq{"repo_id": 0, "owner_id": 0})
|
||||
}
|
||||
|
||||
if opts.Filter != "" {
|
||||
cond = cond.And(builder.Like{"name", opts.Filter})
|
||||
}
|
||||
if !opts.WithDeleted {
|
||||
cond = cond.And(builder.IsNull{"deleted"})
|
||||
}
|
||||
return cond
|
||||
}
|
||||
|
||||
func (opts FindRunnerOptions) toOrder() string {
|
||||
switch opts.Sort {
|
||||
case "online":
|
||||
return "last_online DESC"
|
||||
case "offline":
|
||||
return "last_online ASC"
|
||||
case "alphabetically":
|
||||
return "name ASC"
|
||||
}
|
||||
return "last_online DESC"
|
||||
}
|
||||
|
||||
func CountRunners(opts FindRunnerOptions) (int64, error) {
|
||||
return db.GetEngine(db.DefaultContext).
|
||||
Table(BotRunner{}).
|
||||
Where(opts.toCond()).
|
||||
OrderBy(opts.toOrder()).
|
||||
Count()
|
||||
}
|
||||
|
||||
func FindRunners(opts FindRunnerOptions) (runners RunnerList, err error) {
|
||||
sess := db.GetEngine(db.DefaultContext).
|
||||
Where(opts.toCond()).
|
||||
OrderBy(opts.toOrder())
|
||||
if opts.Page > 0 {
|
||||
sess.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize)
|
||||
}
|
||||
return runners, sess.Find(&runners)
|
||||
}
|
||||
|
||||
// GetUsableRunner returns the usable runner
|
||||
func GetUsableRunner(opts FindRunnerOptions) (*BotRunner, error) {
|
||||
var runner BotRunner
|
||||
has, err := db.GetEngine(db.DefaultContext).
|
||||
Where(opts.toCond()).
|
||||
Asc("last_online").
|
||||
Get(&runner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !has {
|
||||
return nil, ErrRunnerNotExist{}
|
||||
}
|
||||
|
||||
return &runner, nil
|
||||
}
|
||||
|
||||
// GetRunnerByUUID returns a bot runner via uuid
|
||||
func GetRunnerByUUID(uuid string) (*BotRunner, error) {
|
||||
var runner BotRunner
|
||||
has, err := db.GetEngine(db.DefaultContext).Where("uuid=?", uuid).Get(&runner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrRunnerNotExist{
|
||||
UUID: uuid,
|
||||
}
|
||||
}
|
||||
return &runner, nil
|
||||
}
|
||||
|
||||
// GetRunnerByID returns a bot runner via id
|
||||
func GetRunnerByID(id int64) (*BotRunner, error) {
|
||||
var runner BotRunner
|
||||
has, err := db.GetEngine(db.DefaultContext).Where("id=?", id).Get(&runner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrRunnerNotExist{
|
||||
ID: id,
|
||||
}
|
||||
}
|
||||
return &runner, nil
|
||||
}
|
||||
|
||||
// UpdateRunner updates runner's information.
|
||||
func UpdateRunner(ctx context.Context, r *BotRunner, cols ...string) error {
|
||||
e := db.GetEngine(ctx)
|
||||
var err error
|
||||
if len(cols) == 0 {
|
||||
_, err = e.ID(r.ID).AllCols().Update(r)
|
||||
} else {
|
||||
_, err = e.ID(r.ID).Cols(cols...).Update(r)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteRunner deletes a runner by given ID.
|
||||
func DeleteRunner(ctx context.Context, r *BotRunner) error {
|
||||
e := db.GetEngine(ctx)
|
||||
_, err := e.Delete(r)
|
||||
return err
|
||||
}
|
||||
|
||||
// FindRunnersByRepoID returns all workers for the repository
|
||||
func FindRunnersByRepoID(repoID int64) ([]*BotRunner, error) {
|
||||
var runners []*BotRunner
|
||||
err := db.GetEngine(db.DefaultContext).Where("repo_id=? OR repo_id=0", repoID).
|
||||
Find(&runners)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = db.GetEngine(db.DefaultContext).Join("INNER", "repository", "repository.owner_id = bot_runner.owner_id").Find(&runners)
|
||||
return runners, err
|
||||
}
|
||||
|
||||
// NewRunner creates new runner.
|
||||
func NewRunner(ctx context.Context, t *BotRunner) error {
|
||||
_, err := db.GetEngine(ctx).Insert(t)
|
||||
return err
|
||||
}
|
||||
81
models/actions/runner_list.go
Normal file
81
models/actions/runner_list.go
Normal file
@@ -0,0 +1,81 @@
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package actions
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"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/container"
|
||||
)
|
||||
|
||||
type RunnerList []*BotRunner
|
||||
|
||||
// GetUserIDs returns a slice of user's id
|
||||
func (runners RunnerList) GetUserIDs() []int64 {
|
||||
userIDsMap := make(map[int64]struct{})
|
||||
for _, runner := range runners {
|
||||
if runner.OwnerID == 0 {
|
||||
continue
|
||||
}
|
||||
userIDsMap[runner.OwnerID] = struct{}{}
|
||||
}
|
||||
userIDs := make([]int64, 0, len(userIDsMap))
|
||||
for userID := range userIDsMap {
|
||||
userIDs = append(userIDs, userID)
|
||||
}
|
||||
return userIDs
|
||||
}
|
||||
|
||||
func (runners RunnerList) LoadOwners(ctx context.Context) error {
|
||||
userIDs := runners.GetUserIDs()
|
||||
users := make(map[int64]*user_model.User, len(userIDs))
|
||||
if err := db.GetEngine(ctx).In("id", userIDs).Find(&users); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, runner := range runners {
|
||||
if runner.OwnerID > 0 && runner.Owner == nil {
|
||||
runner.Owner = users[runner.OwnerID]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (runners RunnerList) getRepoIDs() []int64 {
|
||||
repoIDs := make(container.Set[int64], len(runners))
|
||||
for _, runner := range runners {
|
||||
if runner.RepoID == 0 {
|
||||
continue
|
||||
}
|
||||
if _, ok := repoIDs[runner.RepoID]; !ok {
|
||||
repoIDs[runner.RepoID] = struct{}{}
|
||||
}
|
||||
}
|
||||
return repoIDs.Values()
|
||||
}
|
||||
|
||||
func (runners RunnerList) LoadRepos(ctx context.Context) error {
|
||||
repoIDs := runners.getRepoIDs()
|
||||
repos := make(map[int64]*repo_model.Repository, len(repoIDs))
|
||||
if err := db.GetEngine(ctx).In("id", repoIDs).Find(&repos); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, runner := range runners {
|
||||
if runner.RepoID > 0 && runner.Repo == nil {
|
||||
runner.Repo = repos[runner.RepoID]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (runners RunnerList) LoadAttributes(ctx context.Context) error {
|
||||
if err := runners.LoadOwners(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return runners.LoadRepos(ctx)
|
||||
}
|
||||
97
models/actions/runner_token.go
Normal file
97
models/actions/runner_token.go
Normal file
@@ -0,0 +1,97 @@
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package actions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"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/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
// ErrRunnerNotExist represents an error for bot runner not exist
|
||||
type ErrRunnerTokenNotExist struct {
|
||||
Token string
|
||||
}
|
||||
|
||||
func (err ErrRunnerTokenNotExist) Error() string {
|
||||
return fmt.Sprintf("runner token [%s] is not exist", err.Token)
|
||||
}
|
||||
|
||||
// BotRunnerToken represents runner tokens
|
||||
type BotRunnerToken struct {
|
||||
ID int64
|
||||
Token string `xorm:"UNIQUE"`
|
||||
OwnerID int64 `xorm:"index"` // org level runner, 0 means system
|
||||
Owner *user_model.User `xorm:"-"`
|
||||
RepoID int64 `xorm:"index"` // repo level runner, if orgid also is zero, then it's a global
|
||||
Repo *repo_model.Repository `xorm:"-"`
|
||||
IsActive bool
|
||||
|
||||
Created timeutil.TimeStamp `xorm:"created"`
|
||||
Updated timeutil.TimeStamp `xorm:"updated"`
|
||||
Deleted timeutil.TimeStamp `xorm:"deleted"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
db.RegisterModel(new(BotRunnerToken))
|
||||
}
|
||||
|
||||
// GetRunnerToken returns a bot runner via token
|
||||
func GetRunnerToken(token string) (*BotRunnerToken, error) {
|
||||
var runnerToken BotRunnerToken
|
||||
has, err := db.GetEngine(db.DefaultContext).Where("token=?", token).Get(&runnerToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrRunnerTokenNotExist{
|
||||
Token: token,
|
||||
}
|
||||
}
|
||||
return &runnerToken, nil
|
||||
}
|
||||
|
||||
// UpdateRunnerToken updates runner token information.
|
||||
func UpdateRunnerToken(ctx context.Context, r *BotRunnerToken, cols ...string) (err error) {
|
||||
e := db.GetEngine(ctx)
|
||||
|
||||
if len(cols) == 0 {
|
||||
_, err = e.ID(r.ID).AllCols().Update(r)
|
||||
} else {
|
||||
_, err = e.ID(r.ID).Cols(cols...).Update(r)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// NewRunnerToken creates a new runner token
|
||||
func NewRunnerToken(ownerID, repoID int64) (*BotRunnerToken, error) {
|
||||
token, err := util.CryptoRandomString(40)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
runnerToken := &BotRunnerToken{
|
||||
OwnerID: ownerID,
|
||||
RepoID: repoID,
|
||||
IsActive: false,
|
||||
Token: token,
|
||||
}
|
||||
_, err = db.GetEngine(db.DefaultContext).Insert(runnerToken)
|
||||
return runnerToken, err
|
||||
}
|
||||
|
||||
// GetUnactivatedRunnerToken returns a unactivated runner token
|
||||
func GetUnactivatedRunnerToken(ownerID, repoID int64) (*BotRunnerToken, error) {
|
||||
var runnerToken BotRunnerToken
|
||||
has, err := db.GetEngine(db.DefaultContext).Where("owner_id=? AND repo_id=? AND is_active=?", ownerID, repoID, false).OrderBy("id DESC").Get(&runnerToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrRunnerTokenNotExist{}
|
||||
}
|
||||
return &runnerToken, nil
|
||||
}
|
||||
90
models/actions/status.go
Normal file
90
models/actions/status.go
Normal file
@@ -0,0 +1,90 @@
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package actions
|
||||
|
||||
import runnerv1 "code.gitea.io/bots-proto-go/runner/v1"
|
||||
|
||||
// Status represents the status of BotRun, BotRunJob, BotTask, or BotTaskStep
|
||||
type Status int
|
||||
|
||||
const (
|
||||
StatusUnknown Status = iota // 0, consistent with runnerv1.Result_RESULT_UNSPECIFIED
|
||||
StatusSuccess // 1, consistent with runnerv1.Result_RESULT_SUCCESS
|
||||
StatusFailure // 2, consistent with runnerv1.Result_RESULT_FAILURE
|
||||
StatusCancelled // 3, consistent with runnerv1.Result_RESULT_CANCELLED
|
||||
StatusSkipped // 4, consistent with runnerv1.Result_RESULT_SKIPPED
|
||||
StatusWaiting // 5, isn't a runnerv1.Result
|
||||
StatusRunning // 6, isn't a runnerv1.Result
|
||||
StatusBlocked // 7, isn't a runnerv1.Result
|
||||
)
|
||||
|
||||
// String returns the string name of the Status
|
||||
func (s Status) String() string {
|
||||
return statusNames[s]
|
||||
}
|
||||
|
||||
// IsDone returns whether the Status is final
|
||||
func (s Status) IsDone() bool {
|
||||
return s.In(StatusSuccess, StatusFailure, StatusCancelled, StatusSkipped)
|
||||
}
|
||||
|
||||
// HasRun returns whether the Status is a result of running
|
||||
func (s Status) HasRun() bool {
|
||||
return s.In(StatusSuccess, StatusFailure)
|
||||
}
|
||||
|
||||
func (s Status) IsUnknown() bool {
|
||||
return s == StatusUnknown
|
||||
}
|
||||
|
||||
func (s Status) IsSuccess() bool {
|
||||
return s == StatusSuccess
|
||||
}
|
||||
|
||||
func (s Status) IsFailure() bool {
|
||||
return s == StatusFailure
|
||||
}
|
||||
|
||||
func (s Status) IsCancelled() bool {
|
||||
return s == StatusCancelled
|
||||
}
|
||||
|
||||
func (s Status) IsSkipped() bool {
|
||||
return s == StatusSkipped
|
||||
}
|
||||
|
||||
func (s Status) IsWaiting() bool {
|
||||
return s == StatusWaiting
|
||||
}
|
||||
|
||||
func (s Status) IsRunning() bool {
|
||||
return s == StatusRunning
|
||||
}
|
||||
|
||||
func (s Status) In(statuses ...Status) bool {
|
||||
for _, v := range statuses {
|
||||
if s == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s Status) AsResult() runnerv1.Result {
|
||||
if s.IsDone() {
|
||||
return runnerv1.Result(s)
|
||||
}
|
||||
return runnerv1.Result_RESULT_UNSPECIFIED
|
||||
}
|
||||
|
||||
var statusNames = map[Status]string{
|
||||
StatusUnknown: "unknown",
|
||||
StatusWaiting: "waiting",
|
||||
StatusRunning: "running",
|
||||
StatusSuccess: "success",
|
||||
StatusFailure: "failure",
|
||||
StatusCancelled: "cancelled",
|
||||
StatusSkipped: "skipped",
|
||||
StatusBlocked: "blocked",
|
||||
}
|
||||
567
models/actions/task.go
Normal file
567
models/actions/task.go
Normal file
@@ -0,0 +1,567 @@
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package actions
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/subtle"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
runnerv1 "code.gitea.io/bots-proto-go/runner/v1"
|
||||
lru "github.com/hashicorp/golang-lru"
|
||||
"github.com/nektos/act/pkg/jobparser"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
// BotTask represents a distribution of job
|
||||
type BotTask struct {
|
||||
ID int64
|
||||
JobID int64
|
||||
Job *BotRunJob `xorm:"-"`
|
||||
Steps []*BotTaskStep `xorm:"-"`
|
||||
Attempt int64
|
||||
RunnerID int64 `xorm:"index"`
|
||||
Status Status `xorm:"index"`
|
||||
Started timeutil.TimeStamp `xorm:"index"`
|
||||
Stopped timeutil.TimeStamp
|
||||
|
||||
RepoID int64 `xorm:"index"`
|
||||
OwnerID int64 `xorm:"index"`
|
||||
CommitSHA string `xorm:"index"`
|
||||
IsForkPullRequest bool
|
||||
|
||||
Token string `xorm:"-"`
|
||||
TokenHash string `xorm:"UNIQUE"` // sha256 of token
|
||||
TokenSalt string
|
||||
TokenLastEight string `xorm:"index token_last_eight"`
|
||||
|
||||
LogFilename string // file name of log
|
||||
LogInStorage bool // read log from database or from storage
|
||||
LogLength int64 // lines count
|
||||
LogSize int64 // blob size
|
||||
LogIndexes *LogIndexes `xorm:"BLOB"` // line number to offset
|
||||
LogExpired bool // files that are too old will be deleted
|
||||
|
||||
Created timeutil.TimeStamp `xorm:"created"`
|
||||
Updated timeutil.TimeStamp `xorm:"updated index"`
|
||||
}
|
||||
|
||||
var successfulTokenTaskCache *lru.Cache
|
||||
|
||||
func init() {
|
||||
db.RegisterModel(new(BotTask), func() error {
|
||||
if setting.SuccessfulTokensCacheSize > 0 {
|
||||
var err error
|
||||
successfulTokenTaskCache, err = lru.New(setting.SuccessfulTokensCacheSize)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to allocate Task cache: %v", err)
|
||||
}
|
||||
} else {
|
||||
successfulTokenTaskCache = nil
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (task *BotTask) TakeTime() time.Duration {
|
||||
if task.Started == 0 {
|
||||
return 0
|
||||
}
|
||||
started := task.Started.AsTime()
|
||||
if task.Status.IsDone() {
|
||||
return task.Stopped.AsTime().Sub(started)
|
||||
}
|
||||
task.Stopped.AsTime().Sub(started)
|
||||
return time.Since(started).Truncate(time.Second)
|
||||
}
|
||||
|
||||
func (task *BotTask) IsStopped() bool {
|
||||
return task.Stopped > 0
|
||||
}
|
||||
|
||||
func (task *BotTask) GetRepo() string {
|
||||
return "xxxx"
|
||||
}
|
||||
|
||||
func (task *BotTask) GetCommitSHA() string {
|
||||
if task.Job == nil {
|
||||
return ""
|
||||
}
|
||||
if task.Job.Run == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return task.Job.Run.CommitSHA
|
||||
}
|
||||
|
||||
func (task *BotTask) GetCommitSHAShort() string {
|
||||
commitSHA := task.GetCommitSHA()
|
||||
if len(commitSHA) > 8 {
|
||||
return commitSHA[:8]
|
||||
}
|
||||
return commitSHA
|
||||
}
|
||||
|
||||
func (task *BotTask) GetBuildViewLink() string {
|
||||
if task.Job == nil || task.Job.Run == nil || task.Job.Run.Repo == nil {
|
||||
return ""
|
||||
}
|
||||
return task.Job.Run.Repo.Link() + "/bots/runs/" + strconv.FormatInt(task.ID, 10)
|
||||
}
|
||||
|
||||
func (task *BotTask) GetCommitLink() string {
|
||||
if task.Job == nil || task.Job.Run == nil || task.Job.Run.Repo == nil {
|
||||
return ""
|
||||
}
|
||||
if commitSHA := task.GetCommitSHA(); commitSHA != "" {
|
||||
return task.Job.Run.Repo.CommitLink(commitSHA)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (task *BotTask) GetRepoName() string {
|
||||
if task.Job == nil || task.Job.Run == nil || task.Job.Run.Repo == nil {
|
||||
return ""
|
||||
}
|
||||
return task.Job.Run.Repo.FullName()
|
||||
}
|
||||
|
||||
func (task *BotTask) GetRepoLink() string {
|
||||
if task.Job == nil || task.Job.Run == nil || task.Job.Run.Repo == nil {
|
||||
return ""
|
||||
}
|
||||
return task.Job.Run.Repo.Link()
|
||||
}
|
||||
|
||||
func (task *BotTask) LoadJob(ctx context.Context) error {
|
||||
if task.Job == nil {
|
||||
job, err := GetRunJobByID(ctx, task.JobID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
task.Job = job
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadAttributes load Job Steps if not loaded
|
||||
func (task *BotTask) LoadAttributes(ctx context.Context) error {
|
||||
if task == nil {
|
||||
return nil
|
||||
}
|
||||
if err := task.LoadJob(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := task.Job.LoadAttributes(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if task.Steps == nil { // be careful, an empty slice (not nil) also means loaded
|
||||
steps, err := GetTaskStepsByTaskID(ctx, task.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
task.Steps = steps
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (task *BotTask) GenerateToken() (err error) {
|
||||
task.Token, task.TokenSalt, task.TokenHash, task.TokenLastEight, err = generateSaltedToken()
|
||||
return err
|
||||
}
|
||||
|
||||
type LogIndexes []int64
|
||||
|
||||
func (indexes *LogIndexes) FromDB(b []byte) error {
|
||||
reader := bytes.NewReader(b)
|
||||
for {
|
||||
v, err := binary.ReadVarint(reader)
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("binary ReadVarint: %w", err)
|
||||
}
|
||||
*indexes = append(*indexes, v)
|
||||
}
|
||||
}
|
||||
|
||||
func (indexes *LogIndexes) ToDB() ([]byte, error) {
|
||||
buf, i := make([]byte, binary.MaxVarintLen64*len(*indexes)), 0
|
||||
for _, v := range *indexes {
|
||||
n := binary.PutVarint(buf[i:], v)
|
||||
i += n
|
||||
}
|
||||
return buf[:i], nil
|
||||
}
|
||||
|
||||
func GetTaskByID(ctx context.Context, id int64) (*BotTask, error) {
|
||||
var task BotTask
|
||||
has, err := db.GetEngine(ctx).Where("id=?", id).Get(&task)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, fmt.Errorf("task with id %d: %w", id, util.ErrNotExist)
|
||||
}
|
||||
|
||||
return &task, nil
|
||||
}
|
||||
|
||||
func GetRunningTaskByToken(ctx context.Context, token string) (*BotTask, error) {
|
||||
errNotExist := fmt.Errorf("task with token %q: %w", token, util.ErrNotExist)
|
||||
if token == "" {
|
||||
return nil, errNotExist
|
||||
}
|
||||
// A token is defined as being SHA1 sum these are 40 hexadecimal bytes long
|
||||
if len(token) != 40 {
|
||||
return nil, errNotExist
|
||||
}
|
||||
for _, x := range []byte(token) {
|
||||
if x < '0' || (x > '9' && x < 'a') || x > 'f' {
|
||||
return nil, errNotExist
|
||||
}
|
||||
}
|
||||
|
||||
lastEight := token[len(token)-8:]
|
||||
|
||||
if id := getTaskIDFromCache(token); id > 0 {
|
||||
task := &BotTask{
|
||||
TokenLastEight: lastEight,
|
||||
}
|
||||
// Re-get the task from the db in case it has been deleted in the intervening period
|
||||
has, err := db.GetEngine(db.DefaultContext).ID(id).Get(task)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if has {
|
||||
return task, nil
|
||||
}
|
||||
successfulTokenTaskCache.Remove(token)
|
||||
}
|
||||
|
||||
var tasks []*BotTask
|
||||
err := db.GetEngine(ctx).Where("token_last_eight = ? AND status = ?", lastEight, StatusRunning).Find(&tasks)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if len(tasks) == 0 {
|
||||
return nil, errNotExist
|
||||
}
|
||||
|
||||
for _, t := range tasks {
|
||||
tempHash := auth_model.HashToken(token, t.TokenSalt)
|
||||
if subtle.ConstantTimeCompare([]byte(t.TokenHash), []byte(tempHash)) == 1 {
|
||||
if successfulTokenTaskCache != nil {
|
||||
successfulTokenTaskCache.Add(token, t.ID)
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
}
|
||||
return nil, errNotExist
|
||||
}
|
||||
|
||||
func CreateTaskForRunner(ctx context.Context, runner *BotRunner) (*BotTask, bool, error) {
|
||||
dbCtx, commiter, err := db.TxContext(ctx)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
defer commiter.Close()
|
||||
ctx = dbCtx.WithContext(ctx)
|
||||
|
||||
e := db.GetEngine(ctx)
|
||||
|
||||
jobCond := builder.NewCond()
|
||||
if runner.RepoID != 0 {
|
||||
jobCond = builder.Eq{"repo_id": runner.RepoID}
|
||||
} else if runner.OwnerID != 0 {
|
||||
jobCond = builder.In("repo_id", builder.Select("id").From("repository").Where(builder.Eq{"owner_id": runner.OwnerID}))
|
||||
}
|
||||
if jobCond.IsValid() {
|
||||
jobCond = builder.In("run_id", builder.Select("id").From("bot_run").Where(jobCond))
|
||||
}
|
||||
|
||||
var jobs []*BotRunJob
|
||||
if err := e.Where("task_id=? AND status=?", 0, StatusWaiting).And(jobCond).Asc("id").Find(&jobs); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
// TODO: a more efficient way to filter labels
|
||||
var job *BotRunJob
|
||||
labels := runner.AgentLabels
|
||||
labels = append(labels, runner.CustomLabels...)
|
||||
log.Trace("runner labels: %v", labels)
|
||||
for _, v := range jobs {
|
||||
if isSubset(labels, v.RunsOn) {
|
||||
job = v
|
||||
break
|
||||
}
|
||||
}
|
||||
if job == nil {
|
||||
return nil, false, nil
|
||||
}
|
||||
if err := job.LoadAttributes(ctx); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
now := timeutil.TimeStampNow()
|
||||
job.Attempt++
|
||||
job.Started = now
|
||||
job.Status = StatusRunning
|
||||
|
||||
task := &BotTask{
|
||||
JobID: job.ID,
|
||||
Attempt: job.Attempt,
|
||||
RunnerID: runner.ID,
|
||||
Started: now,
|
||||
Status: StatusRunning,
|
||||
RepoID: job.RepoID,
|
||||
OwnerID: job.OwnerID,
|
||||
CommitSHA: job.CommitSHA,
|
||||
IsForkPullRequest: job.IsForkPullRequest,
|
||||
}
|
||||
if err := task.GenerateToken(); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
var workflowJob *jobparser.Job
|
||||
if gots, err := jobparser.Parse(job.WorkflowPayload); err != nil {
|
||||
return nil, false, fmt.Errorf("parse workflow of job %d: %w", job.ID, err)
|
||||
} else if len(gots) != 1 {
|
||||
return nil, false, fmt.Errorf("workflow of job %d: not signle workflow", job.ID)
|
||||
} else {
|
||||
_, workflowJob = gots[0].Job()
|
||||
}
|
||||
|
||||
if _, err := e.Insert(task); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
task.LogFilename = logFileName(job.Run.Repo.FullName(), task.ID)
|
||||
if _, err := e.ID(task.ID).Cols("log_filename").Update(task); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
steps := make([]*BotTaskStep, len(workflowJob.Steps))
|
||||
for i, v := range workflowJob.Steps {
|
||||
steps[i] = &BotTaskStep{
|
||||
Name: v.String(),
|
||||
TaskID: task.ID,
|
||||
Number: int64(i),
|
||||
RepoID: task.RepoID,
|
||||
Status: StatusWaiting,
|
||||
}
|
||||
}
|
||||
if _, err := e.Insert(steps); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
task.Steps = steps
|
||||
|
||||
job.TaskID = task.ID
|
||||
if n, err := UpdateRunJob(ctx, job, builder.Eq{"task_id": 0}); err != nil {
|
||||
return nil, false, err
|
||||
} else if n != 1 {
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
if job.Run.Status.IsWaiting() {
|
||||
job.Run.Status = StatusRunning
|
||||
job.Run.Started = now
|
||||
if err := UpdateRun(ctx, job.Run, "status", "started"); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
}
|
||||
|
||||
task.Job = job
|
||||
|
||||
if err := commiter.Commit(); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
return task, true, nil
|
||||
}
|
||||
|
||||
func UpdateTask(ctx context.Context, task *BotTask, cols ...string) error {
|
||||
sess := db.GetEngine(ctx).ID(task.ID)
|
||||
if len(cols) > 0 {
|
||||
sess.Cols(cols...)
|
||||
}
|
||||
_, err := sess.Update(task)
|
||||
return err
|
||||
}
|
||||
|
||||
func UpdateTaskByState(state *runnerv1.TaskState) (*BotTask, error) {
|
||||
stepStates := map[int64]*runnerv1.StepState{}
|
||||
for _, v := range state.Steps {
|
||||
stepStates[v.Id] = v
|
||||
}
|
||||
|
||||
ctx, commiter, err := db.TxContext(db.DefaultContext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer commiter.Close()
|
||||
|
||||
e := db.GetEngine(ctx)
|
||||
|
||||
task := &BotTask{}
|
||||
if has, err := e.ID(state.Id).Get(task); err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, util.ErrNotExist
|
||||
}
|
||||
|
||||
if state.Result != runnerv1.Result_RESULT_UNSPECIFIED {
|
||||
task.Status = Status(state.Result)
|
||||
task.Stopped = timeutil.TimeStamp(state.StoppedAt.AsTime().Unix())
|
||||
if _, err := UpdateRunJob(ctx, &BotRunJob{
|
||||
ID: task.JobID,
|
||||
Status: task.Status,
|
||||
Stopped: task.Stopped,
|
||||
}, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := e.ID(task.ID).Update(task); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := task.LoadAttributes(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
prevStepDone := true
|
||||
for _, step := range task.Steps {
|
||||
var result runnerv1.Result
|
||||
if v, ok := stepStates[step.Number]; ok {
|
||||
result = v.Result
|
||||
step.LogIndex = v.LogIndex
|
||||
step.LogLength = v.LogLength
|
||||
step.Started = convertTimestamp(v.StartedAt)
|
||||
step.Stopped = convertTimestamp(v.StoppedAt)
|
||||
}
|
||||
if result != runnerv1.Result_RESULT_UNSPECIFIED {
|
||||
step.Status = Status(result)
|
||||
prevStepDone = true
|
||||
} else if prevStepDone {
|
||||
step.Status = StatusRunning
|
||||
prevStepDone = false
|
||||
}
|
||||
if _, err := e.ID(step.ID).Update(step); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := commiter.Commit(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return task, nil
|
||||
}
|
||||
|
||||
func StopTask(ctx context.Context, taskID int64, status Status) error {
|
||||
if !status.IsDone() {
|
||||
return fmt.Errorf("cannot stop task with status %v", status)
|
||||
}
|
||||
e := db.GetEngine(ctx)
|
||||
|
||||
task := &BotTask{}
|
||||
if has, err := e.ID(taskID).Get(task); err != nil {
|
||||
return err
|
||||
} else if !has {
|
||||
return util.ErrNotExist
|
||||
}
|
||||
if task.Status.IsDone() {
|
||||
return nil
|
||||
}
|
||||
|
||||
now := timeutil.TimeStampNow()
|
||||
task.Status = status
|
||||
task.Stopped = now
|
||||
if _, err := UpdateRunJob(ctx, &BotRunJob{
|
||||
ID: task.JobID,
|
||||
Status: task.Status,
|
||||
Stopped: task.Stopped,
|
||||
}, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := e.ID(task.ID).Update(task); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := task.LoadAttributes(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, step := range task.Steps {
|
||||
if !step.Status.IsDone() {
|
||||
step.Status = status
|
||||
if step.Started == 0 {
|
||||
step.Started = now
|
||||
}
|
||||
step.Stopped = now
|
||||
}
|
||||
if _, err := e.ID(step.ID).Update(step); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func isSubset(set, subset []string) bool {
|
||||
m := make(map[string]struct{}, len(set))
|
||||
for _, v := range set {
|
||||
m[v] = struct{}{}
|
||||
}
|
||||
for _, v := range subset {
|
||||
if _, ok := m[v]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func convertTimestamp(timestamp *timestamppb.Timestamp) timeutil.TimeStamp {
|
||||
if timestamp.GetSeconds() == 0 && timestamp.GetNanos() == 0 {
|
||||
return timeutil.TimeStamp(0)
|
||||
}
|
||||
return timeutil.TimeStamp(timestamp.AsTime().Unix())
|
||||
}
|
||||
|
||||
func logFileName(repoFullName string, taskID int64) string {
|
||||
return fmt.Sprintf("%s/%02x/%d.log", repoFullName, taskID%256, taskID)
|
||||
}
|
||||
|
||||
func getTaskIDFromCache(token string) int64 {
|
||||
if successfulTokenTaskCache == nil {
|
||||
return 0
|
||||
}
|
||||
tInterface, ok := successfulTokenTaskCache.Get(token)
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
t, ok := tInterface.(int64)
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
return t
|
||||
}
|
||||
108
models/actions/task_list.go
Normal file
108
models/actions/task_list.go
Normal file
@@ -0,0 +1,108 @@
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package actions
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
type TaskList []*BotTask
|
||||
|
||||
func (tasks TaskList) GetJobIDs() []int64 {
|
||||
jobIDsMap := make(map[int64]struct{})
|
||||
for _, t := range tasks {
|
||||
if t.JobID == 0 {
|
||||
continue
|
||||
}
|
||||
jobIDsMap[t.JobID] = struct{}{}
|
||||
}
|
||||
jobIDs := make([]int64, 0, len(jobIDsMap))
|
||||
for jobID := range jobIDsMap {
|
||||
jobIDs = append(jobIDs, jobID)
|
||||
}
|
||||
return jobIDs
|
||||
}
|
||||
|
||||
func (tasks TaskList) LoadJobs(ctx context.Context) error {
|
||||
jobIDs := tasks.GetJobIDs()
|
||||
jobs := make(map[int64]*BotRunJob, len(jobIDs))
|
||||
if err := db.GetEngine(ctx).In("id", jobIDs).Find(&jobs); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, t := range tasks {
|
||||
if t.JobID > 0 && t.Job == nil {
|
||||
t.Job = jobs[t.JobID]
|
||||
}
|
||||
}
|
||||
|
||||
var jobsList RunJobList = make([]*BotRunJob, 0, len(jobs))
|
||||
for _, j := range jobs {
|
||||
jobsList = append(jobsList, j)
|
||||
}
|
||||
return jobsList.LoadAttributes(ctx, true)
|
||||
}
|
||||
|
||||
func (tasks TaskList) LoadAttributes(ctx context.Context) error {
|
||||
return tasks.LoadJobs(ctx)
|
||||
}
|
||||
|
||||
type FindTaskOptions struct {
|
||||
db.ListOptions
|
||||
RepoID int64
|
||||
OwnerID int64
|
||||
CommitSHA string
|
||||
Status Status
|
||||
UpdatedBefore timeutil.TimeStamp
|
||||
StartedBefore timeutil.TimeStamp
|
||||
RunnerID int64
|
||||
IDOrderDesc bool
|
||||
}
|
||||
|
||||
func (opts FindTaskOptions) toConds() builder.Cond {
|
||||
cond := builder.NewCond()
|
||||
if opts.RepoID > 0 {
|
||||
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
|
||||
}
|
||||
if opts.OwnerID > 0 {
|
||||
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
|
||||
}
|
||||
if opts.CommitSHA != "" {
|
||||
cond = cond.And(builder.Eq{"commit_sha": opts.CommitSHA})
|
||||
}
|
||||
if opts.Status > StatusUnknown {
|
||||
cond = cond.And(builder.Eq{"status": opts.Status})
|
||||
}
|
||||
if opts.UpdatedBefore > 0 {
|
||||
cond = cond.And(builder.Lt{"updated": opts.UpdatedBefore})
|
||||
}
|
||||
if opts.StartedBefore > 0 {
|
||||
cond = cond.And(builder.Lt{"started": opts.StartedBefore})
|
||||
}
|
||||
if opts.RunnerID > 0 {
|
||||
cond = cond.And(builder.Eq{"runner_id": opts.RunnerID})
|
||||
}
|
||||
return cond
|
||||
}
|
||||
|
||||
func FindTasks(ctx context.Context, opts FindTaskOptions) (TaskList, int64, error) {
|
||||
e := db.GetEngine(ctx).Where(opts.toConds())
|
||||
if opts.PageSize > 0 && opts.Page >= 1 {
|
||||
e.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize)
|
||||
}
|
||||
if opts.IDOrderDesc {
|
||||
e.OrderBy("id DESC")
|
||||
}
|
||||
var tasks TaskList
|
||||
total, err := e.FindAndCount(&tasks)
|
||||
return tasks, total, err
|
||||
}
|
||||
|
||||
func CountTasks(ctx context.Context, opts FindTaskOptions) (int64, error) {
|
||||
return db.GetEngine(ctx).Where(opts.toConds()).Count(new(BotTask))
|
||||
}
|
||||
49
models/actions/task_step.go
Normal file
49
models/actions/task_step.go
Normal file
@@ -0,0 +1,49 @@
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package actions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
)
|
||||
|
||||
// BotTaskStep represents a step of BotTask
|
||||
type BotTaskStep struct {
|
||||
ID int64
|
||||
Name string
|
||||
TaskID int64 `xorm:"index unique(task_number)"`
|
||||
Number int64 `xorm:"index unique(task_number)"`
|
||||
RepoID int64 `xorm:"index"`
|
||||
Status Status `xorm:"index"`
|
||||
LogIndex int64
|
||||
LogLength int64
|
||||
Started timeutil.TimeStamp
|
||||
Stopped timeutil.TimeStamp
|
||||
Created timeutil.TimeStamp `xorm:"created"`
|
||||
Updated timeutil.TimeStamp `xorm:"updated"`
|
||||
}
|
||||
|
||||
func (step *BotTaskStep) TakeTime() time.Duration {
|
||||
if step.Started == 0 {
|
||||
return 0
|
||||
}
|
||||
started := step.Started.AsTime()
|
||||
if step.Status.IsDone() {
|
||||
return step.Stopped.AsTime().Sub(started)
|
||||
}
|
||||
step.Stopped.AsTime().Sub(started)
|
||||
return time.Since(started).Truncate(time.Second)
|
||||
}
|
||||
|
||||
func init() {
|
||||
db.RegisterModel(new(BotTaskStep))
|
||||
}
|
||||
|
||||
func GetTaskStepsByTaskID(ctx context.Context, taskID int64) ([]*BotTaskStep, error) {
|
||||
var steps []*BotTaskStep
|
||||
return steps, db.GetEngine(ctx).Where("task_id=?", taskID).OrderBy("number").Find(&steps)
|
||||
}
|
||||
33
models/actions/task_test.go
Normal file
33
models/actions/task_test.go
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package actions
|
||||
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestLogIndexes_ToDB(t *testing.T) {
|
||||
tests := []struct {
|
||||
indexes LogIndexes
|
||||
}{
|
||||
{
|
||||
indexes: []int64{1, 2, 0, -1, -2, math.MaxInt64, math.MinInt64},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
got, err := tt.indexes.ToDB()
|
||||
require.NoError(t, err)
|
||||
|
||||
indexes := LogIndexes{}
|
||||
require.NoError(t, indexes.FromDB(got))
|
||||
|
||||
assert.Equal(t, tt.indexes, indexes)
|
||||
})
|
||||
}
|
||||
}
|
||||
25
models/actions/utils.go
Normal file
25
models/actions/utils.go
Normal file
@@ -0,0 +1,25 @@
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package actions
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
func generateSaltedToken() (string, string, string, string, error) {
|
||||
salt, err := util.CryptoRandomString(10)
|
||||
if err != nil {
|
||||
return "", "", "", "", err
|
||||
}
|
||||
buf, err := util.CryptoRandomBytes(20)
|
||||
if err != nil {
|
||||
return "", "", "", "", err
|
||||
}
|
||||
token := hex.EncodeToString(buf)
|
||||
hash := auth_model.HashToken(token, salt)
|
||||
return token, salt, hash, token[:8], nil
|
||||
}
|
||||
Reference in New Issue
Block a user