mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2026-05-12 22:10:25 +00:00
github.com/robfig/cron is used for parsing cron schedules of scheduled Forgejo Actions workflows. It has not seen an update in roughly six years and looks abandoned. There are multiple code paths that trigger panics instead of errors. It is replaced by github.com/gdgvda/cron, which is one of the few maintained forks. github.com/gdgvda/cron was picked because its behaviour is fully backwards-compatible and the developers are responsive. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12365 Reviewed-by: limiting-factor <limiting-factor@noreply.codeberg.org> Reviewed-by: Gusted <gusted@noreply.codeberg.org>
96 lines
2.5 KiB
Go
96 lines
2.5 KiB
Go
// Copyright 2023 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package actions
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"time"
|
|
|
|
"forgejo.org/models/db"
|
|
repo_model "forgejo.org/models/repo"
|
|
"forgejo.org/modules/optional"
|
|
"forgejo.org/modules/timeutil"
|
|
|
|
"github.com/gdgvda/cron"
|
|
)
|
|
|
|
// 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
|
|
TimeZone optional.Option[string]
|
|
|
|
Created timeutil.TimeStamp `xorm:"created"`
|
|
Updated timeutil.TimeStamp `xorm:"updated"`
|
|
}
|
|
|
|
func NewActionScheduleSpec(cron string, tz optional.Option[string], referenceTime time.Time) (*ActionScheduleSpec, error) {
|
|
spec := &ActionScheduleSpec{
|
|
Spec: cron,
|
|
TimeZone: tz,
|
|
}
|
|
cronSchedule, err := spec.Parse()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
spec.Next = timeutil.TimeStamp(cronSchedule.Next(referenceTime).Unix())
|
|
return spec, nil
|
|
}
|
|
|
|
// Parse parses the spec and returns a cron.Schedule
|
|
// Unlike the default cron parser, Parse uses UTC timezone as the default if none is specified.
|
|
func (s *ActionScheduleSpec) Parse() (cron.Schedule, error) {
|
|
parser, err := cron.NewDefaultParser(cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
schedule, err := parser.Parse(s.Spec)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// If `timezone` is not defined in the workflow, but the spec includes a timezone, use it.
|
|
if !s.TimeZone.Has() && (strings.HasPrefix(s.Spec, "TZ=") || strings.HasPrefix(s.Spec, "CRON_TZ=")) {
|
|
return schedule, nil
|
|
}
|
|
|
|
var location *time.Location
|
|
if present, tz := s.TimeZone.Get(); present {
|
|
location, err = time.LoadLocation(tz)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
} else {
|
|
// UTC is the default time zone.
|
|
location = time.UTC
|
|
}
|
|
|
|
return schedule.WithLocation(location), nil
|
|
}
|
|
|
|
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
|
|
}
|