Add cron schedules and sync docs
This commit is contained in:
@@ -11,6 +11,7 @@ import (
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/robfig/cron/v3"
|
||||
)
|
||||
|
||||
var idPattern = regexp.MustCompile(`^[A-Za-z0-9._:-]+$`)
|
||||
@@ -58,6 +59,7 @@ type CheckConfig struct {
|
||||
Name string `toml:"name"`
|
||||
Command string `toml:"command"`
|
||||
Interval Duration `toml:"interval"`
|
||||
Cron string `toml:"cron"`
|
||||
Timeout Duration `toml:"timeout"`
|
||||
NotificationsEnabled *bool `toml:"notifications_enabled"`
|
||||
DedupeKey string `toml:"dedupe_key"`
|
||||
@@ -211,14 +213,24 @@ func (c *Config) Validate() error {
|
||||
if strings.TrimSpace(ch.Command) == "" {
|
||||
return fmt.Errorf("check %q: command is required", ch.ID)
|
||||
}
|
||||
if ch.Interval.Duration <= 0 {
|
||||
return fmt.Errorf("check %q: interval must be > 0", ch.ID)
|
||||
}
|
||||
if ch.Timeout.Duration <= 0 {
|
||||
return fmt.Errorf("check %q: timeout must be > 0", ch.ID)
|
||||
}
|
||||
if ch.Timeout.Duration > ch.Interval.Duration {
|
||||
if ch.Interval.Duration < 0 {
|
||||
return fmt.Errorf("check %q: interval must be > 0", ch.ID)
|
||||
}
|
||||
ch.Cron = strings.TrimSpace(ch.Cron)
|
||||
hasInterval := ch.Interval.Duration > 0
|
||||
hasCron := ch.Cron != ""
|
||||
switch {
|
||||
case hasInterval == hasCron:
|
||||
return fmt.Errorf("check %q: exactly one of interval or cron is required", ch.ID)
|
||||
case hasInterval && ch.Timeout.Duration > ch.Interval.Duration:
|
||||
return fmt.Errorf("check %q: timeout must be <= interval", ch.ID)
|
||||
case hasCron:
|
||||
if _, err := ParseCronSchedule(ch.Cron); err != nil {
|
||||
return fmt.Errorf("check %q: invalid cron: %w", ch.ID, err)
|
||||
}
|
||||
}
|
||||
if len(ch.DedupeKey) > 256 {
|
||||
return fmt.Errorf("check %q: dedupe_key too long", ch.ID)
|
||||
@@ -253,6 +265,42 @@ func (c CheckConfig) NotificationsOn() bool {
|
||||
return c.NotificationsEnabled == nil || *c.NotificationsEnabled
|
||||
}
|
||||
|
||||
func (c CheckConfig) UsesInterval() bool {
|
||||
return c.Interval.Duration > 0
|
||||
}
|
||||
|
||||
func (c CheckConfig) UsesCron() bool {
|
||||
return strings.TrimSpace(c.Cron) != ""
|
||||
}
|
||||
|
||||
func ParseCronSchedule(spec string) (cron.Schedule, error) {
|
||||
spec = strings.TrimSpace(spec)
|
||||
if spec == "" {
|
||||
return nil, fmt.Errorf("cron is empty")
|
||||
}
|
||||
withoutTZ := specWithoutTZ(spec)
|
||||
lower := strings.ToLower(withoutTZ)
|
||||
if strings.HasPrefix(lower, "@every") {
|
||||
return nil, fmt.Errorf("@every is not supported; use interval")
|
||||
}
|
||||
if lower == "@reboot" {
|
||||
return nil, fmt.Errorf("@reboot is not supported")
|
||||
}
|
||||
return cron.ParseStandard(spec)
|
||||
}
|
||||
|
||||
func specWithoutTZ(spec string) string {
|
||||
fields := strings.Fields(spec)
|
||||
if len(fields) == 0 {
|
||||
return ""
|
||||
}
|
||||
first := fields[0]
|
||||
if strings.HasPrefix(first, "CRON_TZ=") || strings.HasPrefix(first, "TZ=") {
|
||||
return strings.ToLower(strings.Join(fields[1:], " "))
|
||||
}
|
||||
return strings.ToLower(strings.Join(fields, " "))
|
||||
}
|
||||
|
||||
func validateResourceLimits(checkID string, limits ResourceLimits) error {
|
||||
if limits.CPUTime.Duration < 0 {
|
||||
return fmt.Errorf("check %q: resource_limits.cpu_time must be > 0", checkID)
|
||||
|
||||
Reference in New Issue
Block a user