Files
nmon/internal/checks/params.go

110 lines
3.1 KiB
Go

package checks
import (
"encoding/json"
"errors"
"fmt"
"net/netip"
"net/url"
"strings"
"time"
"github.com/v10networks/nmon/internal/store"
)
// Params are the per-check settings kept as JSON in checks.params. Fields that
// do not apply to a kind are ignored.
type Params struct {
// TimeoutS is the deadline of one run in seconds (default: the runner's, never more than the interval).
TimeoutS int `json:"timeout_s,omitempty"`
// ExpectStatus is the HTTP status that counts as success (default: any 2xx).
ExpectStatus int `json:"expect_status,omitempty"`
// BodyContains must appear in the first 1 MiB of an HTTP response body.
BodyContains string `json:"body_contains,omitempty"`
// Server is the DNS server to ask, "ip" or "ip:port" ("[v6]:port").
Server string `json:"server,omitempty"`
// Expect is an address that must be among the DNS answers.
Expect string `json:"expect,omitempty"`
// RecordType is "A", "AAAA" or empty for both.
RecordType string `json:"record_type,omitempty"`
// WarnDays fails a TLS check when the certificate has fewer days left (default 14).
WarnDays int `json:"warn_days,omitempty"`
// Insecure skips chain and name verification in a TLS check (expiry is still measured).
Insecure bool `json:"insecure,omitempty"`
}
// Defaults.
const (
DefaultIntervalS = 60
DefaultWarnDays = 14
)
// ParseParams decodes checks.params; empty means all defaults. Unknown fields are an error.
func ParseParams(raw string) (Params, error) {
var p Params
if strings.TrimSpace(raw) == "" {
return p, nil
}
dec := json.NewDecoder(strings.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&p); err != nil {
return Params{}, fmt.Errorf("check params: %w", err)
}
return p, nil
}
// Validate reports why c cannot run, for the API to reject a bad definition.
func Validate(c store.Check) error {
p, err := ParseParams(c.Params)
if err != nil {
return err
}
if p.TimeoutS < 0 || p.WarnDays < 0 || p.ExpectStatus < 0 || p.ExpectStatus > 599 {
return errors.New("check params: negative or out-of-range value")
}
switch c.Kind {
case store.CheckPing:
return nil
case store.CheckHTTP:
return validateHTTP(c.Target)
case store.CheckTLS:
_, _, err := tlsTarget(c.Target)
return err
case store.CheckDNS:
return validateDNS(p)
}
return fmt.Errorf("unknown check kind %q", c.Kind)
}
func validateHTTP(target string) error {
u, err := url.Parse(target)
if err != nil || (u.Scheme != "http" && u.Scheme != "https") || u.Hostname() == "" {
return errors.New("http check target must be an http(s) URL")
}
return nil
}
func validateDNS(p Params) error {
if _, err := dnsServer(p.Server); err != nil {
return err
}
if p.Expect != "" {
if _, err := netip.ParseAddr(p.Expect); err != nil {
return errors.New("dns check expect must be an IP address")
}
}
_, err := recordTypes(p.RecordType)
return err
}
func (p Params) timeout(def time.Duration, interval time.Duration) time.Duration {
d := def
if p.TimeoutS > 0 {
d = time.Duration(p.TimeoutS) * time.Second
}
if interval > 0 && d > interval {
d = interval
}
return d
}