wip: config
This commit is contained in:
parent
eb7bde3cbe
commit
bd4ba67ad2
@ -19,10 +19,9 @@ const (
|
|||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
servicePaths []string
|
servicePaths []string
|
||||||
|
cron []*cron
|
||||||
}
|
}
|
||||||
|
|
||||||
type cron struct{}
|
|
||||||
|
|
||||||
func Read() (*Config, error) {
|
func Read() (*Config, error) {
|
||||||
viper.SetEnvPrefix(EnvPrefix)
|
viper.SetEnvPrefix(EnvPrefix)
|
||||||
viper.BindEnv(EnvConfigPath)
|
viper.BindEnv(EnvConfigPath)
|
||||||
@ -34,7 +33,7 @@ func Read() (*Config, error) {
|
|||||||
svcdir string
|
svcdir string
|
||||||
serviceAvailable bool
|
serviceAvailable bool
|
||||||
cronAvailable bool
|
cronAvailable bool
|
||||||
cron []cron
|
cron []*cron
|
||||||
crontabfile string
|
crontabfile string
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -61,6 +60,7 @@ func Read() (*Config, error) {
|
|||||||
crontabfile = filepath.Join(configPath, CrontabFileName)
|
crontabfile = filepath.Join(configPath, CrontabFileName)
|
||||||
cron, err = readCrontab(crontabfile)
|
cron, err = readCrontab(crontabfile)
|
||||||
if len(cron) > 0 {
|
if len(cron) > 0 {
|
||||||
|
outConfig.cron = cron
|
||||||
cronAvailable = true
|
cronAvailable = true
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@ -1,5 +1,311 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
func readCrontab(path string) ([]cron, error) {
|
import (
|
||||||
return nil, nil
|
"bufio"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gitea.suyono.dev/suyono/wingmate"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CronExactSpec interface {
|
||||||
|
CronTimeSpec
|
||||||
|
Value() uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
type CronMultipleOccurrenceSpec interface {
|
||||||
|
CronTimeSpec
|
||||||
|
Values() []uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
type CronTimeSpec interface {
|
||||||
|
Type() wingmate.CronTimeType
|
||||||
|
Match(uint8) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// type Cron interface {
|
||||||
|
// Minute() CronTimeSpec
|
||||||
|
// Hour() CronTimeSpec
|
||||||
|
// DayOfMonth() CronTimeSpec
|
||||||
|
// Month() CronTimeSpec
|
||||||
|
// DayOfWeek() CronTimeSpec
|
||||||
|
// }
|
||||||
|
|
||||||
|
type cron struct {
|
||||||
|
minute CronTimeSpec
|
||||||
|
hour CronTimeSpec
|
||||||
|
dom CronTimeSpec
|
||||||
|
month CronTimeSpec
|
||||||
|
dow CronTimeSpec
|
||||||
|
command string
|
||||||
|
lastRun time.Time
|
||||||
|
hasRun bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type cronField int
|
||||||
|
|
||||||
|
const (
|
||||||
|
CrontabEntryRegex = `^\s*(?P<minute>\S+)\s+(?P<hour>\S+)\s+(?P<dom>\S+)\s+(?P<month>\S+)\s+(?P<dow>\S+)\s+(?P<command>\S.*\S)\s*$`
|
||||||
|
CrontabSubmatchLen = 7
|
||||||
|
|
||||||
|
minute cronField = iota
|
||||||
|
hour
|
||||||
|
dom
|
||||||
|
month
|
||||||
|
dow
|
||||||
|
)
|
||||||
|
|
||||||
|
func readCrontab(path string) ([]*cron, error) {
|
||||||
|
var (
|
||||||
|
file *os.File
|
||||||
|
err error
|
||||||
|
scanner *bufio.Scanner
|
||||||
|
line string
|
||||||
|
re *regexp.Regexp
|
||||||
|
parts []string
|
||||||
|
retval []*cron
|
||||||
|
)
|
||||||
|
|
||||||
|
if re, err = regexp.Compile(CrontabEntryRegex); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if file, err = os.Open(path); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = file.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
retval = make([]*cron, 0)
|
||||||
|
scanner = bufio.NewScanner(file)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line = scanner.Text()
|
||||||
|
|
||||||
|
parts = re.FindStringSubmatch(line)
|
||||||
|
if len(parts) != CrontabSubmatchLen {
|
||||||
|
wingmate.Log().Error().Msgf("invalid entry %s", line)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
c := &cron{
|
||||||
|
hasRun: false,
|
||||||
|
}
|
||||||
|
if err = c.setField(minute, parts[1]); err != nil {
|
||||||
|
wingmate.Log().Error().Msgf("error parsing minute field %#v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = c.setField(hour, parts[2]); err != nil {
|
||||||
|
wingmate.Log().Error().Msgf("error parsing hour field %#v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = c.setField(dom, parts[3]); err != nil {
|
||||||
|
wingmate.Log().Error().Msgf("error parsing day of month field %#v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = c.setField(month, parts[4]); err != nil {
|
||||||
|
wingmate.Log().Error().Msgf("error parsing month field %#v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = c.setField(dow, parts[5]); err != nil {
|
||||||
|
wingmate.Log().Error().Msgf("error parsing day of week field %#v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
c.command = parts[6]
|
||||||
|
|
||||||
|
retval = append(retval, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
return retval, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cron) TimeToRun(now time.Time) bool {
|
||||||
|
if !c.hasRun {
|
||||||
|
c.lastRun = now
|
||||||
|
c.hasRun = true
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if now.Sub(c.lastRun) <= time.Minute && now.Minute() == c.lastRun.Minute() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.minute.Match(uint8(now.Minute())) &&
|
||||||
|
c.hour.Match(uint8(now.Hour())) &&
|
||||||
|
c.dom.Match(uint8(now.Day())) &&
|
||||||
|
c.month.Match(uint8(now.Month())) &&
|
||||||
|
c.dow.Match(uint8(now.Weekday())) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
type fieldRange struct {
|
||||||
|
min int
|
||||||
|
max int
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRange(min, max int) *fieldRange {
|
||||||
|
return &fieldRange{
|
||||||
|
min: min,
|
||||||
|
max: max,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fieldRange) valid(u uint8) bool {
|
||||||
|
i := int(u)
|
||||||
|
|
||||||
|
return i >= f.min && i <= f.max
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cron) setField(field cronField, input string) error {
|
||||||
|
var (
|
||||||
|
fr *fieldRange
|
||||||
|
cField *CronTimeSpec
|
||||||
|
err error
|
||||||
|
parsed64 uint64
|
||||||
|
parsed uint8
|
||||||
|
multi []uint8
|
||||||
|
current uint8
|
||||||
|
multiStr []string
|
||||||
|
)
|
||||||
|
switch field {
|
||||||
|
case minute:
|
||||||
|
fr = newRange(0, 59)
|
||||||
|
cField = &c.minute
|
||||||
|
case hour:
|
||||||
|
fr = newRange(0, 23)
|
||||||
|
cField = &c.hour
|
||||||
|
case dom:
|
||||||
|
fr = newRange(1, 31)
|
||||||
|
cField = &c.dom
|
||||||
|
case month:
|
||||||
|
fr = newRange(1, 12)
|
||||||
|
cField = &c.month
|
||||||
|
case dow:
|
||||||
|
fr = newRange(0, 6)
|
||||||
|
cField = &c.dow
|
||||||
|
default:
|
||||||
|
return errors.New("invalid cron field descriptor")
|
||||||
|
}
|
||||||
|
|
||||||
|
if input == "*" {
|
||||||
|
*cField = &specAny{}
|
||||||
|
} else if strings.HasPrefix(input, "*/") {
|
||||||
|
if parsed64, err = strconv.ParseUint(input[2:], 10, 8); err != nil {
|
||||||
|
return fmt.Errorf("error parse field %#v with input %s: %w", field, input, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed = uint8(parsed64)
|
||||||
|
if fr.valid(parsed) {
|
||||||
|
return fmt.Errorf("error parse field %#v with input %s: invalid value", field, input)
|
||||||
|
}
|
||||||
|
multi = make([]uint8, 0)
|
||||||
|
current = parsed
|
||||||
|
for fr.valid(current) {
|
||||||
|
multi = append(multi, current)
|
||||||
|
current += parsed
|
||||||
|
}
|
||||||
|
|
||||||
|
*cField = &specMultiOccurrence{
|
||||||
|
values: multi,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
multiStr = strings.Split(input, ",")
|
||||||
|
if len(multiStr) > 1 {
|
||||||
|
multi = make([]uint8, 0)
|
||||||
|
for _, s := range multiStr {
|
||||||
|
if parsed64, err = strconv.ParseUint(s, 10, 8); err != nil {
|
||||||
|
return fmt.Errorf("error parse field %#v with input %s: %w", field, input, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed = uint8(parsed64)
|
||||||
|
if fr.valid(parsed) {
|
||||||
|
return fmt.Errorf("error parse field %#v with input %s: invalid value", field, input)
|
||||||
|
}
|
||||||
|
|
||||||
|
multi = append(multi, parsed)
|
||||||
|
}
|
||||||
|
|
||||||
|
*cField = &specMultiOccurrence{
|
||||||
|
values: multi,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if parsed64, err = strconv.ParseUint(input, 10, 8); err != nil {
|
||||||
|
return fmt.Errorf("error parse field %#v with input %s: %w", field, input, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed = uint8(parsed64)
|
||||||
|
if fr.valid(parsed) {
|
||||||
|
return fmt.Errorf("error parse field %#v with input %s: invalid value", field, input)
|
||||||
|
}
|
||||||
|
|
||||||
|
*cField = &specExact{
|
||||||
|
value: parsed,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type specAny struct{}
|
||||||
|
|
||||||
|
func (a *specAny) Type() wingmate.CronTimeType {
|
||||||
|
return wingmate.Any
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *specAny) Match(u uint8) bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
type specExact struct {
|
||||||
|
value uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *specExact) Type() wingmate.CronTimeType {
|
||||||
|
return wingmate.Exact
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *specExact) Match(u uint8) bool {
|
||||||
|
return u == e.value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *specExact) Value() uint8 {
|
||||||
|
return e.value
|
||||||
|
}
|
||||||
|
|
||||||
|
type specMultiOccurrence struct {
|
||||||
|
values []uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *specMultiOccurrence) Type() wingmate.CronTimeType {
|
||||||
|
return wingmate.MultipleOccurrence
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *specMultiOccurrence) Match(u uint8) bool {
|
||||||
|
for _, v := range m.values {
|
||||||
|
if v == u {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *specMultiOccurrence) Values() []uint8 {
|
||||||
|
out := make([]uint8, len(m.values))
|
||||||
|
copy(out, m.values)
|
||||||
|
return out
|
||||||
}
|
}
|
||||||
|
|||||||
21
init/init.go
21
init/init.go
@ -3,34 +3,13 @@ package init
|
|||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitea.suyono.dev/suyono/wingmate"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Path interface {
|
type Path interface {
|
||||||
Path() string
|
Path() string
|
||||||
}
|
}
|
||||||
|
|
||||||
type CronExactSpec interface {
|
|
||||||
CronTimeSpec
|
|
||||||
Value() uint8
|
|
||||||
}
|
|
||||||
|
|
||||||
type CronMultipleOccurrenceSpec interface {
|
|
||||||
CronTimeSpec
|
|
||||||
MultipleValues() []uint8
|
|
||||||
}
|
|
||||||
|
|
||||||
type CronTimeSpec interface {
|
|
||||||
Type() wingmate.CronTimeType
|
|
||||||
}
|
|
||||||
|
|
||||||
type Cron interface {
|
type Cron interface {
|
||||||
Minute() CronTimeSpec
|
|
||||||
Hour() CronTimeSpec
|
|
||||||
DayOfMonth() CronTimeSpec
|
|
||||||
Month() CronTimeSpec
|
|
||||||
DayOfWeek() CronTimeSpec
|
|
||||||
Command() Path
|
Command() Path
|
||||||
TimeToRun(time.Time) bool
|
TimeToRun(time.Time) bool
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user