Cron with interval 1 second, yaml based configs http://gogocron.blindage.org/
go
Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  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. GracefulStop string `yaml:"gracefulstop,omitempty"` // seconds between task term and kill
  39. }
  40. type cronTasks []cronTask
  41. // search for tasks that must be executed now
  42. func filterTasksToExecute(tasks cronTasks) cronTasks {
  43. now := time.Now()
  44. var tasksToExecute cronTasks
  45. for _, task := range tasks {
  46. taskIsForExecute := true
  47. if task.RunSecond != "" && taskIsForExecute {
  48. taskIsForExecute = isReadyToExec(task.RunSecond, now.Second())
  49. }
  50. if task.RunMinute != "" && taskIsForExecute {
  51. taskIsForExecute = isReadyToExec(task.RunMinute, now.Minute())
  52. }
  53. if task.RunHour != "" && taskIsForExecute {
  54. taskIsForExecute = isReadyToExec(task.RunHour, now.Hour())
  55. }
  56. if task.RunDom != "" && taskIsForExecute {
  57. taskIsForExecute = isReadyToExec(task.RunDom, now.Day())
  58. }
  59. if task.RunMonth != "" && taskIsForExecute {
  60. taskIsForExecute = isReadyToExec(task.RunMonth, int(now.Month()))
  61. }
  62. if task.RunDow != "" && taskIsForExecute {
  63. taskIsForExecute = isReadyToExec(task.RunDow, int(now.Weekday()))
  64. }
  65. if taskIsForExecute {
  66. tasksToExecute = append(tasksToExecute, task)
  67. }
  68. }
  69. return tasksToExecute
  70. }
  71. // executeTasks runs batch of planned tasks on go routines with context
  72. func executeTasks(tasks cronTasks) {
  73. //now := time.Now()
  74. //log.Printf("Execute started: %v", now)
  75. // run every task in own context
  76. ctx, ctxCancel := context.WithCancel(context.Background())
  77. defer ctxCancel()
  78. // iterate tasks and run them in go routine
  79. for _, task := range tasks {
  80. switch task.LogType {
  81. case "full":
  82. task.Env = append(task.Env, "LOGTYPE=full")
  83. case "disabled":
  84. task.Env = append(task.Env, "LOGTYPE=disabled")
  85. default:
  86. task.LogType = "info"
  87. task.Env = append(task.Env, "LOGTYPE=info")
  88. }
  89. if task.LogType != "disabled" {
  90. log.Printf("TASK: %#v TIMEOUT: %#v COMMANDS: %#v", task.Name, task.TimeOut, task.Commands)
  91. }
  92. if task.TimeOut != "" {
  93. timeout, err := time.ParseDuration(task.TimeOut)
  94. if err != nil {
  95. log.Fatalln("Error, task definition malformed!", task.Name)
  96. }
  97. ctx, ctxCancelTimeout := context.WithTimeout(ctx, timeout)
  98. defer ctxCancelTimeout()
  99. go executeTask(ctx, task)
  100. } else {
  101. go executeTask(ctx, task)
  102. }
  103. }
  104. select {}
  105. }
  106. func executeTask(ctx context.Context, task cronTask) {
  107. // flag to stop timed out tasks
  108. timeoutFlag := false
  109. // send gracefulstop with context
  110. type contextKey string
  111. gracefulStop := contextKey("gracefulstopvalue")
  112. ctxRunCmd := context.WithValue(ctx, gracefulStop, task.GracefulStop)
  113. // iterate commands list
  114. for _, cmd := range task.Commands {
  115. if timeoutFlag {
  116. break
  117. }
  118. select {
  119. case <-ctxRunCmd.Done():
  120. log.Printf("Timeout exceeded for task %v", task.Name)
  121. timeoutFlag = true
  122. default:
  123. // su required to run as other users, requires pty to run
  124. runCmd(ctxRunCmd, task.Env, "su", "-c", cmd, task.AsUser)
  125. }
  126. }
  127. }
  128. func main() {
  129. log.Println("gogocron 1.1.1 by Vladimir Smagin (http://gogocron.blindage.org)")
  130. // Load config at startup, once
  131. gogoConfig := loadConfig()
  132. // Load tasks
  133. tasks := loadTasks(gogoConfig)
  134. // Goroutine reloads tasks if SIGHUP received
  135. sigchan := make(chan os.Signal, 1)
  136. signal.Notify(sigchan, syscall.SIGHUP)
  137. go func() {
  138. for signame := range sigchan {
  139. log.Println("Received signal HUP, reloading configuration", signame)
  140. tasks = loadTasks(gogoConfig)
  141. }
  142. }()
  143. tickchan := make(chan struct{})
  144. go ticktack(tickchan, gogoConfig)
  145. for {
  146. select {
  147. case <-tickchan:
  148. tasksToExecute := filterTasksToExecute(tasks)
  149. if len(tasksToExecute) > 0 {
  150. go executeTasks(tasksToExecute)
  151. }
  152. }
  153. }
  154. }