wip: refactor for new config format

This commit is contained in:
Suyono 2023-12-31 13:51:17 +11:00
parent b2668287ae
commit 22fee125bc
10 changed files with 580 additions and 198 deletions

55
.idea/remote-targets.xml generated Normal file
View File

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteTargetsManager">
<targets>
<target name="golang-dev:1.21-bookworm-user" type="docker" uuid="5b79636b-3db9-4e2a-9fd3-03277653ae58">
<config>
<option name="targetPlatform">
<TargetPlatform />
</option>
<option name="buildImageConfig">
<BuildImageConfig>
<option name="dockerFile" value="docker/bookworm/Dockerfile" />
</BuildImageConfig>
</option>
<option name="buildNotPull" value="false" />
<option name="containerConfig">
<ContainerConfig>
<option name="runCliOptions" value="-u &quot;1000:1000&quot; --rm" />
</ContainerConfig>
</option>
<option name="pullImageConfig">
<PullImageConfig>
<option name="tagToPull" value="golang-dev:1.21-bookworm-user" />
</PullImageConfig>
</option>
</config>
<ContributedStateBase type="GoLanguageRuntime">
<config>
<option name="compiledExecutablesVolume">
<VolumeState>
<option name="targetSpecificBits">
<map>
<entry key="mountAsVolume" value="false" />
</map>
</option>
</VolumeState>
</option>
<option name="goPath" value="/go" />
<option name="goRoot" value="/usr/local/go/bin/go" />
<option name="goVersion" value="go1.21.5 linux/amd64" />
<option name="projectSourcesVolume">
<VolumeState>
<option name="targetSpecificBits">
<map>
<entry key="mountAsVolume" value="false" />
</map>
</option>
</VolumeState>
</option>
</config>
</ContributedStateBase>
</target>
</targets>
</component>
</project>

View File

