package main import ( "bytes" "database/sql" "database/sql/driver" "encoding/json" "flag" "fmt" "io" "io/ioutil" "log" "log/slog" "os" "strconv" "strings" "sync" "time" "git.zero-knowledge.org/gibheer/monzero" ) var ( configPath = flag.String("config", "moncheck.conf", "path to the config file") ) type ( Config struct { DB string `json:"db"` Timeout string `json:"timeout"` Wait string `json:"wait"` Path []string `json:"path"` Workers int `json:"workers"` CheckerID int `json:"checker_id"` Log struct { Format string `json:"format"` Level string `json:"level"` Output string `json:"output"` } `json:"log"` } States []int ) func main() { flag.Parse() raw, err := ioutil.ReadFile(*configPath) if err != nil { log.Fatalf("could not read config: %s", err) } config := Config{Timeout: "30s", Wait: "30s", Workers: 25} if err := json.Unmarshal(raw, &config); err != nil { log.Fatalf("could not parse config: %s", err) } logger := parseLogger(config) if err := os.Setenv("PATH", strings.Join(config.Path, ":")); err != nil { logger.Error("could not set PATH", "error", err, "configured path", config.Path) os.Exit(1) } waitDuration, err := time.ParseDuration(config.Wait) if err != nil { logger.Error("could not parse wait duration", "error", err, "wait duration", config.Wait) os.Exit(1) } timeout, err := time.ParseDuration(config.Timeout) if err != nil { logger.Error("could not parse timeout", "error", err, "timeout", config.Timeout) os.Exit(1) } db, err := sql.Open("postgres", config.DB) if err != nil { logger.Error("could not open database connection", "error", err) os.Exit(1) } hostname, err := os.Hostname() if err != nil { logger.Error("could not resolve hostname", "error", err) os.Exit(1) } checker, err := monzero.NewChecker(monzero.CheckerConfig{ CheckerID: config.CheckerID, DB: db, Timeout: timeout, HostIdentifier: hostname, Executor: monzero.CheckExec, }) if err != nil { logger.Error("could not create checker instance", "error", err) os.Exit(1) } for i := 0; i < config.Workers; i++ { go check(checker, waitDuration, logger) } wg := sync.WaitGroup{} wg.Add(1) wg.Wait() } func check(checker *monzero.Checker, waitDuration time.Duration, logger *slog.Logger) { for { if err := checker.Next(); err != nil { if err != monzero.ErrNoCheck { logger.Info("check returned error", "error", err) } time.Sleep(waitDuration) } } } func (s *States) Value() (driver.Value, error) { last := len(*s) if last == 0 { return "{}", nil } result := strings.Builder{} _, err := result.WriteString("{") if err != nil { return "", fmt.Errorf("could not write to buffer: %s", err) } for i, state := range *s { if _, err := fmt.Fprintf(&result, "%d", state); err != nil { return "", fmt.Errorf("could not write to buffer: %s", err) } if i < last-1 { if _, err := result.WriteString(","); err != nil { return "", fmt.Errorf("could not write to buffer: %s", err) } } } if _, err := result.WriteString("}"); err != nil { return "", fmt.Errorf("could not write to buffer: %s", err) } return result.String(), nil } func (s *States) Scan(src interface{}) error { switch src := src.(type) { case []byte: tmp := bytes.Trim(src, "{}") states := bytes.Split(tmp, []byte(",")) result := make([]int, len(states)) for i, state := range states { var err error result[i], err = strconv.Atoi(string(state)) if err != nil { return fmt.Errorf("could not parse element %s: %s", state, err) } } *s = result return nil default: return fmt.Errorf("could not convert %T to states", src) } } // Append prepends the new state before all others. func (s *States) Add(state int) { vals := *s statePos := 5 if len(vals) < 6 { statePos = len(vals) } *s = append([]int{state}, vals[:statePos]...) return } // ToOK returns true when the state returns from != 0 to 0. func (s *States) ToOK() bool { vals := *s if len(vals) == 0 { return false } if len(vals) <= 1 { return vals[0] == 0 } if vals[0] == 0 && vals[1] > 0 { return true } return false } // parse the log settings and generate the slog output func parseLogger(config Config) *slog.Logger { var output io.Writer switch config.Log.Output { case "", "stderr": output = os.Stderr case "stdout": output = os.Stdout default: var err error output, err = os.OpenFile(config.Log.Output, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0640) if err != nil { log.Fatalf("could not open log file handler: %s", err) } } var level slog.Level switch config.Log.Level { case "debug": level = slog.LevelDebug case "", "info": level = slog.LevelInfo case "warn": level = slog.LevelWarn case "error": level = slog.LevelError default: log.Fatalf("unknown log level '%s', only 'debug', 'info', 'warn' and 'error' are supported", config.Log.Level) } var handler slog.Handler switch config.Log.Format { case "", "text": handler = slog.NewTextHandler(output, &slog.HandlerOptions{Level: level}) case "json": handler = slog.NewJSONHandler(output, &slog.HandlerOptions{Level: level}) default: log.Fatalf("unknown log format '%s', only 'text' and 'json' are supported", config.Log.Format) } return slog.New(handler) }