Files
arsam f1b7aeae07 Import source: internal/config/config
Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
2026-09-18 23:19:53 +03:30

167 lines
4.1 KiB
Go

// Package config loads and validates the targets.yaml file.
package config
import (
"fmt"
"os"
"time"
"gopkg.in/yaml.v3"
)
// Duration wraps time.Duration so YAML values like "30s" parse naturally.
type Duration struct{ time.Duration }
func (d *Duration) UnmarshalYAML(value *yaml.Node) error {
var s string
if err := value.Decode(&s); err != nil {
return err
}
dur, err := time.ParseDuration(s)
if err != nil {
return fmt.Errorf("invalid duration %q: %w", s, err)
}
d.Duration = dur
return nil
}
// CheckType enumerates the supported probe kinds.
type CheckType string
const (
CheckHTTP CheckType = "http"
CheckTCP CheckType = "tcp"
CheckTLS CheckType = "tls"
CheckICMP CheckType = "icmp"
CheckDocker CheckType = "docker"
)
// Target is one monitored endpoint.
type Target struct {
ID string `yaml:"id"`
Name string `yaml:"name"`
Type CheckType `yaml:"type"`
// Type-specific endpoints.
URL string `yaml:"url"` // http
Address string `yaml:"address"` // tcp, tls (host:port)
Host string `yaml:"host"` // icmp
Container string `yaml:"container"` // docker
Socket string `yaml:"socket"` // docker (default /var/run/docker.sock)
Interval Duration `yaml:"interval"`
Timeout Duration `yaml:"timeout"`
// Flap detection: N consecutive failures before "down",
// M consecutive successes before "up" again.
FailThreshold int `yaml:"fail_threshold"`
RecoverThreshold int `yaml:"recover_threshold"`
ExpectStatus int `yaml:"expect_status"` // http: exact status (0 = any < 400)
WarnDays int `yaml:"warn_days"` // tls: degraded when cert expires sooner
DegradedLatencyMS float64 `yaml:"degraded_latency_ms"` // any: degraded above this latency
}
// Settings holds server-level options.
type Settings struct {
Listen string `yaml:"listen"`
DB string `yaml:"db"`
HistoryDays int `yaml:"history_days"`
}
// Config is the full parsed targets.yaml.
type Config struct {
Settings Settings `yaml:"settings"`
Targets []Target `yaml:"targets"`
}
// Load reads, parses, validates and applies defaults to a config file.
func Load(path string) (*Config, error) {
raw, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var cfg Config
if err := yaml.Unmarshal(raw, &cfg); err != nil {
return nil, fmt.Errorf("parse %s: %w", path, err)
}
if err := cfg.validate(); err != nil {
return nil, err
}
cfg.applyDefaults()
return &cfg, nil
}
func (c *Config) validate() error {
if len(c.Targets) == 0 {
return fmt.Errorf("no targets defined")
}
seen := map[string]bool{}
for i, t := range c.Targets {
if t.ID == "" {
return fmt.Errorf("target %d: missing id", i)
}
if seen[t.ID] {
return fmt.Errorf("duplicate target id %q", t.ID)
}
seen[t.ID] = true
switch t.Type {
case CheckHTTP:
if t.URL == "" {
return fmt.Errorf("target %q: http check requires url", t.ID)
}
case CheckTCP, CheckTLS:
if t.Address == "" {
return fmt.Errorf("target %q: %s check requires address (host:port)", t.ID, t.Type)
}
case CheckICMP:
if t.Host == "" {
return fmt.Errorf("target %q: icmp check requires host", t.ID)
}
case CheckDocker:
if t.Container == "" {
return fmt.Errorf("target %q: docker check requires container", t.ID)
}
default:
return fmt.Errorf("target %q: unknown check type %q", t.ID, t.Type)
}
}
return nil
}
func (c *Config) applyDefaults() {
if c.Settings.Listen == "" {
c.Settings.Listen = ":8080"
}
if c.Settings.DB == "" {
c.Settings.DB = "dash.db"
}
if c.Settings.HistoryDays <= 0 {
c.Settings.HistoryDays = 30
}
for i := range c.Targets {
t := &c.Targets[i]
if t.Name == "" {
t.Name = t.ID
}
if t.Interval.Duration <= 0 {
t.Interval.Duration = 30 * time.Second
}
if t.Timeout.Duration <= 0 {
t.Timeout.Duration = 5 * time.Second
}
if t.FailThreshold <= 0 {
t.FailThreshold = 3
}
if t.RecoverThreshold <= 0 {
t.RecoverThreshold = 2
}
if t.Type == CheckTLS && t.WarnDays <= 0 {
t.WarnDays = 14
}
if t.Type == CheckDocker && t.Socket == "" {
t.Socket = "/var/run/docker.sock"
}
}
}