Continue implmenting base features
This commit is contained in:
parent
9d51740b05
commit
f27366251c
116
app/main.go
116
app/main.go
|
|
@ -5,16 +5,14 @@ import (
|
|||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
|
||||
"jobwatch/state"
|
||||
"jobwatch/types"
|
||||
)
|
||||
|
|
@ -71,47 +69,12 @@ func evalOutput(job types.Job, output []string) (types.CMKState, error) {
|
|||
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")
|
||||
}
|
||||
func runJob(job types.Job) (state.State, error) {
|
||||
|
||||
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) {
|
||||
st := state.StateFromJob(job)
|
||||
st.LastExitCode = -1
|
||||
st.LastState = int(types.UNKNOWN)
|
||||
st.LastRun = time.Now().Format(time.RFC3339)
|
||||
|
||||
cmd := exec.Command(job.Cmd, append(job.Args, job.InstArgs...)...)
|
||||
|
||||
|
|
@ -124,43 +87,48 @@ func runJob(job types.Job) (types.CMKState, error) {
|
|||
cmd.Stderr = io.MultiWriter(os.Stderr, &stderrBuf, &stdcombinedBuf)
|
||||
}
|
||||
|
||||
var state = 0
|
||||
var stcode = 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()
|
||||
stcode = exitError.ProcessState.ExitCode()
|
||||
} else {
|
||||
// cmd not found etc
|
||||
return types.UNKNOWN, err
|
||||
return st, err
|
||||
}
|
||||
if state == 0 { // Exec failed, but exitcode, be sure to get error!
|
||||
state = int(types.UNKNOWN)
|
||||
if stcode == 0 { // Exec failed, but exitcode, be sure to get error!
|
||||
stcode = int(types.UNKNOWN)
|
||||
}
|
||||
} else {
|
||||
state = cmd.ProcessState.ExitCode()
|
||||
stcode = cmd.ProcessState.ExitCode()
|
||||
}
|
||||
log.Printf("- ExitCode of Command: %v", state)
|
||||
var exitCode = stcode
|
||||
log.Printf("- ExitCode of Command: %v", stcode)
|
||||
|
||||
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 (stcode == int(em.From)) || (em.From == -1 && stcode > 0) {
|
||||
stcode = int(em.To)
|
||||
}
|
||||
}
|
||||
if state > int(types.UNKNOWN) {
|
||||
state = int(types.CRIT)
|
||||
if stcode > int(types.UNKNOWN) {
|
||||
stcode = int(types.CRIT)
|
||||
}
|
||||
log.Printf("- State after ExitCodeMapping: %v", state)
|
||||
log.Printf("- State after ExitCodeMapping: %v", stcode)
|
||||
|
||||
outText := string(stdcombinedBuf.Bytes())
|
||||
log_state, err := evalOutput(job, strings.Split(outText, "\n"))
|
||||
if int(log_state) > state {
|
||||
state = int(log_state)
|
||||
if int(log_state) > stcode {
|
||||
stcode = int(log_state)
|
||||
}
|
||||
|
||||
log.Printf("- State Output-evaluation: %v", state)
|
||||
log.Printf("- State after Output-evaluation: %v", stcode)
|
||||
|
||||
return types.CMKState(state), err
|
||||
st.LastExitCode = exitCode
|
||||
st.LastState = stcode
|
||||
st.LastRun = time.Now().Format(time.RFC3339)
|
||||
|
||||
return st, err
|
||||
}
|
||||
|
||||
func setup() (*types.Job, error) {
|
||||
|
|
@ -178,17 +146,26 @@ func setup() (*types.Job, error) {
|
|||
if debug {
|
||||
log.SetOutput(os.Stderr)
|
||||
} else {
|
||||
log.SetOutput(nil)
|
||||
log.SetOutput(ioutil.Discard)
|
||||
}
|
||||
|
||||
if !regexp.MustCompile("^[a-z0-9]*$").MatchString(job_instance) {
|
||||
return nil, fmt.Errorf("-i invalid chars")
|
||||
if jobdir == "" && jobid == "" && job_instance == "" {
|
||||
// No params -> Show mode
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if !regexp.MustCompile("^[a-z0-9\\-]*$").MatchString(jobid) {
|
||||
return nil, fmt.Errorf("-j: invalid chars")
|
||||
}
|
||||
|
||||
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 {
|
||||
if job, err := types.LoadJob(jobdir, jobid); err == nil {
|
||||
job.InstArgs = flag.Args()
|
||||
job.InstId = job_instance
|
||||
return job, nil
|
||||
|
|
@ -202,17 +179,26 @@ func main() {
|
|||
|
||||
job, err := setup()
|
||||
|
||||
var res = types.UNKNOWN
|
||||
if job == nil && err == nil {
|
||||
fmt.Println("<<<jobwatch>>>")
|
||||
state.ShowAll()
|
||||
} else {
|
||||
var res state.State
|
||||
if err == nil {
|
||||
res, err = runJob(*job)
|
||||
}
|
||||
|
||||
res.ReEval()
|
||||
res.SaveState()
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("! Error running job %+v\n", err)
|
||||
os.Exit(int(types.UNKNOWN))
|
||||
} else {
|
||||
os.Exit(int(res))
|
||||
os.Exit(int(res.LastState))
|
||||
}
|
||||
|
||||
state.WriteLog(*job)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,16 @@
|
|||
package state
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"jobwatch/timespec"
|
||||
"io/ioutil"
|
||||
"jobwatch/types"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
/*
|
||||
|
|
@ -15,13 +22,194 @@ 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"`
|
||||
LastRunAge int `json:"last_run_age_seconds"`
|
||||
LastRunAgeState int `json:"last_run_age_state"`
|
||||
LastRunWarnAt timespec.TimeSpec `json:"last_run_warn_at"`
|
||||
LastRunCritAt timespec.TimeSpec `json:"last_run_crit_at"`
|
||||
LastRunWarnAt types.TimeSpec `json:"last_run_warn_at"`
|
||||
LastRunCritAt types.TimeSpec `json:"last_run_crit_at"`
|
||||
LastExitCode int `json:"last_exit_code"`
|
||||
LastState int `json:"last_state"`
|
||||
}
|
||||
|
||||
type summaryEntry struct {
|
||||
State State `json:"state"`
|
||||
LogEntries []string `json:"log_entries"`
|
||||
}
|
||||
|
||||
type summaryPerUser struct {
|
||||
UserId string `json:"userd_id"`
|
||||
Path string `json:"-"`
|
||||
Entries []summaryEntry `json:"entries"`
|
||||
}
|
||||
|
||||
type summary struct {
|
||||
Entries []summaryPerUser `json:"all"`
|
||||
}
|
||||
|
||||
func getStateDir() (string, error) {
|
||||
var dir string
|
||||
dir = "/var/lib/jobwatch/states"
|
||||
|
||||
if os.Getuid() > 0 {
|
||||
if od, err := os.UserHomeDir(); err == nil {
|
||||
dir = od + "/.jobwatch/states"
|
||||
}
|
||||
}
|
||||
|
||||
err := os.MkdirAll(dir, 0770)
|
||||
|
||||
return dir, err
|
||||
}
|
||||
|
||||
func findAllStateDirs() []summaryPerUser {
|
||||
var dirs []summaryPerUser
|
||||
var res []summaryPerUser
|
||||
|
||||
var su summaryPerUser
|
||||
su.UserId = "root"
|
||||
su.Path = "/var/lib/jobwatch/states"
|
||||
|
||||
dirs = append(dirs, su)
|
||||
f, err := os.Open("/etc/passwd")
|
||||
if err == nil {
|
||||
defer f.Close()
|
||||
|
||||
rdr := bufio.NewReader(f)
|
||||
for {
|
||||
line, err := rdr.ReadString(10)
|
||||
parts := strings.Split(line, ":")
|
||||
|
||||
if len(parts) > 6 {
|
||||
hd := parts[5]
|
||||
var su summaryPerUser
|
||||
su.UserId = parts[0]
|
||||
su.Path = filepath.FromSlash(hd + "/.jobwatch/states")
|
||||
dirs = append(dirs, su)
|
||||
}
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for _, d := range dirs {
|
||||
if _, err := os.Stat(d.Path); err == nil {
|
||||
res = append(res, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func LoadState(path string) (*State, error) {
|
||||
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var res State
|
||||
err = json.Unmarshal(data, &res)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error unmashaling state: %v", err)
|
||||
}
|
||||
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
func WriteLog(job types.Job, line ...string) {
|
||||
fmt.Println("B")
|
||||
}
|
||||
|
||||
func (state *State) ReEval() error {
|
||||
lr, err := time.Parse(time.RFC3339, state.LastRun)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
age := time.Now().Sub(lr)
|
||||
state.LastRunAge = int(age.Seconds())
|
||||
if age.Seconds() > float64(state.LastRunCritAt.AsSeconds()) {
|
||||
state.LastRunAgeState = int(types.CRIT)
|
||||
} else if age.Seconds() > float64(state.LastRunWarnAt.AsSeconds()) {
|
||||
state.LastRunAgeState = int(types.WARN)
|
||||
} else {
|
||||
state.LastRunAgeState = int(types.OK)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func StateFromJob(job types.Job) State {
|
||||
var res State
|
||||
res.JobId = job.Id
|
||||
res.JobInstanceId = job.InstId
|
||||
res.LastRunCritAt = job.LastRunCrit
|
||||
res.LastRunWarnAt = job.LastRunWarn
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func (st State) SaveState() error {
|
||||
dir, err := getStateDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err := json.MarshalIndent(st, " ", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error mashaling state: %v", err)
|
||||
}
|
||||
|
||||
if st.JobInstanceId == "" {
|
||||
dir = filepath.FromSlash(fmt.Sprintf("%v/%v.state.json", dir, st.JobId))
|
||||
} else {
|
||||
dir = filepath.FromSlash(fmt.Sprintf("%v/%v_%v.state.json", dir, st.JobId, st.JobInstanceId))
|
||||
}
|
||||
|
||||
if err := os.WriteFile(dir, data, 0600); err != nil {
|
||||
return fmt.Errorf("Error writing state file %v: %v", dir, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ShowAll() error {
|
||||
dirs := findAllStateDirs()
|
||||
|
||||
for i, d := range dirs {
|
||||
fsi, err := ioutil.ReadDir(d.Path)
|
||||
log.Printf("Scanning path %v", d.Path)
|
||||
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, e := range fsi {
|
||||
p := filepath.FromSlash(d.Path + "/" + e.Name())
|
||||
log.Printf("Processing state file %v", p)
|
||||
|
||||
st, err := LoadState(p)
|
||||
if err == nil {
|
||||
var se summaryEntry
|
||||
se.State = *st
|
||||
if err := se.State.ReEval(); err != nil {
|
||||
log.Printf("Ignoring error while reeval: %v", err)
|
||||
}
|
||||
d.Entries = append(d.Entries, se)
|
||||
dirs[i] = d
|
||||
} else {
|
||||
log.Printf("Ignoring error processing state file %v: %v", p, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(dirs) > 0 {
|
||||
bytes, err := json.MarshalIndent(dirs, " ", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("%v", string(bytes))
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package timespec
|
||||
package types
|
||||
|
||||
type TimeUnit string
|
||||
|
||||
|
|
@ -10,8 +10,8 @@ const (
|
|||
)
|
||||
|
||||
type TimeSpec struct {
|
||||
Value int `yaml:"val"`
|
||||
Unit TimeUnit `yaml:"unit"`
|
||||
Value int `yaml:"val" json:"val"`
|
||||
Unit TimeUnit `yaml:"unit" json:"unit"`
|
||||
}
|
||||
|
||||
func (e TimeSpec) HasValidUnit() bool {
|
||||
|
|
@ -1,6 +1,13 @@
|
|||
package types
|
||||
|
||||
import "jobwatch/timespec"
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
type CMKState int32
|
||||
|
||||
|
|
@ -23,6 +30,7 @@ type ExistCodeMapEntry struct {
|
|||
}
|
||||
|
||||
type Job struct {
|
||||
Id string
|
||||
Cmd string `yaml:"cmd"`
|
||||
Args []string `yaml:"args"`
|
||||
OutputLog string
|
||||
|
|
@ -31,6 +39,47 @@ type Job struct {
|
|||
LogMatches []LogMatch `yaml:"log_matches"`
|
||||
InstArgs []string
|
||||
InstId string
|
||||
LastRunWarn timespec.TimeSpec `yaml:"last_run_warn"`
|
||||
LastRunCrit timespec.TimeSpec `yaml:"last_run_crit"`
|
||||
LastRunWarn TimeSpec `yaml:"last_run_warn"`
|
||||
LastRunCrit TimeSpec `yaml:"last_run_crit"`
|
||||
}
|
||||
|
||||
func LoadJob(jobdir string, jobid string) (*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 := 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)
|
||||
}
|
||||
job.Id = jobid
|
||||
return &job, nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue