forked from go-gitea/gitea
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge remote-tracking branch 'giteaofficial/main'
* giteaofficial/main: Fix review bar misalignment (go-gitea#26711) Use "small-loading-icon" insead of "btn-octicon is-loading" (go-gitea#26710) Improve Image Diff UI (go-gitea#26696) Make issue template field template access correct template data (go-gitea#26698) add Upload URL to release API (go-gitea#26663) Add merge files files to GetCommitFileStatus (go-gitea#20515) PATCH branch-protection updates check list even when checks are disabled (go-gitea#26351) Add `member`, `collaborator`, `contributor`, and `first-time contributor` roles and tooltips (go-gitea#26658) chore(actions): support cron schedule task (go-gitea#26655)
- Loading branch information
Showing
55 changed files
with
956 additions
and
109 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
// Copyright 2023 The Gitea Authors. All rights reserved. | ||
// SPDX-License-Identifier: MIT | ||
|
||
package actions | ||
|
||
import ( | ||
"context" | ||
"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" | ||
webhook_module "code.gitea.io/gitea/modules/webhook" | ||
|
||
"github.com/robfig/cron/v3" | ||
) | ||
|
||
// ActionSchedule represents a schedule of a workflow file | ||
type ActionSchedule struct { | ||
ID int64 | ||
Title string | ||
Specs []string | ||
RepoID int64 `xorm:"index"` | ||
Repo *repo_model.Repository `xorm:"-"` | ||
OwnerID int64 `xorm:"index"` | ||
WorkflowID string | ||
TriggerUserID int64 | ||
TriggerUser *user_model.User `xorm:"-"` | ||
Ref string | ||
CommitSHA string | ||
Event webhook_module.HookEventType | ||
EventPayload string `xorm:"LONGTEXT"` | ||
Content []byte | ||
Created timeutil.TimeStamp `xorm:"created"` | ||
Updated timeutil.TimeStamp `xorm:"updated"` | ||
} | ||
|
||
func init() { | ||
db.RegisterModel(new(ActionSchedule)) | ||
} | ||
|
||
// GetSchedulesMapByIDs returns the schedules by given id slice. | ||
func GetSchedulesMapByIDs(ids []int64) (map[int64]*ActionSchedule, error) { | ||
schedules := make(map[int64]*ActionSchedule, len(ids)) | ||
return schedules, db.GetEngine(db.DefaultContext).In("id", ids).Find(&schedules) | ||
} | ||
|
||
// GetReposMapByIDs returns the repos by given id slice. | ||
func GetReposMapByIDs(ids []int64) (map[int64]*repo_model.Repository, error) { | ||
repos := make(map[int64]*repo_model.Repository, len(ids)) | ||
return repos, db.GetEngine(db.DefaultContext).In("id", ids).Find(&repos) | ||
} | ||
|
||
var cronParser = cron.NewParser(cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor) | ||
|
||
// CreateScheduleTask creates new schedule task. | ||
func CreateScheduleTask(ctx context.Context, rows []*ActionSchedule) error { | ||
// Return early if there are no rows to insert | ||
if len(rows) == 0 { | ||
return nil | ||
} | ||
|
||
// Begin transaction | ||
ctx, committer, err := db.TxContext(ctx) | ||
if err != nil { | ||
return err | ||
} | ||
defer committer.Close() | ||
|
||
// Loop through each schedule row | ||
for _, row := range rows { | ||
// Create new schedule row | ||
if err = db.Insert(ctx, row); err != nil { | ||
return err | ||
} | ||
|
||
// Loop through each schedule spec and create a new spec row | ||
now := time.Now() | ||
|
||
for _, spec := range row.Specs { | ||
// Parse the spec and check for errors | ||
schedule, err := cronParser.Parse(spec) | ||
if err != nil { | ||
continue // skip to the next spec if there's an error | ||
} | ||
|
||
// Insert the new schedule spec row | ||
if err = db.Insert(ctx, &ActionScheduleSpec{ | ||
RepoID: row.RepoID, | ||
ScheduleID: row.ID, | ||
Spec: spec, | ||
Next: timeutil.TimeStamp(schedule.Next(now).Unix()), | ||
}); err != nil { | ||
return err | ||
} | ||
} | ||
} | ||
|
||
// Commit transaction | ||
return committer.Commit() | ||
} | ||
|
||
func DeleteScheduleTaskByRepo(ctx context.Context, id int64) error { | ||
ctx, committer, err := db.TxContext(ctx) | ||
if err != nil { | ||
return err | ||
} | ||
defer committer.Close() | ||
|
||
if _, err := db.GetEngine(ctx).Delete(&ActionSchedule{RepoID: id}); err != nil { | ||
return err | ||
} | ||
|
||
if _, err := db.GetEngine(ctx).Delete(&ActionScheduleSpec{RepoID: id}); err != nil { | ||
return err | ||
} | ||
|
||
return committer.Commit() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
// Copyright 2023 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" | ||
|
||
"xorm.io/builder" | ||
) | ||
|
||
type ScheduleList []*ActionSchedule | ||
|
||
// GetUserIDs returns a slice of user's id | ||
func (schedules ScheduleList) GetUserIDs() []int64 { | ||
ids := make(container.Set[int64], len(schedules)) | ||
for _, schedule := range schedules { | ||
ids.Add(schedule.TriggerUserID) | ||
} | ||
return ids.Values() | ||
} | ||
|
||
func (schedules ScheduleList) GetRepoIDs() []int64 { | ||
ids := make(container.Set[int64], len(schedules)) | ||
for _, schedule := range schedules { | ||
ids.Add(schedule.RepoID) | ||
} | ||
return ids.Values() | ||
} | ||
|
||
func (schedules ScheduleList) LoadTriggerUser(ctx context.Context) error { | ||
userIDs := schedules.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 _, schedule := range schedules { | ||
if schedule.TriggerUserID == user_model.ActionsUserID { | ||
schedule.TriggerUser = user_model.NewActionsUser() | ||
} else { | ||
schedule.TriggerUser = users[schedule.TriggerUserID] | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
func (schedules ScheduleList) LoadRepos() error { | ||
repoIDs := schedules.GetRepoIDs() | ||
repos, err := repo_model.GetRepositoriesMapByIDs(repoIDs) | ||
if err != nil { | ||
return err | ||
} | ||
for _, schedule := range schedules { | ||
schedule.Repo = repos[schedule.RepoID] | ||
} | ||
return nil | ||
} | ||
|
||
type FindScheduleOptions struct { | ||
db.ListOptions | ||
RepoID int64 | ||
OwnerID int64 | ||
} | ||
|
||
func (opts FindScheduleOptions) 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}) | ||
} | ||
|
||
return cond | ||
} | ||
|
||
func FindSchedules(ctx context.Context, opts FindScheduleOptions) (ScheduleList, int64, error) { | ||
e := db.GetEngine(ctx).Where(opts.toConds()) | ||
if !opts.ListAll && opts.PageSize > 0 && opts.Page >= 1 { | ||
e.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize) | ||
} | ||
var schedules ScheduleList | ||
total, err := e.Desc("id").FindAndCount(&schedules) | ||
return schedules, total, err | ||
} | ||
|
||
func CountSchedules(ctx context.Context, opts FindScheduleOptions) (int64, error) { | ||
return db.GetEngine(ctx).Where(opts.toConds()).Count(new(ActionSchedule)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
// Copyright 2023 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" | ||
"code.gitea.io/gitea/modules/timeutil" | ||
|
||
"github.com/robfig/cron/v3" | ||
) | ||
|
||
// ActionScheduleSpec represents a schedule spec of a workflow file | ||
type ActionScheduleSpec struct { | ||
ID int64 | ||
RepoID int64 `xorm:"index"` | ||
Repo *repo_model.Repository `xorm:"-"` | ||
ScheduleID int64 `xorm:"index"` | ||
Schedule *ActionSchedule `xorm:"-"` | ||
|
||
// Next time the job will run, or the zero time if Cron has not been | ||
// started or this entry's schedule is unsatisfiable | ||
Next timeutil.TimeStamp `xorm:"index"` | ||
// Prev is the last time this job was run, or the zero time if never. | ||
Prev timeutil.TimeStamp | ||
Spec string | ||
|
||
Created timeutil.TimeStamp `xorm:"created"` | ||
Updated timeutil.TimeStamp `xorm:"updated"` | ||
} | ||
|
||
func (s *ActionScheduleSpec) Parse() (cron.Schedule, error) { | ||
return cronParser.Parse(s.Spec) | ||
} | ||
|
||
func init() { | ||
db.RegisterModel(new(ActionScheduleSpec)) | ||
} | ||
|
||
func UpdateScheduleSpec(ctx context.Context, spec *ActionScheduleSpec, cols ...string) error { | ||
sess := db.GetEngine(ctx).ID(spec.ID) | ||
if len(cols) > 0 { | ||
sess.Cols(cols...) | ||
} | ||
_, err := sess.Update(spec) | ||
return err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
// Copyright 2023 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" | ||
"code.gitea.io/gitea/modules/container" | ||
|
||
"xorm.io/builder" | ||
) | ||
|
||
type SpecList []*ActionScheduleSpec | ||
|
||
func (specs SpecList) GetScheduleIDs() []int64 { | ||
ids := make(container.Set[int64], len(specs)) | ||
for _, spec := range specs { | ||
ids.Add(spec.ScheduleID) | ||
} | ||
return ids.Values() | ||
} | ||
|
||
func (specs SpecList) LoadSchedules() error { | ||
scheduleIDs := specs.GetScheduleIDs() | ||
schedules, err := GetSchedulesMapByIDs(scheduleIDs) | ||
if err != nil { | ||
return err | ||
} | ||
for _, spec := range specs { | ||
spec.Schedule = schedules[spec.ScheduleID] | ||
} | ||
|
||
repoIDs := specs.GetRepoIDs() | ||
repos, err := GetReposMapByIDs(repoIDs) | ||
if err != nil { | ||
return err | ||
} | ||
for _, spec := range specs { | ||
spec.Repo = repos[spec.RepoID] | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (specs SpecList) GetRepoIDs() []int64 { | ||
ids := make(container.Set[int64], len(specs)) | ||
for _, spec := range specs { | ||
ids.Add(spec.RepoID) | ||
} | ||
return ids.Values() | ||
} | ||
|
||
func (specs SpecList) LoadRepos() error { | ||
repoIDs := specs.GetRepoIDs() | ||
repos, err := repo_model.GetRepositoriesMapByIDs(repoIDs) | ||
if err != nil { | ||
return err | ||
} | ||
for _, spec := range specs { | ||
spec.Repo = repos[spec.RepoID] | ||
} | ||
return nil | ||
} | ||
|
||
type FindSpecOptions struct { | ||
db.ListOptions | ||
RepoID int64 | ||
Next int64 | ||
} | ||
|
||
func (opts FindSpecOptions) toConds() builder.Cond { | ||
cond := builder.NewCond() | ||
if opts.RepoID > 0 { | ||
cond = cond.And(builder.Eq{"repo_id": opts.RepoID}) | ||
} | ||
|
||
if opts.Next > 0 { | ||
cond = cond.And(builder.Lte{"next": opts.Next}) | ||
} | ||
|
||
return cond | ||
} | ||
|
||
func FindSpecs(ctx context.Context, opts FindSpecOptions) (SpecList, 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 specs SpecList | ||
total, err := e.Desc("id").FindAndCount(&specs) | ||
if err != nil { | ||
return nil, 0, err | ||
} | ||
|
||
if err := specs.LoadSchedules(); err != nil { | ||
return nil, 0, err | ||
} | ||
return specs, total, nil | ||
} | ||
|
||
func CountSpecs(ctx context.Context, opts FindSpecOptions) (int64, error) { | ||
return db.GetEngine(ctx).Where(opts.toConds()).Count(new(ActionScheduleSpec)) | ||
} |
Oops, something went wrong.