aboutsummaryrefslogblamecommitdiff
path: root/cmd/moncheck/main.go
blob: d8f0a74fb4f974b94d13adf1dd063b07f8f4d723 (plain) (tree)
1
2
3
4
5
6
7
8
9



               
                      
                             

                       
             
            

                   
                  
            

                 
              

              
                                                
                                          







                                                                                      





                                                      





                                                     
         

                    








                                                            
                                                                  



                                                             

                                     
                                                                                 

                                                                                                

         

                                                            

                                                                                                         
         

                                                          

                                                                                                
         
 
                                             
                       

                                                                                

         

                                      

                                                                        

         

                                                                 



                                                  
                                       
          
                       

                                                                               
         
 
                                             
                                                       





                              
                                                                                       
             

                                                      
                                                                                 
                         
                                                
                 

         
























































                                                                                               














                                                           










































                                                                                                                              
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)
}