Browse Source

#1 reload config after HUP signal received

tags/version-1.0
Vladimir Smagin 8 months ago
parent
commit
816f3b1c18
3 changed files with 71 additions and 5 deletions
  1. 5
    3
      configs/test1.yml
  2. 6
    0
      configs/test2.yml
  3. 60
    2
      gogocron.go

+ 5
- 3
configs/test1.yml View File

@@ -1,8 +1,10 @@
---
name: "Print base64 of 20 random symbols"
runsecond: "5,15,35,45"
runminute: "51"
runsecond: "*/5"
timeout: 1
env:
- TESTVAR="test variable"
commands:
- "head -c 20 /dev/urandom |base64"
- ls
- ls
- echo $TESTVAR

+ 6
- 0
configs/test2.yml View File

@@ -0,0 +1,6 @@
---
name: "Test task #2"
runsecond: "*/6"
timeout: 1
commands:
- "head -c 2 /dev/urandom |base64"

+ 60
- 2
gogocron.go View File

@@ -1,7 +1,12 @@
package main

import (
"context"
"log"
"os"
"os/exec"
"os/signal"
"syscall"
"time"
)

@@ -14,6 +19,7 @@ type cronTask struct {
RunMonth string `yaml:"runmonth,omitempty"` // month
RunDow string `yaml:"rundow,omitempty"` // day of week
TimeOut int `yaml:"timeout,omitempty"` // exec with timeout, seconds
Env []string `yaml:"env,omitempty"` // array of env variables
Commands []string `yaml:"commands"` // array of commands to exec
}

@@ -29,15 +35,19 @@ func filterTasksToExecute(tasks cronTasks) cronTasks {
if task.RunSecond != "" && taskIsForExecute {
taskIsForExecute = isReadyToExec(task.RunSecond, now.Second())
}

if task.RunMinute != "" && taskIsForExecute {
taskIsForExecute = isReadyToExec(task.RunMinute, now.Minute())
}

if task.RunHour != "" && taskIsForExecute {
taskIsForExecute = isReadyToExec(task.RunHour, now.Hour())
}

if task.RunDom != "" && taskIsForExecute {
taskIsForExecute = isReadyToExec(task.RunDom, now.Day())
}

if task.RunMonth != "" && taskIsForExecute {
taskIsForExecute = isReadyToExec(task.RunMonth, int(now.Month()))
}
@@ -53,9 +63,45 @@ func filterTasksToExecute(tasks cronTasks) cronTasks {
return tasksToExecute
}

func runCmd(ctx context.Context, env []string, cmdname string, params ...string) error {
log.Println("runCmd:", cmdname, params)
cmd := exec.Command(cmdname, params...)

// set env variables
cmd.Env = append(cmd.Env, os.Environ()...)
cmd.Env = append(cmd.Env, env...)

log.Printf("runCmd: env %v", env)
// set stdout, stderr
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr

cmd.Start()

// Use a channel to signal completion so we can use a select statement
done := make(chan error)
go func() { done <- cmd.Wait() }()

// The select statement allows us to execute based on which channel
// we get a message from first.
select {
case <-ctx.Done():
// Timeout happened first, kill the process and print a message.
cmd.Process.Signal(syscall.SIGTERM)
time.AfterFunc(10*time.Second, func() {
if !cmd.ProcessState.Exited() {
cmd.Process.Kill()
}
})
return ctx.Err()
case err := <-done:
return err
}

return nil
}

// executeTasks runs batch of planned tasks on go routines with context
// TODO: add execute function with context
// TODO: add context.timeout if task.Timeout present
func executeTasks(tasks cronTasks) {
now := time.Now()
log.Printf("Execute started: %v", now)
@@ -67,7 +113,19 @@ func executeTasks(tasks cronTasks) {
}

func main() {
// Load config at startup
tasks := loadConfig()

// Goroutine reloads config if SIGHUP received
sigchan := make(chan os.Signal, 1)
signal.Notify(sigchan, syscall.SIGHUP)
go func() {
for signame := range sigchan {
log.Println("Received signal HUP, reloading configuration", signame)
tasks = loadConfig()
}
}()

tickchan := make(chan struct{})
go ticktack(tickchan)


Loading…
Cancel
Save