Cron with interval 1 second, yaml based configs http://gogocron.blindage.org/
go
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

gogocron.go 4.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. /*
  2. Copyright by Vladimir Smagin (21h) 2018
  3. http://blindage.org email: 21h@blindage.org
  4. Project page: http://gogocron.blindage.org
  5. */
  6. package main
  7. import (
  8. "context"
  9. "log"
  10. "os"
  11. "os/signal"
  12. "syscall"
  13. "time"
  14. )
  15. const (
  16. cronDefaultConfigLocation string = "/etc/gogocron/gogocron.yml"
  17. cronDefaultTasksLocation string = "/etc/gogocron/configs"
  18. cronDefaultPrecision string = "100ms"
  19. )
  20. type cronConfig struct {
  21. Precision time.Duration
  22. PrecisionString string `yaml:"ticktack_precision,omitempty"` // how frequently check seconds change
  23. TasksLocation string `yaml:"tasks_location,omitempty"` // where tasks configs stored
  24. }
  25. type cronTask struct {
  26. Name string `yaml:"name"` // name of task
  27. AsUser string `yaml:"user,omitempty"` // run as user
  28. RunSecond string `yaml:"runsecond,omitempty"` // second
  29. RunMinute string `yaml:"runminute,omitempty"` // minute
  30. RunHour string `yaml:"runhour,omitempty"` // hour
  31. RunDom string `yaml:"rundom,omitempty"` // day of month
  32. RunMonth string `yaml:"runmonth,omitempty"` // month
  33. RunDow string `yaml:"rundow,omitempty"` // day of week
  34. TimeOut string `yaml:"timeout,omitempty"` // exec with timeout, seconds
  35. Env []string `yaml:"env,omitempty"` // array of env variables
  36. Commands []string `yaml:"commands"` // array of commands to exec
  37. LogType string `yaml:"logtype,omitempty"` // type of logging: info, full, disabled
  38. }
  39. type cronTasks []cronTask
  40. // search for tasks that must be executed now
  41. func filterTasksToExecute(tasks cronTasks) cronTasks {
  42. now := time.Now()
  43. var tasksToExecute cronTasks
  44. for _, task := range tasks {
  45. taskIsForExecute := true
  46. if task.RunSecond != "" && taskIsForExecute {
  47. taskIsForExecute = isReadyToExec(task.RunSecond, now.Second())
  48. }
  49. if task.RunMinute != "" && taskIsForExecute {
  50. taskIsForExecute = isReadyToExec(task.RunMinute, now.Minute())
  51. }
  52. if task.RunHour != "" && taskIsForExecute {
  53. taskIsForExecute = isReadyToExec(task.RunHour, now.Hour())
  54. }
  55. if task.RunDom != "" && taskIsForExecute {
  56. taskIsForExecute = isReadyToExec(task.RunDom, now.Day())
  57. }
  58. if task.RunMonth != "" && taskIsForExecute {
  59. taskIsForExecute = isReadyToExec(task.RunMonth, int(now.Month()))
  60. }
  61. if task.RunDow != "" && taskIsForExecute {
  62. taskIsForExecute = isReadyToExec(task.RunDow, int(now.Weekday()))
  63. }
  64. if taskIsForExecute {
  65. tasksToExecute = append(tasksToExecute, task)
  66. }
  67. }
  68. return tasksToExecute
  69. }
  70. // executeTasks runs batch of planned tasks on go routines with context
  71. func executeTasks(tasks cronTasks) {
  72. //now := time.Now()
  73. //log.Printf("Execute started: %v", now)
  74. // run every task in own context
  75. ctx, ctxCancel := context.WithCancel(context.Background())
  76. defer ctxCancel()
  77. // iterate tasks and run them in go routine
  78. for _, task := range tasks {
  79. switch task.LogType {
  80. case "full":
  81. task.Env = append(task.Env, "LOGTYPE=full")
  82. case "disabled":
  83. task.Env = append(task.Env, "LOGTYPE=disabled")
  84. default:
  85. task.LogType = "info"
  86. task.Env = append(task.Env, "LOGTYPE=info")
  87. }
  88. if task.LogType != "disabled" {
  89. log.Printf("TASK: %#v TIMEOUT: %#v COMMANDS: %#v", task.Name, task.TimeOut, task.Commands)
  90. }
  91. if task.TimeOut != "" {
  92. timeout, err := time.ParseDuration(task.TimeOut)
  93. if err != nil {
  94. log.Fatalln("Error, task definition malformed!", task.Name)
  95. }
  96. ctx, ctxCancelTimeout := context.WithTimeout(ctx, timeout)
  97. defer ctxCancelTimeout()
  98. go executeTask(ctx, task)
  99. } else {
  100. go executeTask(ctx, task)
  101. }
  102. }
  103. select {}
  104. }
  105. func executeTask(ctx context.Context, task cronTask) {
  106. // flag to stop timed out tasks
  107. timeoutFlag := false
  108. // iterate commands list
  109. for _, cmd := range task.Commands {
  110. if timeoutFlag {
  111. break
  112. }
  113. select {
  114. case <-ctx.Done():
  115. log.Printf("Timeout exceeded for task %v", task.Name)
  116. timeoutFlag = true
  117. default:
  118. // su required to run as other users, requires pty to run
  119. runCmd(ctx, task.Env, "su", "-c", cmd, task.AsUser)
  120. }
  121. }
  122. }
  123. func main() {
  124. log.Println("gogocron 1.1 by Vladimir Smagin (http://gogocron.blindage.org)")
  125. // Load config at startup, once
  126. gogoConfig := loadConfig()
  127. // Load tasks
  128. tasks := loadTasks(gogoConfig)
  129. // Goroutine reloads tasks if SIGHUP received
  130. sigchan := make(chan os.Signal, 1)
  131. signal.Notify(sigchan, syscall.SIGHUP)
  132. go func() {
  133. for signame := range sigchan {
  134. log.Println("Received signal HUP, reloading configuration", signame)
  135. tasks = loadTasks(gogoConfig)
  136. }
  137. }()
  138. tickchan := make(chan struct{})
  139. go ticktack(tickchan, gogoConfig)
  140. for {
  141. select {
  142. case <-tickchan:
  143. tasksToExecute := filterTasksToExecute(tasks)
  144. if len(tasksToExecute) > 0 {
  145. go executeTasks(tasksToExecute)
  146. }
  147. }
  148. }
  149. }