Gibheer
efa2f5b307
With monzero supporting slog to get debug output from check commands, moncheck now supports that too by changing the level to debug.
234 lines
5.3 KiB
Go
234 lines
5.3 KiB
Go
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,
|
|
Logger: logger,
|
|
})
|
|
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)
|
|
}
|