diff --git a/README.md b/README.md index 4cbc2ba..6e8caf6 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,46 @@ ## Usage -1. Create `gron.d` directory (`JobConfigsPath` variable from `gron.conf` variable). -2. Create job config in `gron.d/job1.conf` ([TOML](https://en.wikipedia.org/wiki/TOML) format). See examples in [_jobExamples](_jobExamples). +1. Create `gron.d` directory +2. Create job config in `gron.d/job1.conf` ([TOML](https://en.wikipedia.org/wiki/TOML) format): + ```toml + Type = "cmd" # command execution + Category = "Test jobs" # jobs category name + Cron = "* * * * *" # cron instructions -3. Launch `gron` binary. -4. HTTP interface available on http://127.0.0.1:9876. + Command = "echo Hello" # command to execute + ``` + + SQL job: + ```toml + Type = "sql" # sql execution + Cron = "* * * * *" # cron instructions + Description = "execute procedure every minute" # job description + + Driver = "pgx" # "pgx" for Postgresql, "oracle" for Oracle, "sqlserver" for Microsoft SQL Server + ConnectionString = "postgres://login:password@host:port/database?sslmode=disable" # each driver has different syntax + SqlText = "CALL procedure" # command to execute + ``` + + Add other options if needed: + ```toml + Description = "print Hello every minute" # job description + NumberOfRestartAttemts = 3 # number of restart attemts + RestartSec = 5 # the time to sleep before restarting a job (seconds) + RestartRule = "on-error" # Configures whether the job shall be restarted when the job process exits + + OnSuccessCmd = "echo 'Job finished.'" # execute cmd on job success + OnErrorCmd = "echo 'Error occurred: {{.Error}}'" # execute cmd on job error + + + OnSuccessHttpGetUrl = "" + OnErrorHttpGetUrl = "http://127.0.0.1/alerts?title={{.JobName}}%20failed&message={{.Error}}&tags=warning" + + OnSuccessHttpPostUrl = "http://127.0.0.1/alerts" + OnSuccessMessageFmt = "Job {{.JobName}} finished." + + OnErrorHttpPostUrl = "http://127.0.0.1/alerts" + OnErrorMessageFmt = "Job {{.JobName}} failed:\n\n{{.Error}}" + ``` +3. Launch `gron` binary +4. HTTP interface available on http://127.0.0.1:9876 diff --git a/_jobExamples/advanced.conf b/_jobExamples/advanced.conf deleted file mode 100644 index cb4719d..0000000 --- a/_jobExamples/advanced.conf +++ /dev/null @@ -1,23 +0,0 @@ -Type = "cmd" # command execution -Category = "Test jobs" # jobs category name -Description = "print 'Hello' every minute" # job description -Cron = "* * * * *" # cron instructions - -Command = "echo Hello" # command to execute - -NumberOfRestartAttemts = 3 # number of restart attemts -RestartSec = 5 # the time to sleep before restarting a job (seconds) -RestartRule = "on-error" # Configures whether the job shall be restarted when the job process exits - -OnSuccessCmd = "echo 'Job finished.'" # execute cmd on job success -OnErrorCmd = "echo 'Error occurred: {{.Error}}'" # execute cmd on job error - -# HTTP client callbacks -OnSuccessHttpGetUrl = "http://127.0.0.1/alerts?title={{.JobName}}%20finished" -OnErrorHttpGetUrl = "http://127.0.0.1/alerts?title={{.JobName}}%20failed&message={{.Error}}&tags=warning" - -OnSuccessHttpPostUrl = "http://127.0.0.1/alerts" -OnSuccessMessageFmt = "Job {{.JobName}} finished." - -OnErrorHttpPostUrl = "http://127.0.0.1/alerts" -OnErrorMessageFmt = "Job {{.JobName}} failed:\n\n{{.Error}}" \ No newline at end of file diff --git a/_jobExamples/basic.conf b/_jobExamples/basic.conf deleted file mode 100644 index 6250967..0000000 --- a/_jobExamples/basic.conf +++ /dev/null @@ -1,6 +0,0 @@ -Type = "cmd" # command execution -Category = "Test jobs" # jobs category name -Description = "print 'Hello' every minute" # job description -Cron = "* * * * *" # cron instructions - -Command = "echo Hello" # command to execute diff --git a/_jobExamples/call_sql_procedure.conf b/_jobExamples/call_sql_procedure.conf deleted file mode 100644 index d32ed5f..0000000 --- a/_jobExamples/call_sql_procedure.conf +++ /dev/null @@ -1,7 +0,0 @@ -Type = "sql" # sql execution -Cron = "* * * * *" # cron instructions -Description = "execute procedure every minute" # job description - -Driver = "pgx" # "pgx" for Postgresql, "oracle" for Oracle, "sqlserver" for Microsoft SQL Server -ConnectionString = "postgres://login:password@host:port/database?sslmode=disable" # each driver has different syntax -SqlText = "CALL procedure" # command to execute diff --git a/consts.go b/consts.go index cbc1aa4..fe248bc 100644 --- a/consts.go +++ b/consts.go @@ -1,6 +1,8 @@ package main const ( + programName = "gron" + defaultConfigFileName = "gron.conf" defaultOnSuccessMessageFmt = "Job {{.JobName}} finished." diff --git a/go.mod b/go.mod index 5e586a9..57dd24f 100644 --- a/go.mod +++ b/go.mod @@ -3,15 +3,14 @@ module github.com/nxshock/gron go 1.18 require ( - github.com/BurntSushi/toml v1.2.1 + github.com/BurntSushi/toml v1.2.0 github.com/antonfisher/nested-logrus-formatter v1.3.1 github.com/creasty/defaults v1.6.0 github.com/denisenkom/go-mssqldb v0.12.3 - github.com/gorilla/websocket v1.5.0 github.com/jackc/pgx v3.6.2+incompatible github.com/nxshock/logwriter v0.0.0-20220514172136-b1385d4106de github.com/robfig/cron/v3 v3.0.1 - github.com/sijms/go-ora/v2 v2.5.6 + github.com/sijms/go-ora/v2 v2.5.3 github.com/sirupsen/logrus v1.9.0 github.com/stretchr/testify v1.8.0 ) @@ -29,8 +28,8 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/shopspring/decimal v1.3.1 // indirect - golang.org/x/crypto v0.3.0 // indirect - golang.org/x/sys v0.2.0 // indirect + golang.org/x/crypto v0.1.0 // indirect + golang.org/x/sys v0.1.0 // indirect golang.org/x/text v0.4.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index de5e9ae..8694b4a 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,8 @@ github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw= github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0= github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8= -github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= -github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0= +github.com/BurntSushi/toml v1.2.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/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= @@ -23,8 +23,6 @@ github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0kt github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= -github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= -github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 h1:vr3AYkKovP8uR8AvSGGUK1IDqRa5lAAvEkZG1LKaCRc= github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= github.com/jackc/pgx v3.6.2+incompatible h1:2zP5OD7kiyR3xzRYMhOcXVvkDZsImVXfj+yIyTQf3/o= @@ -55,8 +53,8 @@ github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBO github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/sijms/go-ora/v2 v2.5.6 h1:V53uhbcVpPrGBICnwBJx780+lcqfbTWLCi376B7Dr5A= -github.com/sijms/go-ora/v2 v2.5.6/go.mod h1:EHxlY6x7y9HAsdfumurRfTd+v8NrEOTR3Xl4FWlH6xk= +github.com/sijms/go-ora/v2 v2.5.3 h1:klGKmhqRONVTtIzTdfYTvrW94kdJkdmZl93u2A3vchI= +github.com/sijms/go-ora/v2 v2.5.3/go.mod h1:EHxlY6x7y9HAsdfumurRfTd+v8NrEOTR3Xl4FWlH6xk= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -68,8 +66,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A= -golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -80,8 +78,8 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= diff --git a/httpserver.go b/httpserver.go index 3d1edc3..fb2fbf7 100644 --- a/httpserver.go +++ b/httpserver.go @@ -8,49 +8,11 @@ import ( "net" "net/http" "sort" - "sync" "time" - "github.com/gorilla/websocket" log "github.com/sirupsen/logrus" ) -type WsConnections struct { - connections map[*websocket.Conn]struct{} - mutex sync.Mutex -} - -func (wc *WsConnections) Add(c *websocket.Conn) { - wc.mutex.Lock() - defer wc.mutex.Unlock() - - wc.connections[c] = struct{}{} -} - -func (wc *WsConnections) Delete(c *websocket.Conn) { - wc.mutex.Lock() - defer wc.mutex.Unlock() - - delete(wc.connections, c) -} - -func (wc *WsConnections) Send(message interface{}) { - for conn := range wc.connections { - go func(conn *websocket.Conn) { _ = conn.WriteJSON(message) }(conn) - } -} - -var upgrader = websocket.Upgrader{ - ReadBufferSize: 1024, - WriteBufferSize: 1024, - CheckOrigin: func(r *http.Request) bool { - return true - }, -} - -var wsConnections = &WsConnections{ - connections: make(map[*websocket.Conn]struct{})} - func httpServer(listenAddress string) { if listenAddress == "none" { return @@ -61,7 +23,6 @@ func httpServer(listenAddress string) { http.HandleFunc("/shutdown", handleShutdown) http.HandleFunc("/start", handleForceStart) http.HandleFunc("/details", handleDetails) - http.HandleFunc("/ws", handleWebSocket) log.WithField("job", "http_server").Fatal(http.ListenAndServe(listenAddress, nil)) } @@ -78,9 +39,10 @@ func handler(w http.ResponseWriter, r *http.Request) { globalMutex.RLock() buf := new(bytes.Buffer) + jobEntries := kernel.c.Entries() jobs := make(map[string][]*Job) - for _, jobEntry := range kernel.c.Entries() { + for _, jobEntry := range jobEntries { job := jobEntry.Job.(*Job) job.NextLaunch = jobEntry.Next.Format(config.TimeFormat) jobs[job.JobConfig.Category] = append(jobs[job.JobConfig.Category], job) @@ -111,41 +73,6 @@ func handler(w http.ResponseWriter, r *http.Request) { _, _ = buf.WriteTo(w) } -func handleWebSocket(w http.ResponseWriter, r *http.Request) { - conn, err := upgrader.Upgrade(w, r, nil) - if err != nil { - return - } - - wsConnections.Add(conn) - defer wsConnections.Delete(conn) - - var startMessage struct { - JobName string - } - - for { - err := conn.ReadJSON(&startMessage) - if err != nil { - log.Println(err) - break - } - - for _, jobEntry := range kernel.c.Entries() { - job := jobEntry.Job.(*Job) - if job.Name == startMessage.JobName { - host, _, err := net.SplitHostPort(conn.RemoteAddr().String()) - if err != nil { - host = r.RemoteAddr - } - log.WithField("job", "http_server").Printf("Forced start %s from %s.", job.Name, host) - go job.Run() - break - } - } - } -} - func handleForceStart(w http.ResponseWriter, r *http.Request) { jobName := r.FormValue("jobName") if jobName == "" { @@ -153,7 +80,9 @@ func handleForceStart(w http.ResponseWriter, r *http.Request) { return } - for _, jobEntry := range kernel.c.Entries() { + jobEntries := kernel.c.Entries() + + for _, jobEntry := range jobEntries { job := jobEntry.Job.(*Job) if job.Name == jobName { host, _, err := net.SplitHostPort(r.RemoteAddr) diff --git a/job.go b/job.go index 07ab288..21691fb 100644 --- a/job.go +++ b/job.go @@ -128,16 +128,6 @@ func (j *Job) openAndMergeLog() (logEntry *log.Entry, jobLogFile *os.File) { } func (j *Job) Run() { - // TODO: переписать неоптимальный цикл - for _, jobEntry := range kernel.c.Entries() { - if jobEntry.Job.(*Job) != j { - continue - } - - j.NextLaunch = jobEntry.Next.Format(config.TimeFormat) - break - } - log, jobLogFile := j.openAndMergeLog() defer jobLogFile.Close() @@ -175,8 +165,6 @@ func (j *Job) runTry(log *log.Entry, jobLogFile *os.File) error { j.LastStartTime = startTime.Format(config.TimeFormat) globalMutex.Unlock() - wsConnections.Send(j) - var err error switch j.JobConfig.Type { case Cmd: @@ -208,8 +196,6 @@ func (j *Job) runTry(log *log.Entry, jobLogFile *os.File) error { j.LastExecutionDuration = endTime.Sub(startTime).Truncate(time.Second).String() globalMutex.Unlock() - wsConnections.Send(j) - return err } @@ -332,5 +318,12 @@ func (j *Job) errorMessage(err error) string { } func runSimpleCmd(command string, args ...string) error { - return exec.Command(command, args...).Run() + log.Println(command) + log.Println(args) + err := exec.Command(command, args...).Run() + if err != nil { + log.Println(">>", err) + } + + return err } diff --git a/strutils.go b/strutils.go index a7554c4..994d16f 100644 --- a/strutils.go +++ b/strutils.go @@ -8,6 +8,6 @@ import ( func format(fmt string, v interface{}) string { t := new(template.Template) b := new(strings.Builder) - _ = template.Must(t.Parse(fmt)).Execute(b, v) // TODO: обработать возможные ошибки + template.Must(t.Parse(fmt)).Execute(b, v) // TODO: обработать возможные ошибки return b.String() } diff --git a/webui/index.htm b/webui/index.htm index defdeed..673a867 100644 --- a/webui/index.htm +++ b/webui/index.htm @@ -6,7 +6,6 @@ gron - @@ -36,9 +35,10 @@ Details {{range (index $.Jobs .)}} - + - {{.Name}} +
+ {{.Name}} {{.JobConfig.Description}} @@ -54,54 +54,5 @@ {{end}} - \ No newline at end of file