diff --git a/README.md b/README.md
index 413c473b0..bb0327f71 100644
--- a/README.md
+++ b/README.md
@@ -273,6 +273,7 @@ You can then configure alerts to be triggered when an endpoint is unhealthy once
| `endpoints[].ssh.username` | SSH username (e.g. example). | Required `""` |
| `endpoints[].ssh.password` | SSH password (e.g. password). | Required `""` |
| `endpoints[].alerts` | List of all alerts for a given endpoint.
See [Alerting](#alerting). | `[]` |
+| `endpoints[].maintenance-windows` | List of all maintenance windows for a given endpoint.
See [Maintenance](#maintenance). | `[]` |
| `endpoints[].client` | [Client configuration](#client-configuration). | `{}` |
| `endpoints[].ui` | UI configuration at the endpoint level. | `{}` |
| `endpoints[].ui.hide-conditions` | Whether to hide conditions from the results. Note that this only hides conditions from results evaluated from the moment this was enabled. | `false` |
@@ -1710,6 +1711,19 @@ maintenance:
- Monday
- Thursday
```
+You can also specify maintenance windows on a per-endpoint basis:
+```yaml
+endpoints:
+ - name: endpoint-1
+ url: "https://example.org"
+ maintenance-windows:
+ - start: "07:30"
+ duration: 40m
+ timezone: "Europe/Berlin"
+ - start: "14:30"
+ duration: 1h
+ timezone: "Europe/Berlin"
+```
### Security
diff --git a/config/endpoint/endpoint.go b/config/endpoint/endpoint.go
index 65e45019d..a0fdb8758 100644
--- a/config/endpoint/endpoint.go
+++ b/config/endpoint/endpoint.go
@@ -13,6 +13,7 @@ import (
"strings"
"time"
+ "github.com/TwiN/gatus/v5/config/maintenance"
"github.com/TwiN/gatus/v5/alerting/alert"
"github.com/TwiN/gatus/v5/client"
"github.com/TwiN/gatus/v5/config/endpoint/dns"
@@ -104,6 +105,9 @@ type Endpoint struct {
// 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"`
+
// DNSConfig is the configuration for DNS monitoring
DNSConfig *dns.Config `yaml:"dns,omitempty"`
@@ -219,6 +223,11 @@ func (e *Endpoint) ValidateAndSetDefaults() error {
if e.Type() == TypeUNKNOWN {
return ErrUnknownEndpointType
}
+ for _, maintenanceWindow := range e.MaintenanceWindows {
+ if err := maintenanceWindow.ValidateAndSetDefaults(); err != nil {
+ return err
+ }
+ }
// Make sure that the request can be created
_, err := http.NewRequest(e.Method, e.URL, bytes.NewBuffer([]byte(e.Body)))
if err != nil {
diff --git a/config/endpoint/endpoint_test.go b/config/endpoint/endpoint_test.go
index 5e165abad..884cacb9d 100644
--- a/config/endpoint/endpoint_test.go
+++ b/config/endpoint/endpoint_test.go
@@ -16,6 +16,7 @@ import (
"github.com/TwiN/gatus/v5/config/endpoint/dns"
"github.com/TwiN/gatus/v5/config/endpoint/ssh"
"github.com/TwiN/gatus/v5/config/endpoint/ui"
+ "github.com/TwiN/gatus/v5/config/maintenance"
"github.com/TwiN/gatus/v5/test"
)
@@ -390,10 +391,11 @@ func TestEndpoint_Type(t *testing.T) {
func TestEndpoint_ValidateAndSetDefaults(t *testing.T) {
endpoint := Endpoint{
- Name: "website-health",
- URL: "https://twin.sh/health",
- Conditions: []Condition{Condition("[STATUS] == 200")},
- Alerts: []*alert.Alert{{Type: alert.TypePagerDuty}},
+ Name: "website-health",
+ URL: "https://twin.sh/health",
+ Conditions: []Condition{Condition("[STATUS] == 200")},
+ Alerts: []*alert.Alert{{Type: alert.TypePagerDuty}},
+ MaintenanceWindows: []*maintenance.Config{{Start: "03:50", Duration: 4 * time.Hour}},
}
if err := endpoint.ValidateAndSetDefaults(); err != nil {
t.Errorf("Expected no error, got %v", err)
@@ -432,6 +434,15 @@ func TestEndpoint_ValidateAndSetDefaults(t *testing.T) {
if endpoint.Alerts[0].FailureThreshold != 3 {
t.Error("Endpoint alert should've defaulted to a failure threshold of 3")
}
+ if len(endpoint.MaintenanceWindows) != 1 {
+ t.Error("Endpoint should've had 1 maintenance window")
+ }
+ if !endpoint.MaintenanceWindows[0].IsEnabled() {
+ t.Error("Endpoint maintenance should've defaulted to true")
+ }
+ if endpoint.MaintenanceWindows[0].Timezone != "UTC" {
+ t.Error("Endpoint maintenance should've defaulted to UTC")
+ }
}
func TestEndpoint_ValidateAndSetDefaultsWithInvalidCondition(t *testing.T) {
diff --git a/watchdog/watchdog.go b/watchdog/watchdog.go
index 38c373bc7..7c5a15488 100644
--- a/watchdog/watchdog.go
+++ b/watchdog/watchdog.go
@@ -80,7 +80,14 @@ func execute(ep *endpoint.Endpoint, alertingConfig *alerting.Config, maintenance
} else {
logr.Infof("[watchdog.execute] Monitored group=%s; endpoint=%s; key=%s; success=%v; errors=%d; duration=%s", ep.Group, ep.Name, ep.Key(), result.Success, len(result.Errors), result.Duration.Round(time.Millisecond))
}
- if !maintenanceConfig.IsUnderMaintenance() {
+ inEndpointMaintenanceWindow := false
+ for _, maintenanceWindow := range ep.MaintenanceWindows {
+ if maintenanceWindow.IsUnderMaintenance() {
+ logr.Debug("[watchdog.execute] Under endpoint maintenance window")
+ inEndpointMaintenanceWindow = true
+ }
+ }
+ if !maintenanceConfig.IsUnderMaintenance() && !inEndpointMaintenanceWindow {
// TODO: Consider moving this after the monitoring lock is unlocked? I mean, how much noise can a single alerting provider cause...
HandleAlerting(ep, result, alertingConfig)
} else {