mirror of
https://github.com/TwiN/gatus.git
synced 2024-11-24 17:04:42 +01:00
54221eff9b
Fix #848
162 lines
5.1 KiB
Go
162 lines
5.1 KiB
Go
package maintenance
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
_ "time/tzdata" // Required for IANA timezone support
|
|
)
|
|
|
|
var (
|
|
errInvalidMaintenanceStartFormat = errors.New("invalid maintenance start format: must be hh:mm, between 00:00 and 23:59 inclusively (e.g. 23:00)")
|
|
errInvalidMaintenanceDuration = errors.New("invalid maintenance duration: must be bigger than 0 (e.g. 30m)")
|
|
errInvalidDayName = fmt.Errorf("invalid value specified for 'on'. supported values are %s", longDayNames)
|
|
errInvalidTimezone = errors.New("invalid timezone specified or format not supported. Use IANA timezone format (e.g. America/Sao_Paulo)")
|
|
|
|
longDayNames = []string{
|
|
"Sunday",
|
|
"Monday",
|
|
"Tuesday",
|
|
"Wednesday",
|
|
"Thursday",
|
|
"Friday",
|
|
"Saturday",
|
|
}
|
|
)
|
|
|
|
// Config allows for the configuration of a maintenance period.
|
|
// During this maintenance period, no alerts will be sent.
|
|
//
|
|
// Uses UTC by default.
|
|
type Config struct {
|
|
Enabled *bool `yaml:"enabled"` // Whether the maintenance period is enabled. Enabled by default if nil.
|
|
Start string `yaml:"start"` // Time at which the maintenance period starts (e.g. 23:00)
|
|
Duration time.Duration `yaml:"duration"` // Duration of the maintenance period (e.g. 4h)
|
|
Timezone string `yaml:"timezone"` // Timezone in string format which the maintenance period is configured (e.g. America/Sao_Paulo)
|
|
|
|
// Every is a list of days of the week during which maintenance period applies.
|
|
// See longDayNames for list of valid values.
|
|
// Every day if empty.
|
|
Every []string `yaml:"every"`
|
|
|
|
TimezoneLocation *time.Location // Timezone in location format which the maintenance period is configured
|
|
durationToStartFromMidnight time.Duration
|
|
}
|
|
|
|
func GetDefaultConfig() *Config {
|
|
defaultValue := false
|
|
return &Config{
|
|
Enabled: &defaultValue,
|
|
}
|
|
}
|
|
|
|
// IsEnabled returns whether maintenance is enabled or not
|
|
func (c *Config) IsEnabled() bool {
|
|
if c.Enabled == nil {
|
|
return true
|
|
}
|
|
return *c.Enabled
|
|
}
|
|
|
|
// ValidateAndSetDefaults validates the maintenance configuration and sets the default values if necessary.
|
|
//
|
|
// Must be called once in the application's lifecycle before IsUnderMaintenance is called, since it
|
|
// also sets durationToStartFromMidnight.
|
|
func (c *Config) ValidateAndSetDefaults() error {
|
|
if c == nil || !c.IsEnabled() {
|
|
// Don't waste time validating if maintenance is not enabled.
|
|
return nil
|
|
}
|
|
for _, day := range c.Every {
|
|
isDayValid := false
|
|
for _, longDayName := range longDayNames {
|
|
if day == longDayName {
|
|
isDayValid = true
|
|
break
|
|
}
|
|
}
|
|
if !isDayValid {
|
|
return errInvalidDayName
|
|
}
|
|
}
|
|
var err error
|
|
c.durationToStartFromMidnight, err = hhmmToDuration(c.Start)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if c.Duration <= 0 || c.Duration > 24*time.Hour {
|
|
return errInvalidMaintenanceDuration
|
|
}
|
|
if c.Timezone != "" {
|
|
c.TimezoneLocation, err = time.LoadLocation(c.Timezone)
|
|
if err != nil {
|
|
return fmt.Errorf("%w: %w", errInvalidTimezone, err)
|
|
}
|
|
} else {
|
|
c.Timezone = "UTC"
|
|
c.TimezoneLocation = time.UTC
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// IsUnderMaintenance checks whether the endpoints that Gatus monitors are within the configured maintenance window
|
|
func (c *Config) IsUnderMaintenance() bool {
|
|
if !c.IsEnabled() {
|
|
return false
|
|
}
|
|
now := time.Now()
|
|
if c.TimezoneLocation != nil {
|
|
now = now.In(c.TimezoneLocation)
|
|
}
|
|
var dayWhereMaintenancePeriodWouldStart time.Time
|
|
if now.Hour() >= int(c.durationToStartFromMidnight.Hours()) {
|
|
dayWhereMaintenancePeriodWouldStart = now.Truncate(24 * time.Hour)
|
|
} else {
|
|
dayWhereMaintenancePeriodWouldStart = now.Add(-c.Duration).Truncate(24 * time.Hour)
|
|
}
|
|
hasMaintenanceEveryDay := len(c.Every) == 0
|
|
hasMaintenancePeriodScheduledToStartOnThatWeekday := c.hasDay(dayWhereMaintenancePeriodWouldStart.Weekday().String())
|
|
if !hasMaintenanceEveryDay && !hasMaintenancePeriodScheduledToStartOnThatWeekday {
|
|
// The day when the maintenance period would start is not scheduled
|
|
// to have any maintenance, so we can just return false.
|
|
return false
|
|
}
|
|
startOfMaintenancePeriod := dayWhereMaintenancePeriodWouldStart.Add(c.durationToStartFromMidnight)
|
|
endOfMaintenancePeriod := startOfMaintenancePeriod.Add(c.Duration)
|
|
return now.After(startOfMaintenancePeriod) && now.Before(endOfMaintenancePeriod)
|
|
}
|
|
|
|
func (c *Config) hasDay(day string) bool {
|
|
for _, d := range c.Every {
|
|
if d == day {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func hhmmToDuration(s string) (time.Duration, error) {
|
|
if len(s) != 5 {
|
|
return 0, errInvalidMaintenanceStartFormat
|
|
}
|
|
var hours, minutes int
|
|
var err error
|
|
if hours, err = extractNumericalValueFromPotentiallyZeroPaddedString(s[:2]); err != nil {
|
|
return 0, err
|
|
}
|
|
if minutes, err = extractNumericalValueFromPotentiallyZeroPaddedString(s[3:5]); err != nil {
|
|
return 0, err
|
|
}
|
|
duration := (time.Duration(hours) * time.Hour) + (time.Duration(minutes) * time.Minute)
|
|
if hours < 0 || hours > 23 || minutes < 0 || minutes > 59 || duration < 0 || duration >= 24*time.Hour {
|
|
return 0, errInvalidMaintenanceStartFormat
|
|
}
|
|
return duration, nil
|
|
}
|
|
|
|
func extractNumericalValueFromPotentiallyZeroPaddedString(s string) (int, error) {
|
|
return strconv.Atoi(strings.TrimPrefix(s, "0"))
|
|
}
|