@ -1,60 +1,53 @@
package main
import (
"time"
"gitea.suyono.dev/suyono/wingmate/config"
wminit "gitea.suyono.dev/suyono/wingmate/init"
"gitea.suyono.dev/suyono/wingmate/task"
)
type wPath struct {
path string
}
func (p wPath) Path() string {
return p.path
}
type wConfig struct {
services []wminit.Path
cron []wminit.Cron
tasks *task.Tasks
}
func (c wConfig) Services() []wminit.Path {
return c.services
func (c *wConfig) Tasks() wminit.Tasks {
return c.tasks
}
func (c wConfig) Cron() []wminit.Cron {
return c.cron
}
type wCron struct {
iCron *config.Cron
}
func (c wCron) TimeToRun(now time.Time) bool {
return c.iCron.TimeToRun(now)
}
func (c wCron) Command() wminit.Path {
return wPath{
path: c.iCron.Command(),
}
}
func convert(cfg *config.Config) wConfig {
retval := wConfig{
services: make([]wminit.Path, 0, len(cfg.ServicePaths)),
cron: make([]wminit.Cron, 0, len(cfg.Cron)),
func convert(cfg *config.Config) *wConfig {
retval := &wConfig{
tasks: task.NewTasks(),
}
for _, s := range cfg.ServicePaths {
retval.services = append(retval.services, wPath{path: s})
retval.tasks.AddV0Service(s)
}
var schedule task.CronSchedule
for _, c := range cfg.Cron {
retval.cron = append(retval.cron, wCron{iCron: c})
schedule.Minute = convertSchedule(c.Minute)
schedule.Hour = convertSchedule(c.Hour)
schedule.DoM = convertSchedule(c.DoM)
schedule.Month = convertSchedule(c.Month)
schedule.DoW = convertSchedule(c.DoW)
retval.tasks.AddV0Cron(schedule, c.Command)
}
return retval
}
func convertSchedule(cfg config.CronTimeSpec) task.CronTimeSpec {
switch v := cfg.(type) {
case config.SpecAny:
return task.NewCronAnySpec()
case config.SpecExact:
return task.NewCronExactSpec(v.Value())
case config.SpecMultiOccurrence:
return task.NewCronMultiOccurrenceSpec(v.Values()...)
}
panic("invalid conversion")
return nil
}

View File

@ -10,33 +10,35 @@ import (
"github.com/stretchr/testify/assert"
)
func TestRead(t *testing.T) {
type testEntry struct {
name string
testFunc func(t *testing.T)
}
const (
serviceDir = "service"
)
var (
configDir string
err error
)
const serviceDir = "service"
setup := func(t *testing.T) {
func setup(t *testing.T) {
var err error
if configDir, err = os.MkdirTemp("", "wingmate-*-test"); err != nil {
t.Fatal("setup", err)
}
viper.Set(EnvConfigPath, configDir)
}
tear := func(t *testing.T) {
if err = os.RemoveAll(configDir); err != nil {
func tear(t *testing.T) {
if err := os.RemoveAll(configDir); err != nil {
t.Fatal("tear", err)
}
}
func TestRead(t *testing.T) {
type testEntry struct {
name string
testFunc func(t *testing.T)
}
mkSvcDir := func(t *testing.T) {
if err := os.MkdirAll(path.Join(configDir, serviceDir), 0755); err != nil {
t.Fatal("create dir", err)

View File

@ -4,46 +4,34 @@ import (
"bufio"
"errors"
"fmt"
"gitea.suyono.dev/suyono/wingmate"
"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() wingmate.CronTimeType
//Match(uint8) bool
}
type Cron struct {
minute CronTimeSpec
hour CronTimeSpec
dom CronTimeSpec
month CronTimeSpec
dow CronTimeSpec
command string
lastRun time.Time
hasRun bool
Minute CronTimeSpec
Hour CronTimeSpec
DoM CronTimeSpec
Month CronTimeSpec
DoW CronTimeSpec
Command string
}
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
CrontabEntryRegexPattern = `^\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*$`
CrontabCommentLineRegexPattern = `^\s*#.*$`
CrontabCommentSuffixRegexPattern = `^\s*([^#]+)#.*$`
CrontabSubMatchLen = 7
minute cronField = iota
hour
@ -52,21 +40,22 @@ const (
dow
)
var (
crontabEntryRegex = regexp.MustCompile(CrontabEntryRegexPattern)
crontabCommentLineRegex = regexp.MustCompile(CrontabCommentLineRegexPattern)
crontabCommentSuffixRegex = regexp.MustCompile(CrontabCommentSuffixRegexPattern)
)
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
}
@ -79,41 +68,48 @@ func readCrontab(path string) ([]*Cron, error) {
for scanner.Scan() {
line = scanner.Text()
parts = re.FindStringSubmatch(line)
if len(parts) != CrontabSubmatchLen {
if crontabCommentLineRegex.MatchString(line) {
continue
}
parts = crontabCommentSuffixRegex.FindStringSubmatch(line)
if len(parts) == 2 {
line = parts[1]
}
parts = crontabEntryRegex.FindStringSubmatch(line)
if len(parts) != CrontabSubMatchLen {
wingmate.Log().Error().Msgf("invalid entry %s", line)
continue
}
c := &Cron{
hasRun: false,
}
c := &Cron{}
if err = c.setField(minute, parts[1]); err != nil {
wingmate.Log().Error().Msgf("error parsing minute field %+v", err)
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)
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)
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)
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)
wingmate.Log().Error().Msgf("error parsing Day of Week field %+v", err)
continue
}
c.command = parts[6]
c.Command = parts[6]
retval = append(retval, c)
}
@ -121,35 +117,6 @@ func readCrontab(path string) ([]*Cron, error) {
return retval, nil
}
func (c *Cron) Command() string {
return c.command
}
func (c *Cron) TimeToRun(now time.Time) bool {
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())) {
if c.hasRun {
if now.Sub(c.lastRun) <= time.Minute && now.Minute() == c.lastRun.Minute() {
return false
} else {
c.lastRun = now
return true
}
} else {
c.lastRun = now
c.hasRun = true
return true
}
}
return false
}
type fieldRange struct {
min int
max int
@ -182,25 +149,25 @@ func (c *Cron) setField(field cronField, input string) error {
switch field {
case minute:
fr = newRange(0, 59)
cField = &c.minute
cField = &c.Minute
case hour:
fr = newRange(0, 23)
cField = &c.hour
cField = &c.Hour
case dom:
fr = newRange(1, 31)
cField = &c.dom
cField = &c.DoM
case month:
fr = newRange(1, 12)
cField = &c.month
cField = &c.Month
case dow:
fr = newRange(0, 6)
cField = &c.dow
cField = &c.DoW
default:
return errors.New("invalid cron field descriptor")
}
if input == "*" {
*cField = &specAny{}
*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)
@ -217,7 +184,7 @@ func (c *Cron) setField(field cronField, input string) error {
current += parsed
}
*cField = &specMultiOccurrence{
*cField = &SpecMultiOccurrence{
values: multi,
}
} else {
@ -237,7 +204,7 @@ func (c *Cron) setField(field cronField, input string) error {
multi = append(multi, parsed)
}
*cField = &specMultiOccurrence{
*cField = &SpecMultiOccurrence{
values: multi,
}
} else {
@ -250,7 +217,7 @@ func (c *Cron) setField(field cronField, input string) error {
return fmt.Errorf("error parse field %+v with input %s: invalid value", field, input)
}
*cField = &specExact{
*cField = &SpecExact{
value: parsed,
}
}
@ -259,51 +226,21 @@ func (c *Cron) setField(field cronField, input string) error {
return nil
}
type specAny struct{}
type SpecAny struct{}
func (a *specAny) Type() wingmate.CronTimeType {
return wingmate.Any
}
func (a *specAny) Match(u uint8) bool {
return true
}
type specExact struct {
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 {
func (e *SpecExact) Value() uint8 {
return e.value
}
type specMultiOccurrence struct {
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 {
func (m *SpecMultiOccurrence) Values() []uint8 {
out := make([]uint8, len(m.values))
copy(out, m.values)
return out

128
config/crontab_test.go Normal file
View File

@ -0,0 +1,128 @@
package config
import (
"gitea.suyono.dev/suyono/wingmate"
"github.com/stretchr/testify/assert"
"os"
"path/filepath"
"testing"
)
const (
crontabFileName = "crontab"
)
func TestCrontab(t *testing.T) {
type testEntry struct {
name string
crontab string
wantErr bool
}
_ = wingmate.NewLog(os.Stderr)
tests := []testEntry{
{
name: "positive",
crontab: crontabTestCase0,
wantErr: false,
},
{
name: "with comment",
crontab: crontabTestCase1,
wantErr: false,
},
{
name: "various values",
crontab: crontabTestCase2,
wantErr: false,
},
{
name: "failed to parse",
crontab: crontabTestCase3,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
setup(t)
defer tear(t)
writeCrontab(t, tt.crontab)
cfg, err := Read()
if tt.wantErr != (err != nil) {
t.Fatalf("wantErr is %v but err is %+v", tt.wantErr, err)
}
t.Logf("cfg: %+v", cfg)
for _, c := range cfg.Cron {
t.Logf("%+v", c)
}
})
}
}
func writeCrontab(t *testing.T, content string) {
var (
f *os.File
err error
)
if f, err = os.Create(filepath.Join(configDir, crontabFileName)); err != nil {
t.Fatal("create crontab file", err)
}
defer func() {
_ = f.Close()
}()
if _, err = f.Write([]byte(content)); err != nil {
t.Fatal("writing crontab file", err)
}
}
const crontabTestCase0 = `* * * * * /path/to/executable`
const crontabTestCase1 = `# this is a comment
## comment with space
* * * * * /path/to/executable
* * * * * /path/to/executable # comment as a suffix
`
const crontabTestCase2 = `# first comment
*/5 13 3,5,7 * * /path/to/executable`
const crontabTestCase3 = `a 13 3,5,7 * * /path/to/executable
*/5 a 3,5,7 * * /path/to/executable
*/5 13 a * * /path/to/executable
*/5 13 3,5,7 a * /path/to/executable
*/5 13 3,5,7 * a /path/to/executable
*/x 13 3,5,7 * a /path/to/executable
76 13 3,5,7 * a /path/to/executable
*/75 13 3,5,7 * a /path/to/executable
*/5 13 3,x,7 * a /path/to/executable
*/5 13 3,5,67 * a /path/to/executable
*/5 13 * * /path/to/executable
*/5 13 3,5,7 * * /path/to/executable`
func TestSpecExact(t *testing.T) {
var val uint8 = 45
s := SpecExact{
value: val,
}
assert.Equal(t, val, s.Value())
}
func TestSpecMulti(t *testing.T) {
val := []uint8{3, 5, 7, 15}
s := SpecMultiOccurrence{
values: val,
}
assert.ElementsMatch(t, val, s.Values())
}
func TestInvalidField(t *testing.T) {
c := &Cron{}
assert.NotNil(t, c.setField(cronField(99), "x"))
}

View File

@ -13,7 +13,7 @@ const (
cronTag = "cron"
)
func (i *Init) cron(wg *sync.WaitGroup, cron Cron, exitFlag <-chan any) {
func (i *Init) cron(wg *sync.WaitGroup, cron CronTask, exitFlag <-chan any) {
defer wg.Done()
var (
@ -21,35 +21,40 @@ func (i *Init) cron(wg *sync.WaitGroup, cron Cron, exitFlag <-chan any) {
err error
stdout io.ReadCloser
stderr io.ReadCloser
cmd *exec.Cmd
)
ticker := time.NewTicker(time.Second * 30)
cron:
for {
if cron.TimeToRun(time.Now()) {
wingmate.Log().Info().Str(cronTag, cron.Command().Path()).Msg("executing")
cmd := exec.Command(cron.Command().Path())
wingmate.Log().Info().Str(cronTag, cron.Name()).Msg("executing")
if len(cron.Command()) == 1 {
cmd = exec.Command(cron.Command()[0])
} else {
cmd = exec.Command(cron.Command()[0], cron.Command()[1:]...)
}
iwg = &sync.WaitGroup{}
if stdout, err = cmd.StdoutPipe(); err != nil {
wingmate.Log().Error().Str(cronTag, cron.Command().Path()).Msgf("stdout pipe: %+v", err)
wingmate.Log().Error().Str(cronTag, cron.Name()).Msgf("stdout pipe: %+v", err)
goto fail
}
if stderr, err = cmd.StderrPipe(); err != nil {
wingmate.Log().Error().Str(cronTag, cron.Command().Path()).Msgf("stderr pipe: %+v", err)
wingmate.Log().Error().Str(cronTag, cron.Name()).Msgf("stderr pipe: %+v", err)
_ = stdout.Close()
goto fail
}
iwg.Add(1)
go i.pipeReader(iwg, stdout, cronTag, cron.Command().Path())
go i.pipeReader(iwg, stdout, cronTag, cron.Name())
iwg.Add(1)
go i.pipeReader(iwg, stderr, cronTag, cron.Command().Path())
go i.pipeReader(iwg, stderr, cronTag, cron.Name())
if err := cmd.Start(); err != nil {
wingmate.Log().Error().Msgf("starting cron %s error %+v", cron.Command().Path(), err)
wingmate.Log().Error().Msgf("starting cron %s error %+v", cron.Name(), err)
_ = stdout.Close()
_ = stderr.Close()
iwg.Wait()
@ -59,7 +64,7 @@ cron:
iwg.Wait()
if err = cmd.Wait(); err != nil {
wingmate.Log().Error().Str(cronTag, cron.Command().Path()).Msgf("got error when waiting: %+v", err)
wingmate.Log().Error().Str(cronTag, cron.Name()).Msgf("got error when waiting: %+v", err)
}
}

View File

@ -6,18 +6,37 @@ import (
"time"
)
type Path interface {
Path() string
type Tasks interface {
List() []Task
Services() []Task
Crones() []CronTask
Get(string) (Task, error)
}
type Cron interface {
Command() Path
type UserGroup interface {
}
type TaskStatus interface {
}
type Task interface {
Name() string
Command() []string
Environ() []string
Setsid() bool
UserGroup() UserGroup
Background() bool
WorkingDir() string
Status() TaskStatus
}
type CronTask interface {
Task
TimeToRun(time.Time) bool
}
type Config interface {
Services() []Path
Cron() []Cron
Tasks() Tasks
}
type Init struct {
@ -49,12 +68,12 @@ func (i *Init) Start() {
wg.Add(1)
go i.sighandler(wg, signalTrigger, sighandlerExit, sigchld)
for _, s := range i.config.Services() {
for _, s := range i.config.Tasks().Services() {
wg.Add(1)
go i.service(wg, s, signalTrigger)
}
for _, c := range i.config.Cron() {
for _, c := range i.config.Tasks().Crones() {
wg.Add(1)
go i.cron(wg, c, signalTrigger)
}

View File

@ -14,7 +14,7 @@ const (
serviceTag = "service"
)
func (i *Init) service(wg *sync.WaitGroup, path Path, exitFlag <-chan any) {
func (i *Init) service(wg *sync.WaitGroup, task Task, exitFlag <-chan any) {
defer wg.Done()
var (
@ -23,37 +23,42 @@ func (i *Init) service(wg *sync.WaitGroup, path Path, exitFlag <-chan any) {
stderr io.ReadCloser
stdout io.ReadCloser
failStatus bool
cmd *exec.Cmd
)
defer func() {
wingmate.Log().Info().Str(serviceTag, path.Path()).Msg("stopped")
wingmate.Log().Info().Str(serviceTag, task.Name()).Msg("stopped")
}()
service:
for {
failStatus = false
cmd := exec.Command(path.Path())
if len(task.Command()) == 1 {
cmd = exec.Command(task.Command()[0])
} else {
cmd = exec.Command(task.Command()[0], task.Command()[1:]...)
}
iwg = &sync.WaitGroup{}
if stdout, err = cmd.StdoutPipe(); err != nil {
wingmate.Log().Error().Str(serviceTag, path.Path()).Msgf("stdout pipe: %#v", err)
wingmate.Log().Error().Str(serviceTag, task.Name()).Msgf("stdout pipe: %#v", err)
failStatus = true
goto fail
}
iwg.Add(1)
go i.pipeReader(iwg, stdout, serviceTag, path.Path())
go i.pipeReader(iwg, stdout, serviceTag, task.Name())
if stderr, err = cmd.StderrPipe(); err != nil {
wingmate.Log().Error().Str(serviceTag, path.Path()).Msgf("stderr pipe: %#v", err)
wingmate.Log().Error().Str(serviceTag, task.Name()).Msgf("stderr pipe: %#v", err)
_ = stdout.Close()
failStatus = true
goto fail
}
iwg.Add(1)
go i.pipeReader(iwg, stderr, serviceTag, path.Path())
go i.pipeReader(iwg, stderr, serviceTag, task.Name())
if err = cmd.Start(); err != nil {
wingmate.Log().Error().Msgf("starting service %s error %#v", path.Path(), err)
wingmate.Log().Error().Msgf("starting service %s error %#v", task.Name(), err)
failStatus = true
_ = stdout.Close()
_ = stderr.Close()
@ -64,7 +69,7 @@ service:
iwg.Wait()
if err = cmd.Wait(); err != nil {
wingmate.Log().Error().Str(serviceTag, path.Path()).Msgf("got error when waiting: %+v", err)
wingmate.Log().Error().Str(serviceTag, task.Name()).Msgf("got error when waiting: %+v", err)
}
fail:
if failStatus {

139
task/cron.go Normal file
View File

@ -0,0 +1,139 @@
package task
import (
wminit "gitea.suyono.dev/suyono/wingmate/init"
"time"
)
type CronSchedule struct {
Minute CronTimeSpec
Hour CronTimeSpec
DoM CronTimeSpec
Month CronTimeSpec
DoW CronTimeSpec
}
type CronTimeSpec interface {
Match(uint8) bool
}
type CronAnySpec struct {
}
func NewCronAnySpec() *CronAnySpec {
return &CronAnySpec{}
}
func (cas *CronAnySpec) Match(u uint8) bool {
return true
}
type CronExactSpec struct {
value uint8
}
func NewCronExactSpec(v uint8) *CronExactSpec {
return &CronExactSpec{
value: v,
}
}
func (ces *CronExactSpec) Match(u uint8) bool {
return u == ces.value
}
type CronMultiOccurrenceSpec struct {
values []uint8
}
func NewCronMultiOccurrenceSpec(v ...uint8) *CronMultiOccurrenceSpec {
retval := &CronMultiOccurrenceSpec{}
if len(v) > 0 {
retval.values = make([]uint8, len(v))
copy(retval.values, v)
}
return retval
}
func (cms *CronMultiOccurrenceSpec) Match(u uint8) bool {
for _, v := range cms.values {
if v == u {
return true
}
}
return false
}
type Cron struct {
CronSchedule
name string
command []string
lastRun time.Time
hasRun bool //NOTE: make sure initialised as false
}
func (c *Cron) Name() string {
return c.name
}
func (c *Cron) Command() []string {
retval := make([]string, len(c.command))
copy(retval, c.command)
return retval
}
func (c *Cron) Environ() []string {
panic("not implemented")
return nil
}
func (c *Cron) Setsid() bool {
panic("not implemented")
return false
}
func (c *Cron) UserGroup() wminit.UserGroup {
panic("not implemented")
return nil
}
func (c *Cron) Background() bool {
panic("not implemented")
return false
}
func (c *Cron) WorkingDir() string {
panic("not implemented")
return ""
}
func (c *Cron) Status() wminit.TaskStatus {
panic("not implemented")
return nil
}
func (c *Cron) TimeToRun(now time.Time) bool {
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())) {
if c.hasRun {
if now.Sub(c.lastRun) <= time.Minute && now.Minute() == c.lastRun.Minute() {
return false
} else {
c.lastRun = now
return true
}
} else {
c.lastRun = now
c.hasRun = true
return true
}
}
return false
}

99
task/task.go Normal file
View File

@ -0,0 +1,99 @@
package task
import (
wminit "gitea.suyono.dev/suyono/wingmate/init"
)
type Tasks struct {
//services []wminit.Path
//cron []wminit.Cron
services []wminit.Task
crones []wminit.CronTask
}
func NewTasks() *Tasks {
return &Tasks{
services: make([]wminit.Task, 0),
crones: make([]wminit.CronTask, 0),
}
}
func (ts *Tasks) AddV0Service(path string) {
ts.services = append(ts.services, &Task{
name: path,
command: []string{path},
})
}
func (ts *Tasks) AddV0Cron(schedule CronSchedule, path string) {
ts.crones = append(ts.crones, &Cron{
CronSchedule: schedule,
name: path,
command: []string{path},
})
}
func (ts *Tasks) List() []wminit.Task {
panic("not implemented")
return nil
}
func (ts *Tasks) Services() []wminit.Task {
panic("not implemented")
return nil
}
func (ts *Tasks) Crones() []wminit.CronTask {
panic("not implemented")
return nil
}
func (ts *Tasks) Get(name string) (wminit.Task, error) {
panic("not implemented")
return nil, nil
}
type Task struct {
name string
command []string
}
func (t *Task) Name() string {
return t.name
}
func (t *Task) Command() []string {
retval := make([]string, len(t.command))
copy(retval, t.command)
return retval
}
func (t *Task) Environ() []string {
panic("not implemented")
return nil
}
func (t *Task) Setsid() bool {
panic("not implemented")
return false
}
func (t *Task) UserGroup() wminit.UserGroup {
panic("not implemented")
return nil
}
func (t *Task) Background() bool {
panic("not implemented")
return false
}
func (t *Task) WorkingDir() string {
panic("not implemented")
return ""
}
func (t *Task) Status() wminit.TaskStatus {
panic("not implemented")
return nil
}