diff --git a/README.md b/README.md
index bc52cf04..11809690 100644
--- a/README.md
+++ b/README.md
@@ -60,6 +60,7 @@ Have any feedback or questions? [Create a discussion](https://github.com/TwiN/ga
- [Configuring Google Chat alerts](#configuring-google-chat-alerts)
- [Configuring Gotify alerts](#configuring-gotify-alerts)
- [Configuring HomeAssistant alerts](#configuring-homeassistant-alerts)
+ - [Configuring Ilert alerts](#configuring-ilert-alerts)
- [Configuring Incident.io alerts](#configuring-incidentio-alerts)
- [Configuring JetBrains Space alerts](#configuring-jetbrains-space-alerts)
- [Configuring Matrix alerts](#configuring-matrix-alerts)
@@ -590,6 +591,7 @@ endpoints:
| `alerting.gitlab` | Configuration for alerts of type `gitlab`.
See [Configuring GitLab alerts](#configuring-gitlab-alerts). | `{}` |
| `alerting.googlechat` | Configuration for alerts of type `googlechat`.
See [Configuring Google Chat alerts](#configuring-google-chat-alerts). | `{}` |
| `alerting.gotify` | Configuration for alerts of type `gotify`.
See [Configuring Gotify alerts](#configuring-gotify-alerts). | `{}` |
+| `alerting.ilert` | Configuration for alerts of type `ilert`.
See [Configuring ilert alerts](#configuring-ilert-alerts). | `{}` |
| `alerting.incident-io` | Configuration for alerts of type `incident-io`.
See [Configuring Incident.io alerts](#configuring-incidentio-alerts). | `{}` |
| `alerting.jetbrainsspace` | Configuration for alerts of type `jetbrainsspace`.
See [Configuring JetBrains Space alerts](#configuring-jetbrains-space-alerts). | `{}` |
| `alerting.matrix` | Configuration for alerts of type `matrix`.
See [Configuring Matrix alerts](#configuring-matrix-alerts). | `{}` |
@@ -897,6 +899,51 @@ endpoints:
| `alerting.gotify.title` | Title of the notification | `"Gatus: "` |
| `alerting.gotify.default-alert` | Default alert configuration.
See [Setting a default alert](#setting-a-default-alert). | N/A |
+#### Configuring ilert alerts
+| Parameter | Description | Default |
+|:---------------------------------------|:-------------------------------------------------------------------------------------------|:--------|
+| `alerting.ilert` | Configuration for alerts of type `ilert` | `{}` |
+| `alerting.ilert.integration-key` | ilert Alert Source integration key | `""` |
+| `alerting.ilert.default-alert` | Default alert configuration.
See [Setting a default alert](#setting-a-default-alert) | N/A |
+| `alerting.ilert.overrides` | List of overrides that may be prioritized over the default configuration | `[]` |
+| `alerting.ilert.overrides[].group` | Endpoint group for which the configuration will be overridden by this configuration | `""` |
+| `alerting.ilert.overrides[].*` | See `alerting.ilert.*` parameters | `{}` |
+
+It is highly recommended to set `endpoints[].alerts[].send-on-resolved` to `true` for alerts
+of type `ilert`, because unlike other alerts, the operation resulting from setting said
+parameter to `true` will not create another alert but mark the alert as resolved on
+ilert instead.
+
+Behavior:
+- By default, `alerting.ilert.integration-key` is used as the integration key
+- If the endpoint being evaluated belongs to a group (`endpoints[].group`) matching the value of `alerting.ilert.overrides[].group`, the provider will use that override's integration key instead of `alerting.ilert.integration-key`'s
+
+```yaml
+alerting:
+ ilert:
+ integration-key: "********************************"
+ # You can also add group-specific integration keys, which will
+ # override the integration key above for the specified groups
+ overrides:
+ - group: "core"
+ integration-key: "********************************"
+
+endpoints:
+ - name: website
+ url: "https://twin.sh/health"
+ interval: 30s
+ conditions:
+ - "[STATUS] == 200"
+ - "[BODY].status == UP"
+ - "[RESPONSE_TIME] < 300"
+ alerts:
+ - type: ilert
+ failure-threshold: 3
+ success-threshold: 5
+ send-on-resolved: true
+ description: "healthcheck failed"
+```
+
```yaml
alerting:
gotify:
diff --git a/alerting/alert/type.go b/alerting/alert/type.go
index 1f36d6e1..1ec49c32 100644
--- a/alerting/alert/type.go
+++ b/alerting/alert/type.go
@@ -32,8 +32,11 @@ const (
// TypeGotify is the Type for the gotify alerting provider
TypeGotify Type = "gotify"
- // TypeHomeAssistant is the Type for the homeassistant alerting provider
+ // TypeHomeAssistant is the Type for the homeassistant alerting provider
TypeHomeAssistant Type = "homeassistant"
+
+ // TypeIlert is the Type for the ilert alerting provider
+ TypeIlert Type = "ilert"
// TypeIncidentIO is the Type for the incident-io alerting provider
TypeIncidentIO Type = "incident-io"
diff --git a/alerting/config.go b/alerting/config.go
index 58c1d647..30ca1e07 100644
--- a/alerting/config.go
+++ b/alerting/config.go
@@ -15,7 +15,8 @@ import (
"github.com/TwiN/gatus/v5/alerting/provider/gitlab"
"github.com/TwiN/gatus/v5/alerting/provider/googlechat"
"github.com/TwiN/gatus/v5/alerting/provider/gotify"
- "github.com/TwiN/gatus/v5/alerting/provider/homeassistant"
+ "github.com/TwiN/gatus/v5/alerting/provider/homeassistant"
+ "github.com/TwiN/gatus/v5/alerting/provider/ilert"
"github.com/TwiN/gatus/v5/alerting/provider/incidentio"
"github.com/TwiN/gatus/v5/alerting/provider/jetbrainsspace"
"github.com/TwiN/gatus/v5/alerting/provider/matrix"
@@ -62,9 +63,12 @@ type Config struct {
// Gotify is the configuration for the gotify alerting provider
Gotify *gotify.AlertProvider `yaml:"gotify,omitempty"`
-
+
// HomeAssistant is the configuration for the homeassistant alerting provider
HomeAssistant *homeassistant.AlertProvider `yaml:"homeassistant,omitempty"`
+
+ // Ilert is the configuration for the ilert alerting provider
+ Ilert *ilert.AlertProvider `yaml:"ilert,omitempty"`
// IncidentIO is the configuration for the incident-io alerting provider
IncidentIO *incidentio.AlertProvider `yaml:"incident-io,omitempty"`
diff --git a/alerting/provider/ilert/ilert.go b/alerting/provider/ilert/ilert.go
new file mode 100644
index 00000000..ba1822b5
--- /dev/null
+++ b/alerting/provider/ilert/ilert.go
@@ -0,0 +1,168 @@
+package ilert
+
+import (
+ "bytes"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "net/http"
+
+ "github.com/TwiN/gatus/v5/alerting/alert"
+ "github.com/TwiN/gatus/v5/client"
+ "github.com/TwiN/gatus/v5/config/endpoint"
+ "gopkg.in/yaml.v3"
+)
+
+const (
+ restAPIUrl = "https://api.ilert.com/api/v1/events/gatus/"
+)
+
+var (
+ ErrIntegrationKeyNotSet = errors.New("integration key is not set")
+ ErrDuplicateGroupOverride = errors.New("duplicate group override")
+)
+
+type Config struct {
+ IntegrationKey string `yaml:"integration-key"`
+}
+
+func (cfg *Config) Validate() error {
+ if len(cfg.IntegrationKey) == 0 {
+ return ErrIntegrationKeyNotSet
+ }
+ return nil
+}
+
+func (cfg *Config) Merge(override *Config) {
+ if len(override.IntegrationKey) > 0 {
+ cfg.IntegrationKey = override.IntegrationKey
+ }
+}
+
+// AlertProvider is the configuration necessary for sending an alert using ilert
+type AlertProvider struct {
+ DefaultConfig Config `yaml:",inline"`
+
+ // DefaultAlert is the default alert configuration to use for endpoints with an alert of the appropriate type
+ DefaultAlert *alert.Alert `yaml:"default-alert,omitempty"`
+
+ // Overrides is a list of Override that may be prioritized over the default configuration
+ Overrides []Override `yaml:"overrides,omitempty"`
+}
+
+type Override struct {
+ Group string `yaml:"group"`
+ Config `yaml:",inline"`
+}
+
+func (provider *AlertProvider) Validate() error {
+ registeredGroups := make(map[string]bool)
+ if provider.Overrides != nil {
+ for _, override := range provider.Overrides {
+ if isAlreadyRegistered := registeredGroups[override.Group]; isAlreadyRegistered || override.Group == "" {
+ return ErrDuplicateGroupOverride
+ }
+ registeredGroups[override.Group] = true
+ }
+ }
+ return provider.DefaultConfig.Validate()
+}
+
+func (provider *AlertProvider) Send(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) error {
+ cfg, err := provider.GetConfig(ep.Group, alert)
+ if err != nil {
+ return err
+ }
+ buffer := bytes.NewBuffer(provider.buildRequestBody(cfg, ep, alert, result, resolved))
+
+ req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", restAPIUrl, cfg.IntegrationKey), buffer)
+ if err != nil {
+ return err
+ }
+ req.Header.Set("Content-Type", "application/json")
+ response, err := client.GetHTTPClient(nil).Do(req)
+ if err != nil {
+ return err
+ }
+ defer response.Body.Close()
+ if response.StatusCode > 399 {
+ body, _ := io.ReadAll(response.Body)
+ return fmt.Errorf("call to provider alert returned status code %d: %s", response.StatusCode, string(body))
+ }
+
+ return err
+}
+
+type Body struct {
+ Alert alert.Alert `json:"alert"`
+ Name string `json:"name"`
+ Group string `json:"group"`
+ Status string `json:"status"`
+ Title string `json:"title"`
+ Details string `json:"details,omitempty"`
+ ConditionResults []*endpoint.ConditionResult `json:"condition_results"`
+ URL string `json:"url"`
+}
+
+func (provider *AlertProvider) buildRequestBody(cfg *Config, ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) []byte {
+ var details, status string
+ if resolved {
+ status = "resolved"
+ } else {
+ status = "firing"
+ }
+
+ if len(alert.GetDescription()) > 0 {
+ details = alert.GetDescription()
+ } else {
+ details = "No description"
+ }
+
+ var body []byte
+ body, _ = json.Marshal(Body{
+ Alert: *alert,
+ Name: ep.Name,
+ Group: ep.Group,
+ Title: ep.DisplayName(),
+ Status: status,
+ Details: details,
+ ConditionResults: result.ConditionResults,
+ URL: ep.URL,
+ })
+ return body
+}
+
+func (provider *AlertProvider) GetDefaultAlert() *alert.Alert {
+ return provider.DefaultAlert
+}
+
+func (provider *AlertProvider) GetConfig(group string, alert *alert.Alert) (*Config, error) {
+ cfg := provider.DefaultConfig
+ // Handle group overrides
+ if provider.Overrides != nil {
+ for _, override := range provider.Overrides {
+ if group == override.Group {
+ cfg.Merge(&override.Config)
+ break
+ }
+ }
+ }
+ // Handle alert overrides
+ if len(alert.ProviderOverride) != 0 {
+ overrideConfig := Config{}
+ if err := yaml.Unmarshal(alert.ProviderOverrideAsBytes(), &overrideConfig); err != nil {
+ return nil, err
+ }
+ cfg.Merge(&overrideConfig)
+ }
+ // Validate the configuration
+ err := cfg.Validate()
+ return &cfg, err
+}
+
+// ValidateOverrides validates the alert's provider override and, if present, the group override
+func (provider *AlertProvider) ValidateOverrides(group string, alert *alert.Alert) error {
+ _, err := provider.GetConfig(group, alert)
+ return err
+}
diff --git a/alerting/provider/ilert/ilert_test.go b/alerting/provider/ilert/ilert_test.go
new file mode 100644
index 00000000..3f18d60e
--- /dev/null
+++ b/alerting/provider/ilert/ilert_test.go
@@ -0,0 +1,322 @@
+package ilert
+
+import (
+ "bytes"
+ "encoding/json"
+ "io"
+ "net/http"
+ "testing"
+
+ "github.com/TwiN/gatus/v5/alerting/alert"
+ "github.com/TwiN/gatus/v5/client"
+ "github.com/TwiN/gatus/v5/config/endpoint"
+ "github.com/TwiN/gatus/v5/test"
+)
+
+func TestAlertProvider_Validate(t *testing.T) {
+ scenarios := []struct {
+ name string
+ provider AlertProvider
+ expected bool
+ }{
+ {
+ name: "valid",
+ provider: AlertProvider{
+ DefaultConfig: Config{
+ IntegrationKey: "some-random-key",
+ },
+ },
+ expected: true,
+ },
+ {
+ name: "invalid-integration-key",
+ provider: AlertProvider{
+ DefaultConfig: Config{
+ IntegrationKey: "",
+ },
+ },
+ expected: false,
+ },
+ }
+ for _, scenario := range scenarios {
+ t.Run(scenario.name, func(t *testing.T) {
+ err := scenario.provider.Validate()
+ if scenario.expected && err != nil {
+ t.Error("expected no error, got", err.Error())
+ }
+ if !scenario.expected && err == nil {
+ t.Error("expected error, got none")
+ }
+ })
+ }
+}
+
+func TestAlertProvider_ValidateWithOverride(t *testing.T) {
+ providerWithInvalidOverrideGroup := AlertProvider{
+ DefaultConfig: Config{IntegrationKey: "00000000000000000000000000000001"},
+ Overrides: []Override{
+ {
+ Config: Config{IntegrationKey: "00000000000000000000000000000002"},
+ Group: "",
+ },
+ },
+ }
+ if err := providerWithInvalidOverrideGroup.Validate(); err == nil {
+ t.Error("provider Group shouldn't have been valid")
+ }
+ providerWithValidOverride := AlertProvider{
+ DefaultConfig: Config{IntegrationKey: "00000000000000000000000000000001"},
+ Overrides: []Override{
+ {
+ Config: Config{IntegrationKey: "00000000000000000000000000000002"},
+ Group: "group",
+ },
+ },
+ }
+ if err := providerWithValidOverride.Validate(); err != nil {
+ t.Error("provider should've been valid, got error:", err.Error())
+ }
+}
+
+func TestAlertProvider_Send(t *testing.T) {
+ defer client.InjectHTTPClient(nil)
+ firstDescription := "description-1"
+ secondDescription := "description-2"
+ sendOnResolved := true
+ scenarios := []struct {
+ Name string
+ Provider AlertProvider
+ Alert alert.Alert
+ Resolved bool
+ MockRoundTripper test.MockRoundTripper
+ ExpectedError bool
+ }{
+ {
+ Name: "triggered",
+ Provider: AlertProvider{DefaultConfig: Config{
+ IntegrationKey: "some-integration-key",
+ }},
+ Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 3, FailureThreshold: 3, ResolveKey: "123", Type: "ilert", SendOnResolved: &sendOnResolved},
+ Resolved: false,
+ MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response {
+ var b bytes.Buffer
+
+ reader := io.NopCloser(&b)
+ return &http.Response{StatusCode: http.StatusAccepted, Body: reader}
+ }),
+ ExpectedError: false,
+ },
+ {
+ Name: "triggered-error",
+ Provider: AlertProvider{DefaultConfig: Config{
+ IntegrationKey: "some-integration-key",
+ }},
+ Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 3, FailureThreshold: 3, ResolveKey: "123", Type: "ilert", SendOnResolved: &sendOnResolved},
+ Resolved: false,
+ MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response {
+ return &http.Response{StatusCode: http.StatusBadRequest, Body: http.NoBody}
+ }),
+ ExpectedError: true,
+ },
+ {
+ Name: "resolved",
+ Provider: AlertProvider{DefaultConfig: Config{
+ IntegrationKey: "some-integration-key",
+ }},
+ Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 3, FailureThreshold: 3, ResolveKey: "123", Type: "ilert", SendOnResolved: &sendOnResolved},
+ Resolved: true,
+ MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response {
+ var b bytes.Buffer
+ reader := io.NopCloser(&b)
+ return &http.Response{StatusCode: http.StatusAccepted, Body: reader}
+ }),
+ ExpectedError: false,
+ },
+ }
+ for _, scenario := range scenarios {
+ t.Run(scenario.Name, func(t *testing.T) {
+ client.InjectHTTPClient(&http.Client{Transport: scenario.MockRoundTripper})
+ err := scenario.Provider.Send(
+ &endpoint.Endpoint{Name: "endpoint-name"},
+ &scenario.Alert,
+ &endpoint.Result{
+ ConditionResults: []*endpoint.ConditionResult{
+ {Condition: "[CONNECTED] == true", Success: scenario.Resolved},
+ {Condition: "[STATUS] == 200", Success: scenario.Resolved},
+ },
+ },
+ scenario.Resolved,
+ )
+ if scenario.ExpectedError && err == nil {
+ t.Error("expected error, got none")
+ }
+ if !scenario.ExpectedError && err != nil {
+ t.Error("expected no error, got", err.Error())
+ }
+ })
+ }
+}
+
+func TestAlertProvider_BuildRequestBody(t *testing.T) {
+ firstDescription := "description-1"
+ secondDescription := "description-2"
+ sendOnResolved := true
+
+ scenarios := []struct {
+ Name string
+ Provider AlertProvider
+ Alert alert.Alert
+ Resolved bool
+ ExpectedBody string
+ }{
+ {
+ Name: "triggered",
+ Provider: AlertProvider{DefaultConfig: Config{IntegrationKey: "some-integration-key"}},
+ Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 3, FailureThreshold: 3, ResolveKey: "123", Type: "ilert", SendOnResolved: &sendOnResolved},
+ Resolved: false,
+ ExpectedBody: `{"alert":{"Type":"ilert","Enabled":null,"FailureThreshold":3,"SuccessThreshold":3,"Description":"description-1","SendOnResolved":true,"ProviderOverride":null,"ResolveKey":"123","Triggered":false},"name":"endpoint-name","group":"","status":"firing","title":"endpoint-name","details":"description-1","condition_results":[{"condition":"[CONNECTED] == true","success":false},{"condition":"[STATUS] == 200","success":false}],"url":""}`,
+ },
+ {
+ Name: "resolved",
+ Provider: AlertProvider{DefaultConfig: Config{IntegrationKey: "some-integration-key"}},
+ Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 4, FailureThreshold: 3, ResolveKey: "123", Type: "ilert", SendOnResolved: &sendOnResolved},
+ Resolved: true,
+ ExpectedBody: `{"alert":{"Type":"ilert","Enabled":null,"FailureThreshold":3,"SuccessThreshold":4,"Description":"description-1","SendOnResolved":true,"ProviderOverride":null,"ResolveKey":"123","Triggered":false},"name":"endpoint-name","group":"","status":"resolved","title":"endpoint-name","details":"description-1","condition_results":[{"condition":"[CONNECTED] == true","success":true},{"condition":"[STATUS] == 200","success":true}],"url":""}`,
+ },
+ {
+ Name: "group-override",
+ Provider: AlertProvider{DefaultConfig: Config{IntegrationKey: "some-integration-key"}, Overrides: []Override{{Group: "g", Config: Config{IntegrationKey: "different-integration-key"}}}},
+ Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3, ResolveKey: "123", Type: "ilert", SendOnResolved: &sendOnResolved},
+ Resolved: false,
+ ExpectedBody: `{"alert":{"Type":"ilert","Enabled":null,"FailureThreshold":3,"SuccessThreshold":5,"Description":"description-2","SendOnResolved":true,"ProviderOverride":null,"ResolveKey":"123","Triggered":false},"name":"endpoint-name","group":"","status":"firing","title":"endpoint-name","details":"description-2","condition_results":[{"condition":"[CONNECTED] == true","success":false},{"condition":"[STATUS] == 200","success":false}],"url":""}`,
+ },
+ }
+
+ for _, scenario := range scenarios {
+ t.Run(scenario.Name, func(t *testing.T) {
+ cfg, err := scenario.Provider.GetConfig("g", &scenario.Alert)
+ if err != nil {
+ t.Error("expected no error, got", err.Error())
+ }
+ body := scenario.Provider.buildRequestBody(
+ cfg,
+ &endpoint.Endpoint{Name: "endpoint-name"},
+ &scenario.Alert,
+ &endpoint.Result{
+ ConditionResults: []*endpoint.ConditionResult{
+ {Condition: "[CONNECTED] == true", Success: scenario.Resolved},
+ {Condition: "[STATUS] == 200", Success: scenario.Resolved},
+ },
+ },
+ scenario.Resolved,
+ )
+ if string(body) != scenario.ExpectedBody {
+ t.Errorf("expected:\n%s\ngot:\n%s", scenario.ExpectedBody, body)
+ }
+ out := make(map[string]interface{})
+ if err := json.Unmarshal(body, &out); err != nil {
+ t.Error("expected body to be valid JSON, got error:", err.Error())
+ }
+ })
+ }
+}
+
+func TestAlertProvider_GetDefaultAlert(t *testing.T) {
+ if (&AlertProvider{DefaultAlert: &alert.Alert{}}).GetDefaultAlert() == nil {
+ t.Error("expected default alert to be not nil")
+ }
+ if (&AlertProvider{DefaultAlert: nil}).GetDefaultAlert() != nil {
+ t.Error("expected default alert to be nil")
+ }
+}
+
+func TestAlertProvider_GetConfig(t *testing.T) {
+ scenarios := []struct {
+ Name string
+ Provider AlertProvider
+ InputGroup string
+ InputAlert alert.Alert
+ ExpectedOutput Config
+ }{
+ {
+ Name: "provider-no-override-specify-no-group-should-default",
+ Provider: AlertProvider{
+ DefaultConfig: Config{IntegrationKey: "00000000000000000000000000000001"},
+ Overrides: nil,
+ },
+ InputGroup: "",
+ InputAlert: alert.Alert{},
+ ExpectedOutput: Config{IntegrationKey: "00000000000000000000000000000001"},
+ },
+ {
+ Name: "provider-no-override-specify-group-should-default",
+ Provider: AlertProvider{
+ DefaultConfig: Config{IntegrationKey: "00000000000000000000000000000001"},
+ Overrides: nil,
+ },
+ InputGroup: "group",
+ InputAlert: alert.Alert{},
+ ExpectedOutput: Config{IntegrationKey: "00000000000000000000000000000001"},
+ },
+ {
+ Name: "provider-with-override-specify-no-group-should-default",
+ Provider: AlertProvider{
+ DefaultConfig: Config{IntegrationKey: "00000000000000000000000000000001"},
+ Overrides: []Override{
+ {
+ Group: "group",
+ Config: Config{IntegrationKey: "00000000000000000000000000000002"},
+ },
+ },
+ },
+ InputGroup: "",
+ InputAlert: alert.Alert{},
+ ExpectedOutput: Config{IntegrationKey: "00000000000000000000000000000001"},
+ },
+ {
+ Name: "provider-with-override-specify-group-should-override",
+ Provider: AlertProvider{
+ DefaultConfig: Config{IntegrationKey: "00000000000000000000000000000001"},
+ Overrides: []Override{
+ {
+ Group: "group",
+ Config: Config{IntegrationKey: "00000000000000000000000000000002"},
+ },
+ },
+ },
+ InputGroup: "group",
+ InputAlert: alert.Alert{},
+ ExpectedOutput: Config{IntegrationKey: "00000000000000000000000000000002"},
+ },
+ {
+ Name: "provider-with-group-override-and-alert-override--alert-override-should-take-precedence",
+ Provider: AlertProvider{
+ DefaultConfig: Config{IntegrationKey: "00000000000000000000000000000001"},
+ Overrides: []Override{
+ {
+ Group: "group",
+ Config: Config{IntegrationKey: "00000000000000000000000000000002"},
+ },
+ },
+ },
+ InputGroup: "group",
+ InputAlert: alert.Alert{ProviderOverride: map[string]any{"integration-key": "00000000000000000000000000000003"}},
+ ExpectedOutput: Config{IntegrationKey: "00000000000000000000000000000003"},
+ },
+ }
+ for _, scenario := range scenarios {
+ t.Run(scenario.Name, func(t *testing.T) {
+ got, err := scenario.Provider.GetConfig(scenario.InputGroup, &scenario.InputAlert)
+ if err != nil {
+ t.Fatalf("unexpected error: %s", err)
+ }
+ if got.IntegrationKey != scenario.ExpectedOutput.IntegrationKey {
+ t.Errorf("expected %s, got %s", scenario.ExpectedOutput.IntegrationKey, got.IntegrationKey)
+ }
+ // Test ValidateOverrides as well, since it really just calls GetConfig
+ if err = scenario.Provider.ValidateOverrides(scenario.InputGroup, &scenario.InputAlert); err != nil {
+ t.Errorf("unexpected error: %s", err)
+ }
+ })
+ }
+}
diff --git a/alerting/provider/provider.go b/alerting/provider/provider.go
index 227fc6df..1b76dfd5 100644
--- a/alerting/provider/provider.go
+++ b/alerting/provider/provider.go
@@ -10,8 +10,9 @@ import (
"github.com/TwiN/gatus/v5/alerting/provider/github"
"github.com/TwiN/gatus/v5/alerting/provider/gitlab"
"github.com/TwiN/gatus/v5/alerting/provider/googlechat"
- "github.com/TwiN/gatus/v5/alerting/provider/gotify"
+ "github.com/TwiN/gatus/v5/alerting/provider/gotify"
"github.com/TwiN/gatus/v5/alerting/provider/homeassistant"
+ "github.com/TwiN/gatus/v5/alerting/provider/ilert"
"github.com/TwiN/gatus/v5/alerting/provider/incidentio"
"github.com/TwiN/gatus/v5/alerting/provider/jetbrainsspace"
"github.com/TwiN/gatus/v5/alerting/provider/matrix"
@@ -82,8 +83,9 @@ var (
_ AlertProvider = (*github.AlertProvider)(nil)
_ AlertProvider = (*gitlab.AlertProvider)(nil)
_ AlertProvider = (*googlechat.AlertProvider)(nil)
- _ AlertProvider = (*gotify.AlertProvider)(nil)
+ _ AlertProvider = (*gotify.AlertProvider)(nil)
_ AlertProvider = (*homeassistant.AlertProvider)(nil)
+ _ AlertProvider = (*ilert.AlertProvider)(nil)
_ AlertProvider = (*incidentio.AlertProvider)(nil)
_ AlertProvider = (*jetbrainsspace.AlertProvider)(nil)
_ AlertProvider = (*matrix.AlertProvider)(nil)
@@ -109,8 +111,9 @@ var (
_ Config[github.Config] = (*github.Config)(nil)
_ Config[gitlab.Config] = (*gitlab.Config)(nil)
_ Config[googlechat.Config] = (*googlechat.Config)(nil)
- _ Config[gotify.Config] = (*gotify.Config)(nil)
+ _ Config[gotify.Config] = (*gotify.Config)(nil)
_ Config[homeassistant.Config] = (*homeassistant.Config)(nil)
+ _ Config[ilert.Config] = (*ilert.Config)(nil)
_ Config[incidentio.Config] = (*incidentio.Config)(nil)
_ Config[jetbrainsspace.Config] = (*jetbrainsspace.Config)(nil)
_ Config[matrix.Config] = (*matrix.Config)(nil)
diff --git a/config/config.go b/config/config.go
index 40b50d72..608a1384 100644
--- a/config/config.go
+++ b/config/config.go
@@ -411,7 +411,8 @@ func validateAlertingConfig(alertingConfig *alerting.Config, endpoints []*endpoi
alert.TypeGitea,
alert.TypeGoogleChat,
alert.TypeGotify,
- alert.TypeHomeAssistant,
+ alert.TypeHomeAssistant,
+ alert.TypeIlert,
alert.TypeIncidentIO,
alert.TypeJetBrainsSpace,
alert.TypeMatrix,