mirror of
https://github.com/aptible/supercronic.git
synced 2025-09-27 04:36:23 +08:00
139 lines
2.7 KiB
Go
139 lines
2.7 KiB
Go
package crontab
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"io"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/aptible/supercronic/cronexpr"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
var (
|
|
jobLineSeparator = regexp.MustCompile(`\S+`)
|
|
envLineMatcher = regexp.MustCompile(`^([^\s=]+)\s*=\s*(.*)$`)
|
|
|
|
parameterCounts = []int{
|
|
7, // POSIX + seconds + years
|
|
6, // POSIX + years
|
|
5, // POSIX
|
|
1, // shorthand (e.g. @hourly)
|
|
}
|
|
)
|
|
|
|
func parseJobLine(line string) (*CrontabLine, error) {
|
|
indices := jobLineSeparator.FindAllStringIndex(line, -1)
|
|
|
|
for _, count := range parameterCounts {
|
|
if len(indices) <= count {
|
|
continue
|
|
}
|
|
|
|
scheduleEnds := indices[count-1][1]
|
|
commandStarts := indices[count][0]
|
|
|
|
toParse := line[:scheduleEnds]
|
|
|
|
// TODO: Should receive a logger?
|
|
logrus.Debugf("try parse (%d fields): '%s'", count, toParse)
|
|
|
|
expr, err := cronexpr.ParseStrict(toParse)
|
|
|
|
if err != nil {
|
|
logrus.Debugf("failed to parse (%d fields): '%s': failed: %v", count, toParse, err)
|
|
continue
|
|
}
|
|
|
|
return &CrontabLine{
|
|
Expression: expr,
|
|
Schedule: line[:scheduleEnds],
|
|
Command: line[commandStarts:],
|
|
}, nil
|
|
}
|
|
|
|
return nil, fmt.Errorf("bad crontab line: '%s' (use -debug for details)", line)
|
|
}
|
|
|
|
func ParseCrontab(reader io.Reader) (*Crontab, error) {
|
|
scanner := bufio.NewScanner(reader)
|
|
|
|
position := 0
|
|
|
|
jobs := make([]*Job, 0)
|
|
|
|
environ := make(map[string]string)
|
|
shell := "/bin/sh"
|
|
tz := time.Local
|
|
|
|
for scanner.Scan() {
|
|
line := strings.TrimLeft(scanner.Text(), " \t")
|
|
|
|
if line == "" {
|
|
continue
|
|
}
|
|
|
|
if line[0] == '#' {
|
|
continue
|
|
}
|
|
|
|
r := envLineMatcher.FindAllStringSubmatch(line, -1)
|
|
if len(r) == 1 && len(r[0]) == 3 {
|
|
envKey := r[0][1]
|
|
envVal := r[0][2]
|
|
|
|
// Remove quotes (this emulates what Vixie cron does)
|
|
if envVal[0] == '"' || envVal[0] == '\'' {
|
|
if len(envVal) > 1 && envVal[0] == envVal[len(envVal)-1] {
|
|
envVal = envVal[1 : len(envVal)-1]
|
|
}
|
|
}
|
|
|
|
if envKey == "SHELL" {
|
|
logrus.Infof("processes will be spawned using shell: %s", envVal)
|
|
shell = envVal
|
|
}
|
|
|
|
if envKey == "USER" {
|
|
logrus.Warnf("processes will NOT be spawned as USER=%s", envVal)
|
|
}
|
|
|
|
if envKey == "CRON_TZ" {
|
|
var err error
|
|
tz, err = time.LoadLocation(envVal)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
logrus.Infof("processes will be spawned using TZ: %v", tz)
|
|
}
|
|
|
|
environ[envKey] = envVal
|
|
|
|
continue
|
|
}
|
|
|
|
jobLine, err := parseJobLine(line)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
jobs = append(jobs, &Job{CrontabLine: *jobLine, Position: position})
|
|
position++
|
|
}
|
|
|
|
if err := scanner.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &Crontab{
|
|
Jobs: jobs,
|
|
Context: &Context{
|
|
Shell: shell,
|
|
Environ: environ,
|
|
Timezone: tz,
|
|
},
|
|
}, nil
|
|
}
|