diff --git a/.drone.yml b/.drone.yml index 659d9b7..b59a8dc 100644 --- a/.drone.yml +++ b/.drone.yml @@ -13,7 +13,7 @@ steps: - cp deploy /data - cp config.yml.sample /data/config.yml - cd /data && tar cvf release.tar --exclude=release.tar . -- name: relase +- name: release image: plugins/gitea-release volumes: - name: dist diff --git a/README.md b/README.md index 85fd27a..17560dc 100644 --- a/README.md +++ b/README.md @@ -17,4 +17,9 @@ listens: path: /test-post headers: {} script: /tmp/run.sh + +# cron job setting +cron_jobs: + - cron_time: '* * * * *' + script: /tmp/run.sh ``` diff --git a/cmd/svc/main.go b/cmd/svc/main.go index 52f4a55..b1379a6 100644 --- a/cmd/svc/main.go +++ b/cmd/svc/main.go @@ -1,20 +1,24 @@ package main import ( + "bufio" "context" "deploy/pkg/config" "deploy/pkg/logger" "deploy/pkg/server" "deploy/pkg/tools" + "errors" "flag" "fmt" "log" "net/http" "os" + "os/exec" "os/signal" "syscall" "time" + "github.com/robfig/cron/v3" "github.com/sirupsen/logrus" "gopkg.in/fsnotify.v1" ) @@ -22,7 +26,6 @@ import ( var cfgPath string = "./config.yml" func main() { - flag.StringVar(&cfgPath, "c", "./config.yml", "config yaml path") flag.Parse() @@ -82,11 +85,20 @@ func runService(reload <-chan struct{}) { log := logger.NewLogger(nil) var svc *http.Server + var cron *Cron // infinity loop for { + cfg, err := config.Load(cfgPath) + if err != nil { + log.Fatal(err) + } + if svc == nil { - svc = startServer(log) + svc = startServer(cfg, log) + } + if cron == nil { + cron = startCron(cfg, log) } select { @@ -95,8 +107,11 @@ func runService(reload <-chan struct{}) { // channel closed exit program if err := stopServer(svc); err != nil { log.Fatal(err) - return } + if err := stopCron(cron); err != nil { + log.Fatal(err) + } + return } if time.Since(lastReload).Seconds() < float64(2*time.Second) { break @@ -106,16 +121,96 @@ func runService(reload <-chan struct{}) { if err := stopServer(svc); err != nil { log.Fatal(err) } + if err := stopCron(cron); err != nil { + log.Fatal(err) + } + svc = nil + cron = nil } } } -func startServer(log *logrus.Logger) *http.Server { - cfg, err := config.Load(cfgPath) - if err != nil { - log.Fatal(err) +type Cron struct { + *cron.Cron + Logger *logrus.Logger +} + +func (c *Cron) setJobs(cfg *config.Config) { + for _, v := range cfg.CronJobs { + c.AddFunc(v.CronTime, func() { + cmd := exec.Command(v.Script) + + // r, w := io.Pipe() + nlog := c.Logger.WithFields(logrus.Fields{"module": "cron", "script": v.Script}) + reader, err := cmd.StdoutPipe() + if err != nil { + nlog.Warnf("setup pipe out fail: %+v\n", err) + } + errReader, err := cmd.StderrPipe() + if err != nil { + nlog.Warnf("setup pipe out fail: %+v\n", err) + } + + if err := cmd.Start(); err != nil { + nlog.Warnf("start script fail: %+v\n", err) + } + + go func() { + in := bufio.NewScanner(reader) + for in.Scan() { + nlog.Debugf(in.Text()) + } + if err := in.Err(); err != nil { + nlog.Warnf("script error: %+v\n", err) + } + }() + go func() { + in := bufio.NewScanner(errReader) + for in.Scan() { + nlog.Debugf(in.Text()) + } + if err := in.Err(); err != nil { + nlog.Warnf("script error: %+v\n", err) + } + }() + + }) } +} + +func startCron(cfg *config.Config, log *logrus.Logger) *Cron { + tz, err := time.LoadLocation("Asia/Taipei") + if err != nil { + panic(err) + } + c := cron.New( + cron.WithLocation(tz), + cron.WithParser(cron.NewParser(cron.Minute|cron.Hour|cron.Dom|cron.Month|cron.Dow)), + ) + + c2 := &Cron{Cron: c, Logger: log} + + c2.setJobs(cfg) + c2.Start() + + return c2 +} + +func stopCron(c *Cron) error { + ctx := c.Stop() + + select { + case <-ctx.Done(): + return nil + case <-time.Tick(time.Second * 5): + // skip stop jobs + return errors.New("stop jobs fail") + } +} + +func startServer(cfg *config.Config, log *logrus.Logger) *http.Server { + engine := server.NewServer(log) // set routes if err := engine.SetRoutes(cfg.Listens); err != nil { diff --git a/config.yml.sample b/config.yml.sample index 02386f6..c41812c 100644 --- a/config.yml.sample +++ b/config.yml.sample @@ -7,3 +7,7 @@ listens: path: /t headers: {} script: /tmp/run.sh + +cron_jobs: + - cron_time: '0 1 * * *' + script: /tmp/run.sh diff --git a/go.mod b/go.mod index 051b937..8afd494 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module deploy -go 1.17 +go 1.16 require ( github.com/gin-gonic/gin v1.7.2 @@ -24,6 +24,7 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.1 // indirect github.com/otakukaze/envconfig v1.0.4 // indirect + github.com/robfig/cron/v3 v3.0.0 // indirect github.com/ugorji/go/codec v1.2.6 // indirect golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a // indirect golang.org/x/sys v0.0.0-20210611083646-a4fc73990273 // indirect diff --git a/go.sum b/go.sum index f7d8b09..3677b92 100644 --- a/go.sum +++ b/go.sum @@ -48,6 +48,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/robfig/cron/v3 v3.0.0 h1:kQ6Cb7aHOHTSzNVNEhmp8EcWKLb4CbiMW9h9VyIhO4E= +github.com/robfig/cron/v3 v3.0.0/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/pkg/config/config.go b/pkg/config/config.go index 3931f42..f152798 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -7,19 +7,26 @@ import ( "strings" errors "github.com/pkg/errors" + "github.com/robfig/cron/v3" confLoader "github.com/otakukaze/config-loader" ) type Config struct { - Server Server `yaml:"server"` - Listens []Listen `yaml:"listens"` + Server Server `yaml:"server"` + Listens []Listen `yaml:"listens"` + CronJobs []CronJob `yaml:"cron_jobs"` } type Server struct { Port int `yaml:"port" env:"SERVER_PORT" default:"10230"` } +type CronJob struct { + CronTime string `yaml:"cron_time"` + Script string `yaml:"script"` +} + type Listen struct { HTTP *HTTPListen `yaml:"http"` } @@ -88,5 +95,19 @@ func Load(p ...string) (*Config, error) { } } + // check cronJob + cronParser := cron.NewParser(cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow) + for _, v := range cfg.CronJobs { + _, err := cronParser.Parse(v.CronTime) + if err != nil { + return nil, err + } + + // check script exists + if !tools.CheckExists(v.Script, false) { + return nil, errors.WithStack(fmt.Errorf("cron (%s) script not exists: %s\n", v.CronTime, v.Script)) + } + } + return cfg, nil } diff --git a/pkg/server/server.go b/pkg/server/server.go index 3e22cfc..6be0d7b 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -47,7 +47,7 @@ func (s *HTTPServer) setHandler(check map[string]string, script string) gin.Hand cmd := exec.Command(script) // r, w := io.Pipe() - nlog := s.Logger.WithFields(logrus.Fields{"script": script}) + nlog := s.Logger.WithFields(logrus.Fields{"module": "http", "script": script}) reader, err := cmd.StdoutPipe() if err != nil { nlog.Warnf("setup pipe out fail: %+v\n", err) diff --git a/pkg/set/set.go b/pkg/set/set.go index 6cd08c8..d0a9366 100644 --- a/pkg/set/set.go +++ b/pkg/set/set.go @@ -22,7 +22,7 @@ func (s Set) Size() int { func (s Set) Keys() []interface{} { keys := make([]interface{}, 0) - for k, _ := range s { + for k := range s { keys = append(keys, k) }