mirror of
https://github.com/nxshock/gron.git
synced 2024-11-27 03:41:00 +05:00
Initial commit
This commit is contained in:
commit
87dcffda99
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
21
.gitignore
vendored
Normal file
21
.gitignore
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
# If you prefer the allow list template instead of the deny list, see community template:
|
||||
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
||||
#
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
# Go workspace file
|
||||
go.work
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 nxshock
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
15
README.md
Normal file
15
README.md
Normal file
@ -0,0 +1,15 @@
|
||||
# go-cron
|
||||
|
||||
*cron-like job scheduler*
|
||||
|
||||
## Usage
|
||||
|
||||
1. Create `jobs.d` directory
|
||||
2. Create job config in `jobs.d/job1.conf` ([TOML](https://en.wikipedia.org/wiki/TOML) format):
|
||||
```toml
|
||||
Cron = "* * * * *" # cron instructions
|
||||
Command = "echo Hello" # command to execute
|
||||
Description = "print Hello every minute" # job description
|
||||
```
|
||||
3. Launch `go-cron` binary
|
||||
4. HTTP interface available on http://127.0.0.1:9876
|
18
consts.go
Normal file
18
consts.go
Normal file
@ -0,0 +1,18 @@
|
||||
package main
|
||||
|
||||
import formatter "github.com/antonfisher/nested-logrus-formatter"
|
||||
|
||||
const (
|
||||
timeFormat = "02.01.2006 15:04:05"
|
||||
logFileName = "log.txt"
|
||||
logFilesPath = "logs"
|
||||
listenAddress = "127.0.0.1:9876"
|
||||
)
|
||||
|
||||
var (
|
||||
logFormat = &formatter.Formatter{
|
||||
TimestampFormat: timeFormat,
|
||||
HideKeys: true,
|
||||
NoColors: true,
|
||||
TrimMessages: true}
|
||||
)
|
18
go.mod
Normal file
18
go.mod
Normal file
@ -0,0 +1,18 @@
|
||||
module github.com/nxshock/go-cron
|
||||
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.0.0
|
||||
github.com/antonfisher/nested-logrus-formatter v1.3.1
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/stretchr/testify v1.7.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
|
||||
)
|
23
go.sum
Normal file
23
go.sum
Normal file
@ -0,0 +1,23 @@
|
||||
github.com/BurntSushi/toml v1.0.0 h1:dtDWrepsVPfW9H/4y7dDgFc2MBUSeJhlaDtK13CxFlU=
|
||||
github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/antonfisher/nested-logrus-formatter v1.3.1 h1:NFJIr+pzwv5QLHTPyKz9UMEoHck02Q9L0FP13b/xSbQ=
|
||||
github.com/antonfisher/nested-logrus-formatter v1.3.1/go.mod h1:6WTfyWFkBc9+zyBaKIqRrg/KwMqBbodBjgbHjDz7zjA=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
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.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/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=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
58
httpserver.go
Normal file
58
httpserver.go
Normal file
@ -0,0 +1,58 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func httpServer(listenAddress string) {
|
||||
http.HandleFunc("/", handler)
|
||||
http.HandleFunc("/start", handleForceStart)
|
||||
log.WithField("job", "http_server").Fatal(http.ListenAndServe(listenAddress, nil))
|
||||
}
|
||||
|
||||
func handler(w http.ResponseWriter, r *http.Request) {
|
||||
if r.RequestURI != "/" {
|
||||
http.Error(w, "", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
currentRunningJobsMutex.RLock()
|
||||
buf := new(bytes.Buffer)
|
||||
jobEntries := c.Entries()
|
||||
var jobs []*Job
|
||||
for _, v := range jobEntries {
|
||||
jobs = append(jobs, v.Job.(*Job))
|
||||
}
|
||||
indexTemplate.ExecuteTemplate(buf, "index", jobs)
|
||||
currentRunningJobsMutex.RUnlock()
|
||||
|
||||
buf.WriteTo(w)
|
||||
}
|
||||
|
||||
func handleForceStart(w http.ResponseWriter, r *http.Request) {
|
||||
jobName := r.FormValue("jobName")
|
||||
if jobName == "" {
|
||||
http.Error(w, "job name is not specified", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
jobEntries := c.Entries()
|
||||
|
||||
for _, jobEntry := range jobEntries {
|
||||
job := jobEntry.Job.(*Job)
|
||||
if job.FileName == jobName {
|
||||
log.WithField("job", "http_server").Printf("forced start %s", job.FileName)
|
||||
go job.Run()
|
||||
time.Sleep(time.Second / 4) // wait some time for job start
|
||||
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
http.Error(w, fmt.Sprintf("there is no job with name %s", jobName), http.StatusBadRequest)
|
||||
}
|
154
index.htm
Normal file
154
index.htm
Normal file
@ -0,0 +1,154 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<title>go-cron</title>
|
||||
<style>
|
||||
/*
|
||||
Based on base16 architecture for building themes
|
||||
https://github.com/chriskempson/base16
|
||||
*/
|
||||
:root {
|
||||
--base00: #282c34;
|
||||
/* Default Background */
|
||||
--base01: #353b45;
|
||||
/* Lighter Background */
|
||||
--base02: #3e4451;
|
||||
/* Selection Background */
|
||||
--base03: #545862;
|
||||
/* Comments, Invisibles, Line Highlighting */
|
||||
--base04: #565c64;
|
||||
/* Dark Foreground (Used for status bars) */
|
||||
--base05: #abb2bf;
|
||||
/* Default Foreground, Caret, Delimiters, Operators */
|
||||
--base06: #b6bdca;
|
||||
/* Light Foreground (Not often used) */
|
||||
--base07: #c8ccd4;
|
||||
/* Light Background (Not often used) */
|
||||
--base08: #e06c75;
|
||||
/* Red */
|
||||
--base09: #d19a66;
|
||||
/* Orange */
|
||||
--base0A: #e5c07b;
|
||||
/* Yellow */
|
||||
--base0B: #98c379;
|
||||
/* Green */
|
||||
--base0C: #56b6c2;
|
||||
/* Aqua */
|
||||
--base0D: #61afef;
|
||||
/* Blue */
|
||||
--base0E: #c678dd;
|
||||
/* Purple */
|
||||
--base0F: #be5046;
|
||||
/* Dark red */
|
||||
}
|
||||
|
||||
* {
|
||||
font-family: Verdana;
|
||||
font-size: 14px;
|
||||
color: var(--base05);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html {
|
||||
height: 100%
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--base00);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-wrap: nowrap;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-top: 1em;
|
||||
font-size: 150%;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
main {
|
||||
flex-grow: 1;
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: var(--base02);
|
||||
}
|
||||
|
||||
td,
|
||||
th {
|
||||
border: 1px solid var(--base03);
|
||||
padding: 0.25em;
|
||||
}
|
||||
|
||||
button {
|
||||
width: 100%;
|
||||
color: #fff;
|
||||
background-color: var(--base01);
|
||||
border: 1px solid var(--base03);
|
||||
}
|
||||
|
||||
form {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.smaller {
|
||||
font-size: 75%;
|
||||
}
|
||||
|
||||
.nowrap {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.errorbg {
|
||||
background-color: var(--base08);
|
||||
}
|
||||
.runningbg {
|
||||
background-color: var(--base0B);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Job list</h1>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Description</th>
|
||||
<th>Cron</th>
|
||||
<th>Status</th>
|
||||
<th>Last start time</th>
|
||||
<th>Last finish time</th>
|
||||
<th>Last execution time</th>
|
||||
<th>Last error</th>
|
||||
</tr>
|
||||
{{range .}}<tr>
|
||||
<td>
|
||||
<form action="/start" method="get" id="form-{{.Name}}"></form>
|
||||
<button{{if gt .CurrentRunningCount 0}} class="runningbg"{{else}}{{if .LastError}} class="errorbg"{{end}}{{end}} type="submit" form="form-{{.Name}}" name="jobName" value="{{.Name}}"{{if gt .CurrentRunningCount 0}} disabled{{end}}>{{.Name}}</button>
|
||||
</td>
|
||||
<td class="smaller">{{.Description}}</td>
|
||||
<td class="nowrap" align="right">{{.Cron}}</td>
|
||||
<td>{{if gt .CurrentRunningCount 1}}running {{.CurrentRunningCount}} jobs{{else}}{{if .CurrentRunningCount}}running{{end}}{{end}}</td>
|
||||
<td>{{.LastStartTime}}</td>
|
||||
<td>{{.LastEndTime}}</td>
|
||||
<td align="right">{{.LastExecutionDuration}}</td>
|
||||
<td class="smaller">{{.LastError}}</td>
|
||||
</tr>{{end}}
|
||||
</table>
|
||||
</main>
|
||||
</body>
|
||||
|
||||
</html>
|
115
job.go
Normal file
115
job.go
Normal file
@ -0,0 +1,115 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type JobConfig struct {
|
||||
Cron string
|
||||
Command string
|
||||
Description string
|
||||
}
|
||||
|
||||
var currentRunningJobsMutex sync.RWMutex
|
||||
|
||||
func readJob(filePath string) (*Job, error) {
|
||||
var jobConfig JobConfig
|
||||
|
||||
_, err := toml.DecodeFile(filePath, &jobConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
command, params := parseCommand(jobConfig.Command)
|
||||
|
||||
job := &Job{
|
||||
Name: strings.TrimSuffix(filepath.Base(filePath), filepath.Ext(filePath)),
|
||||
Cron: jobConfig.Cron,
|
||||
Command: command,
|
||||
Params: params,
|
||||
FileName: strings.TrimSuffix(filepath.Base(filePath), filepath.Ext(filepath.Base(filePath))),
|
||||
Description: jobConfig.Description}
|
||||
|
||||
return job, nil
|
||||
}
|
||||
|
||||
func (js *JobConfig) Write() {
|
||||
buf := new(bytes.Buffer)
|
||||
toml.NewEncoder(buf).Encode(*js)
|
||||
ioutil.WriteFile("job.conf", buf.Bytes(), 0644)
|
||||
}
|
||||
|
||||
type Job struct {
|
||||
Name string // from filename
|
||||
|
||||
Cron string // cron decription
|
||||
Command string // command for execution
|
||||
Params []string // command params
|
||||
FileName string // short job name
|
||||
Description string // job description
|
||||
|
||||
// Fields for stats
|
||||
CurrentRunningCount int
|
||||
LastStartTime string
|
||||
LastEndTime string
|
||||
LastExecutionDuration string
|
||||
LastError string
|
||||
}
|
||||
|
||||
func (j *Job) Run() {
|
||||
startTime := time.Now()
|
||||
|
||||
currentRunningJobsMutex.Lock()
|
||||
j.CurrentRunningCount++
|
||||
j.LastStartTime = startTime.Format(timeFormat)
|
||||
currentRunningJobsMutex.Unlock()
|
||||
|
||||
jobLogFile, _ := os.OpenFile(filepath.Join(logFilesPath, j.FileName+".txt"), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
|
||||
defer jobLogFile.Close()
|
||||
defer jobLogFile.WriteString("\n")
|
||||
|
||||
l := log.New()
|
||||
l.SetOutput(jobLogFile)
|
||||
l.SetFormatter(logFormat)
|
||||
|
||||
log.WithField("job", j.FileName).Info("started")
|
||||
l.Info("started")
|
||||
|
||||
cmd := exec.Command(j.Command, j.Params...)
|
||||
cmd.Stdout = jobLogFile
|
||||
cmd.Stderr = jobLogFile
|
||||
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
log.WithField("job", j.FileName).Error(err.Error())
|
||||
l.WithField("job", j.FileName).Error(err.Error())
|
||||
|
||||
currentRunningJobsMutex.Lock()
|
||||
j.LastError = err.Error()
|
||||
currentRunningJobsMutex.Unlock()
|
||||
} else {
|
||||
currentRunningJobsMutex.Lock()
|
||||
j.LastError = ""
|
||||
currentRunningJobsMutex.Unlock()
|
||||
}
|
||||
|
||||
endTime := time.Now()
|
||||
log.WithField("job", j.FileName).Infof("finished (%s)", endTime.Sub(startTime).Truncate(time.Second).String())
|
||||
l.Infof("finished (%s)", endTime.Sub(startTime).Truncate(time.Second).String())
|
||||
|
||||
currentRunningJobsMutex.Lock()
|
||||
j.CurrentRunningCount--
|
||||
j.LastEndTime = endTime.Format(timeFormat)
|
||||
j.LastExecutionDuration = endTime.Sub(startTime).Truncate(time.Second).String()
|
||||
currentRunningJobsMutex.Unlock()
|
||||
}
|
22
job_test.go
Normal file
22
job_test.go
Normal file
@ -0,0 +1,22 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestReadJob(t *testing.T) {
|
||||
expectedJob := &Job{
|
||||
Name: "job",
|
||||
Cron: "* * * * *",
|
||||
Command: "command",
|
||||
Params: []string{"param1 param1", "param2"},
|
||||
FileName: "job",
|
||||
Description: "comment"}
|
||||
|
||||
job, err := readJob("tests/job.conf")
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, expectedJob, job)
|
||||
}
|
17
log.go
Normal file
17
log.go
Normal file
@ -0,0 +1,17 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var logFile *os.File
|
||||
|
||||
func initLogFile() {
|
||||
var err error
|
||||
logFile, err = os.OpenFile(logFileName, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
85
main.go
Normal file
85
main.go
Normal file
@ -0,0 +1,85 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"github.com/robfig/cron/v3"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var c *cron.Cron
|
||||
|
||||
func init() {
|
||||
err := os.MkdirAll(logFilesPath, 0644)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
initLogFile()
|
||||
|
||||
log.SetFormatter(logFormat)
|
||||
//multiWriter := io.MultiWriter(os.Stderr, logFile)
|
||||
//log.SetOutput(multiWriter)
|
||||
log.SetOutput(logFile)
|
||||
log.SetLevel(log.InfoLevel)
|
||||
|
||||
initTemplate()
|
||||
|
||||
go httpServer(listenAddress)
|
||||
|
||||
c = cron.New()
|
||||
}
|
||||
|
||||
func main() {
|
||||
log := log.WithField("job", "core")
|
||||
|
||||
log.Info("started")
|
||||
|
||||
err := filepath.Walk("jobs.d", func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if filepath.Ext(info.Name()) == ".conf" {
|
||||
job, err := readJob(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = c.AddJob(job.Cron, job)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
if len(c.Entries()) == 0 {
|
||||
log.Fatal("no jobs loaded")
|
||||
}
|
||||
|
||||
log.Infof("loaded jobs count: %d", len(c.Entries()))
|
||||
|
||||
c.Start()
|
||||
|
||||
intChan := make(chan os.Signal)
|
||||
signal.Notify(intChan, syscall.SIGTERM)
|
||||
<-intChan
|
||||
|
||||
log.Info("got stop signal")
|
||||
|
||||
err = logFile.Close()
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
}
|
20
parser.go
Normal file
20
parser.go
Normal file
@ -0,0 +1,20 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func parseCommand(s string) (command string, params []string) {
|
||||
quoted := false
|
||||
items := strings.FieldsFunc(s, func(r rune) bool {
|
||||
if r == '"' {
|
||||
quoted = !quoted
|
||||
}
|
||||
return !quoted && r == ' '
|
||||
})
|
||||
for i := range items {
|
||||
items[i] = strings.Trim(items[i], `"`)
|
||||
}
|
||||
|
||||
return items[0], items[1:]
|
||||
}
|
21
template.go
Normal file
21
template.go
Normal file
@ -0,0 +1,21 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"html/template"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
//go:embed index.htm
|
||||
var indexTemplateStr string
|
||||
|
||||
var indexTemplate *template.Template
|
||||
|
||||
func initTemplate() {
|
||||
var err error
|
||||
indexTemplate, err = template.New("index").Parse(indexTemplateStr) // TODO: optimize
|
||||
if err != nil {
|
||||
log.Fatalln("init template error:", err)
|
||||
}
|
||||
}
|
3
tests/job.conf
Normal file
3
tests/job.conf
Normal file
@ -0,0 +1,3 @@
|
||||
Cron = "* * * * *"
|
||||
Command = 'command "param1 param1" param2'
|
||||
Description = "comment"
|
Loading…
Reference in New Issue
Block a user