Initial
This commit is contained in:
commit
9d51740b05
|
|
@ -0,0 +1,5 @@
|
||||||
|
module jobwatch
|
||||||
|
|
||||||
|
go 1.18
|
||||||
|
|
||||||
|
require gopkg.in/yaml.v2 v2.4.0
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
|
@ -0,0 +1,218 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
|
||||||
|
"jobwatch/state"
|
||||||
|
"jobwatch/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Jobs root-idfile
|
||||||
|
|
||||||
|
func evalOutput(job types.Job, output []string) (types.CMKState, error) {
|
||||||
|
var res types.CMKState = types.OK
|
||||||
|
var logLines []string
|
||||||
|
var rcs []*regexp.Regexp
|
||||||
|
|
||||||
|
for _, m := range job.LogMatches {
|
||||||
|
r, err := regexp.Compile(m.Regex)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
} else {
|
||||||
|
rcs = append(rcs, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine state and wanted loglines
|
||||||
|
for _, v := range output {
|
||||||
|
for idx, m := range job.LogMatches {
|
||||||
|
if rcs[idx].Match([]byte(v)) {
|
||||||
|
if m.State > res {
|
||||||
|
res = m.State
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.AltMsg != "" {
|
||||||
|
v = fmt.Sprintf(m.AltMsg, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
var p = ""
|
||||||
|
switch s := m.State; s {
|
||||||
|
case types.OK:
|
||||||
|
p = "O"
|
||||||
|
case types.WARN:
|
||||||
|
p = "W"
|
||||||
|
case types.CRIT:
|
||||||
|
p = "C"
|
||||||
|
case types.UNKNOWN:
|
||||||
|
p = "U"
|
||||||
|
}
|
||||||
|
p += ": "
|
||||||
|
logLines = append(logLines, time.Now().Format("2006-01-2 15:04:05")+" "+p+v)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, oln := range logLines {
|
||||||
|
log.Printf("- Matched LogLine %v\n", oln)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadJob(jobdir string, jobid string) (*types.Job, error) {
|
||||||
|
// Find jobfile
|
||||||
|
var dirs []string
|
||||||
|
if jobdir != "" {
|
||||||
|
dirs = append(dirs, jobdir)
|
||||||
|
} else {
|
||||||
|
if hd, err := os.UserHomeDir(); err == nil {
|
||||||
|
dirs = append(dirs, filepath.FromSlash(hd)+"/jobwatch.d")
|
||||||
|
}
|
||||||
|
dirs = append(dirs, "/etc/jobwatch.d")
|
||||||
|
}
|
||||||
|
|
||||||
|
var jobfile = ""
|
||||||
|
for _, d := range dirs {
|
||||||
|
jobfile = filepath.FromSlash(d + "/" + jobid + ".job.yml")
|
||||||
|
if _, err := os.Stat(jobfile); err == nil {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
jobfile = ""
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if jobfile == "" {
|
||||||
|
return nil, fmt.Errorf("JobFile not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load job from jobfile
|
||||||
|
job := types.Job{}
|
||||||
|
bytes, _ := os.ReadFile(jobfile)
|
||||||
|
err := yaml.Unmarshal(bytes, &job)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
log.Printf("%+v\n", job)
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("! Error reading job %v: %+v\n", jobfile, err)
|
||||||
|
}
|
||||||
|
return &job, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func runJob(job types.Job) (types.CMKState, error) {
|
||||||
|
|
||||||
|
cmd := exec.Command(job.Cmd, append(job.Args, job.InstArgs...)...)
|
||||||
|
|
||||||
|
var stdoutBuf, stderrBuf, stdcombinedBuf bytes.Buffer
|
||||||
|
if job.HideOutput {
|
||||||
|
cmd.Stdout = io.MultiWriter(&stdoutBuf, &stdcombinedBuf)
|
||||||
|
cmd.Stderr = io.MultiWriter(&stderrBuf, &stdcombinedBuf)
|
||||||
|
} else {
|
||||||
|
cmd.Stdout = io.MultiWriter(os.Stdout, &stdoutBuf, &stdcombinedBuf)
|
||||||
|
cmd.Stderr = io.MultiWriter(os.Stderr, &stderrBuf, &stdcombinedBuf)
|
||||||
|
}
|
||||||
|
|
||||||
|
var state = 0
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
// cmd executed, but failed w/ non-zero
|
||||||
|
if exitError, ok := err.(*exec.ExitError); ok {
|
||||||
|
state = exitError.ProcessState.ExitCode()
|
||||||
|
} else {
|
||||||
|
// cmd not found etc
|
||||||
|
return types.UNKNOWN, err
|
||||||
|
}
|
||||||
|
if state == 0 { // Exec failed, but exitcode, be sure to get error!
|
||||||
|
state = int(types.UNKNOWN)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
state = cmd.ProcessState.ExitCode()
|
||||||
|
}
|
||||||
|
log.Printf("- ExitCode of Command: %v", state)
|
||||||
|
|
||||||
|
for _, em := range job.ExitCodeMap {
|
||||||
|
// From == -1 -> Alle non-zero-states mappen
|
||||||
|
if (state == int(em.From)) || (em.From == -1 && state > 0) {
|
||||||
|
state = int(em.To)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if state > int(types.UNKNOWN) {
|
||||||
|
state = int(types.CRIT)
|
||||||
|
}
|
||||||
|
log.Printf("- State after ExitCodeMapping: %v", state)
|
||||||
|
|
||||||
|
outText := string(stdcombinedBuf.Bytes())
|
||||||
|
log_state, err := evalOutput(job, strings.Split(outText, "\n"))
|
||||||
|
if int(log_state) > state {
|
||||||
|
state = int(log_state)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("- State Output-evaluation: %v", state)
|
||||||
|
|
||||||
|
return types.CMKState(state), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func setup() (*types.Job, error) {
|
||||||
|
var jobdir string
|
||||||
|
var jobid string
|
||||||
|
var job_instance string
|
||||||
|
var debug bool
|
||||||
|
|
||||||
|
flag.StringVar(&jobdir, "jd", "", "JobDir. Default: $HOME/etc/jobwatch.d /etc/jobwatch.d'")
|
||||||
|
flag.StringVar(&jobid, "j", "", "JobId. reads $jobDir/$job.job.yml Default ''")
|
||||||
|
flag.StringVar(&job_instance, "i", "", "JobId Instance. Default ''")
|
||||||
|
flag.BoolVar(&debug, "d", false, "Debug")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if debug {
|
||||||
|
log.SetOutput(os.Stderr)
|
||||||
|
} else {
|
||||||
|
log.SetOutput(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !regexp.MustCompile("^[a-z0-9]*$").MatchString(job_instance) {
|
||||||
|
return nil, fmt.Errorf("-i invalid chars")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf(". Raw args : %+v\n", flag.Args())
|
||||||
|
log.Printf(". Job onstance id : %+v\n", job_instance)
|
||||||
|
|
||||||
|
if job, err := loadJob(jobdir, jobid); err == nil {
|
||||||
|
job.InstArgs = flag.Args()
|
||||||
|
job.InstId = job_instance
|
||||||
|
return job, nil
|
||||||
|
} else {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
fmt.Println("Jobwatch 0.1")
|
||||||
|
|
||||||
|
job, err := setup()
|
||||||
|
|
||||||
|
var res = types.UNKNOWN
|
||||||
|
if err == nil {
|
||||||
|
res, err = runJob(*job)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("! Error running job %+v\n", err)
|
||||||
|
os.Exit(int(types.UNKNOWN))
|
||||||
|
} else {
|
||||||
|
os.Exit(int(res))
|
||||||
|
}
|
||||||
|
|
||||||
|
state.WriteLog(*job)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
cmd: /usr/bin/w
|
||||||
|
args:
|
||||||
|
- -i
|
||||||
|
exitcode_map:
|
||||||
|
- from: 23
|
||||||
|
to: 3
|
||||||
|
- from: -1
|
||||||
|
to: 1
|
||||||
|
- from: 0
|
||||||
|
to: 1
|
||||||
|
log_matches:
|
||||||
|
- regex: .*192.168.*.*
|
||||||
|
state: 1
|
||||||
|
- regex: "-"
|
||||||
|
state: 2
|
||||||
|
alt_msg: "User logged in at console (%v)"
|
||||||
|
hide_output: True
|
||||||
|
last_run_warn:
|
||||||
|
val: 8
|
||||||
|
unit: "h"
|
||||||
|
last_run_crit:
|
||||||
|
val: 16
|
||||||
|
unit: "h"
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
package state
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"jobwatch/timespec"
|
||||||
|
"jobwatch/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
||||||
|
type State struct {
|
||||||
|
JobId string `json:"job_id"`
|
||||||
|
JobInstanceId string `json:"job_inst_id"`
|
||||||
|
LastRun string `json:"last_run"`
|
||||||
|
LastRunAge int `json:"last_run_age"`
|
||||||
|
LastRunAgeState int `json:"last_run_age_state"`
|
||||||
|
LastRunWarnAt timespec.TimeSpec `json:"last_run_warn_at"`
|
||||||
|
LastRunCritAt timespec.TimeSpec `json:"last_run_crit_at"`
|
||||||
|
LastExitCode int `json:"last_exit_code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteLog(job types.Job, line ...string) {
|
||||||
|
fmt.Println("B")
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
package timespec
|
||||||
|
|
||||||
|
type TimeUnit string
|
||||||
|
|
||||||
|
const (
|
||||||
|
Sec = "s"
|
||||||
|
Min = "m"
|
||||||
|
Hour = "h"
|
||||||
|
Day = "d"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TimeSpec struct {
|
||||||
|
Value int `yaml:"val"`
|
||||||
|
Unit TimeUnit `yaml:"unit"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e TimeSpec) HasValidUnit() bool {
|
||||||
|
return (e.Unit == "" ||
|
||||||
|
e.Unit == Sec ||
|
||||||
|
e.Unit == Min ||
|
||||||
|
e.Unit == Hour ||
|
||||||
|
e.Unit == Day)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e TimeSpec) AsSeconds() int {
|
||||||
|
if e.Unit == "" || e.Unit == Min {
|
||||||
|
return e.Value * 60
|
||||||
|
} else if e.Unit == "" || e.Unit == Hour {
|
||||||
|
return e.Value * 60 * 60
|
||||||
|
} else if e.Unit == "" || e.Unit == Day {
|
||||||
|
return e.Value * 60 * 60 * 24
|
||||||
|
}
|
||||||
|
return e.Value
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
package types
|
||||||
|
|
||||||
|
import "jobwatch/timespec"
|
||||||
|
|
||||||
|
type CMKState int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
OK CMKState = iota
|
||||||
|
WARN
|
||||||
|
CRIT
|
||||||
|
UNKNOWN
|
||||||
|
)
|
||||||
|
|
||||||
|
type LogMatch struct {
|
||||||
|
Regex string `yaml:"regex"`
|
||||||
|
State CMKState `yaml:"state"`
|
||||||
|
AltMsg string `yaml:"alt_msg"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExistCodeMapEntry struct {
|
||||||
|
From int32 `yaml:"from"`
|
||||||
|
To int32 `yaml:"to"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Job struct {
|
||||||
|
Cmd string `yaml:"cmd"`
|
||||||
|
Args []string `yaml:"args"`
|
||||||
|
OutputLog string
|
||||||
|
ExitCodeMap []ExistCodeMapEntry `yaml:"exitcode_map"`
|
||||||
|
HideOutput bool `yaml:"hide_output"`
|
||||||
|
LogMatches []LogMatch `yaml:"log_matches"`
|
||||||
|
InstArgs []string
|
||||||
|
InstId string
|
||||||
|
LastRunWarn timespec.TimeSpec `yaml:"last_run_warn"`
|
||||||
|
LastRunCrit timespec.TimeSpec `yaml:"last_run_crit"`
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue