/* Copyright (C) 2022, M. Höß, hoess@gmx.net. MIT Licensed */ package main import ( "bytes" "flag" "fmt" "io" "io/ioutil" "log" "os" "os/exec" "regexp" "strings" "time" "jobwatch/state" "jobwatch/types" ) // Jobs root-idfile func evalOutput(job types.Job, output []string) (types.CMKState, []state.LogEntry, error) { var res types.CMKState = types.OK var logLines []state.LogEntry var rcs []*regexp.Regexp for _, m := range job.LogMatches { r, err := regexp.Compile(m.Regex) if err != nil { return 0, logLines, 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 le state.LogEntry le.State = state.ToLogState(m.State) le.Date = time.Now().Format("2006-01-2 15:04:05") le.Text = v logLines = append(logLines, le) break } } } for _, oln := range logLines { log.Printf("- Matched LogLine %v\n", oln) } return res, logLines, nil } func runJob(job types.Job) (state.State, []state.LogEntry, 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...)...) 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 stcode = 0 if err := cmd.Run(); err != nil { // cmd executed, but failed w/ non-zero if exitError, ok := err.(*exec.ExitError); ok { stcode = exitError.ProcessState.ExitCode() } else { // cmd not found etc return st, []state.LogEntry{}, err } if stcode == 0 { // Exec failed, but exitcode, be sure to get error! stcode = int(types.UNKNOWN) } } else { stcode = cmd.ProcessState.ExitCode() } var exitCode = stcode log.Printf("- ExitCode of Command: %v", stcode) for _, em := range job.ExitCodeMap { // From == -1 -> Alle non-zero-states mappen if (stcode == int(em.From)) || (em.From == -1 && stcode > 0) { stcode = int(em.To) } } if stcode > int(types.UNKNOWN) { stcode = int(types.CRIT) } log.Printf("- State after ExitCodeMapping: %v", stcode) outText := string(stdcombinedBuf.Bytes()) log_state, logLines, err := evalOutput(job, strings.Split(outText, "\n")) if int(log_state) > stcode { stcode = int(log_state) } log.Printf("- State after Output-evaluation: %v", stcode) st.LastExitCode = exitCode st.LastState = stcode st.LastRun = time.Now().Format(time.RFC3339) return st, logLines, 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(ioutil.Discard) } 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 := types.LoadJob(jobdir, jobid); err == nil { job.InstArgs = flag.Args() job.InstId = job_instance return job, nil } else { log.Printf("! Error loading job : %v\n", err) return nil, err } } func main() { job, err := setup() if job == nil && err == nil { fmt.Println("<<>>") state.ShowAll() fmt.Println("<<>>") state.ShowAllLogs() fmt.Println("<<<>>>") } else { fmt.Fprintln(os.Stderr, "Jobwatch 0.1 (C) 2022 hoess@gmx.net") var exitCode = 0 var logLines []state.LogEntry var res state.State defer func() { os.Exit(exitCode) }() lock, err := state.Lock(job.GetJobBaseFileName(), "JOB") log.Println("Locked on job") if err == nil { defer func() { log.Println("Unlocking job") state.Unlock(lock) }() if err != nil { log.Println("Can't acquire lock, skipping") } if err == nil { res, logLines, err = runJob(*job) res.ReEval() res.SaveState() } if err == nil { err = state.AppendLog(*job, logLines) } } if err != nil { fmt.Printf("! Error running job %+v\n", err) exitCode = int(types.UNKNOWN) } else { exitCode = int(res.LastState) } } }