Gibheer
6ea4d2c82d
lib/pq is out of maintenance for some time now, so switch to the newer more active library. Looks like it finally stabilized after a long time.
235 lines
5.3 KiB
Go
235 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"
|
|
_ "github.com/jackc/pgx/v5/stdlib"
|
|
)
|
|
|
|
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("pgx", 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)
|
|
}
|