diff --git a/README.md b/README.md index 6e8caf6..4cbc2ba 100644 --- a/README.md +++ b/README.md @@ -4,46 +4,8 @@ ## Usage -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 +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). - 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 +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 new file mode 100644 index 0000000..d45d2b5 --- /dev/null +++ b/_jobExamples/advanced.conf @@ -0,0 +1,24 @@ +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 +WorkingDir = "/tmp" # working directory + +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 new file mode 100644 index 0000000..6250967 --- /dev/null +++ b/_jobExamples/basic.conf @@ -0,0 +1,6 @@ +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 new file mode 100644 index 0000000..d32ed5f --- /dev/null +++ b/_jobExamples/call_sql_procedure.conf @@ -0,0 +1,7 @@ +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 fe248bc..408ac35 100644 --- a/consts.go +++ b/consts.go @@ -1,10 +1,12 @@ package main -const ( - programName = "gron" +import "time" +const ( defaultConfigFileName = "gron.conf" + defaultDbTimeout = 24 * time.Hour + defaultOnSuccessMessageFmt = "Job {{.JobName}} finished." defaultOnErrorMessageFmt = "Job {{.JobName}} failed:\n\n{{.Error}}" ) diff --git a/go.mod b/go.mod index 57dd24f..dc171c2 100644 --- a/go.mod +++ b/go.mod @@ -3,18 +3,21 @@ module github.com/nxshock/gron go 1.18 require ( - github.com/BurntSushi/toml v1.2.0 + github.com/BurntSushi/toml v1.3.2 github.com/antonfisher/nested-logrus-formatter v1.3.1 - github.com/creasty/defaults v1.6.0 + github.com/creasty/defaults v1.7.0 github.com/denisenkom/go-mssqldb v0.12.3 + github.com/gorilla/websocket v1.5.1 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.3 - github.com/sirupsen/logrus v1.9.0 + github.com/sijms/go-ora/v2 v2.8.10 + github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.8.0 ) +require golang.org/x/net v0.22.0 // indirect + require ( github.com/cockroachdb/apd v1.1.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect @@ -28,9 +31,9 @@ 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.1.0 // indirect - golang.org/x/sys v0.1.0 // indirect - golang.org/x/text v0.4.0 // indirect + golang.org/x/crypto v0.21.0 // indirect + golang.org/x/sys v0.18.0 // indirect + golang.org/x/text v0.14.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 8694b4a..5f5e14f 100644 --- a/go.sum +++ b/go.sum @@ -1,29 +1,27 @@ 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.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0= -github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/BurntSushi/toml v1.3.2/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= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/creasty/defaults v1.6.0 h1:ltuE9cfphUtlrBeomuu8PEyISTXnxqkBIoQfXgv7BSc= -github.com/creasty/defaults v1.6.0/go.mod h1:iGzKe6pbEHnpMPtfDXZEr0NVxWnPTjb1bbDy08fPzYM= +github.com/creasty/defaults v1.7.0 h1:eNdqZvc5B509z18lD8yc212CAqJNvfT1Jq6L8WowdBA= +github.com/creasty/defaults v1.7.0/go.mod h1:iGzKe6pbEHnpMPtfDXZEr0NVxWnPTjb1bbDy08fPzYM= 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/denisenkom/go-mssqldb v0.12.3 h1:pBSGx9Tq67pBOTLmxNuirNTeB8Vjmf886Kx+8Y+8shw= github.com/denisenkom/go-mssqldb v0.12.3/go.mod h1:k0mtMFOnU+AihqFxPMiF05rtiDrorD1Vrm1KEz5hxDo= github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= -github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0= github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= 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/jackc/fake v0.0.0-20150926172116-812a484cc733 h1:vr3AYkKovP8uR8AvSGGUK1IDqRa5lAAvEkZG1LKaCRc= +github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= +github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= 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= github.com/jackc/pgx v3.6.2+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= @@ -31,13 +29,10 @@ github.com/kardianos/service v1.2.2 h1:ZvePhAHfvo0A7Mftk/tEzqEZ7Q4lgnR8sGz4xu1YX github.com/kardianos/service v1.2.2/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs= github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= github.com/nxshock/logwriter v0.0.0-20220514172136-b1385d4106de h1:z3mN9UkoAech2jk3bYNkxy+5wn2+M6tLAbXIiSJsffI= @@ -45,32 +40,30 @@ github.com/nxshock/logwriter v0.0.0-20220514172136-b1385d4106de/go.mod h1:V24Kcz github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= 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.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= -github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= 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.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/sijms/go-ora/v2 v2.8.10 h1:Ekhx0I+A9qVBy1eOLa2eIhHWWYwVTa0MM78KS6h+5fg= +github.com/sijms/go-ora/v2 v2.8.10/go.mod h1:EHxlY6x7y9HAsdfumurRfTd+v8NrEOTR3Xl4FWlH6xk= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 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.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= -golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= 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= +golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -78,22 +71,20 @@ 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.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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= -golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/httpclient.go b/httpclient.go index eedf49d..036a59c 100644 --- a/httpclient.go +++ b/httpclient.go @@ -61,7 +61,7 @@ func httpGet(addrFmt, jobName, text string) error { if err != nil { return err } - defer resp.Body.Close() // TODO: нужно ли закрывать Body при наличии ошибки? + defer resp.Body.Close() return nil } diff --git a/httpserver.go b/httpserver.go index fb2fbf7..5926815 100644 --- a/httpserver.go +++ b/httpserver.go @@ -8,11 +8,54 @@ import ( "net" "net/http" "sort" + "sync" "time" + "github.com/gorilla/websocket" log "github.com/sirupsen/logrus" ) +type WsConnections struct { + connections map[*WsConnection]struct{} + mutex sync.Mutex +} + +func (wc *WsConnections) Add(c *websocket.Conn) { + wc.mutex.Lock() + defer wc.mutex.Unlock() + + wc.connections[NewWsConnection(c)] = struct{}{} +} + +func (wc *WsConnections) Delete(c *websocket.Conn) { + wc.mutex.Lock() + defer wc.mutex.Unlock() + + for k := range wc.connections { + if k.w == c { + delete(wc.connections, k) + break + } + } +} + +func (wc *WsConnections) Send(message interface{}) { + for conn := range wc.connections { + go func(conn *WsConnection) { _ = conn.Send(message) }(conn) + } +} + +var upgrader = websocket.Upgrader{ + ReadBufferSize: 1024, + WriteBufferSize: 1024, + CheckOrigin: func(r *http.Request) bool { + return true + }, +} + +var wsConnections = &WsConnections{ + connections: make(map[*WsConnection]struct{})} + func httpServer(listenAddress string) { if listenAddress == "none" { return @@ -23,6 +66,7 @@ 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)) } @@ -39,10 +83,9 @@ 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 jobEntries { + for _, jobEntry := range kernel.c.Entries() { job := jobEntry.Job.(*Job) job.NextLaunch = jobEntry.Next.Format(config.TimeFormat) jobs[job.JobConfig.Category] = append(jobs[job.JobConfig.Category], job) @@ -73,6 +116,41 @@ 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 == "" { @@ -80,9 +158,7 @@ func handleForceStart(w http.ResponseWriter, r *http.Request) { return } - jobEntries := kernel.c.Entries() - - for _, jobEntry := range jobEntries { + for _, jobEntry := range kernel.c.Entries() { job := jobEntry.Job.(*Job) if job.Name == jobName { host, _, err := net.SplitHostPort(r.RemoteAddr) diff --git a/job.go b/job.go index 062abb3..5cede9a 100644 --- a/job.go +++ b/job.go @@ -1,6 +1,7 @@ package main import ( + "context" "database/sql" "errors" "fmt" @@ -24,7 +25,8 @@ type JobConfig struct { Category string // JobType = Cmd - Command string // command for execution + Command string // command for execution + WorkingDir string // working directory // JobType = Sql Driver string @@ -84,8 +86,13 @@ func readJob(filePath string) (*Job, error) { return nil, fmt.Errorf("unknown job type id: %v", int(jobConfig.Type)) // TODO: add job name to log } + jobName := strings.TrimSuffix(filepath.Base(filePath), filepath.Ext(filePath)) + jobName = strings.ReplaceAll(jobName, " ", "") + jobName = strings.ReplaceAll(jobName, "_", "") + jobName = strings.ReplaceAll(jobName, "#", "") + job := &Job{ - Name: strings.TrimSuffix(filepath.Base(filePath), filepath.Ext(filePath)), + Name: jobName, Status: Inactive, JobConfig: jobConfig} @@ -111,7 +118,7 @@ func splitCommandAndParams(s string) (command string, params []string) { // jobLogFile - job log file with needs to be closed after job is done func (j *Job) openAndMergeLog() (logEntry *log.Entry, jobLogFile *os.File) { jobLogFile, _ = os.OpenFile(filepath.Join(config.LogFilesPath, j.Name+".log"), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) // TODO: handle error - jobLogFile.WriteString("\n") + _, _ = jobLogFile.WriteString("\n") logWriter := io.MultiWriter(mainLogFile, jobLogFile) @@ -128,6 +135,16 @@ 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() @@ -165,6 +182,8 @@ 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: @@ -185,7 +204,7 @@ func (j *Job) runTry(log *log.Entry, jobLogFile *os.File) error { j.LastError = "" globalMutex.Unlock() } - go j.runFinishCallback(err) + go j.runFinishCallback(log, err) endTime := time.Now() log.Infof("Finished (%s).", endTime.Sub(startTime).Truncate(time.Second).String()) @@ -196,6 +215,8 @@ 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 } @@ -205,6 +226,7 @@ func (j *Job) runCmd(jobLogFile *os.File) error { cmd := exec.Command(command, params...) cmd.Stdout = jobLogFile cmd.Stderr = jobLogFile + cmd.Dir = j.JobConfig.WorkingDir return cmd.Run() } @@ -226,7 +248,10 @@ func (j *Job) runSql(jobLogFile *os.File) error { } defer db.Close() - _, err = db.Exec(j.JobConfig.SqlText) + ctx, cancel := context.WithTimeout(context.Background(), defaultDbTimeout) + defer cancel() + + _, err = db.ExecContext(ctx, j.JobConfig.SqlText) if err != nil { return err } @@ -234,44 +259,53 @@ func (j *Job) runSql(jobLogFile *os.File) error { return nil } -func (j *Job) runFinishCallback(err error) error { +func (j *Job) runFinishCallback(log *log.Entry, jobErr error) error { s := j.JobConfig.OnSuccessCmd // Fill variables errStr := "" - if err != nil { - errStr = err.Error() + if jobErr != nil { + errStr = jobErr.Error() } - if err != nil { + if jobErr != nil { s = format(s, struct{ Error string }{Error: errStr}) } - if err == nil && j.JobConfig.OnSuccessCmd != "" { + if jobErr == nil && j.JobConfig.OnSuccessCmd != "" { cmd, params := splitCommandAndParams(s) return runSimpleCmd(cmd, params...) } - if err == nil && j.JobConfig.OnSuccessHttpPostUrl != "" { - httpPost(j.JobConfig.OnSuccessHttpPostUrl, j.successMessage()) // TODO: обработать ошибку + if jobErr == nil && j.JobConfig.OnSuccessHttpPostUrl != "" { + err := httpPost(j.JobConfig.OnSuccessHttpPostUrl, j.successMessage()) + if err != nil { + log.Error(err) + } } - if err == nil && j.JobConfig.OnSuccessHttpGetUrl != "" { - httpGet(j.JobConfig.OnSuccessHttpPostUrl, j.Name, j.successMessage()) // TODO: обработать ошибку + if jobErr == nil && j.JobConfig.OnSuccessHttpGetUrl != "" { + err := httpGet(j.JobConfig.OnSuccessHttpPostUrl, j.Name, j.successMessage()) + if err != nil { + log.Error(err) + } } - if err != nil && j.JobConfig.OnErrorCmd != "" { + if jobErr != nil && j.JobConfig.OnErrorCmd != "" { cmd, params := splitCommandAndParams(s) return runSimpleCmd(cmd, params...) } - if err != nil && j.JobConfig.OnErrorHttpPostUrl != "" { - httpPost(j.JobConfig.OnErrorHttpPostUrl, j.errorMessage(err)) // TODO: обработать ошибку + if jobErr != nil && j.JobConfig.OnErrorHttpPostUrl != "" { + err := httpPost(j.JobConfig.OnErrorHttpPostUrl, j.errorMessage(jobErr)) + if err != nil { + log.Error(err) + } } - if err != nil && j.JobConfig.OnErrorHttpGetUrl != "" { - err2 := httpGet(j.JobConfig.OnErrorHttpGetUrl, j.Name, err.Error()) - if err2 != nil { - log.Errorf("OnErrorHttpGetUrl error: %v", err2) // TODO: сделать формат сообщения по стандарту + if jobErr != nil && j.JobConfig.OnErrorHttpGetUrl != "" { + err := httpGet(j.JobConfig.OnErrorHttpGetUrl, j.Name, jobErr.Error()) + if err != nil { + log.Errorf("OnErrorHttpGetUrl error: %v", err) // TODO: сделать формат сообщения по стандарту } } @@ -309,12 +343,5 @@ func (j *Job) errorMessage(err error) string { } func runSimpleCmd(command string, args ...string) error { - log.Println(command) - log.Println(args) - err := exec.Command(command, args...).Run() - if err != nil { - log.Println(">>", err) - } - - return err + return exec.Command(command, args...).Run() } diff --git a/main.go b/main.go index 4d55ea7..46e1667 100644 --- a/main.go +++ b/main.go @@ -94,6 +94,6 @@ func main() { err = s.Run() if err != nil { - logger.Error(err) + _ = logger.Error(err) } } diff --git a/make.bat b/make.bat index d8d144b..204b24b 100644 --- a/make.bat +++ b/make.bat @@ -1 +1 @@ -go build -ldflags "-s -w -H windowsgui" -buildmode=pie -trimpath +go build -ldflags "-s -w" -buildmode=pie -trimpath diff --git a/strutils.go b/strutils.go index 994d16f..a7554c4 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/wconn.go b/wconn.go new file mode 100644 index 0000000..ea1c72a --- /dev/null +++ b/wconn.go @@ -0,0 +1,23 @@ +package main + +import ( + "sync" + + "github.com/gorilla/websocket" +) + +type WsConnection struct { + w *websocket.Conn + mu sync.Mutex +} + +func NewWsConnection(w *websocket.Conn) *WsConnection { + return &WsConnection{w: w} +} + +func (w *WsConnection) Send(message any) error { + w.mu.Lock() + defer w.mu.Unlock() + + return w.w.WriteJSON(message) +} diff --git a/webui/index.htm b/webui/index.htm index 673a867..9bb71f4 100644 --- a/webui/index.htm +++ b/webui/index.htm @@ -6,6 +6,7 @@ gron + @@ -35,10 +36,9 @@ Details {{range (index $.Jobs .)}} - + -
- {{.Name}} + {{.Name}} {{.JobConfig.Description}} @@ -54,5 +54,56 @@ {{end}} + \ No newline at end of file diff --git a/webui/style.css b/webui/style.css index 2f9f7d9..d678a6d 100644 --- a/webui/style.css +++ b/webui/style.css @@ -106,6 +106,7 @@ th { table button { width: 100%; + text-align: left; color: #fff; background-color: var(--base01); border: 0; @@ -158,7 +159,7 @@ pre { .dropdown { position: relative; - display: inline-block; + display: block; float: right; }