mirror of
https://github.com/TwiN/gatus.git
synced 2024-12-14 11:58:04 +00:00
feat(alerting): add timezone for maintenance (#653)
* feat(alerting): add timezone for maintenance * Update config/maintenance/maintenance.go * docs: Add example of maintenance.timezone in readme.md * fix: Only set time to timezone location if the location is set * fix: Include the original error in the message --------- Co-authored-by: TwiN <twin@linux.com>
This commit is contained in:
parent
b2191391f6
commit
af00dfdb73
3 changed files with 86 additions and 6 deletions
|
@ -1459,15 +1459,15 @@ To do that, you'll have to use the maintenance configuration:
|
||||||
| `maintenance.enabled` | Whether the maintenance period is enabled | `true` |
|
| `maintenance.enabled` | Whether the maintenance period is enabled | `true` |
|
||||||
| `maintenance.start` | Time at which the maintenance window starts in `hh:mm` format (e.g. `23:00`) | Required `""` |
|
| `maintenance.start` | Time at which the maintenance window starts in `hh:mm` format (e.g. `23:00`) | Required `""` |
|
||||||
| `maintenance.duration` | Duration of the maintenance window (e.g. `1h`, `30m`) | Required `""` |
|
| `maintenance.duration` | Duration of the maintenance window (e.g. `1h`, `30m`) | Required `""` |
|
||||||
|
| `maintenance.timezone` | Timezone of the maintenance window format (e.g. `Europe/Amsterdam`).<br />See [List of tz database time zones](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) for more info | `UTC` |
|
||||||
| `maintenance.every` | Days on which the maintenance period applies (e.g. `[Monday, Thursday]`).<br />If left empty, the maintenance window applies every day | `[]` |
|
| `maintenance.every` | Days on which the maintenance period applies (e.g. `[Monday, Thursday]`).<br />If left empty, the maintenance window applies every day | `[]` |
|
||||||
|
|
||||||
> 📝 The maintenance configuration uses UTC
|
|
||||||
|
|
||||||
Here's an example:
|
Here's an example:
|
||||||
```yaml
|
```yaml
|
||||||
maintenance:
|
maintenance:
|
||||||
start: 23:00
|
start: 23:00
|
||||||
duration: 1h
|
duration: 1h
|
||||||
|
timezone: "Europe/Amsterdam"
|
||||||
every: [Monday, Thursday]
|
every: [Monday, Thursday]
|
||||||
```
|
```
|
||||||
Note that you can also specify each day on separate lines:
|
Note that you can also specify each day on separate lines:
|
||||||
|
@ -1475,6 +1475,7 @@ Note that you can also specify each day on separate lines:
|
||||||
maintenance:
|
maintenance:
|
||||||
start: 23:00
|
start: 23:00
|
||||||
duration: 1h
|
duration: 1h
|
||||||
|
timezone: "Europe/Amsterdam"
|
||||||
every:
|
every:
|
||||||
- Monday
|
- Monday
|
||||||
- Thursday
|
- Thursday
|
||||||
|
|
|
@ -12,6 +12,7 @@ var (
|
||||||
errInvalidMaintenanceStartFormat = errors.New("invalid maintenance start format: must be hh:mm, between 00:00 and 23:59 inclusively (e.g. 23:00)")
|
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)")
|
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)
|
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{
|
longDayNames = []string{
|
||||||
"Sunday",
|
"Sunday",
|
||||||
|
@ -27,17 +28,19 @@ var (
|
||||||
// Config allows for the configuration of a maintenance period.
|
// Config allows for the configuration of a maintenance period.
|
||||||
// During this maintenance period, no alerts will be sent.
|
// During this maintenance period, no alerts will be sent.
|
||||||
//
|
//
|
||||||
// Uses UTC.
|
// Uses UTC by default.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Enabled *bool `yaml:"enabled"` // Whether the maintenance period is enabled. Enabled by default if nil.
|
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)
|
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)
|
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.
|
// Every is a list of days of the week during which maintenance period applies.
|
||||||
// See longDayNames for list of valid values.
|
// See longDayNames for list of valid values.
|
||||||
// Every day if empty.
|
// Every day if empty.
|
||||||
Every []string `yaml:"every"`
|
Every []string `yaml:"every"`
|
||||||
|
|
||||||
|
TimezoneLocation *time.Location // Timezone in location format which the maintenance period is configured
|
||||||
durationToStartFromMidnight time.Duration
|
durationToStartFromMidnight time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,6 +88,15 @@ func (c *Config) ValidateAndSetDefaults() error {
|
||||||
if c.Duration <= 0 || c.Duration > 24*time.Hour {
|
if c.Duration <= 0 || c.Duration > 24*time.Hour {
|
||||||
return errInvalidMaintenanceDuration
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,7 +105,10 @@ func (c Config) IsUnderMaintenance() bool {
|
||||||
if !c.IsEnabled() {
|
if !c.IsEnabled() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
now := time.Now().UTC()
|
now := time.Now()
|
||||||
|
if c.TimezoneLocation != nil {
|
||||||
|
now = now.In(c.TimezoneLocation)
|
||||||
|
}
|
||||||
var dayWhereMaintenancePeriodWouldStart time.Time
|
var dayWhereMaintenancePeriodWouldStart time.Time
|
||||||
if now.Hour() >= int(c.durationToStartFromMidnight.Hours()) {
|
if now.Hour() >= int(c.durationToStartFromMidnight.Hours()) {
|
||||||
dayWhereMaintenancePeriodWouldStart = now.Truncate(24 * time.Hour)
|
dayWhereMaintenancePeriodWouldStart = now.Truncate(24 * time.Hour)
|
||||||
|
|
|
@ -90,6 +90,15 @@ func TestConfig_ValidateAndSetDefaults(t *testing.T) {
|
||||||
},
|
},
|
||||||
expectedError: errInvalidMaintenanceDuration,
|
expectedError: errInvalidMaintenanceDuration,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "invalid-timezone",
|
||||||
|
cfg: &Config{
|
||||||
|
Start: "23:00",
|
||||||
|
Duration: time.Hour,
|
||||||
|
Timezone: "invalid-timezone",
|
||||||
|
},
|
||||||
|
expectedError: errInvalidTimezone,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "every-day-at-2300",
|
name: "every-day-at-2300",
|
||||||
cfg: &Config{
|
cfg: &Config{
|
||||||
|
@ -126,6 +135,33 @@ func TestConfig_ValidateAndSetDefaults(t *testing.T) {
|
||||||
},
|
},
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "timezone-amsterdam",
|
||||||
|
cfg: &Config{
|
||||||
|
Start: "23:00",
|
||||||
|
Duration: time.Hour,
|
||||||
|
Timezone: "Europe/Amsterdam",
|
||||||
|
},
|
||||||
|
expectedError: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "timezone-cet",
|
||||||
|
cfg: &Config{
|
||||||
|
Start: "23:00",
|
||||||
|
Duration: time.Hour,
|
||||||
|
Timezone: "CET",
|
||||||
|
},
|
||||||
|
expectedError: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "timezone-etc-plus-5",
|
||||||
|
cfg: &Config{
|
||||||
|
Start: "23:00",
|
||||||
|
Duration: time.Hour,
|
||||||
|
Timezone: "Etc/GMT+5",
|
||||||
|
},
|
||||||
|
expectedError: nil,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, scenario := range scenarios {
|
for _, scenario := range scenarios {
|
||||||
t.Run(scenario.name, func(t *testing.T) {
|
t.Run(scenario.name, func(t *testing.T) {
|
||||||
|
@ -220,7 +256,25 @@ func TestConfig_IsUnderMaintenance(t *testing.T) {
|
||||||
expected: true,
|
expected: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "under-maintenance-starting-4h-ago-for-3h",
|
name: "under-maintenance-amsterdam-timezone-starting-now-for-2h",
|
||||||
|
cfg: &Config{
|
||||||
|
Start: fmt.Sprintf("%02d:00", now.Hour()),
|
||||||
|
Duration: 2 * time.Hour,
|
||||||
|
Timezone: "Europe/Amsterdam",
|
||||||
|
},
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "under-maintenance-utc-timezone-starting-now-for-2h",
|
||||||
|
cfg: &Config{
|
||||||
|
Start: fmt.Sprintf("%02d:00", now.Hour()),
|
||||||
|
Duration: 2 * time.Hour,
|
||||||
|
Timezone: "UTC",
|
||||||
|
},
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "not-under-maintenance-starting-4h-ago-for-3h",
|
||||||
cfg: &Config{
|
cfg: &Config{
|
||||||
Start: fmt.Sprintf("%02d:00", normalizeHour(now.Hour()-4)),
|
Start: fmt.Sprintf("%02d:00", normalizeHour(now.Hour()-4)),
|
||||||
Duration: 3 * time.Hour,
|
Duration: 3 * time.Hour,
|
||||||
|
@ -228,7 +282,7 @@ func TestConfig_IsUnderMaintenance(t *testing.T) {
|
||||||
expected: false,
|
expected: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "under-maintenance-starting-5h-ago-for-1h",
|
name: "not-under-maintenance-starting-5h-ago-for-1h",
|
||||||
cfg: &Config{
|
cfg: &Config{
|
||||||
Start: fmt.Sprintf("%02d:00", normalizeHour(now.Hour()-5)),
|
Start: fmt.Sprintf("%02d:00", normalizeHour(now.Hour()-5)),
|
||||||
Duration: time.Hour,
|
Duration: time.Hour,
|
||||||
|
@ -253,6 +307,16 @@ func TestConfig_IsUnderMaintenance(t *testing.T) {
|
||||||
},
|
},
|
||||||
expected: false,
|
expected: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "not-under-maintenance-los-angeles-timezone-starting-now-for-2h-today",
|
||||||
|
cfg: &Config{
|
||||||
|
Start: fmt.Sprintf("%02d:00", now.Hour()),
|
||||||
|
Duration: 2 * time.Hour,
|
||||||
|
Timezone: "America/Los_Angeles",
|
||||||
|
Every: []string{now.Weekday().String()},
|
||||||
|
},
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, scenario := range scenarios {
|
for _, scenario := range scenarios {
|
||||||
t.Run(scenario.name, func(t *testing.T) {
|
t.Run(scenario.name, func(t *testing.T) {
|
||||||
|
|
Loading…
Reference in a new issue