package state import ( "bufio" "encoding/json" "fmt" "io/ioutil" "jobwatch/types" "log" "os" "path/filepath" "strings" "time" ) /* */ 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_seconds"` LastRunAgeState int `json:"last_run_age_state"` 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:"user_id"` Path string `json:"-"` Entries []summaryEntry `json:"entries"` } type summary struct { Entries []summaryPerUser `json:"all"` } func getLibDir(subDir string) (string, error) { var dir string dir = "/var/lib/jobwatch" if os.Getuid() > 0 { if od, err := os.UserHomeDir(); err == nil { dir = od + "/.jobwatch" } } if len(subDir) > 0 { dir = filepath.FromSlash(dir + "/" + subDir) } err := os.MkdirAll(dir, 0770) return dir, err } func getStateDir() (string, error) { return getLibDir("states") } 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 (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 { if strings.LastIndex(e.Name(), ".state.json") < 0 { continue } 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\n\n", string(bytes)) } return nil }