mirror of https://github.com/coder/coder.git
feat: add crontab package for supporting autostart/stop. (#844)
* feat: add crontab package for supporting autostart/stop. This is basically a small wrapper around robfig/cron/v3. Fixes #817. * fixup! feat: add crontab package for supporting autostart/stop. This is basically a small wrapper around robfig/cron/v3. * fixup! feat: add crontab package for supporting autostart/stop. This is basically a small wrapper around robfig/cron/v3. * fixup! fixup! feat: add crontab package for supporting autostart/stop. This is basically a small wrapper around robfig/cron/v3. * fix: return struct instead of interface * remove unnecessary interface and export struct * fix: doc comments * rename package to autostart/schedule * address PR comments
This commit is contained in:
parent
b621c59a03
commit
8a1ae18ede
|
@ -0,0 +1,67 @@
|
|||
package schedule_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/coderd/autostart/schedule"
|
||||
)
|
||||
|
||||
func Test_Weekly(t *testing.T) {
|
||||
t.Parallel()
|
||||
testCases := []struct {
|
||||
name string
|
||||
spec string
|
||||
at time.Time
|
||||
expectedNext time.Time
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "with timezone",
|
||||
spec: "CRON_TZ=US/Central 30 9 1-5",
|
||||
at: time.Date(2022, 4, 1, 14, 29, 0, 0, time.UTC),
|
||||
expectedNext: time.Date(2022, 4, 1, 14, 30, 0, 0, time.UTC),
|
||||
expectedError: "",
|
||||
},
|
||||
{
|
||||
name: "without timezone",
|
||||
spec: "30 9 1-5",
|
||||
at: time.Date(2022, 4, 1, 9, 29, 0, 0, time.Local),
|
||||
expectedNext: time.Date(2022, 4, 1, 9, 30, 0, 0, time.Local),
|
||||
expectedError: "",
|
||||
},
|
||||
{
|
||||
name: "invalid schedule",
|
||||
spec: "asdfasdfasdfsd",
|
||||
at: time.Time{},
|
||||
expectedNext: time.Time{},
|
||||
expectedError: "parse schedule: expected exactly 3 fields, found 1: [asdfasdfasdfsd]",
|
||||
},
|
||||
{
|
||||
name: "invalid location",
|
||||
spec: "CRON_TZ=Fictional/Country 30 9 1-5",
|
||||
at: time.Time{},
|
||||
expectedNext: time.Time{},
|
||||
expectedError: "parse schedule: provided bad location Fictional/Country: unknown time zone Fictional/Country",
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
actual, err := schedule.Weekly(testCase.spec)
|
||||
if testCase.expectedError == "" {
|
||||
nextTime := actual.Next(testCase.at)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, testCase.expectedNext, nextTime)
|
||||
require.Equal(t, testCase.spec, actual.String())
|
||||
} else {
|
||||
require.EqualError(t, err, testCase.expectedError)
|
||||
require.Nil(t, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
// package schedule provides utilities for parsing and deserializing
|
||||
// cron-style expressions.
|
||||
package schedule
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/robfig/cron/v3"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// For the purposes of this library, we only need minute, hour, and
|
||||
// day-of-week.
|
||||
const parserFormatWeekly = cron.Minute | cron.Hour | cron.Dow
|
||||
|
||||
var defaultParser = cron.NewParser(parserFormatWeekly)
|
||||
|
||||
// Weekly parses a Schedule from spec scoped to a recurring weekly event.
|
||||
// Spec consists of the following space-delimited fields, in the following order:
|
||||
// - timezone e.g. CRON_TZ=US/Central (optional)
|
||||
// - minutes of hour e.g. 30 (required)
|
||||
// - hour of day e.g. 9 (required)
|
||||
// - day of week e.g. 1 (required)
|
||||
//
|
||||
// Example Usage:
|
||||
// local_sched, _ := schedule.Weekly("59 23 *")
|
||||
// fmt.Println(sched.Next(time.Now().Format(time.RFC3339)))
|
||||
// // Output: 2022-04-04T23:59:00Z
|
||||
// us_sched, _ := schedule.Weekly("CRON_TZ=US/Central 30 9 1-5")
|
||||
// fmt.Println(sched.Next(time.Now()).Format(time.RFC3339))
|
||||
// // Output: 2022-04-04T14:30:00Z
|
||||
func Weekly(spec string) (*Schedule, error) {
|
||||
specSched, err := defaultParser.Parse(spec)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("parse schedule: %w", err)
|
||||
}
|
||||
|
||||
schedule, ok := specSched.(*cron.SpecSchedule)
|
||||
if !ok {
|
||||
return nil, xerrors.Errorf("expected *cron.SpecSchedule but got %T", specSched)
|
||||
}
|
||||
|
||||
cronSched := &Schedule{
|
||||
sched: schedule,
|
||||
spec: spec,
|
||||
}
|
||||
return cronSched, nil
|
||||
}
|
||||
|
||||
// Schedule represents a cron schedule.
|
||||
// It's essentially a thin wrapper for robfig/cron/v3 that implements Stringer.
|
||||
type Schedule struct {
|
||||
sched *cron.SpecSchedule
|
||||
// XXX: there isn't any nice way for robfig/cron to serialize
|
||||
spec string
|
||||
}
|
||||
|
||||
// String serializes the schedule to its original human-friendly format.
|
||||
func (s Schedule) String() string {
|
||||
return s.spec
|
||||
}
|
||||
|
||||
// Next returns the next time in the schedule relative to t.
|
||||
func (s Schedule) Next(t time.Time) time.Time {
|
||||
return s.sched.Next(t)
|
||||
}
|
1
go.mod
1
go.mod
|
@ -222,6 +222,7 @@ require (
|
|||
github.com/prometheus/procfs v0.7.3 // indirect
|
||||
github.com/rivo/tview v0.0.0-20200712113419-c65badfc3d92 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/sirupsen/logrus v1.8.1 // indirect
|
||||
github.com/spf13/afero v1.8.1 // indirect
|
||||
|
|
2
go.sum
2
go.sum
|
@ -1501,6 +1501,8 @@ github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qq
|
|||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
|
|
Loading…
Reference in New Issue