Files
supercronic/crontab/crontab.go
Thomas Orozco 9155ef01d7 Merge pull request #79 from krallin/incorporate-cronexpr
Vendor cronexpr in-tree
2020-11-24 09:00:15 +00:00

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
}