* fix(maintenance): validate maintenance windows for external, DNS and SSH endpoints ExternalEndpoint.ValidateAndSetDefaults never called maintenance.Config.ValidateAndSetDefaults, so the window's start time and timezone were never parsed. IsUnderMaintenance then treated every window as starting at midnight in the server's local timezone, so alerts were still sent during the configured window. Endpoint.ValidateAndSetDefaults had the same problem for DNS and SSH endpoints, which returned before reaching the maintenance window loop. The loop now runs before those early returns. Fixes #1551 Made by an AI agent (Claude Code, model Claude Opus 5). * test(api): assert external endpoint maintenance window suppresses alerting Pushes a failing result for an external endpoint whose maintenance window (Europe/Berlin) is currently active and checks that alerting is skipped, exercising the same maintenance check CreateExternalEndpointResult uses. --------- Co-authored-by: TwiN <twin@linux.com>
106 lines
4.1 KiB
Go
106 lines
4.1 KiB
Go
package endpoint
|
|
|
|
import (
|
|
"errors"
|
|
"time"
|
|
|
|
"github.com/TwiN/gatus/v5/alerting/alert"
|
|
"github.com/TwiN/gatus/v5/config/endpoint/heartbeat"
|
|
"github.com/TwiN/gatus/v5/config/key"
|
|
"github.com/TwiN/gatus/v5/config/maintenance"
|
|
)
|
|
|
|
var (
|
|
// ErrExternalEndpointWithNoToken is the error with which Gatus will panic if an external endpoint is configured without a token.
|
|
ErrExternalEndpointWithNoToken = errors.New("you must specify a token for each external endpoint")
|
|
|
|
// ErrExternalEndpointHeartbeatIntervalTooLow is the error with which Gatus will panic if an external endpoint's heartbeat interval is less than 10 seconds.
|
|
ErrExternalEndpointHeartbeatIntervalTooLow = errors.New("heartbeat interval must be at least 10 seconds")
|
|
)
|
|
|
|
// ExternalEndpoint is an endpoint whose result is pushed from outside Gatus, which means that
|
|
// said endpoints are not monitored by Gatus itself; Gatus only displays their results and takes
|
|
// care of alerting
|
|
type ExternalEndpoint struct {
|
|
// Enabled defines whether to enable the monitoring of the endpoint
|
|
Enabled *bool `yaml:"enabled,omitempty"`
|
|
|
|
// Name of the endpoint. Can be anything.
|
|
Name string `yaml:"name"`
|
|
|
|
// Group the endpoint is a part of. Used for grouping multiple endpoints together on the front end.
|
|
Group string `yaml:"group,omitempty"`
|
|
|
|
// Token is the bearer token that must be provided through the Authorization header to push results to the endpoint
|
|
Token string `yaml:"token,omitempty"`
|
|
|
|
// Alerts is the alerting configuration for the endpoint in case of failure
|
|
Alerts []*alert.Alert `yaml:"alerts,omitempty"`
|
|
|
|
// MaintenanceWindow is the configuration for per-endpoint maintenance windows
|
|
MaintenanceWindows []*maintenance.Config `yaml:"maintenance-windows,omitempty"`
|
|
|
|
// Heartbeat is the configuration that checks if the external endpoint has received new results when it should have.
|
|
Heartbeat heartbeat.Config `yaml:"heartbeat,omitempty"`
|
|
|
|
// NumberOfFailuresInARow is the number of unsuccessful evaluations in a row
|
|
NumberOfFailuresInARow int `yaml:"-"`
|
|
|
|
// NumberOfSuccessesInARow is the number of successful evaluations in a row
|
|
NumberOfSuccessesInARow int `yaml:"-"`
|
|
}
|
|
|
|
// ValidateAndSetDefaults validates the ExternalEndpoint and sets the default values
|
|
func (externalEndpoint *ExternalEndpoint) ValidateAndSetDefaults() error {
|
|
if err := validateEndpointNameGroupAndAlerts(externalEndpoint.Name, externalEndpoint.Group, externalEndpoint.Alerts); err != nil {
|
|
return err
|
|
}
|
|
if len(externalEndpoint.Token) == 0 {
|
|
return ErrExternalEndpointWithNoToken
|
|
}
|
|
if externalEndpoint.Heartbeat.Interval != 0 && externalEndpoint.Heartbeat.Interval < 10*time.Second {
|
|
// If the heartbeat interval is set (non-0), it must be at least 10 seconds.
|
|
return ErrExternalEndpointHeartbeatIntervalTooLow
|
|
}
|
|
for _, maintenanceWindow := range externalEndpoint.MaintenanceWindows {
|
|
if err := maintenanceWindow.ValidateAndSetDefaults(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// IsEnabled returns whether the endpoint is enabled or not
|
|
func (externalEndpoint *ExternalEndpoint) IsEnabled() bool {
|
|
if externalEndpoint.Enabled == nil {
|
|
return true
|
|
}
|
|
return *externalEndpoint.Enabled
|
|
}
|
|
|
|
// DisplayName returns an identifier made up of the Name and, if not empty, the Group.
|
|
func (externalEndpoint *ExternalEndpoint) DisplayName() string {
|
|
if len(externalEndpoint.Group) > 0 {
|
|
return externalEndpoint.Group + "/" + externalEndpoint.Name
|
|
}
|
|
return externalEndpoint.Name
|
|
}
|
|
|
|
// Key returns the unique key for the Endpoint
|
|
func (externalEndpoint *ExternalEndpoint) Key() string {
|
|
return key.ConvertGroupAndNameToKey(externalEndpoint.Group, externalEndpoint.Name)
|
|
}
|
|
|
|
// ToEndpoint converts the ExternalEndpoint to an Endpoint
|
|
func (externalEndpoint *ExternalEndpoint) ToEndpoint() *Endpoint {
|
|
endpoint := &Endpoint{
|
|
Enabled: externalEndpoint.Enabled,
|
|
Name: externalEndpoint.Name,
|
|
Group: externalEndpoint.Group,
|
|
Alerts: externalEndpoint.Alerts,
|
|
NumberOfFailuresInARow: externalEndpoint.NumberOfFailuresInARow,
|
|
NumberOfSuccessesInARow: externalEndpoint.NumberOfSuccessesInARow,
|
|
}
|
|
return endpoint
|
|
}
|