Files
worker/app/models/check_settings.go
Gleb Tv 2c7a0236da feat: publish standalone worker
Separate worker packaging and service lifecycle from the control plane.
2026-07-13 17:55:14 +03:00

156 строки
4.3 KiB
Go

package models
import (
"errors"
"fmt"
"net/http"
"strings"
)
// CheckHeader provides functionality.
type CheckHeader struct {
Key string `json:"key"`
Value string `json:"value"`
}
// CheckSettings provides functionality.
type CheckSettings struct {
// HTTP Basic Auth Username
HTTPUsername string `json:"http_username,omitempty"`
// HTTP Basic Auth Password
HTTPPassword string `json:"http_password,omitempty"`
// ExpectedAnswer (default, redirect, custom)
ExpectedAnswer string `json:"expected_answer,omitempty"`
// Expected redirect location
ExpectedLocation string `json:"expected_location,omitempty"`
// Expected HTTP status codes for custom
ExpectedHTTPCode int `json:"expected_http_code,omitempty"`
// Keyword search type (off, present or absent)
KeywordType string `json:"keyword_type,omitempty"`
// Keyword to search for
KeywordValue string `json:"keyword_value,omitempty"`
SlowTime int `json:"slow_time"`
Timeout int `json:"timeout"`
// Request Settings
CheckIp bool `json:"checkip"` //nolint:revive // accepted lint exception
CheckIPv6 bool `json:"checkipv6"`
RequestHeader []CheckHeader `json:"request_headers"`
RequestMethod string `json:"request_method"`
RequestType string `json:"request_type"`
RequestContent string `json:"request_content"`
// SSH Settings
Port string `json:"port"`
// Host optionally overrides the monitor host for checks that do
// not naturally target the Monitor.Host (e.g. an ICMP/TCP/UDP
// probe to a separate machine, or a different IP family). Used by
// cping/ctcp/cudp.
Host string `json:"host,omitempty"`
// Count is the per-check packet count for ping. Defaults to 1 in
// cping when zero or negative.
Count int `json:"count,omitempty"`
// PacketSize is the ICMP payload size for ping (bytes). Defaults
// to 56 in cping when zero or negative.
PacketSize int `json:"packet_size,omitempty"`
// Distributed marks this check as eligible for execution on the
// distributed worker pool (multi-region, multi-worker) instead of
// only the in-process scheduler. Reserved for paid plans; the
// controller layer enforces the plan check.
Distributed bool `json:"distributed"`
}
// redirectCodes contains all HTTP redirect status codes.
var redirectCodes = []int{300, 301, 302, 303, 307, 308}
// CheckKeyword provides functionality.
func (s *CheckSettings) CheckKeyword(body []byte, warns []string) ([]string, error) {
var err error
switch s.KeywordType {
case "", "off":
return warns, nil
case "present":
if !strings.Contains(string(body), s.KeywordValue) {
err = errors.New("expected keyword " + s.KeywordValue + " not found")
}
case "absent":
if strings.Contains(string(body), s.KeywordValue) {
err = errors.New("unexpected keyword " + s.KeywordValue + " found")
}
}
return warns, err
}
// CheckAnswer provides functionality.
func (s *CheckSettings) CheckAnswer(resp *http.Response, body []byte) ([]string, error) {
// log.Println("check answer, settings:")
// spew.Dump(s)
isRedirect := false
for _, rc := range redirectCodes {
if resp.StatusCode == rc {
isRedirect = true
}
}
location := resp.Header.Get("location")
// log.Println("redirect?", isRedirect, location)
switch s.ExpectedAnswer {
case "", "default":
if isRedirect {
return s.CheckKeyword(body, []string{"redirect: " + location})
} else if resp.StatusCode != 200 {
return []string{}, fmt.Errorf("bad status code: %d", resp.StatusCode)
}
case "redirect":
if !isRedirect {
return []string{}, fmt.Errorf("bad status code: %d (expected redirect)", resp.StatusCode)
}
if s.ExpectedLocation != "" {
if location != s.ExpectedLocation {
return []string{}, fmt.Errorf(
"bad location: %s (expected %s)",
location,
s.ExpectedLocation,
)
}
}
case "custom":
if s.ExpectedHTTPCode == 0 {
s.ExpectedHTTPCode = 200
}
if resp.StatusCode != s.ExpectedHTTPCode {
return []string{}, fmt.Errorf("bad status code: %d (expected %d)", resp.StatusCode, s.ExpectedHTTPCode)
}
if s.ExpectedLocation != "" {
if location != s.ExpectedLocation {
return []string{}, fmt.Errorf(
"bad location: %s (expected %s)",
location,
s.ExpectedLocation,
)
}
}
default:
panic("bad expectedAnswer")
}
// return []string{}, nil
return s.CheckKeyword(body, []string{})
}