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"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gopkg.in/yaml.v2"
|
|
||||||
|
|
||||||
"jobwatch/state"
|
"jobwatch/state"
|
||||||
"jobwatch/types"
|
"jobwatch/types"
|
||||||
)
|
)
|
||||||
|
|
@ -71,47 +69,12 @@ func evalOutput(job types.Job, output []string) (types.CMKState, error) {
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadJob(jobdir string, jobid string) (*types.Job, error) {
|
func runJob(job types.Job) (state.State, 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 = ""
|
st := state.StateFromJob(job)
|
||||||
for _, d := range dirs {
|
st.LastExitCode = -1
|
||||||
jobfile = filepath.FromSlash(d + "/" + jobid + ".job.yml")
|
st.LastState = int(types.UNKNOWN)
|
||||||
if _, err := os.Stat(jobfile); err == nil {
|
st.LastRun = time.Now().Format(time.RFC3339)
|
||||||
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...)...)
|
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)
|
cmd.Stderr = io.MultiWriter(os.Stderr, &stderrBuf, &stdcombinedBuf)
|
||||||
}
|
}
|
||||||
|
|
||||||
var state = 0
|
var stcode = 0
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
// cmd executed, but failed w/ non-zero
|
// cmd executed, but failed w/ non-zero
|
||||||
if exitError, ok := err.(*exec.ExitError); ok {
|
if exitError, ok := err.(*exec.ExitError); ok {
|
||||||
state = exitError.ProcessState.ExitCode()
|
stcode = exitError.ProcessState.ExitCode()
|
||||||
} else {
|
} else {
|
||||||
// cmd not found etc
|
// cmd not found etc
|
||||||
return types.UNKNOWN, err
|
return st, err
|
||||||
}
|
}
|
||||||
if state == 0 { // Exec failed, but exitcode, be sure to get error!
|
if stcode == 0 { // Exec failed, but exitcode, be sure to get error!
|
||||||
state = int(types.UNKNOWN)
|
stcode = int(types.UNKNOWN)
|
||||||
}
|
}
|
||||||
} else {
|
} 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 {
|
for _, em := range job.ExitCodeMap {
|
||||||
// From == -1 -> Alle non-zero-states mappen
|
// From == -1 -> Alle non-zero-states mappen
|
||||||
if (state == int(em.From)) || (em.From == -1 && state > 0) {
|
if (stcode == int(em.From)) || (em.From == -1 && stcode > 0) {
|
||||||
state = int(em.To)
|
stcode = int(em.To)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if state > int(types.UNKNOWN) {
|
if stcode > int(types.UNKNOWN) {
|
||||||
state = int(types.CRIT)
|
stcode = int(types.CRIT)
|
||||||
}
|
}
|
||||||
log.Printf("- State after ExitCodeMapping: %v", state)
|
log.Printf("- State after ExitCodeMapping: %v", stcode)
|
||||||
|
|
||||||
outText := string(stdcombinedBuf.Bytes())
|
outText := string(stdcombinedBuf.Bytes())
|
||||||
log_state, err := evalOutput(job, strings.Split(outText, "\n"))
|
log_state, err := evalOutput(job, strings.Split(outText, "\n"))
|
||||||
if int(log_state) > state {
|
if int(log_state) > stcode {
|
||||||
state = int(log_state)
|
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) {
|
func setup() (*types.Job, error) {
|
||||||
|
|
@ -178,17 +146,26 @@ func setup() (*types.Job, error) {
|
||||||
if debug {
|
if debug {
|
||||||
log.SetOutput(os.Stderr)
|
log.SetOutput(os.Stderr)
|
||||||
} else {
|
} else {
|
||||||
log.SetOutput(nil)
|
log.SetOutput(ioutil.Discard)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !regexp.MustCompile("^[a-z0-9]*$").MatchString(job_instance) {
|
if jobdir == "" && jobid == "" && job_instance == "" {
|
||||||
return nil, fmt.Errorf("-i invalid chars")
|
// 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(". Raw args : %+v\n", flag.Args())
|
||||||
log.Printf(". Job onstance id : %+v\n", job_instance)
|
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.InstArgs = flag.Args()
|
||||||
job.InstId = job_instance
|
job.InstId = job_instance
|
||||||
return job, nil
|
return job, nil
|
||||||
|
|
@ -202,17 +179,26 @@ func main() {
|
||||||
|
|
||||||
job, err := setup()
|
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 {
|
if err == nil {
|
||||||
res, err = runJob(*job)
|
res, err = runJob(*job)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
res.ReEval()
|
||||||
|
res.SaveState()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("! Error running job %+v\n", err)
|
fmt.Printf("! Error running job %+v\n", err)
|
||||||
os.Exit(int(types.UNKNOWN))
|
os.Exit(int(types.UNKNOWN))
|
||||||
} else {
|
} else {
|
||||||
os.Exit(int(res))
|
os.Exit(int(res.LastState))
|
||||||
}
|
}
|
||||||
|
|
||||||
state.WriteLog(*job)
|
state.WriteLog(*job)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,16 @@
|
||||||
package state
|
package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"jobwatch/timespec"
|
"io/ioutil"
|
||||||
"jobwatch/types"
|
"jobwatch/types"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -15,13 +22,194 @@ type State struct {
|
||||||
JobId string `json:"job_id"`
|
JobId string `json:"job_id"`
|
||||||
JobInstanceId string `json:"job_inst_id"`
|
JobInstanceId string `json:"job_inst_id"`
|
||||||
LastRun string `json:"last_run"`
|
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"`
|
LastRunAgeState int `json:"last_run_age_state"`
|
||||||
LastRunWarnAt timespec.TimeSpec `json:"last_run_warn_at"`
|
LastRunWarnAt types.TimeSpec `json:"last_run_warn_at"`
|
||||||
LastRunCritAt timespec.TimeSpec `json:"last_run_crit_at"`
|
LastRunCritAt types.TimeSpec `json:"last_run_crit_at"`
|
||||||
LastExitCode int `json:"last_exit_code"`
|
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) {
|
func WriteLog(job types.Job, line ...string) {
|
||||||
fmt.Println("B")
|
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
|
type TimeUnit string
|
||||||
|
|
||||||
|
|
@ -10,8 +10,8 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
type TimeSpec struct {
|
type TimeSpec struct {
|
||||||
Value int `yaml:"val"`
|
Value int `yaml:"val" json:"val"`
|
||||||
Unit TimeUnit `yaml:"unit"`
|
Unit TimeUnit `yaml:"unit" json:"unit"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e TimeSpec) HasValidUnit() bool {
|
func (e TimeSpec) HasValidUnit() bool {
|
||||||
|
|
@ -1,6 +1,13 @@
|
||||||
package types
|
package types
|
||||||
|
|
||||||
import "jobwatch/timespec"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
)
|
||||||
|
|
||||||
type CMKState int32
|
type CMKState int32
|
||||||
|
|
||||||
|
|
@ -23,6 +30,7 @@ type ExistCodeMapEntry struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Job struct {
|
type Job struct {
|
||||||
|
Id string
|
||||||
Cmd string `yaml:"cmd"`
|
Cmd string `yaml:"cmd"`
|
||||||
Args []string `yaml:"args"`
|
Args []string `yaml:"args"`
|
||||||
OutputLog string
|
OutputLog string
|
||||||
|
|
@ -31,6 +39,47 @@ type Job struct {
|
||||||
LogMatches []LogMatch `yaml:"log_matches"`
|
LogMatches []LogMatch `yaml:"log_matches"`
|
||||||
InstArgs []string
|
InstArgs []string
|
||||||
InstId string
|
InstId string
|
||||||
LastRunWarn timespec.TimeSpec `yaml:"last_run_warn"`
|
LastRunWarn TimeSpec `yaml:"last_run_warn"`
|
||||||
LastRunCrit timespec.TimeSpec `yaml:"last_run_crit"`
|
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