From 9d151fcdb4457c537792fdfcf88ba9e82c074ede Mon Sep 17 00:00:00 2001 From: TwiN Date: Thu, 9 May 2024 22:56:16 -0400 Subject: [PATCH] refactor: Break core package into multiple packages under config/endpoint (#759) * refactor: Partially break core package into dns, result and ssh packages * refactor: Move core package to config/endpoint * refactor: Fix warning about overlapping imported package name with endpoint variable * refactor: Rename EndpointStatus to Status * refactor: Merge result pkg back into endpoint pkg, because it makes more sense * refactor: Rename parameter r to result in Condition.evaluate * refactor: Rename parameter r to result * refactor: Revert accidental change to endpoint.TypeDNS * refactor: Rename parameter r to result * refactor: Merge util package into endpoint package * refactor: Rename parameter r to result --- alerting/alert/alert.go | 2 +- alerting/provider/awsses/awsses.go | 18 +- alerting/provider/awsses/awsses_test.go | 8 +- alerting/provider/custom/custom.go | 20 +- alerting/provider/custom/custom_test.go | 12 +- alerting/provider/discord/discord.go | 14 +- alerting/provider/discord/discord_test.go | 16 +- alerting/provider/email/email.go | 18 +- alerting/provider/email/email_test.go | 8 +- alerting/provider/github/github.go | 12 +- alerting/provider/github/github_test.go | 22 +- alerting/provider/gitlab/gitlab.go | 22 +- alerting/provider/gitlab/gitlab_test.go | 18 +- alerting/provider/googlechat/googlechat.go | 16 +- .../provider/googlechat/googlechat_test.go | 22 +- alerting/provider/gotify/gotify.go | 14 +- alerting/provider/gotify/gotify_test.go | 16 +- .../provider/jetbrainsspace/jetbrainsspace.go | 14 +- .../jetbrainsspace/jetbrainsspace_test.go | 22 +- alerting/provider/matrix/matrix.go | 26 +- alerting/provider/matrix/matrix_test.go | 14 +- alerting/provider/mattermost/mattermost.go | 14 +- .../provider/mattermost/mattermost_test.go | 14 +- alerting/provider/messagebird/messagebird.go | 12 +- .../provider/messagebird/messagebird_test.go | 14 +- alerting/provider/ntfy/ntfy.go | 10 +- alerting/provider/ntfy/ntfy_test.go | 8 +- alerting/provider/opsgenie/opsgenie.go | 54 ++-- alerting/provider/opsgenie/opsgenie_test.go | 44 +-- alerting/provider/pagerduty/pagerduty.go | 14 +- alerting/provider/pagerduty/pagerduty_test.go | 10 +- alerting/provider/provider.go | 4 +- alerting/provider/pushover/pushover.go | 12 +- alerting/provider/pushover/pushover_test.go | 14 +- alerting/provider/slack/slack.go | 14 +- alerting/provider/slack/slack_test.go | 26 +- alerting/provider/teams/teams.go | 16 +- alerting/provider/teams/teams_test.go | 16 +- alerting/provider/telegram/telegram.go | 12 +- alerting/provider/telegram/telegram_test.go | 16 +- alerting/provider/twilio/twilio.go | 12 +- alerting/provider/twilio/twilio_test.go | 8 +- api/badge.go | 2 +- api/badge_test.go | 30 +- api/chart_test.go | 8 +- api/endpoint_status.go | 10 +- api/endpoint_status_test.go | 20 +- api/external_endpoint.go | 4 +- api/external_endpoint_test.go | 4 +- api/spa_test.go | 8 +- client/client.go | 48 +++ client/client_test.go | 96 ++++++ config/config.go | 41 ++- config/config_test.go | 28 +- .../endpoint/common.go | 2 +- .../endpoint/common_test.go | 2 +- {core => config/endpoint}/condition.go | 2 +- .../endpoint}/condition_bench_test.go | 6 +- {core => config/endpoint}/condition_result.go | 2 +- {core => config/endpoint}/condition_test.go | 2 +- config/endpoint/dns/dns.go | 38 +++ config/endpoint/dns/dns_test.go | 27 ++ {core => config/endpoint}/endpoint.go | 275 +++++++++--------- {core => config/endpoint}/endpoint_test.go | 86 +++--- {core => config/endpoint}/event.go | 6 +- {core => config/endpoint}/event_test.go | 6 +- .../endpoint}/external_endpoint.go | 5 +- .../endpoint}/external_endpoint_test.go | 2 +- {util => config/endpoint}/key.go | 2 +- {util => config/endpoint}/key_bench_test.go | 2 +- {util => config/endpoint}/key_test.go | 2 +- {core => config/endpoint}/result.go | 2 +- {core => config/endpoint}/result_test.go | 2 +- {core => config/endpoint/ssh}/ssh.go | 12 +- {core => config/endpoint/ssh}/ssh_test.go | 14 +- .../endpoint/status.go | 18 +- config/endpoint/status_test.go | 19 ++ {core => config/endpoint}/ui/ui.go | 2 +- {core => config/endpoint}/ui/ui_test.go | 0 {core => config/endpoint}/uptime.go | 2 +- config/endpoints/README.md | 1 - controller/controller_test.go | 6 +- core/dns.go | 86 ------ core/dns_test.go | 126 -------- core/endpoint_status_test.go | 19 -- main.go | 4 +- metrics/metrics.go | 18 +- metrics/metrics_test.go | 19 +- storage/store/memory/memory.go | 47 ++- storage/store/memory/memory_test.go | 20 +- storage/store/memory/uptime.go | 8 +- storage/store/memory/uptime_bench_test.go | 6 +- storage/store/memory/uptime_test.go | 48 +-- storage/store/memory/util.go | 22 +- storage/store/memory/util_bench_test.go | 6 +- storage/store/memory/util_test.go | 16 +- storage/store/sql/sql.go | 105 ++++--- storage/store/sql/sql_test.go | 32 +- storage/store/store.go | 16 +- storage/store/store_bench_test.go | 12 +- storage/store/store_test.go | 41 +-- watchdog/alerting.go | 42 +-- watchdog/alerting_test.go | 174 +++++------ watchdog/watchdog.go | 38 +-- 104 files changed, 1216 insertions(+), 1211 deletions(-) rename core/endpoint_common.go => config/endpoint/common.go (98%) rename core/endpoint_common_test.go => config/endpoint/common_test.go (98%) rename {core => config/endpoint}/condition.go (99%) rename {core => config/endpoint}/condition_bench_test.go (98%) rename {core => config/endpoint}/condition_result.go (93%) rename {core => config/endpoint}/condition_test.go (99%) create mode 100644 config/endpoint/dns/dns.go create mode 100644 config/endpoint/dns/dns_test.go rename {core => config/endpoint}/endpoint.go (58%) rename {core => config/endpoint}/endpoint_test.go (95%) rename {core => config/endpoint}/event.go (96%) rename {core => config/endpoint}/event_test.go (89%) rename {core => config/endpoint}/external_endpoint.go (95%) rename {core => config/endpoint}/external_endpoint_test.go (97%) rename {util => config/endpoint}/key.go (96%) rename {util => config/endpoint}/key_bench_test.go (91%) rename {util => config/endpoint}/key_test.go (98%) rename {core => config/endpoint}/result.go (99%) rename {core => config/endpoint}/result_test.go (96%) rename {core => config/endpoint/ssh}/ssh.go (81%) rename {core => config/endpoint/ssh}/ssh_test.go (70%) rename core/endpoint_status.go => config/endpoint/status.go (64%) create mode 100644 config/endpoint/status_test.go rename {core => config/endpoint}/ui/ui.go (97%) rename {core => config/endpoint}/ui/ui_test.go (100%) rename {core => config/endpoint}/uptime.go (98%) delete mode 100644 config/endpoints/README.md delete mode 100644 core/dns.go delete mode 100644 core/dns_test.go delete mode 100644 core/endpoint_status_test.go diff --git a/alerting/alert/alert.go b/alerting/alert/alert.go index c1fbdcf8..4d90ced2 100644 --- a/alerting/alert/alert.go +++ b/alerting/alert/alert.go @@ -10,7 +10,7 @@ var ( ErrAlertWithInvalidDescription = errors.New("alert description must not have \" or \\") ) -// Alert is a core.Endpoint's alert configuration +// Alert is a endpoint.Endpoint's alert configuration type Alert struct { // Type of alert (required) Type Type `yaml:"type"` diff --git a/alerting/provider/awsses/awsses.go b/alerting/provider/awsses/awsses.go index 17ba05cc..955531a1 100644 --- a/alerting/provider/awsses/awsses.go +++ b/alerting/provider/awsses/awsses.go @@ -5,7 +5,7 @@ import ( "strings" "github.com/TwiN/gatus/v5/alerting/alert" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/credentials" @@ -57,14 +57,14 @@ func (provider *AlertProvider) IsValid() bool { } // Send an alert using the provider -func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) error { +func (provider *AlertProvider) Send(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) error { sess, err := provider.createSession() if err != nil { return err } svc := ses.New(sess) - subject, body := provider.buildMessageSubjectAndBody(endpoint, alert, result, resolved) - emails := strings.Split(provider.getToForGroup(endpoint.Group), ",") + subject, body := provider.buildMessageSubjectAndBody(ep, alert, result, resolved) + emails := strings.Split(provider.getToForGroup(ep.Group), ",") input := &ses.SendEmailInput{ Destination: &ses.Destination{ @@ -110,14 +110,14 @@ func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert, } // buildMessageSubjectAndBody builds the message subject and body -func (provider *AlertProvider) buildMessageSubjectAndBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) (string, string) { +func (provider *AlertProvider) buildMessageSubjectAndBody(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) (string, string) { var subject, message string if resolved { - subject = fmt.Sprintf("[%s] Alert resolved", endpoint.DisplayName()) - message = fmt.Sprintf("An alert for %s has been resolved after passing successfully %d time(s) in a row", endpoint.DisplayName(), alert.SuccessThreshold) + subject = fmt.Sprintf("[%s] Alert resolved", ep.DisplayName()) + message = fmt.Sprintf("An alert for %s has been resolved after passing successfully %d time(s) in a row", ep.DisplayName(), alert.SuccessThreshold) } else { - subject = fmt.Sprintf("[%s] Alert triggered", endpoint.DisplayName()) - message = fmt.Sprintf("An alert for %s has been triggered due to having failed %d time(s) in a row", endpoint.DisplayName(), alert.FailureThreshold) + subject = fmt.Sprintf("[%s] Alert triggered", ep.DisplayName()) + message = fmt.Sprintf("An alert for %s has been triggered due to having failed %d time(s) in a row", ep.DisplayName(), alert.FailureThreshold) } var formattedConditionResults string if len(result.ConditionResults) > 0 { diff --git a/alerting/provider/awsses/awsses_test.go b/alerting/provider/awsses/awsses_test.go index d13022b1..35b5eaf3 100644 --- a/alerting/provider/awsses/awsses_test.go +++ b/alerting/provider/awsses/awsses_test.go @@ -4,7 +4,7 @@ import ( "testing" "github.com/TwiN/gatus/v5/alerting/alert" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" ) func TestAlertDefaultProvider_IsValid(t *testing.T) { @@ -95,10 +95,10 @@ func TestAlertProvider_buildRequestBody(t *testing.T) { for _, scenario := range scenarios { t.Run(scenario.Name, func(t *testing.T) { subject, body := scenario.Provider.buildMessageSubjectAndBody( - &core.Endpoint{Name: "endpoint-name"}, + &endpoint.Endpoint{Name: "endpoint-name"}, &scenario.Alert, - &core.Result{ - ConditionResults: []*core.ConditionResult{ + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, {Condition: "[STATUS] == 200", Success: scenario.Resolved}, }, diff --git a/alerting/provider/custom/custom.go b/alerting/provider/custom/custom.go index 052af1ab..fc661edf 100644 --- a/alerting/provider/custom/custom.go +++ b/alerting/provider/custom/custom.go @@ -9,7 +9,7 @@ import ( "github.com/TwiN/gatus/v5/alerting/alert" "github.com/TwiN/gatus/v5/client" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" ) // AlertProvider is the configuration necessary for sending an alert using a custom HTTP request @@ -50,16 +50,16 @@ func (provider *AlertProvider) GetAlertStatePlaceholderValue(resolved bool) stri return status } -func (provider *AlertProvider) buildHTTPRequest(endpoint *core.Endpoint, alert *alert.Alert, resolved bool) *http.Request { +func (provider *AlertProvider) buildHTTPRequest(ep *endpoint.Endpoint, alert *alert.Alert, resolved bool) *http.Request { body, url, method := provider.Body, provider.URL, provider.Method body = strings.ReplaceAll(body, "[ALERT_DESCRIPTION]", alert.GetDescription()) url = strings.ReplaceAll(url, "[ALERT_DESCRIPTION]", alert.GetDescription()) - body = strings.ReplaceAll(body, "[ENDPOINT_NAME]", endpoint.Name) - url = strings.ReplaceAll(url, "[ENDPOINT_NAME]", endpoint.Name) - body = strings.ReplaceAll(body, "[ENDPOINT_GROUP]", endpoint.Group) - url = strings.ReplaceAll(url, "[ENDPOINT_GROUP]", endpoint.Group) - body = strings.ReplaceAll(body, "[ENDPOINT_URL]", endpoint.URL) - url = strings.ReplaceAll(url, "[ENDPOINT_URL]", endpoint.URL) + body = strings.ReplaceAll(body, "[ENDPOINT_NAME]", ep.Name) + url = strings.ReplaceAll(url, "[ENDPOINT_NAME]", ep.Name) + body = strings.ReplaceAll(body, "[ENDPOINT_GROUP]", ep.Group) + url = strings.ReplaceAll(url, "[ENDPOINT_GROUP]", ep.Group) + body = strings.ReplaceAll(body, "[ENDPOINT_URL]", ep.URL) + url = strings.ReplaceAll(url, "[ENDPOINT_URL]", ep.URL) if resolved { body = strings.ReplaceAll(body, "[ALERT_TRIGGERED_OR_RESOLVED]", provider.GetAlertStatePlaceholderValue(true)) url = strings.ReplaceAll(url, "[ALERT_TRIGGERED_OR_RESOLVED]", provider.GetAlertStatePlaceholderValue(true)) @@ -78,8 +78,8 @@ func (provider *AlertProvider) buildHTTPRequest(endpoint *core.Endpoint, alert * return request } -func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) error { - request := provider.buildHTTPRequest(endpoint, alert, resolved) +func (provider *AlertProvider) Send(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) error { + request := provider.buildHTTPRequest(ep, alert, resolved) response, err := client.GetHTTPClient(provider.ClientConfig).Do(request) if err != nil { return err diff --git a/alerting/provider/custom/custom_test.go b/alerting/provider/custom/custom_test.go index 3927bb6c..1678a7fe 100644 --- a/alerting/provider/custom/custom_test.go +++ b/alerting/provider/custom/custom_test.go @@ -8,7 +8,7 @@ import ( "github.com/TwiN/gatus/v5/alerting/alert" "github.com/TwiN/gatus/v5/client" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" "github.com/TwiN/gatus/v5/test" ) @@ -90,10 +90,10 @@ func TestAlertProvider_Send(t *testing.T) { t.Run(scenario.Name, func(t *testing.T) { client.InjectHTTPClient(&http.Client{Transport: scenario.MockRoundTripper}) err := scenario.Provider.Send( - &core.Endpoint{Name: "endpoint-name"}, + &endpoint.Endpoint{Name: "endpoint-name"}, &scenario.Alert, - &core.Result{ - ConditionResults: []*core.ConditionResult{ + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, {Condition: "[STATUS] == 200", Success: scenario.Resolved}, }, @@ -138,7 +138,7 @@ func TestAlertProvider_buildHTTPRequest(t *testing.T) { for _, scenario := range scenarios { t.Run(fmt.Sprintf("resolved-%v-with-default-placeholders", scenario.Resolved), func(t *testing.T) { request := customAlertProvider.buildHTTPRequest( - &core.Endpoint{Name: "endpoint-name", Group: "endpoint-group", URL: "https://example.com"}, + &endpoint.Endpoint{Name: "endpoint-name", Group: "endpoint-group", URL: "https://example.com"}, &alert.Alert{Description: &alertDescription}, scenario.Resolved, ) @@ -188,7 +188,7 @@ func TestAlertProvider_buildHTTPRequestWithCustomPlaceholder(t *testing.T) { for _, scenario := range scenarios { t.Run(fmt.Sprintf("resolved-%v-with-custom-placeholders", scenario.Resolved), func(t *testing.T) { request := customAlertProvider.buildHTTPRequest( - &core.Endpoint{Name: "endpoint-name", Group: "endpoint-group"}, + &endpoint.Endpoint{Name: "endpoint-name", Group: "endpoint-group"}, &alert.Alert{Description: &alertDescription}, scenario.Resolved, ) diff --git a/alerting/provider/discord/discord.go b/alerting/provider/discord/discord.go index d4ac2c96..fe302934 100644 --- a/alerting/provider/discord/discord.go +++ b/alerting/provider/discord/discord.go @@ -9,7 +9,7 @@ import ( "github.com/TwiN/gatus/v5/alerting/alert" "github.com/TwiN/gatus/v5/client" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" ) // AlertProvider is the configuration necessary for sending an alert using Discord @@ -47,9 +47,9 @@ func (provider *AlertProvider) IsValid() bool { } // Send an alert using the provider -func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) error { - buffer := bytes.NewBuffer(provider.buildRequestBody(endpoint, alert, result, resolved)) - request, err := http.NewRequest(http.MethodPost, provider.getWebhookURLForGroup(endpoint.Group), buffer) +func (provider *AlertProvider) Send(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) error { + buffer := bytes.NewBuffer(provider.buildRequestBody(ep, alert, result, resolved)) + request, err := http.NewRequest(http.MethodPost, provider.getWebhookURLForGroup(ep.Group), buffer) if err != nil { return err } @@ -85,14 +85,14 @@ type Field struct { } // buildRequestBody builds the request body for the provider -func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) []byte { +func (provider *AlertProvider) buildRequestBody(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) []byte { var message string var colorCode int if resolved { - message = fmt.Sprintf("An alert for **%s** has been resolved after passing successfully %d time(s) in a row", endpoint.DisplayName(), alert.SuccessThreshold) + message = fmt.Sprintf("An alert for **%s** has been resolved after passing successfully %d time(s) in a row", ep.DisplayName(), alert.SuccessThreshold) colorCode = 3066993 } else { - message = fmt.Sprintf("An alert for **%s** has been triggered due to having failed %d time(s) in a row", endpoint.DisplayName(), alert.FailureThreshold) + message = fmt.Sprintf("An alert for **%s** has been triggered due to having failed %d time(s) in a row", ep.DisplayName(), alert.FailureThreshold) colorCode = 15158332 } var formattedConditionResults string diff --git a/alerting/provider/discord/discord_test.go b/alerting/provider/discord/discord_test.go index 2c8f1230..14d2d9bf 100644 --- a/alerting/provider/discord/discord_test.go +++ b/alerting/provider/discord/discord_test.go @@ -7,7 +7,7 @@ import ( "github.com/TwiN/gatus/v5/alerting/alert" "github.com/TwiN/gatus/v5/client" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" "github.com/TwiN/gatus/v5/test" ) @@ -127,10 +127,10 @@ func TestAlertProvider_Send(t *testing.T) { t.Run(scenario.Name, func(t *testing.T) { client.InjectHTTPClient(&http.Client{Transport: scenario.MockRoundTripper}) err := scenario.Provider.Send( - &core.Endpoint{Name: "endpoint-name"}, + &endpoint.Endpoint{Name: "endpoint-name"}, &scenario.Alert, - &core.Result{ - ConditionResults: []*core.ConditionResult{ + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, {Condition: "[STATUS] == 200", Success: scenario.Resolved}, }, @@ -191,18 +191,18 @@ func TestAlertProvider_buildRequestBody(t *testing.T) { } for _, scenario := range scenarios { t.Run(scenario.Name, func(t *testing.T) { - var conditionResults []*core.ConditionResult + var conditionResults []*endpoint.ConditionResult if !scenario.NoConditions { - conditionResults = []*core.ConditionResult{ + conditionResults = []*endpoint.ConditionResult{ {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, {Condition: "[STATUS] == 200", Success: scenario.Resolved}, {Condition: "[BODY] != \"\"", Success: scenario.Resolved}, } } body := scenario.Provider.buildRequestBody( - &core.Endpoint{Name: "endpoint-name"}, + &endpoint.Endpoint{Name: "endpoint-name"}, &scenario.Alert, - &core.Result{ + &endpoint.Result{ ConditionResults: conditionResults, }, scenario.Resolved, diff --git a/alerting/provider/email/email.go b/alerting/provider/email/email.go index e2fe542b..391d607d 100644 --- a/alerting/provider/email/email.go +++ b/alerting/provider/email/email.go @@ -8,7 +8,7 @@ import ( "github.com/TwiN/gatus/v5/alerting/alert" "github.com/TwiN/gatus/v5/client" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" gomail "gopkg.in/mail.v2" ) @@ -53,17 +53,17 @@ func (provider *AlertProvider) IsValid() bool { } // Send an alert using the provider -func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) error { +func (provider *AlertProvider) Send(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) error { var username string if len(provider.Username) > 0 { username = provider.Username } else { username = provider.From } - subject, body := provider.buildMessageSubjectAndBody(endpoint, alert, result, resolved) + subject, body := provider.buildMessageSubjectAndBody(ep, alert, result, resolved) m := gomail.NewMessage() m.SetHeader("From", provider.From) - m.SetHeader("To", strings.Split(provider.getToForGroup(endpoint.Group), ",")...) + m.SetHeader("To", strings.Split(provider.getToForGroup(ep.Group), ",")...) m.SetHeader("Subject", subject) m.SetBody("text/plain", body) var d *gomail.Dialer @@ -87,14 +87,14 @@ func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert, } // buildMessageSubjectAndBody builds the message subject and body -func (provider *AlertProvider) buildMessageSubjectAndBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) (string, string) { +func (provider *AlertProvider) buildMessageSubjectAndBody(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) (string, string) { var subject, message string if resolved { - subject = fmt.Sprintf("[%s] Alert resolved", endpoint.DisplayName()) - message = fmt.Sprintf("An alert for %s has been resolved after passing successfully %d time(s) in a row", endpoint.DisplayName(), alert.SuccessThreshold) + subject = fmt.Sprintf("[%s] Alert resolved", ep.DisplayName()) + message = fmt.Sprintf("An alert for %s has been resolved after passing successfully %d time(s) in a row", ep.DisplayName(), alert.SuccessThreshold) } else { - subject = fmt.Sprintf("[%s] Alert triggered", endpoint.DisplayName()) - message = fmt.Sprintf("An alert for %s has been triggered due to having failed %d time(s) in a row", endpoint.DisplayName(), alert.FailureThreshold) + subject = fmt.Sprintf("[%s] Alert triggered", ep.DisplayName()) + message = fmt.Sprintf("An alert for %s has been triggered due to having failed %d time(s) in a row", ep.DisplayName(), alert.FailureThreshold) } var formattedConditionResults string if len(result.ConditionResults) > 0 { diff --git a/alerting/provider/email/email_test.go b/alerting/provider/email/email_test.go index 4cab7134..a1134f27 100644 --- a/alerting/provider/email/email_test.go +++ b/alerting/provider/email/email_test.go @@ -4,7 +4,7 @@ import ( "testing" "github.com/TwiN/gatus/v5/alerting/alert" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" ) func TestAlertDefaultProvider_IsValid(t *testing.T) { @@ -97,10 +97,10 @@ func TestAlertProvider_buildRequestBody(t *testing.T) { for _, scenario := range scenarios { t.Run(scenario.Name, func(t *testing.T) { subject, body := scenario.Provider.buildMessageSubjectAndBody( - &core.Endpoint{Name: "endpoint-name"}, + &endpoint.Endpoint{Name: "endpoint-name"}, &scenario.Alert, - &core.Result{ - ConditionResults: []*core.ConditionResult{ + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, {Condition: "[STATUS] == 200", Success: scenario.Resolved}, }, diff --git a/alerting/provider/github/github.go b/alerting/provider/github/github.go index f86b9053..c425d669 100644 --- a/alerting/provider/github/github.go +++ b/alerting/provider/github/github.go @@ -8,7 +8,7 @@ import ( "time" "github.com/TwiN/gatus/v5/alerting/alert" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" "github.com/google/go-github/v48/github" "golang.org/x/oauth2" ) @@ -70,12 +70,12 @@ func (provider *AlertProvider) IsValid() bool { // Send creates an issue in the designed RepositoryURL if the resolved parameter passed is false, // or closes the relevant issue(s) if the resolved parameter passed is true. -func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) error { - title := "alert(gatus): " + endpoint.DisplayName() +func (provider *AlertProvider) Send(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) error { + title := "alert(gatus): " + ep.DisplayName() if !resolved { _, _, err := provider.githubClient.Issues.Create(context.Background(), provider.repositoryOwner, provider.repositoryName, &github.IssueRequest{ Title: github.String(title), - Body: github.String(provider.buildIssueBody(endpoint, alert, result)), + Body: github.String(provider.buildIssueBody(ep, alert, result)), }) if err != nil { return fmt.Errorf("failed to create issue: %w", err) @@ -104,7 +104,7 @@ func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert, } // buildIssueBody builds the body of the issue -func (provider *AlertProvider) buildIssueBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result) string { +func (provider *AlertProvider) buildIssueBody(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result) string { var formattedConditionResults string if len(result.ConditionResults) > 0 { formattedConditionResults = "\n\n## Condition results\n" @@ -122,7 +122,7 @@ func (provider *AlertProvider) buildIssueBody(endpoint *core.Endpoint, alert *al if alertDescription := alert.GetDescription(); len(alertDescription) > 0 { description = ":\n> " + alertDescription } - message := fmt.Sprintf("An alert for **%s** has been triggered due to having failed %d time(s) in a row", endpoint.DisplayName(), alert.FailureThreshold) + message := fmt.Sprintf("An alert for **%s** has been triggered due to having failed %d time(s) in a row", ep.DisplayName(), alert.FailureThreshold) return message + description + formattedConditionResults } diff --git a/alerting/provider/github/github_test.go b/alerting/provider/github/github_test.go index d1f45e48..e69a3197 100644 --- a/alerting/provider/github/github_test.go +++ b/alerting/provider/github/github_test.go @@ -7,7 +7,7 @@ import ( "github.com/TwiN/gatus/v5/alerting/alert" "github.com/TwiN/gatus/v5/client" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" "github.com/TwiN/gatus/v5/test" "github.com/google/go-github/v48/github" ) @@ -85,10 +85,10 @@ func TestAlertProvider_Send(t *testing.T) { scenario.Provider.githubClient = github.NewClient(nil) client.InjectHTTPClient(&http.Client{Transport: scenario.MockRoundTripper}) err := scenario.Provider.Send( - &core.Endpoint{Name: "endpoint-name", Group: "endpoint-group"}, + &endpoint.Endpoint{Name: "endpoint-name", Group: "endpoint-group"}, &scenario.Alert, - &core.Result{ - ConditionResults: []*core.ConditionResult{ + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, {Condition: "[STATUS] == 200", Success: scenario.Resolved}, }, @@ -109,7 +109,7 @@ func TestAlertProvider_buildRequestBody(t *testing.T) { firstDescription := "description-1" scenarios := []struct { Name string - Endpoint core.Endpoint + Endpoint endpoint.Endpoint Provider AlertProvider Alert alert.Alert NoConditions bool @@ -117,14 +117,14 @@ func TestAlertProvider_buildRequestBody(t *testing.T) { }{ { Name: "triggered", - Endpoint: core.Endpoint{Name: "endpoint-name", URL: "https://example.org"}, + Endpoint: endpoint.Endpoint{Name: "endpoint-name", URL: "https://example.org"}, Provider: AlertProvider{}, Alert: alert.Alert{Description: &firstDescription, FailureThreshold: 3}, ExpectedBody: "An alert for **endpoint-name** has been triggered due to having failed 3 time(s) in a row:\n> description-1\n\n## Condition results\n- :white_check_mark: - `[CONNECTED] == true`\n- :x: - `[STATUS] == 200`", }, { Name: "triggered-with-no-description", - Endpoint: core.Endpoint{Name: "endpoint-name", URL: "https://example.org"}, + Endpoint: endpoint.Endpoint{Name: "endpoint-name", URL: "https://example.org"}, Provider: AlertProvider{}, Alert: alert.Alert{FailureThreshold: 10}, ExpectedBody: "An alert for **endpoint-name** has been triggered due to having failed 10 time(s) in a row\n\n## Condition results\n- :white_check_mark: - `[CONNECTED] == true`\n- :x: - `[STATUS] == 200`", @@ -132,7 +132,7 @@ func TestAlertProvider_buildRequestBody(t *testing.T) { { Name: "triggered-with-no-conditions", NoConditions: true, - Endpoint: core.Endpoint{Name: "endpoint-name", URL: "https://example.org"}, + Endpoint: endpoint.Endpoint{Name: "endpoint-name", URL: "https://example.org"}, Provider: AlertProvider{}, Alert: alert.Alert{Description: &firstDescription, FailureThreshold: 10}, ExpectedBody: "An alert for **endpoint-name** has been triggered due to having failed 10 time(s) in a row:\n> description-1", @@ -140,9 +140,9 @@ func TestAlertProvider_buildRequestBody(t *testing.T) { } for _, scenario := range scenarios { t.Run(scenario.Name, func(t *testing.T) { - var conditionResults []*core.ConditionResult + var conditionResults []*endpoint.ConditionResult if !scenario.NoConditions { - conditionResults = []*core.ConditionResult{ + conditionResults = []*endpoint.ConditionResult{ {Condition: "[CONNECTED] == true", Success: true}, {Condition: "[STATUS] == 200", Success: false}, } @@ -150,7 +150,7 @@ func TestAlertProvider_buildRequestBody(t *testing.T) { body := scenario.Provider.buildIssueBody( &scenario.Endpoint, &scenario.Alert, - &core.Result{ConditionResults: conditionResults}, + &endpoint.Result{ConditionResults: conditionResults}, ) if strings.TrimSpace(body) != strings.TrimSpace(scenario.ExpectedBody) { t.Errorf("expected:\n%s\ngot:\n%s", scenario.ExpectedBody, body) diff --git a/alerting/provider/gitlab/gitlab.go b/alerting/provider/gitlab/gitlab.go index f87b4c4e..d9d5676a 100644 --- a/alerting/provider/gitlab/gitlab.go +++ b/alerting/provider/gitlab/gitlab.go @@ -11,7 +11,7 @@ import ( "github.com/TwiN/gatus/v5/alerting/alert" "github.com/TwiN/gatus/v5/client" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" "github.com/google/uuid" ) @@ -51,11 +51,11 @@ func (provider *AlertProvider) IsValid() bool { // Send creates an issue in the designed RepositoryURL if the resolved parameter passed is false, // or closes the relevant issue(s) if the resolved parameter passed is true. -func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) error { +func (provider *AlertProvider) Send(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) error { if len(alert.ResolveKey) == 0 { alert.ResolveKey = uuid.NewString() } - buffer := bytes.NewBuffer(provider.buildAlertBody(endpoint, alert, result, resolved)) + buffer := bytes.NewBuffer(provider.buildAlertBody(ep, alert, result, resolved)) request, err := http.NewRequest(http.MethodPost, provider.WebhookURL, buffer) if err != nil { return err @@ -94,21 +94,21 @@ func (provider *AlertProvider) monitoringTool() string { return "gatus" } -func (provider *AlertProvider) service(endpoint *core.Endpoint) string { +func (provider *AlertProvider) service(ep *endpoint.Endpoint) string { if len(provider.Service) > 0 { return provider.Service } - return endpoint.DisplayName() + return ep.DisplayName() } // buildAlertBody builds the body of the alert -func (provider *AlertProvider) buildAlertBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) []byte { +func (provider *AlertProvider) buildAlertBody(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) []byte { body := AlertBody{ - Title: fmt.Sprintf("alert(%s): %s", provider.monitoringTool(), provider.service(endpoint)), + Title: fmt.Sprintf("alert(%s): %s", provider.monitoringTool(), provider.service(ep)), StartTime: result.Timestamp.Format(time.RFC3339), - Service: provider.service(endpoint), + Service: provider.service(ep), MonitoringTool: provider.monitoringTool(), - Hosts: endpoint.URL, + Hosts: ep.URL, GitlabEnvironmentName: provider.EnvironmentName, Severity: provider.Severity, Fingerprint: alert.ResolveKey, @@ -135,9 +135,9 @@ func (provider *AlertProvider) buildAlertBody(endpoint *core.Endpoint, alert *al } var message string if resolved { - message = fmt.Sprintf("An alert for *%s* has been resolved after passing successfully %d time(s) in a row", endpoint.DisplayName(), alert.SuccessThreshold) + message = fmt.Sprintf("An alert for *%s* has been resolved after passing successfully %d time(s) in a row", ep.DisplayName(), alert.SuccessThreshold) } else { - message = fmt.Sprintf("An alert for *%s* has been triggered due to having failed %d time(s) in a row", endpoint.DisplayName(), alert.FailureThreshold) + message = fmt.Sprintf("An alert for *%s* has been triggered due to having failed %d time(s) in a row", ep.DisplayName(), alert.FailureThreshold) } body.Description = message + description + formattedConditionResults bodyAsJSON, _ := json.Marshal(body) diff --git a/alerting/provider/gitlab/gitlab_test.go b/alerting/provider/gitlab/gitlab_test.go index 8ea389fd..290b2e6e 100644 --- a/alerting/provider/gitlab/gitlab_test.go +++ b/alerting/provider/gitlab/gitlab_test.go @@ -7,7 +7,7 @@ import ( "github.com/TwiN/gatus/v5/alerting/alert" "github.com/TwiN/gatus/v5/client" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" "github.com/TwiN/gatus/v5/test" ) @@ -84,10 +84,10 @@ func TestAlertProvider_Send(t *testing.T) { t.Run(scenario.Name, func(t *testing.T) { client.InjectHTTPClient(&http.Client{Transport: scenario.MockRoundTripper}) err := scenario.Provider.Send( - &core.Endpoint{Name: "endpoint-name", Group: "endpoint-group"}, + &endpoint.Endpoint{Name: "endpoint-name", Group: "endpoint-group"}, &scenario.Alert, - &core.Result{ - ConditionResults: []*core.ConditionResult{ + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, {Condition: "[STATUS] == 200", Success: scenario.Resolved}, }, @@ -108,21 +108,21 @@ func TestAlertProvider_buildAlertBody(t *testing.T) { firstDescription := "description-1" scenarios := []struct { Name string - Endpoint core.Endpoint + Endpoint endpoint.Endpoint Provider AlertProvider Alert alert.Alert ExpectedBody string }{ { Name: "triggered", - Endpoint: core.Endpoint{Name: "endpoint-name", URL: "https://example.org"}, + Endpoint: endpoint.Endpoint{Name: "endpoint-name", URL: "https://example.org"}, Provider: AlertProvider{}, Alert: alert.Alert{Description: &firstDescription, FailureThreshold: 3}, ExpectedBody: "{\"title\":\"alert(gatus): endpoint-name\",\"description\":\"An alert for *endpoint-name* has been triggered due to having failed 3 time(s) in a row:\\n\\u003e description-1\\n\\n## Condition results\\n- :white_check_mark: - `[CONNECTED] == true`\\n- :x: - `[STATUS] == 200`\\n\",\"start_time\":\"0001-01-01T00:00:00Z\",\"service\":\"endpoint-name\",\"monitoring_tool\":\"gatus\",\"hosts\":\"https://example.org\"}", }, { Name: "no-description", - Endpoint: core.Endpoint{Name: "endpoint-name", URL: "https://example.org"}, + Endpoint: endpoint.Endpoint{Name: "endpoint-name", URL: "https://example.org"}, Provider: AlertProvider{}, Alert: alert.Alert{FailureThreshold: 10}, ExpectedBody: "{\"title\":\"alert(gatus): endpoint-name\",\"description\":\"An alert for *endpoint-name* has been triggered due to having failed 10 time(s) in a row\\n\\n## Condition results\\n- :white_check_mark: - `[CONNECTED] == true`\\n- :x: - `[STATUS] == 200`\\n\",\"start_time\":\"0001-01-01T00:00:00Z\",\"service\":\"endpoint-name\",\"monitoring_tool\":\"gatus\",\"hosts\":\"https://example.org\"}", @@ -133,8 +133,8 @@ func TestAlertProvider_buildAlertBody(t *testing.T) { body := scenario.Provider.buildAlertBody( &scenario.Endpoint, &scenario.Alert, - &core.Result{ - ConditionResults: []*core.ConditionResult{ + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ {Condition: "[CONNECTED] == true", Success: true}, {Condition: "[STATUS] == 200", Success: false}, }, diff --git a/alerting/provider/googlechat/googlechat.go b/alerting/provider/googlechat/googlechat.go index 19330c92..fb2f8f97 100644 --- a/alerting/provider/googlechat/googlechat.go +++ b/alerting/provider/googlechat/googlechat.go @@ -9,7 +9,7 @@ import ( "github.com/TwiN/gatus/v5/alerting/alert" "github.com/TwiN/gatus/v5/client" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" ) // AlertProvider is the configuration necessary for sending an alert using Google chat @@ -50,9 +50,9 @@ func (provider *AlertProvider) IsValid() bool { } // Send an alert using the provider -func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) error { - buffer := bytes.NewBuffer(provider.buildRequestBody(endpoint, alert, result, resolved)) - request, err := http.NewRequest(http.MethodPost, provider.getWebhookURLForGroup(endpoint.Group), buffer) +func (provider *AlertProvider) Send(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) error { + buffer := bytes.NewBuffer(provider.buildRequestBody(ep, alert, result, resolved)) + request, err := http.NewRequest(http.MethodPost, provider.getWebhookURLForGroup(ep.Group), buffer) if err != nil { return err } @@ -112,7 +112,7 @@ type OpenLink struct { } // buildRequestBody builds the request body for the provider -func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) []byte { +func (provider *AlertProvider) buildRequestBody(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) []byte { var message, color string if resolved { color = "#36A64F" @@ -143,7 +143,7 @@ func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert * Widgets: []Widgets{ { KeyValue: &KeyValue{ - TopLabel: endpoint.DisplayName(), + TopLabel: ep.DisplayName(), Content: message, ContentMultiline: "true", BottomLabel: description, @@ -166,7 +166,7 @@ func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert * }, }) } - if endpoint.Type() == core.EndpointTypeHTTP { + if ep.Type() == endpoint.TypeHTTP { // We only include a button targeting the URL if the endpoint is an HTTP endpoint // If the URL isn't prefixed with https://, Google Chat will just display a blank message aynways. // See https://github.com/TwiN/gatus/issues/362 @@ -175,7 +175,7 @@ func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert * { TextButton: TextButton{ Text: "URL", - OnClick: OnClick{OpenLink: OpenLink{URL: endpoint.URL}}, + OnClick: OnClick{OpenLink: OpenLink{URL: ep.URL}}, }, }, }, diff --git a/alerting/provider/googlechat/googlechat_test.go b/alerting/provider/googlechat/googlechat_test.go index 7cd2c0a9..78e5e6ea 100644 --- a/alerting/provider/googlechat/googlechat_test.go +++ b/alerting/provider/googlechat/googlechat_test.go @@ -7,7 +7,7 @@ import ( "github.com/TwiN/gatus/v5/alerting/alert" "github.com/TwiN/gatus/v5/client" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" "github.com/TwiN/gatus/v5/test" ) @@ -116,10 +116,10 @@ func TestAlertProvider_Send(t *testing.T) { t.Run(scenario.Name, func(t *testing.T) { client.InjectHTTPClient(&http.Client{Transport: scenario.MockRoundTripper}) err := scenario.Provider.Send( - &core.Endpoint{Name: "endpoint-name", Group: "endpoint-group"}, + &endpoint.Endpoint{Name: "endpoint-name", Group: "endpoint-group"}, &scenario.Alert, - &core.Result{ - ConditionResults: []*core.ConditionResult{ + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, {Condition: "[STATUS] == 200", Success: scenario.Resolved}, }, @@ -141,7 +141,7 @@ func TestAlertProvider_buildRequestBody(t *testing.T) { secondDescription := "description-2" scenarios := []struct { Name string - Endpoint core.Endpoint + Endpoint endpoint.Endpoint Provider AlertProvider Alert alert.Alert Resolved bool @@ -149,7 +149,7 @@ func TestAlertProvider_buildRequestBody(t *testing.T) { }{ { Name: "triggered", - Endpoint: core.Endpoint{Name: "endpoint-name", URL: "https://example.org"}, + Endpoint: endpoint.Endpoint{Name: "endpoint-name", URL: "https://example.org"}, Provider: AlertProvider{}, Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, Resolved: false, @@ -157,7 +157,7 @@ func TestAlertProvider_buildRequestBody(t *testing.T) { }, { Name: "resolved", - Endpoint: core.Endpoint{Name: "endpoint-name", URL: "https://example.org"}, + Endpoint: endpoint.Endpoint{Name: "endpoint-name", URL: "https://example.org"}, Provider: AlertProvider{}, Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, Resolved: true, @@ -165,7 +165,7 @@ func TestAlertProvider_buildRequestBody(t *testing.T) { }, { Name: "icmp-should-not-include-url", // See https://github.com/TwiN/gatus/issues/362 - Endpoint: core.Endpoint{Name: "endpoint-name", URL: "icmp://example.org"}, + Endpoint: endpoint.Endpoint{Name: "endpoint-name", URL: "icmp://example.org"}, Provider: AlertProvider{}, Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, Resolved: false, @@ -173,7 +173,7 @@ func TestAlertProvider_buildRequestBody(t *testing.T) { }, { Name: "tcp-should-not-include-url", // See https://github.com/TwiN/gatus/issues/362 - Endpoint: core.Endpoint{Name: "endpoint-name", URL: "tcp://example.org"}, + Endpoint: endpoint.Endpoint{Name: "endpoint-name", URL: "tcp://example.org"}, Provider: AlertProvider{}, Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, Resolved: false, @@ -185,8 +185,8 @@ func TestAlertProvider_buildRequestBody(t *testing.T) { body := scenario.Provider.buildRequestBody( &scenario.Endpoint, &scenario.Alert, - &core.Result{ - ConditionResults: []*core.ConditionResult{ + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, {Condition: "[STATUS] == 200", Success: scenario.Resolved}, }, diff --git a/alerting/provider/gotify/gotify.go b/alerting/provider/gotify/gotify.go index 7f4bfcf6..7b1f7a61 100644 --- a/alerting/provider/gotify/gotify.go +++ b/alerting/provider/gotify/gotify.go @@ -9,7 +9,7 @@ import ( "github.com/TwiN/gatus/v5/alerting/alert" "github.com/TwiN/gatus/v5/client" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" ) const DefaultPriority = 5 @@ -41,8 +41,8 @@ func (provider *AlertProvider) IsValid() bool { } // Send an alert using the provider -func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) error { - buffer := bytes.NewBuffer(provider.buildRequestBody(endpoint, alert, result, resolved)) +func (provider *AlertProvider) Send(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) error { + buffer := bytes.NewBuffer(provider.buildRequestBody(ep, alert, result, resolved)) request, err := http.NewRequest(http.MethodPost, provider.ServerURL+"/message?token="+provider.Token, buffer) if err != nil { return err @@ -67,12 +67,12 @@ type Body struct { } // buildRequestBody builds the request body for the provider -func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) []byte { +func (provider *AlertProvider) buildRequestBody(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) []byte { var message string if resolved { - message = fmt.Sprintf("An alert for `%s` has been resolved after passing successfully %d time(s) in a row", endpoint.DisplayName(), alert.SuccessThreshold) + message = fmt.Sprintf("An alert for `%s` has been resolved after passing successfully %d time(s) in a row", ep.DisplayName(), alert.SuccessThreshold) } else { - message = fmt.Sprintf("An alert for `%s` has been triggered due to having failed %d time(s) in a row", endpoint.DisplayName(), alert.FailureThreshold) + message = fmt.Sprintf("An alert for `%s` has been triggered due to having failed %d time(s) in a row", ep.DisplayName(), alert.FailureThreshold) } var formattedConditionResults string for _, conditionResult := range result.ConditionResults { @@ -88,7 +88,7 @@ func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert * message += " with the following description: " + alert.GetDescription() } message += formattedConditionResults - title := "Gatus: " + endpoint.DisplayName() + title := "Gatus: " + ep.DisplayName() if provider.Title != "" { title = provider.Title } diff --git a/alerting/provider/gotify/gotify_test.go b/alerting/provider/gotify/gotify_test.go index 19a68b97..af644f48 100644 --- a/alerting/provider/gotify/gotify_test.go +++ b/alerting/provider/gotify/gotify_test.go @@ -6,7 +6,7 @@ import ( "testing" "github.com/TwiN/gatus/v5/alerting/alert" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" ) func TestAlertProvider_IsValid(t *testing.T) { @@ -49,7 +49,7 @@ func TestAlertProvider_buildRequestBody(t *testing.T) { var ( description = "custom-description" //title = "custom-title" - endpoint = "custom-endpoint" + endpointName = "custom-endpoint" ) scenarios := []struct { Name string @@ -63,30 +63,30 @@ func TestAlertProvider_buildRequestBody(t *testing.T) { Provider: AlertProvider{ServerURL: "https://gotify.example.com", Token: "faketoken"}, Alert: alert.Alert{Description: &description, SuccessThreshold: 5, FailureThreshold: 3}, Resolved: false, - ExpectedBody: fmt.Sprintf("{\"message\":\"An alert for `%s` has been triggered due to having failed 3 time(s) in a row with the following description: %s\\n✕ - [CONNECTED] == true\\n✕ - [STATUS] == 200\",\"title\":\"Gatus: custom-endpoint\",\"priority\":0}", endpoint, description), + ExpectedBody: fmt.Sprintf("{\"message\":\"An alert for `%s` has been triggered due to having failed 3 time(s) in a row with the following description: %s\\n✕ - [CONNECTED] == true\\n✕ - [STATUS] == 200\",\"title\":\"Gatus: custom-endpoint\",\"priority\":0}", endpointName, description), }, { Name: "resolved", Provider: AlertProvider{ServerURL: "https://gotify.example.com", Token: "faketoken"}, Alert: alert.Alert{Description: &description, SuccessThreshold: 5, FailureThreshold: 3}, Resolved: true, - ExpectedBody: fmt.Sprintf("{\"message\":\"An alert for `%s` has been resolved after passing successfully 5 time(s) in a row with the following description: %s\\n✓ - [CONNECTED] == true\\n✓ - [STATUS] == 200\",\"title\":\"Gatus: custom-endpoint\",\"priority\":0}", endpoint, description), + ExpectedBody: fmt.Sprintf("{\"message\":\"An alert for `%s` has been resolved after passing successfully 5 time(s) in a row with the following description: %s\\n✓ - [CONNECTED] == true\\n✓ - [STATUS] == 200\",\"title\":\"Gatus: custom-endpoint\",\"priority\":0}", endpointName, description), }, { Name: "custom-title", Provider: AlertProvider{ServerURL: "https://gotify.example.com", Token: "faketoken", Title: "custom-title"}, Alert: alert.Alert{Description: &description, SuccessThreshold: 5, FailureThreshold: 3}, Resolved: false, - ExpectedBody: fmt.Sprintf("{\"message\":\"An alert for `%s` has been triggered due to having failed 3 time(s) in a row with the following description: %s\\n✕ - [CONNECTED] == true\\n✕ - [STATUS] == 200\",\"title\":\"custom-title\",\"priority\":0}", endpoint, description), + ExpectedBody: fmt.Sprintf("{\"message\":\"An alert for `%s` has been triggered due to having failed 3 time(s) in a row with the following description: %s\\n✕ - [CONNECTED] == true\\n✕ - [STATUS] == 200\",\"title\":\"custom-title\",\"priority\":0}", endpointName, description), }, } for _, scenario := range scenarios { t.Run(scenario.Name, func(t *testing.T) { body := scenario.Provider.buildRequestBody( - &core.Endpoint{Name: endpoint}, + &endpoint.Endpoint{Name: endpointName}, &scenario.Alert, - &core.Result{ - ConditionResults: []*core.ConditionResult{ + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, {Condition: "[STATUS] == 200", Success: scenario.Resolved}, }, diff --git a/alerting/provider/jetbrainsspace/jetbrainsspace.go b/alerting/provider/jetbrainsspace/jetbrainsspace.go index bf031663..aa9d928f 100644 --- a/alerting/provider/jetbrainsspace/jetbrainsspace.go +++ b/alerting/provider/jetbrainsspace/jetbrainsspace.go @@ -9,7 +9,7 @@ import ( "github.com/TwiN/gatus/v5/alerting/alert" "github.com/TwiN/gatus/v5/client" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" ) // AlertProvider is the configuration necessary for sending an alert using JetBrains Space @@ -46,8 +46,8 @@ func (provider *AlertProvider) IsValid() bool { } // Send an alert using the provider -func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) error { - buffer := bytes.NewBuffer(provider.buildRequestBody(endpoint, alert, result, resolved)) +func (provider *AlertProvider) Send(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) error { + buffer := bytes.NewBuffer(provider.buildRequestBody(ep, alert, result, resolved)) url := fmt.Sprintf("https://%s.jetbrains.space/api/http/chats/messages/send-message", provider.Project) request, err := http.NewRequest(http.MethodPost, url, buffer) if err != nil { @@ -103,9 +103,9 @@ type Icon struct { } // buildRequestBody builds the request body for the provider -func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) []byte { +func (provider *AlertProvider) buildRequestBody(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) []byte { body := Body{ - Channel: "id:" + provider.getChannelIDForGroup(endpoint.Group), + Channel: "id:" + provider.getChannelIDForGroup(ep.Group), Content: Content{ ClassName: "ChatMessage.Block", Sections: []Section{{ @@ -116,10 +116,10 @@ func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert * } if resolved { body.Content.Style = "SUCCESS" - body.Content.Sections[0].Header = fmt.Sprintf("An alert for *%s* has been resolved after passing successfully %d time(s) in a row", endpoint.DisplayName(), alert.SuccessThreshold) + body.Content.Sections[0].Header = fmt.Sprintf("An alert for *%s* has been resolved after passing successfully %d time(s) in a row", ep.DisplayName(), alert.SuccessThreshold) } else { body.Content.Style = "WARNING" - body.Content.Sections[0].Header = fmt.Sprintf("An alert for *%s* has been triggered due to having failed %d time(s) in a row", endpoint.DisplayName(), alert.FailureThreshold) + body.Content.Sections[0].Header = fmt.Sprintf("An alert for *%s* has been triggered due to having failed %d time(s) in a row", ep.DisplayName(), alert.FailureThreshold) } for _, conditionResult := range result.ConditionResults { icon := "warning" diff --git a/alerting/provider/jetbrainsspace/jetbrainsspace_test.go b/alerting/provider/jetbrainsspace/jetbrainsspace_test.go index 8eae2590..c9fbcd9c 100644 --- a/alerting/provider/jetbrainsspace/jetbrainsspace_test.go +++ b/alerting/provider/jetbrainsspace/jetbrainsspace_test.go @@ -7,7 +7,7 @@ import ( "github.com/TwiN/gatus/v5/alerting/alert" "github.com/TwiN/gatus/v5/client" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" "github.com/TwiN/gatus/v5/test" ) @@ -120,10 +120,10 @@ func TestAlertProvider_Send(t *testing.T) { t.Run(scenario.Name, func(t *testing.T) { client.InjectHTTPClient(&http.Client{Transport: scenario.MockRoundTripper}) err := scenario.Provider.Send( - &core.Endpoint{Name: "endpoint-name"}, + &endpoint.Endpoint{Name: "endpoint-name"}, &scenario.Alert, - &core.Result{ - ConditionResults: []*core.ConditionResult{ + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, {Condition: "[STATUS] == 200", Success: scenario.Resolved}, }, @@ -146,7 +146,7 @@ func TestAlertProvider_buildRequestBody(t *testing.T) { scenarios := []struct { Name string Provider AlertProvider - Endpoint core.Endpoint + Endpoint endpoint.Endpoint Alert alert.Alert Resolved bool ExpectedBody string @@ -154,7 +154,7 @@ func TestAlertProvider_buildRequestBody(t *testing.T) { { Name: "triggered", Provider: AlertProvider{}, - Endpoint: core.Endpoint{Name: "name"}, + Endpoint: endpoint.Endpoint{Name: "name"}, Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, Resolved: false, ExpectedBody: `{"channel":"id:","content":{"className":"ChatMessage.Block","style":"WARNING","sections":[{"className":"MessageSection","elements":[{"className":"MessageText","accessory":{"className":"MessageIcon","icon":{"icon":"warning"},"style":"WARNING"},"style":"WARNING","size":"REGULAR","content":"[CONNECTED] == true"},{"className":"MessageText","accessory":{"className":"MessageIcon","icon":{"icon":"warning"},"style":"WARNING"},"style":"WARNING","size":"REGULAR","content":"[STATUS] == 200"}],"header":"An alert for *name* has been triggered due to having failed 3 time(s) in a row"}]}}`, @@ -162,7 +162,7 @@ func TestAlertProvider_buildRequestBody(t *testing.T) { { Name: "triggered-with-group", Provider: AlertProvider{}, - Endpoint: core.Endpoint{Name: "name", Group: "group"}, + Endpoint: endpoint.Endpoint{Name: "name", Group: "group"}, Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, Resolved: false, ExpectedBody: `{"channel":"id:","content":{"className":"ChatMessage.Block","style":"WARNING","sections":[{"className":"MessageSection","elements":[{"className":"MessageText","accessory":{"className":"MessageIcon","icon":{"icon":"warning"},"style":"WARNING"},"style":"WARNING","size":"REGULAR","content":"[CONNECTED] == true"},{"className":"MessageText","accessory":{"className":"MessageIcon","icon":{"icon":"warning"},"style":"WARNING"},"style":"WARNING","size":"REGULAR","content":"[STATUS] == 200"}],"header":"An alert for *group/name* has been triggered due to having failed 3 time(s) in a row"}]}}`, @@ -170,7 +170,7 @@ func TestAlertProvider_buildRequestBody(t *testing.T) { { Name: "resolved", Provider: AlertProvider{}, - Endpoint: core.Endpoint{Name: "name"}, + Endpoint: endpoint.Endpoint{Name: "name"}, Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, Resolved: true, ExpectedBody: `{"channel":"id:","content":{"className":"ChatMessage.Block","style":"SUCCESS","sections":[{"className":"MessageSection","elements":[{"className":"MessageText","accessory":{"className":"MessageIcon","icon":{"icon":"success"},"style":"SUCCESS"},"style":"SUCCESS","size":"REGULAR","content":"[CONNECTED] == true"},{"className":"MessageText","accessory":{"className":"MessageIcon","icon":{"icon":"success"},"style":"SUCCESS"},"style":"SUCCESS","size":"REGULAR","content":"[STATUS] == 200"}],"header":"An alert for *name* has been resolved after passing successfully 5 time(s) in a row"}]}}`, @@ -178,7 +178,7 @@ func TestAlertProvider_buildRequestBody(t *testing.T) { { Name: "resolved-with-group", Provider: AlertProvider{}, - Endpoint: core.Endpoint{Name: "name", Group: "group"}, + Endpoint: endpoint.Endpoint{Name: "name", Group: "group"}, Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, Resolved: true, ExpectedBody: `{"channel":"id:","content":{"className":"ChatMessage.Block","style":"SUCCESS","sections":[{"className":"MessageSection","elements":[{"className":"MessageText","accessory":{"className":"MessageIcon","icon":{"icon":"success"},"style":"SUCCESS"},"style":"SUCCESS","size":"REGULAR","content":"[CONNECTED] == true"},{"className":"MessageText","accessory":{"className":"MessageIcon","icon":{"icon":"success"},"style":"SUCCESS"},"style":"SUCCESS","size":"REGULAR","content":"[STATUS] == 200"}],"header":"An alert for *group/name* has been resolved after passing successfully 5 time(s) in a row"}]}}`, @@ -189,8 +189,8 @@ func TestAlertProvider_buildRequestBody(t *testing.T) { body := scenario.Provider.buildRequestBody( &scenario.Endpoint, &scenario.Alert, - &core.Result{ - ConditionResults: []*core.ConditionResult{ + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, {Condition: "[STATUS] == 200", Success: scenario.Resolved}, }, diff --git a/alerting/provider/matrix/matrix.go b/alerting/provider/matrix/matrix.go index 92907889..4bc8754d 100644 --- a/alerting/provider/matrix/matrix.go +++ b/alerting/provider/matrix/matrix.go @@ -12,7 +12,7 @@ import ( "github.com/TwiN/gatus/v5/alerting/alert" "github.com/TwiN/gatus/v5/client" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" ) // AlertProvider is the configuration necessary for sending an alert using Matrix @@ -61,9 +61,9 @@ func (provider *AlertProvider) IsValid() bool { } // Send an alert using the provider -func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) error { - buffer := bytes.NewBuffer(provider.buildRequestBody(endpoint, alert, result, resolved)) - config := provider.getConfigForGroup(endpoint.Group) +func (provider *AlertProvider) Send(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) error { + buffer := bytes.NewBuffer(provider.buildRequestBody(ep, alert, result, resolved)) + config := provider.getConfigForGroup(ep.Group) if config.ServerURL == "" { config.ServerURL = defaultServerURL } @@ -103,23 +103,23 @@ type Body struct { } // buildRequestBody builds the request body for the provider -func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) []byte { +func (provider *AlertProvider) buildRequestBody(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) []byte { body, _ := json.Marshal(Body{ MsgType: "m.text", Format: "org.matrix.custom.html", - Body: buildPlaintextMessageBody(endpoint, alert, result, resolved), - FormattedBody: buildHTMLMessageBody(endpoint, alert, result, resolved), + Body: buildPlaintextMessageBody(ep, alert, result, resolved), + FormattedBody: buildHTMLMessageBody(ep, alert, result, resolved), }) return body } // buildPlaintextMessageBody builds the message body in plaintext to include in request -func buildPlaintextMessageBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) string { +func buildPlaintextMessageBody(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) string { var message string if resolved { - message = fmt.Sprintf("An alert for `%s` has been resolved after passing successfully %d time(s) in a row", endpoint.DisplayName(), alert.SuccessThreshold) + message = fmt.Sprintf("An alert for `%s` has been resolved after passing successfully %d time(s) in a row", ep.DisplayName(), alert.SuccessThreshold) } else { - message = fmt.Sprintf("An alert for `%s` has been triggered due to having failed %d time(s) in a row", endpoint.DisplayName(), alert.FailureThreshold) + message = fmt.Sprintf("An alert for `%s` has been triggered due to having failed %d time(s) in a row", ep.DisplayName(), alert.FailureThreshold) } var formattedConditionResults string for _, conditionResult := range result.ConditionResults { @@ -139,12 +139,12 @@ func buildPlaintextMessageBody(endpoint *core.Endpoint, alert *alert.Alert, resu } // buildHTMLMessageBody builds the message body in HTML to include in request -func buildHTMLMessageBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) string { +func buildHTMLMessageBody(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) string { var message string if resolved { - message = fmt.Sprintf("An alert for %s has been resolved after passing successfully %d time(s) in a row", endpoint.DisplayName(), alert.SuccessThreshold) + message = fmt.Sprintf("An alert for %s has been resolved after passing successfully %d time(s) in a row", ep.DisplayName(), alert.SuccessThreshold) } else { - message = fmt.Sprintf("An alert for %s has been triggered due to having failed %d time(s) in a row", endpoint.DisplayName(), alert.FailureThreshold) + message = fmt.Sprintf("An alert for %s has been triggered due to having failed %d time(s) in a row", ep.DisplayName(), alert.FailureThreshold) } var formattedConditionResults string if len(result.ConditionResults) > 0 { diff --git a/alerting/provider/matrix/matrix_test.go b/alerting/provider/matrix/matrix_test.go index 2d9ae57a..923cae55 100644 --- a/alerting/provider/matrix/matrix_test.go +++ b/alerting/provider/matrix/matrix_test.go @@ -7,7 +7,7 @@ import ( "github.com/TwiN/gatus/v5/alerting/alert" "github.com/TwiN/gatus/v5/client" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" "github.com/TwiN/gatus/v5/test" ) @@ -149,10 +149,10 @@ func TestAlertProvider_Send(t *testing.T) { t.Run(scenario.Name, func(t *testing.T) { client.InjectHTTPClient(&http.Client{Transport: scenario.MockRoundTripper}) err := scenario.Provider.Send( - &core.Endpoint{Name: "endpoint-name"}, + &endpoint.Endpoint{Name: "endpoint-name"}, &scenario.Alert, - &core.Result{ - ConditionResults: []*core.ConditionResult{ + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, {Condition: "[STATUS] == 200", Success: scenario.Resolved}, }, @@ -197,10 +197,10 @@ func TestAlertProvider_buildRequestBody(t *testing.T) { for _, scenario := range scenarios { t.Run(scenario.Name, func(t *testing.T) { body := scenario.Provider.buildRequestBody( - &core.Endpoint{Name: "endpoint-name"}, + &endpoint.Endpoint{Name: "endpoint-name"}, &scenario.Alert, - &core.Result{ - ConditionResults: []*core.ConditionResult{ + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, {Condition: "[STATUS] == 200", Success: scenario.Resolved}, }, diff --git a/alerting/provider/mattermost/mattermost.go b/alerting/provider/mattermost/mattermost.go index 41b34542..830dd044 100644 --- a/alerting/provider/mattermost/mattermost.go +++ b/alerting/provider/mattermost/mattermost.go @@ -9,7 +9,7 @@ import ( "github.com/TwiN/gatus/v5/alerting/alert" "github.com/TwiN/gatus/v5/client" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" ) // AlertProvider is the configuration necessary for sending an alert using Mattermost @@ -50,9 +50,9 @@ func (provider *AlertProvider) IsValid() bool { } // Send an alert using the provider -func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) error { - buffer := bytes.NewBuffer([]byte(provider.buildRequestBody(endpoint, alert, result, resolved))) - request, err := http.NewRequest(http.MethodPost, provider.getWebhookURLForGroup(endpoint.Group), buffer) +func (provider *AlertProvider) Send(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) error { + buffer := bytes.NewBuffer([]byte(provider.buildRequestBody(ep, alert, result, resolved))) + request, err := http.NewRequest(http.MethodPost, provider.getWebhookURLForGroup(ep.Group), buffer) if err != nil { return err } @@ -92,13 +92,13 @@ type Field struct { } // buildRequestBody builds the request body for the provider -func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) []byte { +func (provider *AlertProvider) buildRequestBody(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) []byte { var message, color string if resolved { - message = fmt.Sprintf("An alert for *%s* has been resolved after passing successfully %d time(s) in a row", endpoint.DisplayName(), alert.SuccessThreshold) + message = fmt.Sprintf("An alert for *%s* has been resolved after passing successfully %d time(s) in a row", ep.DisplayName(), alert.SuccessThreshold) color = "#36A64F" } else { - message = fmt.Sprintf("An alert for *%s* has been triggered due to having failed %d time(s) in a row", endpoint.DisplayName(), alert.FailureThreshold) + message = fmt.Sprintf("An alert for *%s* has been triggered due to having failed %d time(s) in a row", ep.DisplayName(), alert.FailureThreshold) color = "#DD0000" } var formattedConditionResults string diff --git a/alerting/provider/mattermost/mattermost_test.go b/alerting/provider/mattermost/mattermost_test.go index 016ac406..b476bfc8 100644 --- a/alerting/provider/mattermost/mattermost_test.go +++ b/alerting/provider/mattermost/mattermost_test.go @@ -7,7 +7,7 @@ import ( "github.com/TwiN/gatus/v5/alerting/alert" "github.com/TwiN/gatus/v5/client" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" "github.com/TwiN/gatus/v5/test" ) @@ -120,10 +120,10 @@ func TestAlertProvider_Send(t *testing.T) { t.Run(scenario.Name, func(t *testing.T) { client.InjectHTTPClient(&http.Client{Transport: scenario.MockRoundTripper}) err := scenario.Provider.Send( - &core.Endpoint{Name: "endpoint-name"}, + &endpoint.Endpoint{Name: "endpoint-name"}, &scenario.Alert, - &core.Result{ - ConditionResults: []*core.ConditionResult{ + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, {Condition: "[STATUS] == 200", Success: scenario.Resolved}, }, @@ -168,10 +168,10 @@ func TestAlertProvider_buildRequestBody(t *testing.T) { for _, scenario := range scenarios { t.Run(scenario.Name, func(t *testing.T) { body := scenario.Provider.buildRequestBody( - &core.Endpoint{Name: "endpoint-name"}, + &endpoint.Endpoint{Name: "endpoint-name"}, &scenario.Alert, - &core.Result{ - ConditionResults: []*core.ConditionResult{ + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, {Condition: "[STATUS] == 200", Success: scenario.Resolved}, }, diff --git a/alerting/provider/messagebird/messagebird.go b/alerting/provider/messagebird/messagebird.go index eaf93937..235a0a60 100644 --- a/alerting/provider/messagebird/messagebird.go +++ b/alerting/provider/messagebird/messagebird.go @@ -9,7 +9,7 @@ import ( "github.com/TwiN/gatus/v5/alerting/alert" "github.com/TwiN/gatus/v5/client" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" ) const ( @@ -33,8 +33,8 @@ func (provider *AlertProvider) IsValid() bool { // Send an alert using the provider // Reference doc for messagebird: https://developers.messagebird.com/api/sms-messaging/#send-outbound-sms -func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) error { - buffer := bytes.NewBuffer(provider.buildRequestBody(endpoint, alert, result, resolved)) +func (provider *AlertProvider) Send(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) error { + buffer := bytes.NewBuffer(provider.buildRequestBody(ep, alert, result, resolved)) request, err := http.NewRequest(http.MethodPost, restAPIURL, buffer) if err != nil { return err @@ -60,12 +60,12 @@ type Body struct { } // buildRequestBody builds the request body for the provider -func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) []byte { +func (provider *AlertProvider) buildRequestBody(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) []byte { var message string if resolved { - message = fmt.Sprintf("RESOLVED: %s - %s", endpoint.DisplayName(), alert.GetDescription()) + message = fmt.Sprintf("RESOLVED: %s - %s", ep.DisplayName(), alert.GetDescription()) } else { - message = fmt.Sprintf("TRIGGERED: %s - %s", endpoint.DisplayName(), alert.GetDescription()) + message = fmt.Sprintf("TRIGGERED: %s - %s", ep.DisplayName(), alert.GetDescription()) } body, _ := json.Marshal(Body{ Originator: provider.Originator, diff --git a/alerting/provider/messagebird/messagebird_test.go b/alerting/provider/messagebird/messagebird_test.go index a5a7b3b2..22c9a4d6 100644 --- a/alerting/provider/messagebird/messagebird_test.go +++ b/alerting/provider/messagebird/messagebird_test.go @@ -7,7 +7,7 @@ import ( "github.com/TwiN/gatus/v5/alerting/alert" "github.com/TwiN/gatus/v5/client" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" "github.com/TwiN/gatus/v5/test" ) @@ -83,10 +83,10 @@ func TestAlertProvider_Send(t *testing.T) { t.Run(scenario.Name, func(t *testing.T) { client.InjectHTTPClient(&http.Client{Transport: scenario.MockRoundTripper}) err := scenario.Provider.Send( - &core.Endpoint{Name: "endpoint-name"}, + &endpoint.Endpoint{Name: "endpoint-name"}, &scenario.Alert, - &core.Result{ - ConditionResults: []*core.ConditionResult{ + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, {Condition: "[STATUS] == 200", Success: scenario.Resolved}, }, @@ -131,10 +131,10 @@ func TestAlertProvider_buildRequestBody(t *testing.T) { for _, scenario := range scenarios { t.Run(scenario.Name, func(t *testing.T) { body := scenario.Provider.buildRequestBody( - &core.Endpoint{Name: "endpoint-name"}, + &endpoint.Endpoint{Name: "endpoint-name"}, &scenario.Alert, - &core.Result{ - ConditionResults: []*core.ConditionResult{ + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, {Condition: "[STATUS] == 200", Success: scenario.Resolved}, }, diff --git a/alerting/provider/ntfy/ntfy.go b/alerting/provider/ntfy/ntfy.go index bd10ef83..1530c10f 100644 --- a/alerting/provider/ntfy/ntfy.go +++ b/alerting/provider/ntfy/ntfy.go @@ -11,7 +11,7 @@ import ( "github.com/TwiN/gatus/v5/alerting/alert" "github.com/TwiN/gatus/v5/client" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" ) const ( @@ -46,8 +46,8 @@ func (provider *AlertProvider) IsValid() bool { } // Send an alert using the provider -func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) error { - buffer := bytes.NewBuffer(provider.buildRequestBody(endpoint, alert, result, resolved)) +func (provider *AlertProvider) Send(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) error { + buffer := bytes.NewBuffer(provider.buildRequestBody(ep, alert, result, resolved)) request, err := http.NewRequest(http.MethodPost, provider.URL, buffer) if err != nil { return err @@ -77,7 +77,7 @@ type Body struct { } // buildRequestBody builds the request body for the provider -func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) []byte { +func (provider *AlertProvider) buildRequestBody(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) []byte { var message, formattedConditionResults, tag string if resolved { tag = "white_check_mark" @@ -101,7 +101,7 @@ func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert * message += formattedConditionResults body, _ := json.Marshal(Body{ Topic: provider.Topic, - Title: "Gatus: " + endpoint.DisplayName(), + Title: "Gatus: " + ep.DisplayName(), Message: message, Tags: []string{tag}, Priority: provider.Priority, diff --git a/alerting/provider/ntfy/ntfy_test.go b/alerting/provider/ntfy/ntfy_test.go index 6786f3b3..3ca3e59d 100644 --- a/alerting/provider/ntfy/ntfy_test.go +++ b/alerting/provider/ntfy/ntfy_test.go @@ -5,7 +5,7 @@ import ( "testing" "github.com/TwiN/gatus/v5/alerting/alert" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" ) func TestAlertDefaultProvider_IsValid(t *testing.T) { @@ -92,10 +92,10 @@ func TestAlertProvider_buildRequestBody(t *testing.T) { for _, scenario := range scenarios { t.Run(scenario.Name, func(t *testing.T) { body := scenario.Provider.buildRequestBody( - &core.Endpoint{Name: "endpoint-name"}, + &endpoint.Endpoint{Name: "endpoint-name"}, &scenario.Alert, - &core.Result{ - ConditionResults: []*core.ConditionResult{ + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, {Condition: "[STATUS] == 200", Success: scenario.Resolved}, }, diff --git a/alerting/provider/opsgenie/opsgenie.go b/alerting/provider/opsgenie/opsgenie.go index 5d105661..a0b4167f 100644 --- a/alerting/provider/opsgenie/opsgenie.go +++ b/alerting/provider/opsgenie/opsgenie.go @@ -11,7 +11,7 @@ import ( "github.com/TwiN/gatus/v5/alerting/alert" "github.com/TwiN/gatus/v5/client" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" ) const ( @@ -59,13 +59,13 @@ func (provider *AlertProvider) IsValid() bool { // Send an alert using the provider // // Relevant: https://docs.opsgenie.com/docs/alert-api -func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) error { - err := provider.createAlert(endpoint, alert, result, resolved) +func (provider *AlertProvider) Send(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) error { + err := provider.createAlert(ep, alert, result, resolved) if err != nil { return err } if resolved { - err = provider.closeAlert(endpoint, alert) + err = provider.closeAlert(ep, alert) if err != nil { return err } @@ -75,20 +75,20 @@ func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert, // The alert has been resolved and there's no error, so we can clear the alert's ResolveKey alert.ResolveKey = "" } else { - alert.ResolveKey = provider.alias(buildKey(endpoint)) + alert.ResolveKey = provider.alias(buildKey(ep)) } } return nil } -func (provider *AlertProvider) createAlert(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) error { - payload := provider.buildCreateRequestBody(endpoint, alert, result, resolved) +func (provider *AlertProvider) createAlert(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) error { + payload := provider.buildCreateRequestBody(ep, alert, result, resolved) return provider.sendRequest(restAPI, http.MethodPost, payload) } -func (provider *AlertProvider) closeAlert(endpoint *core.Endpoint, alert *alert.Alert) error { - payload := provider.buildCloseRequestBody(endpoint, alert) - url := restAPI + "/" + provider.alias(buildKey(endpoint)) + "/close?identifierType=alias" +func (provider *AlertProvider) closeAlert(ep *endpoint.Endpoint, alert *alert.Alert) error { + payload := provider.buildCloseRequestBody(ep, alert) + url := restAPI + "/" + provider.alias(buildKey(ep)) + "/close?identifierType=alias" return provider.sendRequest(url, http.MethodPost, payload) } @@ -115,17 +115,17 @@ func (provider *AlertProvider) sendRequest(url, method string, payload interface return nil } -func (provider *AlertProvider) buildCreateRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) alertCreateRequest { +func (provider *AlertProvider) buildCreateRequestBody(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) alertCreateRequest { var message, description string if resolved { - message = fmt.Sprintf("RESOLVED: %s - %s", endpoint.Name, alert.GetDescription()) - description = fmt.Sprintf("An alert for *%s* has been resolved after passing successfully %d time(s) in a row", endpoint.DisplayName(), alert.SuccessThreshold) + message = fmt.Sprintf("RESOLVED: %s - %s", ep.Name, alert.GetDescription()) + description = fmt.Sprintf("An alert for *%s* has been resolved after passing successfully %d time(s) in a row", ep.DisplayName(), alert.SuccessThreshold) } else { - message = fmt.Sprintf("%s - %s", endpoint.Name, alert.GetDescription()) - description = fmt.Sprintf("An alert for *%s* has been triggered due to having failed %d time(s) in a row", endpoint.DisplayName(), alert.FailureThreshold) + message = fmt.Sprintf("%s - %s", ep.Name, alert.GetDescription()) + description = fmt.Sprintf("An alert for *%s* has been triggered due to having failed %d time(s) in a row", ep.DisplayName(), alert.FailureThreshold) } - if endpoint.Group != "" { - message = fmt.Sprintf("[%s] %s", endpoint.Group, message) + if ep.Group != "" { + message = fmt.Sprintf("[%s] %s", ep.Group, message) } var formattedConditionResults string for _, conditionResult := range result.ConditionResults { @@ -138,10 +138,10 @@ func (provider *AlertProvider) buildCreateRequestBody(endpoint *core.Endpoint, a formattedConditionResults += fmt.Sprintf("%s - `%s`\n", prefix, conditionResult.Condition) } description = description + "\n" + formattedConditionResults - key := buildKey(endpoint) + key := buildKey(ep) details := map[string]string{ - "endpoint:url": endpoint.URL, - "endpoint:group": endpoint.Group, + "endpoint:url": ep.URL, + "endpoint:group": ep.Group, "result:hostname": result.Hostname, "result:ip": result.IP, "result:dns_code": result.DNSRCode, @@ -167,10 +167,10 @@ func (provider *AlertProvider) buildCreateRequestBody(endpoint *core.Endpoint, a } } -func (provider *AlertProvider) buildCloseRequestBody(endpoint *core.Endpoint, alert *alert.Alert) alertCloseRequest { +func (provider *AlertProvider) buildCloseRequestBody(ep *endpoint.Endpoint, alert *alert.Alert) alertCloseRequest { return alertCloseRequest{ - Source: buildKey(endpoint), - Note: fmt.Sprintf("RESOLVED: %s - %s", endpoint.Name, alert.GetDescription()), + Source: buildKey(ep), + Note: fmt.Sprintf("RESOLVED: %s - %s", ep.Name, alert.GetDescription()), } } @@ -211,12 +211,12 @@ func (provider *AlertProvider) GetDefaultAlert() *alert.Alert { return provider.DefaultAlert } -func buildKey(endpoint *core.Endpoint) string { - name := toKebabCase(endpoint.Name) - if endpoint.Group == "" { +func buildKey(ep *endpoint.Endpoint) string { + name := toKebabCase(ep.Name) + if ep.Group == "" { return name } - return toKebabCase(endpoint.Group) + "-" + name + return toKebabCase(ep.Group) + "-" + name } func toKebabCase(val string) string { diff --git a/alerting/provider/opsgenie/opsgenie_test.go b/alerting/provider/opsgenie/opsgenie_test.go index ab054b86..746eb387 100644 --- a/alerting/provider/opsgenie/opsgenie_test.go +++ b/alerting/provider/opsgenie/opsgenie_test.go @@ -7,7 +7,7 @@ import ( "github.com/TwiN/gatus/v5/alerting/alert" "github.com/TwiN/gatus/v5/client" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" "github.com/TwiN/gatus/v5/test" ) @@ -79,10 +79,10 @@ func TestAlertProvider_Send(t *testing.T) { t.Run(scenario.Name, func(t *testing.T) { client.InjectHTTPClient(&http.Client{Transport: scenario.MockRoundTripper}) err := scenario.Provider.Send( - &core.Endpoint{Name: "endpoint-name"}, + &endpoint.Endpoint{Name: "endpoint-name"}, &scenario.Alert, - &core.Result{ - ConditionResults: []*core.ConditionResult{ + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, {Condition: "[STATUS] == 200", Success: scenario.Resolved}, }, @@ -106,8 +106,8 @@ func TestAlertProvider_buildCreateRequestBody(t *testing.T) { Name string Provider *AlertProvider Alert *alert.Alert - Endpoint *core.Endpoint - Result *core.Result + Endpoint *endpoint.Endpoint + Result *endpoint.Result Resolved bool want alertCreateRequest }{ @@ -115,8 +115,8 @@ func TestAlertProvider_buildCreateRequestBody(t *testing.T) { Name: "missing all params (unresolved)", Provider: &AlertProvider{}, Alert: &alert.Alert{}, - Endpoint: &core.Endpoint{}, - Result: &core.Result{}, + Endpoint: &endpoint.Endpoint{}, + Result: &endpoint.Result{}, Resolved: false, want: alertCreateRequest{ Message: " - ", @@ -133,8 +133,8 @@ func TestAlertProvider_buildCreateRequestBody(t *testing.T) { Name: "missing all params (resolved)", Provider: &AlertProvider{}, Alert: &alert.Alert{}, - Endpoint: &core.Endpoint{}, - Result: &core.Result{}, + Endpoint: &endpoint.Endpoint{}, + Result: &endpoint.Result{}, Resolved: true, want: alertCreateRequest{ Message: "RESOLVED: - ", @@ -154,11 +154,11 @@ func TestAlertProvider_buildCreateRequestBody(t *testing.T) { Description: &description, FailureThreshold: 3, }, - Endpoint: &core.Endpoint{ + Endpoint: &endpoint.Endpoint{ Name: "my super app", }, - Result: &core.Result{ - ConditionResults: []*core.ConditionResult{ + Result: &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ { Condition: "[STATUS] == 200", Success: true, @@ -194,11 +194,11 @@ func TestAlertProvider_buildCreateRequestBody(t *testing.T) { Description: &description, SuccessThreshold: 4, }, - Endpoint: &core.Endpoint{ + Endpoint: &endpoint.Endpoint{ Name: "my mega app", }, - Result: &core.Result{ - ConditionResults: []*core.ConditionResult{ + Result: &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ { Condition: "[STATUS] == 200", Success: true, @@ -226,17 +226,17 @@ func TestAlertProvider_buildCreateRequestBody(t *testing.T) { Description: &description, FailureThreshold: 6, }, - Endpoint: &core.Endpoint{ + Endpoint: &endpoint.Endpoint{ Name: "my app", Group: "end game", URL: "https://my.go/app", }, - Result: &core.Result{ + Result: &endpoint.Result{ HTTPStatus: 400, Hostname: "my.go", Errors: []string{"error 01", "error 02"}, Success: false, - ConditionResults: []*core.ConditionResult{ + ConditionResults: []*endpoint.ConditionResult{ { Condition: "[STATUS] == 200", Success: false, @@ -279,14 +279,14 @@ func TestAlertProvider_buildCloseRequestBody(t *testing.T) { Name string Provider *AlertProvider Alert *alert.Alert - Endpoint *core.Endpoint + Endpoint *endpoint.Endpoint want alertCloseRequest }{ { Name: "Missing all values", Provider: &AlertProvider{}, Alert: &alert.Alert{}, - Endpoint: &core.Endpoint{}, + Endpoint: &endpoint.Endpoint{}, want: alertCloseRequest{ Source: "", Note: "RESOLVED: - ", @@ -298,7 +298,7 @@ func TestAlertProvider_buildCloseRequestBody(t *testing.T) { Alert: &alert.Alert{ Description: &description, }, - Endpoint: &core.Endpoint{ + Endpoint: &endpoint.Endpoint{ Name: "endpoint name", }, want: alertCloseRequest{ diff --git a/alerting/provider/pagerduty/pagerduty.go b/alerting/provider/pagerduty/pagerduty.go index b3ab6e01..cc76751f 100644 --- a/alerting/provider/pagerduty/pagerduty.go +++ b/alerting/provider/pagerduty/pagerduty.go @@ -10,7 +10,7 @@ import ( "github.com/TwiN/gatus/v5/alerting/alert" "github.com/TwiN/gatus/v5/client" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" ) const ( @@ -52,8 +52,8 @@ func (provider *AlertProvider) IsValid() bool { // Send an alert using the provider // // Relevant: https://developer.pagerduty.com/docs/events-api-v2/trigger-events/ -func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) error { - buffer := bytes.NewBuffer(provider.buildRequestBody(endpoint, alert, result, resolved)) +func (provider *AlertProvider) Send(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) error { + buffer := bytes.NewBuffer(provider.buildRequestBody(ep, alert, result, resolved)) request, err := http.NewRequest(http.MethodPost, restAPIURL, buffer) if err != nil { return err @@ -101,19 +101,19 @@ type Payload struct { } // buildRequestBody builds the request body for the provider -func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) []byte { +func (provider *AlertProvider) buildRequestBody(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) []byte { var message, eventAction, resolveKey string if resolved { - message = fmt.Sprintf("RESOLVED: %s - %s", endpoint.DisplayName(), alert.GetDescription()) + message = fmt.Sprintf("RESOLVED: %s - %s", ep.DisplayName(), alert.GetDescription()) eventAction = "resolve" resolveKey = alert.ResolveKey } else { - message = fmt.Sprintf("TRIGGERED: %s - %s", endpoint.DisplayName(), alert.GetDescription()) + message = fmt.Sprintf("TRIGGERED: %s - %s", ep.DisplayName(), alert.GetDescription()) eventAction = "trigger" resolveKey = "" } body, _ := json.Marshal(Body{ - RoutingKey: provider.getIntegrationKeyForGroup(endpoint.Group), + RoutingKey: provider.getIntegrationKeyForGroup(ep.Group), DedupKey: resolveKey, EventAction: eventAction, Payload: Payload{ diff --git a/alerting/provider/pagerduty/pagerduty_test.go b/alerting/provider/pagerduty/pagerduty_test.go index 4523f280..23d0b410 100644 --- a/alerting/provider/pagerduty/pagerduty_test.go +++ b/alerting/provider/pagerduty/pagerduty_test.go @@ -7,7 +7,7 @@ import ( "github.com/TwiN/gatus/v5/alerting/alert" "github.com/TwiN/gatus/v5/client" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" "github.com/TwiN/gatus/v5/test" ) @@ -115,10 +115,10 @@ func TestAlertProvider_Send(t *testing.T) { t.Run(scenario.Name, func(t *testing.T) { client.InjectHTTPClient(&http.Client{Transport: scenario.MockRoundTripper}) err := scenario.Provider.Send( - &core.Endpoint{Name: "endpoint-name"}, + &endpoint.Endpoint{Name: "endpoint-name"}, &scenario.Alert, - &core.Result{ - ConditionResults: []*core.ConditionResult{ + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, {Condition: "[STATUS] == 200", Success: scenario.Resolved}, }, @@ -161,7 +161,7 @@ func TestAlertProvider_buildRequestBody(t *testing.T) { } for _, scenario := range scenarios { t.Run(scenario.Name, func(t *testing.T) { - body := scenario.Provider.buildRequestBody(&core.Endpoint{Name: "endpoint-name"}, &scenario.Alert, &core.Result{}, scenario.Resolved) + body := scenario.Provider.buildRequestBody(&endpoint.Endpoint{Name: "endpoint-name"}, &scenario.Alert, &endpoint.Result{}, scenario.Resolved) if string(body) != scenario.ExpectedBody { t.Errorf("expected:\n%s\ngot:\n%s", scenario.ExpectedBody, body) } diff --git a/alerting/provider/provider.go b/alerting/provider/provider.go index f8c5257b..a294ad9b 100644 --- a/alerting/provider/provider.go +++ b/alerting/provider/provider.go @@ -21,7 +21,7 @@ import ( "github.com/TwiN/gatus/v5/alerting/provider/teams" "github.com/TwiN/gatus/v5/alerting/provider/telegram" "github.com/TwiN/gatus/v5/alerting/provider/twilio" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" ) // AlertProvider is the interface that each provider should implement @@ -33,7 +33,7 @@ type AlertProvider interface { GetDefaultAlert() *alert.Alert // Send an alert using the provider - Send(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) error + Send(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) error } // ParseWithDefaultAlert parses an Endpoint alert by using the provider's default alert as a baseline diff --git a/alerting/provider/pushover/pushover.go b/alerting/provider/pushover/pushover.go index 6b5343fd..b90b17a5 100644 --- a/alerting/provider/pushover/pushover.go +++ b/alerting/provider/pushover/pushover.go @@ -9,7 +9,7 @@ import ( "github.com/TwiN/gatus/v5/alerting/alert" "github.com/TwiN/gatus/v5/client" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" ) const ( @@ -52,8 +52,8 @@ func (provider *AlertProvider) IsValid() bool { // Send an alert using the provider // Reference doc for pushover: https://pushover.net/api -func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) error { - buffer := bytes.NewBuffer(provider.buildRequestBody(endpoint, alert, result, resolved)) +func (provider *AlertProvider) Send(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) error { + buffer := bytes.NewBuffer(provider.buildRequestBody(ep, alert, result, resolved)) request, err := http.NewRequest(http.MethodPost, restAPIURL, buffer) if err != nil { return err @@ -81,12 +81,12 @@ type Body struct { } // buildRequestBody builds the request body for the provider -func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) []byte { +func (provider *AlertProvider) buildRequestBody(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) []byte { var message string if resolved { - message = fmt.Sprintf("RESOLVED: %s - %s", endpoint.DisplayName(), alert.GetDescription()) + message = fmt.Sprintf("RESOLVED: %s - %s", ep.DisplayName(), alert.GetDescription()) } else { - message = fmt.Sprintf("TRIGGERED: %s - %s", endpoint.DisplayName(), alert.GetDescription()) + message = fmt.Sprintf("TRIGGERED: %s - %s", ep.DisplayName(), alert.GetDescription()) } body, _ := json.Marshal(Body{ Token: provider.ApplicationToken, diff --git a/alerting/provider/pushover/pushover_test.go b/alerting/provider/pushover/pushover_test.go index 57f03fb5..942a82bf 100644 --- a/alerting/provider/pushover/pushover_test.go +++ b/alerting/provider/pushover/pushover_test.go @@ -7,7 +7,7 @@ import ( "github.com/TwiN/gatus/v5/alerting/alert" "github.com/TwiN/gatus/v5/client" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" "github.com/TwiN/gatus/v5/test" ) @@ -95,10 +95,10 @@ func TestAlertProvider_Send(t *testing.T) { t.Run(scenario.Name, func(t *testing.T) { client.InjectHTTPClient(&http.Client{Transport: scenario.MockRoundTripper}) err := scenario.Provider.Send( - &core.Endpoint{Name: "endpoint-name"}, + &endpoint.Endpoint{Name: "endpoint-name"}, &scenario.Alert, - &core.Result{ - ConditionResults: []*core.ConditionResult{ + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, {Condition: "[STATUS] == 200", Success: scenario.Resolved}, }, @@ -150,10 +150,10 @@ func TestAlertProvider_buildRequestBody(t *testing.T) { for _, scenario := range scenarios { t.Run(scenario.Name, func(t *testing.T) { body := scenario.Provider.buildRequestBody( - &core.Endpoint{Name: "endpoint-name"}, + &endpoint.Endpoint{Name: "endpoint-name"}, &scenario.Alert, - &core.Result{ - ConditionResults: []*core.ConditionResult{ + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, {Condition: "[STATUS] == 200", Success: scenario.Resolved}, }, diff --git a/alerting/provider/slack/slack.go b/alerting/provider/slack/slack.go index e498cee1..46d327b2 100644 --- a/alerting/provider/slack/slack.go +++ b/alerting/provider/slack/slack.go @@ -9,7 +9,7 @@ import ( "github.com/TwiN/gatus/v5/alerting/alert" "github.com/TwiN/gatus/v5/client" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" ) // AlertProvider is the configuration necessary for sending an alert using Slack @@ -42,9 +42,9 @@ func (provider *AlertProvider) IsValid() bool { } // Send an alert using the provider -func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) error { - buffer := bytes.NewBuffer(provider.buildRequestBody(endpoint, alert, result, resolved)) - request, err := http.NewRequest(http.MethodPost, provider.getWebhookURLForGroup(endpoint.Group), buffer) +func (provider *AlertProvider) Send(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) error { + buffer := bytes.NewBuffer(provider.buildRequestBody(ep, alert, result, resolved)) + request, err := http.NewRequest(http.MethodPost, provider.getWebhookURLForGroup(ep.Group), buffer) if err != nil { return err } @@ -81,13 +81,13 @@ type Field struct { } // buildRequestBody builds the request body for the provider -func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) []byte { +func (provider *AlertProvider) buildRequestBody(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) []byte { var message, color string if resolved { - message = fmt.Sprintf("An alert for *%s* has been resolved after passing successfully %d time(s) in a row", endpoint.DisplayName(), alert.SuccessThreshold) + message = fmt.Sprintf("An alert for *%s* has been resolved after passing successfully %d time(s) in a row", ep.DisplayName(), alert.SuccessThreshold) color = "#36A64F" } else { - message = fmt.Sprintf("An alert for *%s* has been triggered due to having failed %d time(s) in a row", endpoint.DisplayName(), alert.FailureThreshold) + message = fmt.Sprintf("An alert for *%s* has been triggered due to having failed %d time(s) in a row", ep.DisplayName(), alert.FailureThreshold) color = "#DD0000" } var formattedConditionResults string diff --git a/alerting/provider/slack/slack_test.go b/alerting/provider/slack/slack_test.go index 4bd10e15..f3e95fcd 100644 --- a/alerting/provider/slack/slack_test.go +++ b/alerting/provider/slack/slack_test.go @@ -7,7 +7,7 @@ import ( "github.com/TwiN/gatus/v5/alerting/alert" "github.com/TwiN/gatus/v5/client" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" "github.com/TwiN/gatus/v5/test" ) @@ -116,10 +116,10 @@ func TestAlertProvider_Send(t *testing.T) { t.Run(scenario.Name, func(t *testing.T) { client.InjectHTTPClient(&http.Client{Transport: scenario.MockRoundTripper}) err := scenario.Provider.Send( - &core.Endpoint{Name: "endpoint-name"}, + &endpoint.Endpoint{Name: "endpoint-name"}, &scenario.Alert, - &core.Result{ - ConditionResults: []*core.ConditionResult{ + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, {Condition: "[STATUS] == 200", Success: scenario.Resolved}, }, @@ -142,7 +142,7 @@ func TestAlertProvider_buildRequestBody(t *testing.T) { scenarios := []struct { Name string Provider AlertProvider - Endpoint core.Endpoint + Endpoint endpoint.Endpoint Alert alert.Alert NoConditions bool Resolved bool @@ -151,7 +151,7 @@ func TestAlertProvider_buildRequestBody(t *testing.T) { { Name: "triggered", Provider: AlertProvider{}, - Endpoint: core.Endpoint{Name: "name"}, + Endpoint: endpoint.Endpoint{Name: "name"}, Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, Resolved: false, ExpectedBody: "{\"text\":\"\",\"attachments\":[{\"title\":\":helmet_with_white_cross: Gatus\",\"text\":\"An alert for *name* has been triggered due to having failed 3 time(s) in a row:\\n\\u003e description-1\",\"short\":false,\"color\":\"#DD0000\",\"fields\":[{\"title\":\"Condition results\",\"value\":\":x: - `[CONNECTED] == true`\\n:x: - `[STATUS] == 200`\\n\",\"short\":false}]}]}", @@ -159,7 +159,7 @@ func TestAlertProvider_buildRequestBody(t *testing.T) { { Name: "triggered-with-group", Provider: AlertProvider{}, - Endpoint: core.Endpoint{Name: "name", Group: "group"}, + Endpoint: endpoint.Endpoint{Name: "name", Group: "group"}, Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, Resolved: false, ExpectedBody: "{\"text\":\"\",\"attachments\":[{\"title\":\":helmet_with_white_cross: Gatus\",\"text\":\"An alert for *group/name* has been triggered due to having failed 3 time(s) in a row:\\n\\u003e description-1\",\"short\":false,\"color\":\"#DD0000\",\"fields\":[{\"title\":\"Condition results\",\"value\":\":x: - `[CONNECTED] == true`\\n:x: - `[STATUS] == 200`\\n\",\"short\":false}]}]}", @@ -168,7 +168,7 @@ func TestAlertProvider_buildRequestBody(t *testing.T) { Name: "triggered-with-no-conditions", NoConditions: true, Provider: AlertProvider{}, - Endpoint: core.Endpoint{Name: "name"}, + Endpoint: endpoint.Endpoint{Name: "name"}, Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, Resolved: false, ExpectedBody: "{\"text\":\"\",\"attachments\":[{\"title\":\":helmet_with_white_cross: Gatus\",\"text\":\"An alert for *name* has been triggered due to having failed 3 time(s) in a row:\\n\\u003e description-1\",\"short\":false,\"color\":\"#DD0000\"}]}", @@ -176,7 +176,7 @@ func TestAlertProvider_buildRequestBody(t *testing.T) { { Name: "resolved", Provider: AlertProvider{}, - Endpoint: core.Endpoint{Name: "name"}, + Endpoint: endpoint.Endpoint{Name: "name"}, Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, Resolved: true, ExpectedBody: "{\"text\":\"\",\"attachments\":[{\"title\":\":helmet_with_white_cross: Gatus\",\"text\":\"An alert for *name* has been resolved after passing successfully 5 time(s) in a row:\\n\\u003e description-2\",\"short\":false,\"color\":\"#36A64F\",\"fields\":[{\"title\":\"Condition results\",\"value\":\":white_check_mark: - `[CONNECTED] == true`\\n:white_check_mark: - `[STATUS] == 200`\\n\",\"short\":false}]}]}", @@ -184,7 +184,7 @@ func TestAlertProvider_buildRequestBody(t *testing.T) { { Name: "resolved-with-group", Provider: AlertProvider{}, - Endpoint: core.Endpoint{Name: "name", Group: "group"}, + Endpoint: endpoint.Endpoint{Name: "name", Group: "group"}, Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, Resolved: true, ExpectedBody: "{\"text\":\"\",\"attachments\":[{\"title\":\":helmet_with_white_cross: Gatus\",\"text\":\"An alert for *group/name* has been resolved after passing successfully 5 time(s) in a row:\\n\\u003e description-2\",\"short\":false,\"color\":\"#36A64F\",\"fields\":[{\"title\":\"Condition results\",\"value\":\":white_check_mark: - `[CONNECTED] == true`\\n:white_check_mark: - `[STATUS] == 200`\\n\",\"short\":false}]}]}", @@ -192,9 +192,9 @@ func TestAlertProvider_buildRequestBody(t *testing.T) { } for _, scenario := range scenarios { t.Run(scenario.Name, func(t *testing.T) { - var conditionResults []*core.ConditionResult + var conditionResults []*endpoint.ConditionResult if !scenario.NoConditions { - conditionResults = []*core.ConditionResult{ + conditionResults = []*endpoint.ConditionResult{ {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, {Condition: "[STATUS] == 200", Success: scenario.Resolved}, } @@ -202,7 +202,7 @@ func TestAlertProvider_buildRequestBody(t *testing.T) { body := scenario.Provider.buildRequestBody( &scenario.Endpoint, &scenario.Alert, - &core.Result{ + &endpoint.Result{ ConditionResults: conditionResults, }, scenario.Resolved, diff --git a/alerting/provider/teams/teams.go b/alerting/provider/teams/teams.go index 3dcd1812..671649be 100644 --- a/alerting/provider/teams/teams.go +++ b/alerting/provider/teams/teams.go @@ -9,7 +9,7 @@ import ( "github.com/TwiN/gatus/v5/alerting/alert" "github.com/TwiN/gatus/v5/client" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" ) // AlertProvider is the configuration necessary for sending an alert using Teams @@ -21,7 +21,7 @@ type AlertProvider struct { // Overrides is a list of Override that may be prioritized over the default configuration Overrides []Override `yaml:"overrides,omitempty"` - + // Title is the title of the message that will be sent Title string `yaml:"title,omitempty"` } @@ -47,9 +47,9 @@ func (provider *AlertProvider) IsValid() bool { } // Send an alert using the provider -func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) error { - buffer := bytes.NewBuffer(provider.buildRequestBody(endpoint, alert, result, resolved)) - request, err := http.NewRequest(http.MethodPost, provider.getWebhookURLForGroup(endpoint.Group), buffer) +func (provider *AlertProvider) Send(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) error { + buffer := bytes.NewBuffer(provider.buildRequestBody(ep, alert, result, resolved)) + request, err := http.NewRequest(http.MethodPost, provider.getWebhookURLForGroup(ep.Group), buffer) if err != nil { return err } @@ -81,13 +81,13 @@ type Section struct { } // buildRequestBody builds the request body for the provider -func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) []byte { +func (provider *AlertProvider) buildRequestBody(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) []byte { var message, color string if resolved { - message = fmt.Sprintf("An alert for *%s* has been resolved after passing successfully %d time(s) in a row", endpoint.DisplayName(), alert.SuccessThreshold) + message = fmt.Sprintf("An alert for *%s* has been resolved after passing successfully %d time(s) in a row", ep.DisplayName(), alert.SuccessThreshold) color = "#36A64F" } else { - message = fmt.Sprintf("An alert for *%s* has been triggered due to having failed %d time(s) in a row", endpoint.DisplayName(), alert.FailureThreshold) + message = fmt.Sprintf("An alert for *%s* has been triggered due to having failed %d time(s) in a row", ep.DisplayName(), alert.FailureThreshold) color = "#DD0000" } var formattedConditionResults string diff --git a/alerting/provider/teams/teams_test.go b/alerting/provider/teams/teams_test.go index 7f7cdb21..802a070e 100644 --- a/alerting/provider/teams/teams_test.go +++ b/alerting/provider/teams/teams_test.go @@ -7,7 +7,7 @@ import ( "github.com/TwiN/gatus/v5/alerting/alert" "github.com/TwiN/gatus/v5/client" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" "github.com/TwiN/gatus/v5/test" ) @@ -116,10 +116,10 @@ func TestAlertProvider_Send(t *testing.T) { t.Run(scenario.Name, func(t *testing.T) { client.InjectHTTPClient(&http.Client{Transport: scenario.MockRoundTripper}) err := scenario.Provider.Send( - &core.Endpoint{Name: "endpoint-name"}, + &endpoint.Endpoint{Name: "endpoint-name"}, &scenario.Alert, - &core.Result{ - ConditionResults: []*core.ConditionResult{ + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, {Condition: "[STATUS] == 200", Success: scenario.Resolved}, }, @@ -172,17 +172,17 @@ func TestAlertProvider_buildRequestBody(t *testing.T) { } for _, scenario := range scenarios { t.Run(scenario.Name, func(t *testing.T) { - var conditionResults []*core.ConditionResult + var conditionResults []*endpoint.ConditionResult if !scenario.NoConditions { - conditionResults = []*core.ConditionResult{ + conditionResults = []*endpoint.ConditionResult{ {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, {Condition: "[STATUS] == 200", Success: scenario.Resolved}, } } body := scenario.Provider.buildRequestBody( - &core.Endpoint{Name: "endpoint-name"}, + &endpoint.Endpoint{Name: "endpoint-name"}, &scenario.Alert, - &core.Result{ConditionResults: conditionResults}, + &endpoint.Result{ConditionResults: conditionResults}, scenario.Resolved, ) if string(body) != scenario.ExpectedBody { diff --git a/alerting/provider/telegram/telegram.go b/alerting/provider/telegram/telegram.go index db9ea0a0..f5950a17 100644 --- a/alerting/provider/telegram/telegram.go +++ b/alerting/provider/telegram/telegram.go @@ -9,7 +9,7 @@ import ( "github.com/TwiN/gatus/v5/alerting/alert" "github.com/TwiN/gatus/v5/client" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" ) const defaultAPIURL = "https://api.telegram.org" @@ -36,8 +36,8 @@ func (provider *AlertProvider) IsValid() bool { } // Send an alert using the provider -func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) error { - buffer := bytes.NewBuffer(provider.buildRequestBody(endpoint, alert, result, resolved)) +func (provider *AlertProvider) Send(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) error { + buffer := bytes.NewBuffer(provider.buildRequestBody(ep, alert, result, resolved)) apiURL := provider.APIURL if apiURL == "" { apiURL = defaultAPIURL @@ -66,12 +66,12 @@ type Body struct { } // buildRequestBody builds the request body for the provider -func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) []byte { +func (provider *AlertProvider) buildRequestBody(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) []byte { var message string if resolved { - message = fmt.Sprintf("An alert for *%s* has been resolved:\n—\n _healthcheck passing successfully %d time(s) in a row_\n— ", endpoint.DisplayName(), alert.SuccessThreshold) + message = fmt.Sprintf("An alert for *%s* has been resolved:\n—\n _healthcheck passing successfully %d time(s) in a row_\n— ", ep.DisplayName(), alert.SuccessThreshold) } else { - message = fmt.Sprintf("An alert for *%s* has been triggered:\n—\n _healthcheck failed %d time(s) in a row_\n— ", endpoint.DisplayName(), alert.FailureThreshold) + message = fmt.Sprintf("An alert for *%s* has been triggered:\n—\n _healthcheck failed %d time(s) in a row_\n— ", ep.DisplayName(), alert.FailureThreshold) } var formattedConditionResults string if len(result.ConditionResults) > 0 { diff --git a/alerting/provider/telegram/telegram_test.go b/alerting/provider/telegram/telegram_test.go index 3dcebb07..6c9b9109 100644 --- a/alerting/provider/telegram/telegram_test.go +++ b/alerting/provider/telegram/telegram_test.go @@ -7,7 +7,7 @@ import ( "github.com/TwiN/gatus/v5/alerting/alert" "github.com/TwiN/gatus/v5/client" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" "github.com/TwiN/gatus/v5/test" ) @@ -89,10 +89,10 @@ func TestAlertProvider_Send(t *testing.T) { t.Run(scenario.Name, func(t *testing.T) { client.InjectHTTPClient(&http.Client{Transport: scenario.MockRoundTripper}) err := scenario.Provider.Send( - &core.Endpoint{Name: "endpoint-name"}, + &endpoint.Endpoint{Name: "endpoint-name"}, &scenario.Alert, - &core.Result{ - ConditionResults: []*core.ConditionResult{ + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, {Condition: "[STATUS] == 200", Success: scenario.Resolved}, }, @@ -145,17 +145,17 @@ func TestAlertProvider_buildRequestBody(t *testing.T) { } for _, scenario := range scenarios { t.Run(scenario.Name, func(t *testing.T) { - var conditionResults []*core.ConditionResult + var conditionResults []*endpoint.ConditionResult if !scenario.NoConditions { - conditionResults = []*core.ConditionResult{ + conditionResults = []*endpoint.ConditionResult{ {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, {Condition: "[STATUS] == 200", Success: scenario.Resolved}, } } body := scenario.Provider.buildRequestBody( - &core.Endpoint{Name: "endpoint-name"}, + &endpoint.Endpoint{Name: "endpoint-name"}, &scenario.Alert, - &core.Result{ConditionResults: conditionResults}, + &endpoint.Result{ConditionResults: conditionResults}, scenario.Resolved, ) if string(body) != scenario.ExpectedBody { diff --git a/alerting/provider/twilio/twilio.go b/alerting/provider/twilio/twilio.go index 8370a298..b2879444 100644 --- a/alerting/provider/twilio/twilio.go +++ b/alerting/provider/twilio/twilio.go @@ -10,7 +10,7 @@ import ( "github.com/TwiN/gatus/v5/alerting/alert" "github.com/TwiN/gatus/v5/client" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" ) // AlertProvider is the configuration necessary for sending an alert using Twilio @@ -30,8 +30,8 @@ func (provider *AlertProvider) IsValid() bool { } // Send an alert using the provider -func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) error { - buffer := bytes.NewBuffer([]byte(provider.buildRequestBody(endpoint, alert, result, resolved))) +func (provider *AlertProvider) Send(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) error { + buffer := bytes.NewBuffer([]byte(provider.buildRequestBody(ep, alert, result, resolved))) request, err := http.NewRequest(http.MethodPost, fmt.Sprintf("https://api.twilio.com/2010-04-01/Accounts/%s/Messages.json", provider.SID), buffer) if err != nil { return err @@ -51,12 +51,12 @@ func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert, } // buildRequestBody builds the request body for the provider -func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) string { +func (provider *AlertProvider) buildRequestBody(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) string { var message string if resolved { - message = fmt.Sprintf("RESOLVED: %s - %s", endpoint.DisplayName(), alert.GetDescription()) + message = fmt.Sprintf("RESOLVED: %s - %s", ep.DisplayName(), alert.GetDescription()) } else { - message = fmt.Sprintf("TRIGGERED: %s - %s", endpoint.DisplayName(), alert.GetDescription()) + message = fmt.Sprintf("TRIGGERED: %s - %s", ep.DisplayName(), alert.GetDescription()) } return url.Values{ "To": {provider.To}, diff --git a/alerting/provider/twilio/twilio_test.go b/alerting/provider/twilio/twilio_test.go index 31328a2f..66e1737f 100644 --- a/alerting/provider/twilio/twilio_test.go +++ b/alerting/provider/twilio/twilio_test.go @@ -4,7 +4,7 @@ import ( "testing" "github.com/TwiN/gatus/v5/alerting/alert" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" ) func TestTwilioAlertProvider_IsValid(t *testing.T) { @@ -51,10 +51,10 @@ func TestAlertProvider_buildRequestBody(t *testing.T) { for _, scenario := range scenarios { t.Run(scenario.Name, func(t *testing.T) { body := scenario.Provider.buildRequestBody( - &core.Endpoint{Name: "endpoint-name"}, + &endpoint.Endpoint{Name: "endpoint-name"}, &scenario.Alert, - &core.Result{ - ConditionResults: []*core.ConditionResult{ + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, {Condition: "[STATUS] == 200", Success: scenario.Resolved}, }, diff --git a/api/badge.go b/api/badge.go index fb04fce3..66a8a36b 100644 --- a/api/badge.go +++ b/api/badge.go @@ -9,7 +9,7 @@ import ( "time" "github.com/TwiN/gatus/v5/config" - "github.com/TwiN/gatus/v5/core/ui" + "github.com/TwiN/gatus/v5/config/endpoint/ui" "github.com/TwiN/gatus/v5/storage/store" "github.com/TwiN/gatus/v5/storage/store/common" "github.com/TwiN/gatus/v5/storage/store/common/paging" diff --git a/api/badge_test.go b/api/badge_test.go index d3da9da6..a5be60d2 100644 --- a/api/badge_test.go +++ b/api/badge_test.go @@ -8,8 +8,8 @@ import ( "time" "github.com/TwiN/gatus/v5/config" - "github.com/TwiN/gatus/v5/core" - "github.com/TwiN/gatus/v5/core/ui" + "github.com/TwiN/gatus/v5/config/endpoint" + "github.com/TwiN/gatus/v5/config/endpoint/ui" "github.com/TwiN/gatus/v5/storage/store" "github.com/TwiN/gatus/v5/watchdog" ) @@ -19,7 +19,7 @@ func TestBadge(t *testing.T) { defer cache.Clear() cfg := &config.Config{ Metrics: true, - Endpoints: []*core.Endpoint{ + Endpoints: []*endpoint.Endpoint{ { Name: "frontend", Group: "core", @@ -34,8 +34,8 @@ func TestBadge(t *testing.T) { cfg.Endpoints[0].UIConfig = ui.GetDefaultConfig() cfg.Endpoints[1].UIConfig = ui.GetDefaultConfig() - watchdog.UpdateEndpointStatuses(cfg.Endpoints[0], &core.Result{Success: true, Connected: true, Duration: time.Millisecond, Timestamp: time.Now()}) - watchdog.UpdateEndpointStatuses(cfg.Endpoints[1], &core.Result{Success: false, Connected: false, Duration: time.Second, Timestamp: time.Now()}) + watchdog.UpdateEndpointStatuses(cfg.Endpoints[0], &endpoint.Result{Success: true, Connected: true, Duration: time.Millisecond, Timestamp: time.Now()}) + watchdog.UpdateEndpointStatuses(cfg.Endpoints[1], &endpoint.Result{Success: false, Connected: false, Duration: time.Second, Timestamp: time.Now()}) api := New(cfg) router := api.Router() type Scenario struct { @@ -218,30 +218,30 @@ func TestGetBadgeColorFromResponseTime(t *testing.T) { defer cache.Clear() var ( - firstCondition = core.Condition("[STATUS] == 200") - secondCondition = core.Condition("[RESPONSE_TIME] < 500") - thirdCondition = core.Condition("[CERTIFICATE_EXPIRATION] < 72h") + firstCondition = endpoint.Condition("[STATUS] == 200") + secondCondition = endpoint.Condition("[RESPONSE_TIME] < 500") + thirdCondition = endpoint.Condition("[CERTIFICATE_EXPIRATION] < 72h") ) - firstTestEndpoint := core.Endpoint{ + firstTestEndpoint := endpoint.Endpoint{ Name: "a", URL: "https://example.org/what/ever", Method: "GET", Body: "body", Interval: 30 * time.Second, - Conditions: []core.Condition{firstCondition, secondCondition, thirdCondition}, + Conditions: []endpoint.Condition{firstCondition, secondCondition, thirdCondition}, Alerts: nil, NumberOfFailuresInARow: 0, NumberOfSuccessesInARow: 0, UIConfig: ui.GetDefaultConfig(), } - secondTestEndpoint := core.Endpoint{ + secondTestEndpoint := endpoint.Endpoint{ Name: "b", URL: "https://example.org/what/ever", Method: "GET", Body: "body", Interval: 30 * time.Second, - Conditions: []core.Condition{firstCondition, secondCondition, thirdCondition}, + Conditions: []endpoint.Condition{firstCondition, secondCondition, thirdCondition}, Alerts: nil, NumberOfFailuresInARow: 0, NumberOfSuccessesInARow: 0, @@ -255,10 +255,10 @@ func TestGetBadgeColorFromResponseTime(t *testing.T) { } cfg := &config.Config{ Metrics: true, - Endpoints: []*core.Endpoint{&firstTestEndpoint, &secondTestEndpoint}, + Endpoints: []*endpoint.Endpoint{&firstTestEndpoint, &secondTestEndpoint}, } - testSuccessfulResult := core.Result{ + testSuccessfulResult := endpoint.Result{ Hostname: "example.org", IP: "127.0.0.1", HTTPStatus: 200, @@ -268,7 +268,7 @@ func TestGetBadgeColorFromResponseTime(t *testing.T) { Timestamp: time.Now(), Duration: 150 * time.Millisecond, CertificateExpiration: 10 * time.Hour, - ConditionResults: []*core.ConditionResult{ + ConditionResults: []*endpoint.ConditionResult{ { Condition: "[STATUS] == 200", Success: true, diff --git a/api/chart_test.go b/api/chart_test.go index 341c6725..0c3c769e 100644 --- a/api/chart_test.go +++ b/api/chart_test.go @@ -7,7 +7,7 @@ import ( "time" "github.com/TwiN/gatus/v5/config" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" "github.com/TwiN/gatus/v5/storage/store" "github.com/TwiN/gatus/v5/watchdog" ) @@ -17,7 +17,7 @@ func TestResponseTimeChart(t *testing.T) { defer cache.Clear() cfg := &config.Config{ Metrics: true, - Endpoints: []*core.Endpoint{ + Endpoints: []*endpoint.Endpoint{ { Name: "frontend", Group: "core", @@ -28,8 +28,8 @@ func TestResponseTimeChart(t *testing.T) { }, }, } - watchdog.UpdateEndpointStatuses(cfg.Endpoints[0], &core.Result{Success: true, Duration: time.Millisecond, Timestamp: time.Now()}) - watchdog.UpdateEndpointStatuses(cfg.Endpoints[1], &core.Result{Success: false, Duration: time.Second, Timestamp: time.Now()}) + watchdog.UpdateEndpointStatuses(cfg.Endpoints[0], &endpoint.Result{Success: true, Duration: time.Millisecond, Timestamp: time.Now()}) + watchdog.UpdateEndpointStatuses(cfg.Endpoints[1], &endpoint.Result{Success: false, Duration: time.Second, Timestamp: time.Now()}) api := New(cfg) router := api.Router() type Scenario struct { diff --git a/api/endpoint_status.go b/api/endpoint_status.go index ac9e37b5..41627de2 100644 --- a/api/endpoint_status.go +++ b/api/endpoint_status.go @@ -9,8 +9,8 @@ import ( "github.com/TwiN/gatus/v5/client" "github.com/TwiN/gatus/v5/config" + "github.com/TwiN/gatus/v5/config/endpoint" "github.com/TwiN/gatus/v5/config/remote" - "github.com/TwiN/gatus/v5/core" "github.com/TwiN/gatus/v5/storage/store" "github.com/TwiN/gatus/v5/storage/store/common" "github.com/TwiN/gatus/v5/storage/store/common/paging" @@ -51,11 +51,11 @@ func EndpointStatuses(cfg *config.Config) fiber.Handler { } } -func getEndpointStatusesFromRemoteInstances(remoteConfig *remote.Config) ([]*core.EndpointStatus, error) { +func getEndpointStatusesFromRemoteInstances(remoteConfig *remote.Config) ([]*endpoint.Status, error) { if remoteConfig == nil || len(remoteConfig.Instances) == 0 { return nil, nil } - var endpointStatusesFromAllRemotes []*core.EndpointStatus + var endpointStatusesFromAllRemotes []*endpoint.Status httpClient := client.GetHTTPClient(remoteConfig.ClientConfig) for _, instance := range remoteConfig.Instances { response, err := httpClient.Get(instance.URL) @@ -68,7 +68,7 @@ func getEndpointStatusesFromRemoteInstances(remoteConfig *remote.Config) ([]*cor log.Printf("[api.getEndpointStatusesFromRemoteInstances] Silently failed to retrieve endpoint statuses from %s: %s", instance.URL, err.Error()) continue } - var endpointStatuses []*core.EndpointStatus + var endpointStatuses []*endpoint.Status if err = json.Unmarshal(body, &endpointStatuses); err != nil { _ = response.Body.Close() log.Printf("[api.getEndpointStatusesFromRemoteInstances] Silently failed to retrieve endpoint statuses from %s: %s", instance.URL, err.Error()) @@ -83,7 +83,7 @@ func getEndpointStatusesFromRemoteInstances(remoteConfig *remote.Config) ([]*cor return endpointStatusesFromAllRemotes, nil } -// EndpointStatus retrieves a single core.EndpointStatus by group and endpoint name +// EndpointStatus retrieves a single endpoint.Status by group and endpoint name func EndpointStatus(c *fiber.Ctx) error { page, pageSize := extractPageAndPageSizeFromRequest(c) endpointStatus, err := store.Get().GetEndpointStatusByKey(c.Params("key"), paging.NewEndpointStatusParams().WithResults(page, pageSize).WithEvents(1, common.MaximumNumberOfEvents)) diff --git a/api/endpoint_status_test.go b/api/endpoint_status_test.go index b88a915f..08725393 100644 --- a/api/endpoint_status_test.go +++ b/api/endpoint_status_test.go @@ -8,7 +8,7 @@ import ( "time" "github.com/TwiN/gatus/v5/config" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" "github.com/TwiN/gatus/v5/storage/store" "github.com/TwiN/gatus/v5/watchdog" ) @@ -16,19 +16,19 @@ import ( var ( timestamp = time.Now() - testEndpoint = core.Endpoint{ + testEndpoint = endpoint.Endpoint{ Name: "name", Group: "group", URL: "https://example.org/what/ever", Method: "GET", Body: "body", Interval: 30 * time.Second, - Conditions: []core.Condition{core.Condition("[STATUS] == 200"), core.Condition("[RESPONSE_TIME] < 500"), core.Condition("[CERTIFICATE_EXPIRATION] < 72h")}, + Conditions: []endpoint.Condition{endpoint.Condition("[STATUS] == 200"), endpoint.Condition("[RESPONSE_TIME] < 500"), endpoint.Condition("[CERTIFICATE_EXPIRATION] < 72h")}, Alerts: nil, NumberOfFailuresInARow: 0, NumberOfSuccessesInARow: 0, } - testSuccessfulResult = core.Result{ + testSuccessfulResult = endpoint.Result{ Hostname: "example.org", IP: "127.0.0.1", HTTPStatus: 200, @@ -38,7 +38,7 @@ var ( Timestamp: timestamp, Duration: 150 * time.Millisecond, CertificateExpiration: 10 * time.Hour, - ConditionResults: []*core.ConditionResult{ + ConditionResults: []*endpoint.ConditionResult{ { Condition: "[STATUS] == 200", Success: true, @@ -53,7 +53,7 @@ var ( }, }, } - testUnsuccessfulResult = core.Result{ + testUnsuccessfulResult = endpoint.Result{ Hostname: "example.org", IP: "127.0.0.1", HTTPStatus: 200, @@ -63,7 +63,7 @@ var ( Timestamp: timestamp, Duration: 750 * time.Millisecond, CertificateExpiration: 10 * time.Hour, - ConditionResults: []*core.ConditionResult{ + ConditionResults: []*endpoint.ConditionResult{ { Condition: "[STATUS] == 200", Success: true, @@ -85,7 +85,7 @@ func TestEndpointStatus(t *testing.T) { defer cache.Clear() cfg := &config.Config{ Metrics: true, - Endpoints: []*core.Endpoint{ + Endpoints: []*endpoint.Endpoint{ { Name: "frontend", Group: "core", @@ -96,8 +96,8 @@ func TestEndpointStatus(t *testing.T) { }, }, } - watchdog.UpdateEndpointStatuses(cfg.Endpoints[0], &core.Result{Success: true, Duration: time.Millisecond, Timestamp: time.Now()}) - watchdog.UpdateEndpointStatuses(cfg.Endpoints[1], &core.Result{Success: false, Duration: time.Second, Timestamp: time.Now()}) + watchdog.UpdateEndpointStatuses(cfg.Endpoints[0], &endpoint.Result{Success: true, Duration: time.Millisecond, Timestamp: time.Now()}) + watchdog.UpdateEndpointStatuses(cfg.Endpoints[1], &endpoint.Result{Success: false, Duration: time.Second, Timestamp: time.Now()}) api := New(cfg) router := api.Router() type Scenario struct { diff --git a/api/external_endpoint.go b/api/external_endpoint.go index 93ec5809..4874d805 100644 --- a/api/external_endpoint.go +++ b/api/external_endpoint.go @@ -7,7 +7,7 @@ import ( "time" "github.com/TwiN/gatus/v5/config" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" "github.com/TwiN/gatus/v5/storage/store" "github.com/TwiN/gatus/v5/storage/store/common" "github.com/TwiN/gatus/v5/watchdog" @@ -41,7 +41,7 @@ func CreateExternalEndpointResult(cfg *config.Config) fiber.Handler { return c.Status(401).SendString("invalid token") } // Persist the result in the storage - result := &core.Result{ + result := &endpoint.Result{ Timestamp: time.Now(), Success: c.QueryBool("success"), Errors: []string{}, diff --git a/api/external_endpoint_test.go b/api/external_endpoint_test.go index 72078cb7..60ebcd7b 100644 --- a/api/external_endpoint_test.go +++ b/api/external_endpoint_test.go @@ -9,8 +9,8 @@ import ( "github.com/TwiN/gatus/v5/alerting/alert" "github.com/TwiN/gatus/v5/alerting/provider/discord" "github.com/TwiN/gatus/v5/config" + "github.com/TwiN/gatus/v5/config/endpoint" "github.com/TwiN/gatus/v5/config/maintenance" - "github.com/TwiN/gatus/v5/core" "github.com/TwiN/gatus/v5/storage/store" "github.com/TwiN/gatus/v5/storage/store/common/paging" ) @@ -22,7 +22,7 @@ func TestCreateExternalEndpointResult(t *testing.T) { Alerting: &alerting.Config{ Discord: &discord.AlertProvider{}, }, - ExternalEndpoints: []*core.ExternalEndpoint{ + ExternalEndpoints: []*endpoint.ExternalEndpoint{ { Name: "n", Group: "g", diff --git a/api/spa_test.go b/api/spa_test.go index 250ecdde..11da2adf 100644 --- a/api/spa_test.go +++ b/api/spa_test.go @@ -9,8 +9,8 @@ import ( "time" "github.com/TwiN/gatus/v5/config" + "github.com/TwiN/gatus/v5/config/endpoint" "github.com/TwiN/gatus/v5/config/ui" - "github.com/TwiN/gatus/v5/core" "github.com/TwiN/gatus/v5/storage/store" "github.com/TwiN/gatus/v5/watchdog" ) @@ -20,7 +20,7 @@ func TestSinglePageApplication(t *testing.T) { defer cache.Clear() cfg := &config.Config{ Metrics: true, - Endpoints: []*core.Endpoint{ + Endpoints: []*endpoint.Endpoint{ { Name: "frontend", Group: "core", @@ -34,8 +34,8 @@ func TestSinglePageApplication(t *testing.T) { Title: "example-title", }, } - watchdog.UpdateEndpointStatuses(cfg.Endpoints[0], &core.Result{Success: true, Duration: time.Millisecond, Timestamp: time.Now()}) - watchdog.UpdateEndpointStatuses(cfg.Endpoints[1], &core.Result{Success: false, Duration: time.Second, Timestamp: time.Now()}) + watchdog.UpdateEndpointStatuses(cfg.Endpoints[0], &endpoint.Result{Success: true, Duration: time.Millisecond, Timestamp: time.Now()}) + watchdog.UpdateEndpointStatuses(cfg.Endpoints[1], &endpoint.Result{Success: false, Duration: time.Second, Timestamp: time.Now()}) api := New(cfg) router := api.Router() type Scenario struct { diff --git a/client/client.go b/client/client.go index 7a00a767..b63fb93b 100644 --- a/client/client.go +++ b/client/client.go @@ -16,11 +16,16 @@ import ( "github.com/TwiN/gocache/v2" "github.com/TwiN/whois" "github.com/ishidawataru/sctp" + "github.com/miekg/dns" ping "github.com/prometheus-community/pro-bing" "golang.org/x/crypto/ssh" "golang.org/x/net/websocket" ) +const ( + dnsPort = 53 +) + var ( // injectedHTTPClient is used for testing purposes injectedHTTPClient *http.Client @@ -291,6 +296,49 @@ func QueryWebSocket(address, body string, config *Config) (bool, []byte, error) return true, msg[:n], nil } +func QueryDNS(queryType, queryName, url string) (connected bool, dnsRcode string, body []byte, err error) { + if !strings.Contains(url, ":") { + url = fmt.Sprintf("%s:%d", url, dnsPort) + } + queryTypeAsUint16 := dns.StringToType[queryType] + c := new(dns.Client) + m := new(dns.Msg) + m.SetQuestion(queryName, queryTypeAsUint16) + r, _, err := c.Exchange(m, url) + if err != nil { + return false, "", nil, err + } + connected = true + dnsRcode = dns.RcodeToString[r.Rcode] + for _, rr := range r.Answer { + switch rr.Header().Rrtype { + case dns.TypeA: + if a, ok := rr.(*dns.A); ok { + body = []byte(a.A.String()) + } + case dns.TypeAAAA: + if aaaa, ok := rr.(*dns.AAAA); ok { + body = []byte(aaaa.AAAA.String()) + } + case dns.TypeCNAME: + if cname, ok := rr.(*dns.CNAME); ok { + body = []byte(cname.Target) + } + case dns.TypeMX: + if mx, ok := rr.(*dns.MX); ok { + body = []byte(mx.Mx) + } + case dns.TypeNS: + if ns, ok := rr.(*dns.NS); ok { + body = []byte(ns.Ns) + } + default: + body = []byte("query type is not supported yet") + } + } + return connected, dnsRcode, body, nil +} + // InjectHTTPClient is used to inject a custom HTTP client for testing purposes func InjectHTTPClient(httpClient *http.Client) { injectedHTTPClient = httpClient diff --git a/client/client_test.go b/client/client_test.go index 10cc9ca4..10392e78 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -8,6 +8,8 @@ import ( "testing" "time" + "github.com/TwiN/gatus/v5/config/endpoint/dns" + "github.com/TwiN/gatus/v5/pattern" "github.com/TwiN/gatus/v5/test" ) @@ -334,3 +336,97 @@ func TestTlsRenegotiation(t *testing.T) { }) } } + +func TestQueryDNS(t *testing.T) { + tests := []struct { + name string + inputDNS dns.Config + inputURL string + expectedDNSCode string + expectedBody string + isErrExpected bool + }{ + { + name: "test Config with type A", + inputDNS: dns.Config{ + QueryType: "A", + QueryName: "example.com.", + }, + inputURL: "8.8.8.8", + expectedDNSCode: "NOERROR", + expectedBody: "93.184.215.14", + }, + { + name: "test Config with type AAAA", + inputDNS: dns.Config{ + QueryType: "AAAA", + QueryName: "example.com.", + }, + inputURL: "8.8.8.8", + expectedDNSCode: "NOERROR", + expectedBody: "2606:2800:21f:cb07:6820:80da:af6b:8b2c", + }, + { + name: "test Config with type CNAME", + inputDNS: dns.Config{ + QueryType: "CNAME", + QueryName: "en.wikipedia.org.", + }, + inputURL: "8.8.8.8", + expectedDNSCode: "NOERROR", + expectedBody: "dyna.wikimedia.org.", + }, + { + name: "test Config with type MX", + inputDNS: dns.Config{ + QueryType: "MX", + QueryName: "example.com.", + }, + inputURL: "8.8.8.8", + expectedDNSCode: "NOERROR", + expectedBody: ".", + }, + { + name: "test Config with type NS", + inputDNS: dns.Config{ + QueryType: "NS", + QueryName: "example.com.", + }, + inputURL: "8.8.8.8", + expectedDNSCode: "NOERROR", + expectedBody: "*.iana-servers.net.", + }, + { + name: "test Config with fake type and retrieve error", + inputDNS: dns.Config{ + QueryType: "B", + QueryName: "example", + }, + inputURL: "8.8.8.8", + isErrExpected: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + _, dnsRCode, body, err := QueryDNS(test.inputDNS.QueryType, test.inputDNS.QueryName, test.inputURL) + if test.isErrExpected && err == nil { + t.Errorf("there should be an error") + } + if dnsRCode != test.expectedDNSCode { + t.Errorf("expected DNSRCode to be %s, got %s", test.expectedDNSCode, dnsRCode) + } + if test.inputDNS.QueryType == "NS" { + // Because there are often multiple nameservers backing a single domain, we'll only look at the suffix + if !pattern.Match(test.expectedBody, string(body)) { + t.Errorf("got %s, expected result %s,", string(body), test.expectedBody) + } + } else { + if string(body) != test.expectedBody { + t.Errorf("got %s, expected result %s,", string(body), test.expectedBody) + } + } + }) + time.Sleep(5 * time.Millisecond) + } +} diff --git a/config/config.go b/config/config.go index 667be593..14fde4b0 100644 --- a/config/config.go +++ b/config/config.go @@ -15,14 +15,13 @@ import ( "github.com/TwiN/gatus/v5/alerting/alert" "github.com/TwiN/gatus/v5/alerting/provider" "github.com/TwiN/gatus/v5/config/connectivity" + "github.com/TwiN/gatus/v5/config/endpoint" "github.com/TwiN/gatus/v5/config/maintenance" "github.com/TwiN/gatus/v5/config/remote" "github.com/TwiN/gatus/v5/config/ui" "github.com/TwiN/gatus/v5/config/web" - "github.com/TwiN/gatus/v5/core" "github.com/TwiN/gatus/v5/security" "github.com/TwiN/gatus/v5/storage" - "github.com/TwiN/gatus/v5/util" "gopkg.in/yaml.v3" ) @@ -74,10 +73,10 @@ type Config struct { Alerting *alerting.Config `yaml:"alerting,omitempty"` // Endpoints is the list of endpoints to monitor - Endpoints []*core.Endpoint `yaml:"endpoints,omitempty"` + Endpoints []*endpoint.Endpoint `yaml:"endpoints,omitempty"` // ExternalEndpoints is the list of all external endpoints - ExternalEndpoints []*core.ExternalEndpoint `yaml:"external-endpoints,omitempty"` + ExternalEndpoints []*endpoint.ExternalEndpoint `yaml:"external-endpoints,omitempty"` // Storage is the configuration for how the data is stored Storage *storage.Config `yaml:"storage,omitempty"` @@ -102,20 +101,20 @@ type Config struct { lastFileModTime time.Time // last modification time } -func (config *Config) GetEndpointByKey(key string) *core.Endpoint { +func (config *Config) GetEndpointByKey(key string) *endpoint.Endpoint { for i := 0; i < len(config.Endpoints); i++ { ep := config.Endpoints[i] - if util.ConvertGroupAndEndpointNameToKey(ep.Group, ep.Name) == key { + if ep.Key() == key { return ep } } return nil } -func (config *Config) GetExternalEndpointByKey(key string) *core.ExternalEndpoint { +func (config *Config) GetExternalEndpointByKey(key string) *endpoint.ExternalEndpoint { for i := 0; i < len(config.ExternalEndpoints); i++ { ee := config.ExternalEndpoints[i] - if util.ConvertGroupAndEndpointNameToKey(ee.Group, ee.Name) == key { + if ee.Key() == key { return ee } } @@ -339,12 +338,12 @@ func validateWebConfig(config *Config) error { } func validateEndpointsConfig(config *Config) error { - for _, endpoint := range config.Endpoints { + for _, ep := range config.Endpoints { if config.Debug { - log.Printf("[config.validateEndpointsConfig] Validating endpoint '%s'", endpoint.Name) + log.Printf("[config.validateEndpointsConfig] Validating endpoint '%s'", ep.Name) } - if err := endpoint.ValidateAndSetDefaults(); err != nil { - return fmt.Errorf("invalid endpoint %s: %w", endpoint.DisplayName(), err) + if err := ep.ValidateAndSetDefaults(); err != nil { + return fmt.Errorf("invalid endpoint %s: %w", ep.DisplayName(), err) } } log.Printf("[config.validateEndpointsConfig] Validated %d endpoints", len(config.Endpoints)) @@ -352,12 +351,12 @@ func validateEndpointsConfig(config *Config) error { } func validateExternalEndpointsConfig(config *Config) error { - for _, externalEndpoint := range config.ExternalEndpoints { + for _, ee := range config.ExternalEndpoints { if config.Debug { - log.Printf("[config.validateExternalEndpointsConfig] Validating external endpoint '%s'", externalEndpoint.Name) + log.Printf("[config.validateExternalEndpointsConfig] Validating external endpoint '%s'", ee.Name) } - if err := externalEndpoint.ValidateAndSetDefaults(); err != nil { - return fmt.Errorf("invalid external endpoint %s: %w", externalEndpoint.DisplayName(), err) + if err := ee.ValidateAndSetDefaults(); err != nil { + return fmt.Errorf("invalid external endpoint %s: %w", ee.DisplayName(), err) } } log.Printf("[config.validateExternalEndpointsConfig] Validated %d external endpoints", len(config.ExternalEndpoints)) @@ -381,9 +380,9 @@ func validateSecurityConfig(config *Config) error { // validateAlertingConfig validates the alerting configuration // Note that the alerting configuration has to be validated before the endpoint configuration, because the default alert -// returned by provider.AlertProvider.GetDefaultAlert() must be parsed before core.Endpoint.ValidateAndSetDefaults() +// returned by provider.AlertProvider.GetDefaultAlert() must be parsed before endpoint.Endpoint.ValidateAndSetDefaults() // sets the default alert values when none are set. -func validateAlertingConfig(alertingConfig *alerting.Config, endpoints []*core.Endpoint, debug bool) { +func validateAlertingConfig(alertingConfig *alerting.Config, endpoints []*endpoint.Endpoint, debug bool) { if alertingConfig == nil { log.Printf("[config.validateAlertingConfig] Alerting is not configured") return @@ -417,11 +416,11 @@ func validateAlertingConfig(alertingConfig *alerting.Config, endpoints []*core.E if alertProvider.IsValid() { // Parse alerts with the provider's default alert if alertProvider.GetDefaultAlert() != nil { - for _, endpoint := range endpoints { - for alertIndex, endpointAlert := range endpoint.Alerts { + for _, ep := range endpoints { + for alertIndex, endpointAlert := range ep.Alerts { if alertType == endpointAlert.Type { if debug { - log.Printf("[config.validateAlertingConfig] Parsing alert %d with provider's default alert for provider=%s in endpoint=%s", alertIndex, alertType, endpoint.Name) + log.Printf("[config.validateAlertingConfig] Parsing alert %d with provider's default alert for provider=%s in endpoint=%s", alertIndex, alertType, ep.Name) } provider.ParseWithDefaultAlert(alertProvider.GetDefaultAlert(), endpointAlert) } diff --git a/config/config_test.go b/config/config_test.go index 2f076936..f94b170e 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -29,8 +29,8 @@ import ( "github.com/TwiN/gatus/v5/alerting/provider/telegram" "github.com/TwiN/gatus/v5/alerting/provider/twilio" "github.com/TwiN/gatus/v5/client" + "github.com/TwiN/gatus/v5/config/endpoint" "github.com/TwiN/gatus/v5/config/web" - "github.com/TwiN/gatus/v5/core" "github.com/TwiN/gatus/v5/storage" "gopkg.in/yaml.v3" ) @@ -65,7 +65,7 @@ func TestLoadConfiguration(t *testing.T) { endpoints: - name: website`, }, - expectedError: core.ErrEndpointWithNoURL, + expectedError: endpoint.ErrEndpointWithNoURL, }, { name: "config-file-with-endpoint-that-has-no-conditions", @@ -76,7 +76,7 @@ endpoints: - name: website url: https://twin.sh/health`, }, - expectedError: core.ErrEndpointWithNoCondition, + expectedError: endpoint.ErrEndpointWithNoCondition, }, { name: "config-file", @@ -90,11 +90,11 @@ endpoints: - "[STATUS] == 200"`, }, expectedConfig: &Config{ - Endpoints: []*core.Endpoint{ + Endpoints: []*endpoint.Endpoint{ { Name: "website", URL: "https://twin.sh/health", - Conditions: []core.Condition{"[STATUS] == 200"}, + Conditions: []endpoint.Condition{"[STATUS] == 200"}, }, }, }, @@ -136,21 +136,21 @@ endpoints: - "[BODY].status == UP"`, }, expectedConfig: &Config{ - Endpoints: []*core.Endpoint{ + Endpoints: []*endpoint.Endpoint{ { Name: "one", URL: "https://example.com", - Conditions: []core.Condition{"[CONNECTED] == true", "[STATUS] == 200"}, + Conditions: []endpoint.Condition{"[CONNECTED] == true", "[STATUS] == 200"}, }, { Name: "two", URL: "https://example.org", - Conditions: []core.Condition{"len([BODY]) > 0"}, + Conditions: []endpoint.Condition{"len([BODY]) > 0"}, }, { Name: "three", URL: "https://twin.sh/health", - Conditions: []core.Condition{"[STATUS] == 200", "[BODY].status == UP"}, + Conditions: []endpoint.Condition{"[STATUS] == 200", "[BODY].status == UP"}, }, }, }, @@ -192,17 +192,17 @@ endpoints: Discord: &discord.AlertProvider{WebhookURL: "https://discord.com/api/webhooks/xxx/yyy"}, Slack: &slack.AlertProvider{WebhookURL: "https://hooks.slack.com/services/xxx/yyy/zzz"}, }, - Endpoints: []*core.Endpoint{ + Endpoints: []*endpoint.Endpoint{ { Name: "example", URL: "https://example.org", Interval: 5 * time.Second, - Conditions: []core.Condition{"[STATUS] == 200"}, + Conditions: []endpoint.Condition{"[STATUS] == 200"}, }, { Name: "frontend", URL: "https://example.com", - Conditions: []core.Condition{"[STATUS] == 200"}, + Conditions: []endpoint.Condition{"[STATUS] == 200"}, }, }, }, @@ -689,8 +689,8 @@ endpoints: if config.Endpoints[0].Interval != 60*time.Second { t.Errorf("Interval should have been %s, because it is the default value", 60*time.Second) } - if userAgent := config.Endpoints[0].Headers["User-Agent"]; userAgent != core.GatusUserAgent { - t.Errorf("User-Agent should've been %s because it's the default value, got %s", core.GatusUserAgent, userAgent) + if userAgent := config.Endpoints[0].Headers["User-Agent"]; userAgent != endpoint.GatusUserAgent { + t.Errorf("User-Agent should've been %s because it's the default value, got %s", endpoint.GatusUserAgent, userAgent) } } diff --git a/core/endpoint_common.go b/config/endpoint/common.go similarity index 98% rename from core/endpoint_common.go rename to config/endpoint/common.go index f99e5501..f3da8bf8 100644 --- a/core/endpoint_common.go +++ b/config/endpoint/common.go @@ -1,4 +1,4 @@ -package core +package endpoint import ( "errors" diff --git a/core/endpoint_common_test.go b/config/endpoint/common_test.go similarity index 98% rename from core/endpoint_common_test.go rename to config/endpoint/common_test.go index e1c63178..ab4fc9ef 100644 --- a/core/endpoint_common_test.go +++ b/config/endpoint/common_test.go @@ -1,4 +1,4 @@ -package core +package endpoint import ( "errors" diff --git a/core/condition.go b/config/endpoint/condition.go similarity index 99% rename from core/condition.go rename to config/endpoint/condition.go index 42f70d92..6d36babd 100644 --- a/core/condition.go +++ b/config/endpoint/condition.go @@ -1,4 +1,4 @@ -package core +package endpoint import ( "errors" diff --git a/core/condition_bench_test.go b/config/endpoint/condition_bench_test.go similarity index 98% rename from core/condition_bench_test.go rename to config/endpoint/condition_bench_test.go index 0126871f..82061f69 100644 --- a/core/condition_bench_test.go +++ b/config/endpoint/condition_bench_test.go @@ -1,6 +1,8 @@ -package core +package endpoint -import "testing" +import ( + "testing" +) func BenchmarkCondition_evaluateWithBodyStringAny(b *testing.B) { condition := Condition("[BODY].name == any(john.doe, jane.doe)") diff --git a/core/condition_result.go b/config/endpoint/condition_result.go similarity index 93% rename from core/condition_result.go rename to config/endpoint/condition_result.go index d8bdc1e9..00af8620 100644 --- a/core/condition_result.go +++ b/config/endpoint/condition_result.go @@ -1,4 +1,4 @@ -package core +package endpoint // ConditionResult result of a Condition type ConditionResult struct { diff --git a/core/condition_test.go b/config/endpoint/condition_test.go similarity index 99% rename from core/condition_test.go rename to config/endpoint/condition_test.go index ef2e79ed..3e98912f 100644 --- a/core/condition_test.go +++ b/config/endpoint/condition_test.go @@ -1,4 +1,4 @@ -package core +package endpoint import ( "errors" diff --git a/config/endpoint/dns/dns.go b/config/endpoint/dns/dns.go new file mode 100644 index 00000000..47a29bdc --- /dev/null +++ b/config/endpoint/dns/dns.go @@ -0,0 +1,38 @@ +package dns + +import ( + "errors" + "strings" + + "github.com/miekg/dns" +) + +var ( + // ErrDNSWithNoQueryName is the error with which gatus will panic if a dns is configured without query name + ErrDNSWithNoQueryName = errors.New("you must specify a query name in the DNS configuration") + + // ErrDNSWithInvalidQueryType is the error with which gatus will panic if a dns is configured with invalid query type + ErrDNSWithInvalidQueryType = errors.New("invalid query type in the DNS configuration") +) + +// Config for an Endpoint of type DNS +type Config struct { + // QueryType is the type for the DNS records like A, AAAA, CNAME... + QueryType string `yaml:"query-type"` + + // QueryName is the query for DNS + QueryName string `yaml:"query-name"` +} + +func (d *Config) ValidateAndSetDefault() error { + if len(d.QueryName) == 0 { + return ErrDNSWithNoQueryName + } + if !strings.HasSuffix(d.QueryName, ".") { + d.QueryName += "." + } + if _, ok := dns.StringToType[d.QueryType]; !ok { + return ErrDNSWithInvalidQueryType + } + return nil +} diff --git a/config/endpoint/dns/dns_test.go b/config/endpoint/dns/dns_test.go new file mode 100644 index 00000000..57e96296 --- /dev/null +++ b/config/endpoint/dns/dns_test.go @@ -0,0 +1,27 @@ +package dns + +import ( + "testing" +) + +func TestConfig_ValidateAndSetDefault(t *testing.T) { + dns := &Config{ + QueryType: "A", + QueryName: "", + } + err := dns.ValidateAndSetDefault() + if err == nil { + t.Error("Should've returned an error because endpoint's dns didn't have a query name, which is a mandatory field for dns") + } +} + +func TestConfig_ValidateAndSetDefaultsWithInvalidDNSQueryType(t *testing.T) { + dns := &Config{ + QueryType: "B", + QueryName: "example.com", + } + err := dns.ValidateAndSetDefault() + if err == nil { + t.Error("Should've returned an error because endpoint's dns query type is invalid, it needs to be a valid query name like A, AAAA, CNAME...") + } +} diff --git a/core/endpoint.go b/config/endpoint/endpoint.go similarity index 58% rename from core/endpoint.go rename to config/endpoint/endpoint.go index 749b1d3d..ac765c1a 100644 --- a/core/endpoint.go +++ b/config/endpoint/endpoint.go @@ -1,4 +1,4 @@ -package core +package endpoint import ( "bytes" @@ -15,12 +15,13 @@ import ( "github.com/TwiN/gatus/v5/alerting/alert" "github.com/TwiN/gatus/v5/client" - "github.com/TwiN/gatus/v5/core/ui" - "github.com/TwiN/gatus/v5/util" + "github.com/TwiN/gatus/v5/config/endpoint/dns" + sshconfig "github.com/TwiN/gatus/v5/config/endpoint/ssh" + "github.com/TwiN/gatus/v5/config/endpoint/ui" "golang.org/x/crypto/ssh" ) -type EndpointType string +type Type string const ( // HostHeader is the name of the header used to specify the host @@ -35,17 +36,17 @@ const ( // GatusUserAgent is the default user agent that Gatus uses to send requests. GatusUserAgent = "Gatus/1.0" - EndpointTypeDNS EndpointType = "DNS" - EndpointTypeTCP EndpointType = "TCP" - EndpointTypeSCTP EndpointType = "SCTP" - EndpointTypeUDP EndpointType = "UDP" - EndpointTypeICMP EndpointType = "ICMP" - EndpointTypeSTARTTLS EndpointType = "STARTTLS" - EndpointTypeTLS EndpointType = "TLS" - EndpointTypeHTTP EndpointType = "HTTP" - EndpointTypeWS EndpointType = "WEBSOCKET" - EndpointTypeSSH EndpointType = "SSH" - EndpointTypeUNKNOWN EndpointType = "UNKNOWN" + TypeDNS Type = "DNS" + TypeTCP Type = "TCP" + TypeSCTP Type = "SCTP" + TypeUDP Type = "UDP" + TypeICMP Type = "ICMP" + TypeSTARTTLS Type = "STARTTLS" + TypeTLS Type = "TLS" + TypeHTTP Type = "HTTP" + TypeWS Type = "WEBSOCKET" + TypeSSH Type = "SSH" + TypeUNKNOWN Type = "UNKNOWN" ) var ( @@ -82,12 +83,6 @@ type Endpoint struct { // URL to send the request to URL string `yaml:"url"` - // DNS is the configuration of DNS monitoring - DNS *DNS `yaml:"dns,omitempty"` - - // SSH is the configuration of SSH monitoring. - SSH *SSH `yaml:"ssh,omitempty"` - // Method of the request made to the url of the endpoint Method string `yaml:"method,omitempty"` @@ -109,6 +104,12 @@ type Endpoint struct { // Alerts is the alerting configuration for the endpoint in case of failure Alerts []*alert.Alert `yaml:"alerts,omitempty"` + // DNSConfig is the configuration for DNS monitoring + DNSConfig *dns.Config `yaml:"dns,omitempty"` + + // SSH is the configuration for SSH monitoring + SSHConfig *sshconfig.Config `yaml:"ssh,omitempty"` + // ClientConfig is the configuration of the client used to communicate with the endpoint's target ClientConfig *client.Config `yaml:"client,omitempty"` @@ -123,103 +124,103 @@ type Endpoint struct { } // IsEnabled returns whether the endpoint is enabled or not -func (endpoint *Endpoint) IsEnabled() bool { - if endpoint.Enabled == nil { +func (e *Endpoint) IsEnabled() bool { + if e.Enabled == nil { return true } - return *endpoint.Enabled + return *e.Enabled } // Type returns the endpoint type -func (endpoint *Endpoint) Type() EndpointType { +func (e *Endpoint) Type() Type { switch { - case endpoint.DNS != nil: - return EndpointTypeDNS - case strings.HasPrefix(endpoint.URL, "tcp://"): - return EndpointTypeTCP - case strings.HasPrefix(endpoint.URL, "sctp://"): - return EndpointTypeSCTP - case strings.HasPrefix(endpoint.URL, "udp://"): - return EndpointTypeUDP - case strings.HasPrefix(endpoint.URL, "icmp://"): - return EndpointTypeICMP - case strings.HasPrefix(endpoint.URL, "starttls://"): - return EndpointTypeSTARTTLS - case strings.HasPrefix(endpoint.URL, "tls://"): - return EndpointTypeTLS - case strings.HasPrefix(endpoint.URL, "http://") || strings.HasPrefix(endpoint.URL, "https://"): - return EndpointTypeHTTP - case strings.HasPrefix(endpoint.URL, "ws://") || strings.HasPrefix(endpoint.URL, "wss://"): - return EndpointTypeWS - case strings.HasPrefix(endpoint.URL, "ssh://"): - return EndpointTypeSSH + case e.DNSConfig != nil: + return TypeDNS + case strings.HasPrefix(e.URL, "tcp://"): + return TypeTCP + case strings.HasPrefix(e.URL, "sctp://"): + return TypeSCTP + case strings.HasPrefix(e.URL, "udp://"): + return TypeUDP + case strings.HasPrefix(e.URL, "icmp://"): + return TypeICMP + case strings.HasPrefix(e.URL, "starttls://"): + return TypeSTARTTLS + case strings.HasPrefix(e.URL, "tls://"): + return TypeTLS + case strings.HasPrefix(e.URL, "http://") || strings.HasPrefix(e.URL, "https://"): + return TypeHTTP + case strings.HasPrefix(e.URL, "ws://") || strings.HasPrefix(e.URL, "wss://"): + return TypeWS + case strings.HasPrefix(e.URL, "ssh://"): + return TypeSSH default: - return EndpointTypeUNKNOWN + return TypeUNKNOWN } } // ValidateAndSetDefaults validates the endpoint's configuration and sets the default value of args that have one -func (endpoint *Endpoint) ValidateAndSetDefaults() error { - if err := validateEndpointNameGroupAndAlerts(endpoint.Name, endpoint.Group, endpoint.Alerts); err != nil { +func (e *Endpoint) ValidateAndSetDefaults() error { + if err := validateEndpointNameGroupAndAlerts(e.Name, e.Group, e.Alerts); err != nil { return err } - if len(endpoint.URL) == 0 { + if len(e.URL) == 0 { return ErrEndpointWithNoURL } - if endpoint.ClientConfig == nil { - endpoint.ClientConfig = client.GetDefaultConfig() + if e.ClientConfig == nil { + e.ClientConfig = client.GetDefaultConfig() } else { - if err := endpoint.ClientConfig.ValidateAndSetDefaults(); err != nil { + if err := e.ClientConfig.ValidateAndSetDefaults(); err != nil { return err } } - if endpoint.UIConfig == nil { - endpoint.UIConfig = ui.GetDefaultConfig() + if e.UIConfig == nil { + e.UIConfig = ui.GetDefaultConfig() } else { - if err := endpoint.UIConfig.ValidateAndSetDefaults(); err != nil { + if err := e.UIConfig.ValidateAndSetDefaults(); err != nil { return err } } - if endpoint.Interval == 0 { - endpoint.Interval = 1 * time.Minute + if e.Interval == 0 { + e.Interval = 1 * time.Minute } - if len(endpoint.Method) == 0 { - endpoint.Method = http.MethodGet + if len(e.Method) == 0 { + e.Method = http.MethodGet } - if len(endpoint.Headers) == 0 { - endpoint.Headers = make(map[string]string) + if len(e.Headers) == 0 { + e.Headers = make(map[string]string) } // Automatically add user agent header if there isn't one specified in the endpoint configuration - if _, userAgentHeaderExists := endpoint.Headers[UserAgentHeader]; !userAgentHeaderExists { - endpoint.Headers[UserAgentHeader] = GatusUserAgent + if _, userAgentHeaderExists := e.Headers[UserAgentHeader]; !userAgentHeaderExists { + e.Headers[UserAgentHeader] = GatusUserAgent } // Automatically add "Content-Type: application/json" header if there's no Content-Type set // and endpoint.GraphQL is set to true - if _, contentTypeHeaderExists := endpoint.Headers[ContentTypeHeader]; !contentTypeHeaderExists && endpoint.GraphQL { - endpoint.Headers[ContentTypeHeader] = "application/json" + if _, contentTypeHeaderExists := e.Headers[ContentTypeHeader]; !contentTypeHeaderExists && e.GraphQL { + e.Headers[ContentTypeHeader] = "application/json" } - if len(endpoint.Conditions) == 0 { + if len(e.Conditions) == 0 { return ErrEndpointWithNoCondition } - for _, c := range endpoint.Conditions { - if endpoint.Interval < 5*time.Minute && c.hasDomainExpirationPlaceholder() { + for _, c := range e.Conditions { + if e.Interval < 5*time.Minute && c.hasDomainExpirationPlaceholder() { return ErrInvalidEndpointIntervalForDomainExpirationPlaceholder } if err := c.Validate(); err != nil { return fmt.Errorf("%v: %w", ErrInvalidConditionFormat, err) } } - if endpoint.DNS != nil { - return endpoint.DNS.validateAndSetDefault() + if e.DNSConfig != nil { + return e.DNSConfig.ValidateAndSetDefault() } - if endpoint.SSH != nil { - return endpoint.SSH.validate() + if e.SSHConfig != nil { + return e.SSHConfig.Validate() } - if endpoint.Type() == EndpointTypeUNKNOWN { + if e.Type() == TypeUNKNOWN { return ErrUnknownEndpointType } // Make sure that the request can be created - _, err := http.NewRequest(endpoint.Method, endpoint.URL, bytes.NewBuffer([]byte(endpoint.Body))) + _, err := http.NewRequest(e.Method, e.URL, bytes.NewBuffer([]byte(e.Body))) if err != nil { return err } @@ -227,35 +228,35 @@ func (endpoint *Endpoint) ValidateAndSetDefaults() error { } // DisplayName returns an identifier made up of the Name and, if not empty, the Group. -func (endpoint *Endpoint) DisplayName() string { - if len(endpoint.Group) > 0 { - return endpoint.Group + "/" + endpoint.Name +func (e *Endpoint) DisplayName() string { + if len(e.Group) > 0 { + return e.Group + "/" + e.Name } - return endpoint.Name + return e.Name } // Key returns the unique key for the Endpoint -func (endpoint *Endpoint) Key() string { - return util.ConvertGroupAndEndpointNameToKey(endpoint.Group, endpoint.Name) +func (e *Endpoint) Key() string { + return ConvertGroupAndEndpointNameToKey(e.Group, e.Name) } // Close HTTP connections between watchdog and endpoints to avoid dangling socket file descriptors // on configuration reload. // More context on https://github.com/TwiN/gatus/issues/536 -func (endpoint *Endpoint) Close() { - if endpoint.Type() == EndpointTypeHTTP { - client.GetHTTPClient(endpoint.ClientConfig).CloseIdleConnections() +func (e *Endpoint) Close() { + if e.Type() == TypeHTTP { + client.GetHTTPClient(e.ClientConfig).CloseIdleConnections() } } // EvaluateHealth sends a request to the endpoint's URL and evaluates the conditions of the endpoint. -func (endpoint *Endpoint) EvaluateHealth() *Result { +func (e *Endpoint) EvaluateHealth() *Result { result := &Result{Success: true, Errors: []string{}} // Parse or extract hostname from URL - if endpoint.DNS != nil { - result.Hostname = strings.TrimSuffix(endpoint.URL, ":53") + if e.DNSConfig != nil { + result.Hostname = strings.TrimSuffix(e.URL, ":53") } else { - urlObject, err := url.Parse(endpoint.URL) + urlObject, err := url.Parse(e.URL) if err != nil { result.AddError(err.Error()) } else { @@ -263,11 +264,11 @@ func (endpoint *Endpoint) EvaluateHealth() *Result { } } // Retrieve IP if necessary - if endpoint.needsToRetrieveIP() { - endpoint.getIP(result) + if e.needsToRetrieveIP() { + e.getIP(result) } // Retrieve domain expiration if necessary - if endpoint.needsToRetrieveDomainExpiration() && len(result.Hostname) > 0 { + if e.needsToRetrieveDomainExpiration() && len(result.Hostname) > 0 { var err error if result.DomainExpiration, err = client.GetDomainExpiration(result.Hostname); err != nil { result.AddError(err.Error()) @@ -275,37 +276,37 @@ func (endpoint *Endpoint) EvaluateHealth() *Result { } // Call the endpoint (if there's no errors) if len(result.Errors) == 0 { - endpoint.call(result) + e.call(result) } else { result.Success = false } // Evaluate the conditions - for _, condition := range endpoint.Conditions { - success := condition.evaluate(result, endpoint.UIConfig.DontResolveFailedConditions) + for _, condition := range e.Conditions { + success := condition.evaluate(result, e.UIConfig.DontResolveFailedConditions) if !success { result.Success = false } } result.Timestamp = time.Now() // Clean up parameters that we don't need to keep in the results - if endpoint.UIConfig.HideURL { + if e.UIConfig.HideURL { for errIdx, errorString := range result.Errors { - result.Errors[errIdx] = strings.ReplaceAll(errorString, endpoint.URL, "") + result.Errors[errIdx] = strings.ReplaceAll(errorString, e.URL, "") } } - if endpoint.UIConfig.HideHostname { + if e.UIConfig.HideHostname { for errIdx, errorString := range result.Errors { result.Errors[errIdx] = strings.ReplaceAll(errorString, result.Hostname, "") } result.Hostname = "" } - if endpoint.UIConfig.HideConditions { + if e.UIConfig.HideConditions { result.ConditionResults = nil } return result } -func (endpoint *Endpoint) getIP(result *Result) { +func (e *Endpoint) getIP(result *Result) { if ips, err := net.LookupIP(result.Hostname); err != nil { result.AddError(err.Error()) return @@ -314,24 +315,28 @@ func (endpoint *Endpoint) getIP(result *Result) { } } -func (endpoint *Endpoint) call(result *Result) { +func (e *Endpoint) call(result *Result) { var request *http.Request var response *http.Response var err error var certificate *x509.Certificate - endpointType := endpoint.Type() - if endpointType == EndpointTypeHTTP { - request = endpoint.buildHTTPRequest() + endpointType := e.Type() + if endpointType == TypeHTTP { + request = e.buildHTTPRequest() } startTime := time.Now() - if endpointType == EndpointTypeDNS { - endpoint.DNS.query(endpoint.URL, result) + if endpointType == TypeDNS { + result.Connected, result.DNSRCode, result.Body, err = client.QueryDNS(e.DNSConfig.QueryType, e.DNSConfig.QueryName, e.URL) + if err != nil { + result.AddError(err.Error()) + return + } result.Duration = time.Since(startTime) - } else if endpointType == EndpointTypeSTARTTLS || endpointType == EndpointTypeTLS { - if endpointType == EndpointTypeSTARTTLS { - result.Connected, certificate, err = client.CanPerformStartTLS(strings.TrimPrefix(endpoint.URL, "starttls://"), endpoint.ClientConfig) + } else if endpointType == TypeSTARTTLS || endpointType == TypeTLS { + if endpointType == TypeSTARTTLS { + result.Connected, certificate, err = client.CanPerformStartTLS(strings.TrimPrefix(e.URL, "starttls://"), e.ClientConfig) } else { - result.Connected, certificate, err = client.CanPerformTLS(strings.TrimPrefix(endpoint.URL, "tls://"), endpoint.ClientConfig) + result.Connected, certificate, err = client.CanPerformTLS(strings.TrimPrefix(e.URL, "tls://"), e.ClientConfig) } if err != nil { result.AddError(err.Error()) @@ -339,39 +344,39 @@ func (endpoint *Endpoint) call(result *Result) { } result.Duration = time.Since(startTime) result.CertificateExpiration = time.Until(certificate.NotAfter) - } else if endpointType == EndpointTypeTCP { - result.Connected = client.CanCreateTCPConnection(strings.TrimPrefix(endpoint.URL, "tcp://"), endpoint.ClientConfig) + } else if endpointType == TypeTCP { + result.Connected = client.CanCreateTCPConnection(strings.TrimPrefix(e.URL, "tcp://"), e.ClientConfig) result.Duration = time.Since(startTime) - } else if endpointType == EndpointTypeUDP { - result.Connected = client.CanCreateUDPConnection(strings.TrimPrefix(endpoint.URL, "udp://"), endpoint.ClientConfig) + } else if endpointType == TypeUDP { + result.Connected = client.CanCreateUDPConnection(strings.TrimPrefix(e.URL, "udp://"), e.ClientConfig) result.Duration = time.Since(startTime) - } else if endpointType == EndpointTypeSCTP { - result.Connected = client.CanCreateSCTPConnection(strings.TrimPrefix(endpoint.URL, "sctp://"), endpoint.ClientConfig) + } else if endpointType == TypeSCTP { + result.Connected = client.CanCreateSCTPConnection(strings.TrimPrefix(e.URL, "sctp://"), e.ClientConfig) result.Duration = time.Since(startTime) - } else if endpointType == EndpointTypeICMP { - result.Connected, result.Duration = client.Ping(strings.TrimPrefix(endpoint.URL, "icmp://"), endpoint.ClientConfig) - } else if endpointType == EndpointTypeWS { - result.Connected, result.Body, err = client.QueryWebSocket(endpoint.URL, endpoint.Body, endpoint.ClientConfig) + } else if endpointType == TypeICMP { + result.Connected, result.Duration = client.Ping(strings.TrimPrefix(e.URL, "icmp://"), e.ClientConfig) + } else if endpointType == TypeWS { + result.Connected, result.Body, err = client.QueryWebSocket(e.URL, e.Body, e.ClientConfig) if err != nil { result.AddError(err.Error()) return } result.Duration = time.Since(startTime) - } else if endpointType == EndpointTypeSSH { + } else if endpointType == TypeSSH { var cli *ssh.Client - result.Connected, cli, err = client.CanCreateSSHConnection(strings.TrimPrefix(endpoint.URL, "ssh://"), endpoint.SSH.Username, endpoint.SSH.Password, endpoint.ClientConfig) + result.Connected, cli, err = client.CanCreateSSHConnection(strings.TrimPrefix(e.URL, "ssh://"), e.SSHConfig.Username, e.SSHConfig.Password, e.ClientConfig) if err != nil { result.AddError(err.Error()) return } - result.Success, result.HTTPStatus, err = client.ExecuteSSHCommand(cli, endpoint.Body, endpoint.ClientConfig) + result.Success, result.HTTPStatus, err = client.ExecuteSSHCommand(cli, e.Body, e.ClientConfig) if err != nil { result.AddError(err.Error()) return } result.Duration = time.Since(startTime) } else { - response, err = client.GetHTTPClient(endpoint.ClientConfig).Do(request) + response, err = client.GetHTTPClient(e.ClientConfig).Do(request) result.Duration = time.Since(startTime) if err != nil { result.AddError(err.Error()) @@ -385,7 +390,7 @@ func (endpoint *Endpoint) call(result *Result) { result.HTTPStatus = response.StatusCode result.Connected = response.StatusCode > 0 // Only read the Body if there's a condition that uses the BodyPlaceholder - if endpoint.needsToReadBody() { + if e.needsToReadBody() { result.Body, err = io.ReadAll(response.Body) if err != nil { result.AddError("error reading response body:" + err.Error()) @@ -394,19 +399,19 @@ func (endpoint *Endpoint) call(result *Result) { } } -func (endpoint *Endpoint) buildHTTPRequest() *http.Request { +func (e *Endpoint) buildHTTPRequest() *http.Request { var bodyBuffer *bytes.Buffer - if endpoint.GraphQL { + if e.GraphQL { graphQlBody := map[string]string{ - "query": endpoint.Body, + "query": e.Body, } body, _ := json.Marshal(graphQlBody) bodyBuffer = bytes.NewBuffer(body) } else { - bodyBuffer = bytes.NewBuffer([]byte(endpoint.Body)) + bodyBuffer = bytes.NewBuffer([]byte(e.Body)) } - request, _ := http.NewRequest(endpoint.Method, endpoint.URL, bodyBuffer) - for k, v := range endpoint.Headers { + request, _ := http.NewRequest(e.Method, e.URL, bodyBuffer) + for k, v := range e.Headers { request.Header.Set(k, v) if k == HostHeader { request.Host = v @@ -416,8 +421,8 @@ func (endpoint *Endpoint) buildHTTPRequest() *http.Request { } // needsToReadBody checks if there's any condition that requires the response Body to be read -func (endpoint *Endpoint) needsToReadBody() bool { - for _, condition := range endpoint.Conditions { +func (e *Endpoint) needsToReadBody() bool { + for _, condition := range e.Conditions { if condition.hasBodyPlaceholder() { return true } @@ -426,8 +431,8 @@ func (endpoint *Endpoint) needsToReadBody() bool { } // needsToRetrieveDomainExpiration checks if there's any condition that requires a whois query to be performed -func (endpoint *Endpoint) needsToRetrieveDomainExpiration() bool { - for _, condition := range endpoint.Conditions { +func (e *Endpoint) needsToRetrieveDomainExpiration() bool { + for _, condition := range e.Conditions { if condition.hasDomainExpirationPlaceholder() { return true } @@ -436,8 +441,8 @@ func (endpoint *Endpoint) needsToRetrieveDomainExpiration() bool { } // needsToRetrieveIP checks if there's any condition that requires an IP lookup -func (endpoint *Endpoint) needsToRetrieveIP() bool { - for _, condition := range endpoint.Conditions { +func (e *Endpoint) needsToRetrieveIP() bool { + for _, condition := range e.Conditions { if condition.hasIPPlaceholder() { return true } diff --git a/core/endpoint_test.go b/config/endpoint/endpoint_test.go similarity index 95% rename from core/endpoint_test.go rename to config/endpoint/endpoint_test.go index 18f5be69..c2d434b0 100644 --- a/core/endpoint_test.go +++ b/config/endpoint/endpoint_test.go @@ -1,4 +1,4 @@ -package core +package endpoint import ( "bytes" @@ -13,7 +13,9 @@ import ( "github.com/TwiN/gatus/v5/alerting/alert" "github.com/TwiN/gatus/v5/client" - "github.com/TwiN/gatus/v5/core/ui" + "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/test" ) @@ -279,105 +281,105 @@ func TestEndpoint_IsEnabled(t *testing.T) { func TestEndpoint_Type(t *testing.T) { type args struct { URL string - DNS *DNS - SSH *SSH + DNS *dns.Config + SSH *ssh.Config } tests := []struct { args args - want EndpointType + want Type }{ { args: args{ URL: "8.8.8.8", - DNS: &DNS{ + DNS: &dns.Config{ QueryType: "A", QueryName: "example.com", }, }, - want: EndpointTypeDNS, + want: TypeDNS, }, { args: args{ URL: "tcp://127.0.0.1:6379", }, - want: EndpointTypeTCP, + want: TypeTCP, }, { args: args{ URL: "icmp://example.com", }, - want: EndpointTypeICMP, + want: TypeICMP, }, { args: args{ URL: "sctp://example.com", }, - want: EndpointTypeSCTP, + want: TypeSCTP, }, { args: args{ URL: "udp://example.com", }, - want: EndpointTypeUDP, + want: TypeUDP, }, { args: args{ URL: "starttls://smtp.gmail.com:587", }, - want: EndpointTypeSTARTTLS, + want: TypeSTARTTLS, }, { args: args{ URL: "tls://example.com:443", }, - want: EndpointTypeTLS, + want: TypeTLS, }, { args: args{ URL: "https://twin.sh/health", }, - want: EndpointTypeHTTP, + want: TypeHTTP, }, { args: args{ URL: "wss://example.com/", }, - want: EndpointTypeWS, + want: TypeWS, }, { args: args{ URL: "ws://example.com/", }, - want: EndpointTypeWS, + want: TypeWS, }, { args: args{ URL: "ssh://example.com:22", - SSH: &SSH{ + SSH: &ssh.Config{ Username: "root", Password: "password", }, }, - want: EndpointTypeSSH, + want: TypeSSH, }, { args: args{ URL: "invalid://example.org", }, - want: EndpointTypeUNKNOWN, + want: TypeUNKNOWN, }, { args: args{ URL: "no-scheme", }, - want: EndpointTypeUNKNOWN, + want: TypeUNKNOWN, }, } for _, tt := range tests { t.Run(string(tt.want), func(t *testing.T) { endpoint := Endpoint{ - URL: tt.args.URL, - DNS: tt.args.DNS, + URL: tt.args.URL, + DNSConfig: tt.args.DNS, } if got := endpoint.Type(); got != tt.want { t.Errorf("Endpoint.Type() = %v, want %v", got, tt.want) @@ -477,7 +479,7 @@ func TestEndpoint_ValidateAndSetDefaultsWithDNS(t *testing.T) { endpoint := &Endpoint{ Name: "dns-test", URL: "https://example.com", - DNS: &DNS{ + DNSConfig: &dns.Config{ QueryType: "A", QueryName: "example.com", }, @@ -487,7 +489,7 @@ func TestEndpoint_ValidateAndSetDefaultsWithDNS(t *testing.T) { if err != nil { t.Error("did not expect an error, got", err) } - if endpoint.DNS.QueryName != "example.com." { + if endpoint.DNSConfig.QueryName != "example.com." { t.Error("Endpoint.dns.query-name should be formatted with . suffix") } } @@ -503,13 +505,13 @@ func TestEndpoint_ValidateAndSetDefaultsWithSSH(t *testing.T) { name: "fail when has no user", username: "", password: "password", - expectedErr: ErrEndpointWithoutSSHUsername, + expectedErr: ssh.ErrEndpointWithoutSSHUsername, }, { name: "fail when has no password", username: "username", password: "", - expectedErr: ErrEndpointWithoutSSHPassword, + expectedErr: ssh.ErrEndpointWithoutSSHPassword, }, { name: "success when all fields are set", @@ -524,7 +526,7 @@ func TestEndpoint_ValidateAndSetDefaultsWithSSH(t *testing.T) { endpoint := &Endpoint{ Name: "ssh-test", URL: "https://example.com", - SSH: &SSH{ + SSHConfig: &ssh.Config{ Username: scenario.username, Password: scenario.password, }, @@ -763,7 +765,7 @@ func TestIntegrationEvaluateHealthForDNS(t *testing.T) { endpoint := Endpoint{ Name: "example", URL: "8.8.8.8", - DNS: &DNS{ + DNSConfig: &dns.Config{ QueryType: "A", QueryName: "example.com.", }, @@ -786,7 +788,7 @@ func TestIntegrationEvaluateHealthForDNS(t *testing.T) { } func TestIntegrationEvaluateHealthForSSH(t *testing.T) { - tests := []struct { + scenarios := []struct { name string endpoint Endpoint conditions []Condition @@ -797,9 +799,9 @@ func TestIntegrationEvaluateHealthForSSH(t *testing.T) { endpoint: Endpoint{ Name: "ssh-success", URL: "ssh://localhost", - SSH: &SSH{ - Username: "test", - Password: "test", + SSHConfig: &ssh.Config{ + Username: "scenario", + Password: "scenario", }, Body: "{ \"command\": \"uptime\" }", }, @@ -811,9 +813,9 @@ func TestIntegrationEvaluateHealthForSSH(t *testing.T) { endpoint: Endpoint{ Name: "ssh-failure", URL: "ssh://localhost", - SSH: &SSH{ - Username: "test", - Password: "test", + SSHConfig: &ssh.Config{ + Username: "scenario", + Password: "scenario", }, Body: "{ \"command\": \"uptime\" }", }, @@ -822,13 +824,13 @@ func TestIntegrationEvaluateHealthForSSH(t *testing.T) { }, } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - test.endpoint.ValidateAndSetDefaults() - test.endpoint.Conditions = test.conditions - result := test.endpoint.EvaluateHealth() - if result.Success != test.success { - t.Errorf("Expected success to be %v, but was %v", test.success, result.Success) + for _, scenario := range scenarios { + t.Run(scenario.name, func(t *testing.T) { + scenario.endpoint.ValidateAndSetDefaults() + scenario.endpoint.Conditions = scenario.conditions + result := scenario.endpoint.EvaluateHealth() + if result.Success != scenario.success { + t.Errorf("Expected success to be %v, but was %v", scenario.success, result.Success) } }) } diff --git a/core/event.go b/config/endpoint/event.go similarity index 96% rename from core/event.go rename to config/endpoint/event.go index 43f9cf76..da8d2275 100644 --- a/core/event.go +++ b/config/endpoint/event.go @@ -1,6 +1,8 @@ -package core +package endpoint -import "time" +import ( + "time" +) // Event is something that happens at a specific time type Event struct { diff --git a/core/event_test.go b/config/endpoint/event_test.go similarity index 89% rename from core/event_test.go rename to config/endpoint/event_test.go index 2269178a..bdfe07a2 100644 --- a/core/event_test.go +++ b/config/endpoint/event_test.go @@ -1,6 +1,8 @@ -package core +package endpoint -import "testing" +import ( + "testing" +) func TestNewEventFromResult(t *testing.T) { if event := NewEventFromResult(&Result{Success: true}); event.Type != EventHealthy { diff --git a/core/external_endpoint.go b/config/endpoint/external_endpoint.go similarity index 95% rename from core/external_endpoint.go rename to config/endpoint/external_endpoint.go index 709deea5..b096d395 100644 --- a/core/external_endpoint.go +++ b/config/endpoint/external_endpoint.go @@ -1,10 +1,9 @@ -package core +package endpoint import ( "errors" "github.com/TwiN/gatus/v5/alerting/alert" - "github.com/TwiN/gatus/v5/util" ) var ( @@ -72,7 +71,7 @@ func (externalEndpoint *ExternalEndpoint) DisplayName() string { // Key returns the unique key for the Endpoint func (externalEndpoint *ExternalEndpoint) Key() string { - return util.ConvertGroupAndEndpointNameToKey(externalEndpoint.Group, externalEndpoint.Name) + return ConvertGroupAndEndpointNameToKey(externalEndpoint.Group, externalEndpoint.Name) } // ToEndpoint converts the ExternalEndpoint to an Endpoint diff --git a/core/external_endpoint_test.go b/config/endpoint/external_endpoint_test.go similarity index 97% rename from core/external_endpoint_test.go rename to config/endpoint/external_endpoint_test.go index a79456c3..4ead308b 100644 --- a/core/external_endpoint_test.go +++ b/config/endpoint/external_endpoint_test.go @@ -1,4 +1,4 @@ -package core +package endpoint import ( "testing" diff --git a/util/key.go b/config/endpoint/key.go similarity index 96% rename from util/key.go rename to config/endpoint/key.go index a56a21ec..89c35488 100644 --- a/util/key.go +++ b/config/endpoint/key.go @@ -1,4 +1,4 @@ -package util +package endpoint import "strings" diff --git a/util/key_bench_test.go b/config/endpoint/key_bench_test.go similarity index 91% rename from util/key_bench_test.go rename to config/endpoint/key_bench_test.go index 7f974f41..cfec8c8b 100644 --- a/util/key_bench_test.go +++ b/config/endpoint/key_bench_test.go @@ -1,4 +1,4 @@ -package util +package endpoint import ( "testing" diff --git a/util/key_test.go b/config/endpoint/key_test.go similarity index 98% rename from util/key_test.go rename to config/endpoint/key_test.go index 03341770..c693ba5d 100644 --- a/util/key_test.go +++ b/config/endpoint/key_test.go @@ -1,4 +1,4 @@ -package util +package endpoint import "testing" diff --git a/core/result.go b/config/endpoint/result.go similarity index 99% rename from core/result.go rename to config/endpoint/result.go index 8380d1f4..1d20a5c6 100644 --- a/core/result.go +++ b/config/endpoint/result.go @@ -1,4 +1,4 @@ -package core +package endpoint import ( "time" diff --git a/core/result_test.go b/config/endpoint/result_test.go similarity index 96% rename from core/result_test.go rename to config/endpoint/result_test.go index 899ec7f9..1e521c8f 100644 --- a/core/result_test.go +++ b/config/endpoint/result_test.go @@ -1,4 +1,4 @@ -package core +package endpoint import ( "testing" diff --git a/core/ssh.go b/config/endpoint/ssh/ssh.go similarity index 81% rename from core/ssh.go rename to config/endpoint/ssh/ssh.go index b0349bac..88636473 100644 --- a/core/ssh.go +++ b/config/endpoint/ssh/ssh.go @@ -1,4 +1,4 @@ -package core +package ssh import ( "errors" @@ -12,17 +12,17 @@ var ( ErrEndpointWithoutSSHPassword = errors.New("you must specify a password for each SSH endpoint") ) -type SSH struct { +type Config struct { Username string `yaml:"username,omitempty"` Password string `yaml:"password,omitempty"` } -// validate validates the endpoint -func (s *SSH) validate() error { - if len(s.Username) == 0 { +// Validate the SSH configuration +func (cfg *Config) Validate() error { + if len(cfg.Username) == 0 { return ErrEndpointWithoutSSHUsername } - if len(s.Password) == 0 { + if len(cfg.Password) == 0 { return ErrEndpointWithoutSSHPassword } return nil diff --git a/core/ssh_test.go b/config/endpoint/ssh/ssh_test.go similarity index 70% rename from core/ssh_test.go rename to config/endpoint/ssh/ssh_test.go index 15e70433..ed563028 100644 --- a/core/ssh_test.go +++ b/config/endpoint/ssh/ssh_test.go @@ -1,4 +1,4 @@ -package core +package ssh import ( "errors" @@ -6,20 +6,20 @@ import ( ) func TestSSH_validate(t *testing.T) { - ssh := &SSH{} - if err := ssh.validate(); err == nil { + cfg := &Config{} + if err := cfg.Validate(); err == nil { t.Error("expected an error") } else if !errors.Is(err, ErrEndpointWithoutSSHUsername) { t.Errorf("expected error to be '%v', got '%v'", ErrEndpointWithoutSSHUsername, err) } - ssh.Username = "username" - if err := ssh.validate(); err == nil { + cfg.Username = "username" + if err := cfg.Validate(); err == nil { t.Error("expected an error") } else if !errors.Is(err, ErrEndpointWithoutSSHPassword) { t.Errorf("expected error to be '%v', got '%v'", ErrEndpointWithoutSSHPassword, err) } - ssh.Password = "password" - if err := ssh.validate(); err != nil { + cfg.Password = "password" + if err := cfg.Validate(); err != nil { t.Errorf("expected no error, got '%v'", err) } } diff --git a/core/endpoint_status.go b/config/endpoint/status.go similarity index 64% rename from core/endpoint_status.go rename to config/endpoint/status.go index db1ccd78..f1a725ba 100644 --- a/core/endpoint_status.go +++ b/config/endpoint/status.go @@ -1,16 +1,14 @@ -package core +package endpoint -import "github.com/TwiN/gatus/v5/util" - -// EndpointStatus contains the evaluation Results of an Endpoint -type EndpointStatus struct { +// Status contains the evaluation Results of an Endpoint +type Status struct { // Name of the endpoint Name string `json:"name,omitempty"` // Group the endpoint is a part of. Used for grouping multiple endpoints together on the front end. Group string `json:"group,omitempty"` - // Key is the key representing the EndpointStatus + // Key of the Endpoint Key string `json:"key"` // Results is the list of endpoint evaluation results @@ -27,12 +25,12 @@ type EndpointStatus struct { Uptime *Uptime `json:"-"` } -// NewEndpointStatus creates a new EndpointStatus -func NewEndpointStatus(group, name string) *EndpointStatus { - return &EndpointStatus{ +// NewStatus creates a new Status +func NewStatus(group, name string) *Status { + return &Status{ Name: name, Group: group, - Key: util.ConvertGroupAndEndpointNameToKey(group, name), + Key: ConvertGroupAndEndpointNameToKey(group, name), Results: make([]*Result, 0), Events: make([]*Event, 0), Uptime: NewUptime(), diff --git a/config/endpoint/status_test.go b/config/endpoint/status_test.go new file mode 100644 index 00000000..510f9b51 --- /dev/null +++ b/config/endpoint/status_test.go @@ -0,0 +1,19 @@ +package endpoint + +import ( + "testing" +) + +func TestNewEndpointStatus(t *testing.T) { + ep := &Endpoint{Name: "name", Group: "group"} + status := NewStatus(ep.Group, ep.Name) + if status.Name != ep.Name { + t.Errorf("expected %s, got %s", ep.Name, status.Name) + } + if status.Group != ep.Group { + t.Errorf("expected %s, got %s", ep.Group, status.Group) + } + if status.Key != "group_name" { + t.Errorf("expected %s, got %s", "group_name", status.Key) + } +} diff --git a/core/ui/ui.go b/config/endpoint/ui/ui.go similarity index 97% rename from core/ui/ui.go rename to config/endpoint/ui/ui.go index 7ea6eeec..5a13ccdc 100644 --- a/core/ui/ui.go +++ b/config/endpoint/ui/ui.go @@ -2,7 +2,7 @@ package ui import "errors" -// Config is the UI configuration for core.Endpoint +// Config is the UI configuration for endpoint.Endpoint type Config struct { // HideConditions whether to hide the condition results on the UI HideConditions bool `yaml:"hide-conditions"` diff --git a/core/ui/ui_test.go b/config/endpoint/ui/ui_test.go similarity index 100% rename from core/ui/ui_test.go rename to config/endpoint/ui/ui_test.go diff --git a/core/uptime.go b/config/endpoint/uptime.go similarity index 98% rename from core/uptime.go rename to config/endpoint/uptime.go index 514b4a77..da4d2139 100644 --- a/core/uptime.go +++ b/config/endpoint/uptime.go @@ -1,4 +1,4 @@ -package core +package endpoint // Uptime is the struct that contains the relevant data for calculating the uptime as well as the uptime itself // and some other statistics diff --git a/config/endpoints/README.md b/config/endpoints/README.md deleted file mode 100644 index 9416ef98..00000000 --- a/config/endpoints/README.md +++ /dev/null @@ -1 +0,0 @@ -TODO: move files from core to here. \ No newline at end of file diff --git a/controller/controller_test.go b/controller/controller_test.go index 62a28297..aaefcbdc 100644 --- a/controller/controller_test.go +++ b/controller/controller_test.go @@ -8,8 +8,8 @@ import ( "testing" "github.com/TwiN/gatus/v5/config" + "github.com/TwiN/gatus/v5/config/endpoint" "github.com/TwiN/gatus/v5/config/web" - "github.com/TwiN/gatus/v5/core" "github.com/gofiber/fiber/v2" ) @@ -19,7 +19,7 @@ func TestHandle(t *testing.T) { Address: "0.0.0.0", Port: rand.Intn(65534), }, - Endpoints: []*core.Endpoint{ + Endpoints: []*endpoint.Endpoint{ { Name: "frontend", Group: "core", @@ -64,7 +64,7 @@ func TestHandleTLS(t *testing.T) { t.Run(scenario.name, func(t *testing.T) { cfg := &config.Config{ Web: &web.Config{Address: "0.0.0.0", Port: rand.Intn(65534), TLS: scenario.tls}, - Endpoints: []*core.Endpoint{ + Endpoints: []*endpoint.Endpoint{ {Name: "frontend", Group: "core"}, {Name: "backend", Group: "core"}, }, diff --git a/core/dns.go b/core/dns.go deleted file mode 100644 index 3cae0eb9..00000000 --- a/core/dns.go +++ /dev/null @@ -1,86 +0,0 @@ -package core - -import ( - "errors" - "fmt" - "strings" - - "github.com/miekg/dns" -) - -var ( - // ErrDNSWithNoQueryName is the error with which gatus will panic if a dns is configured without query name - ErrDNSWithNoQueryName = errors.New("you must specify a query name for DNS") - - // ErrDNSWithInvalidQueryType is the error with which gatus will panic if a dns is configured with invalid query type - ErrDNSWithInvalidQueryType = errors.New("invalid query type") -) - -const ( - dnsPort = 53 -) - -// DNS is the configuration for a Endpoint of type DNS -type DNS struct { - // QueryType is the type for the DNS records like A, AAAA, CNAME... - QueryType string `yaml:"query-type"` - - // QueryName is the query for DNS - QueryName string `yaml:"query-name"` -} - -func (d *DNS) validateAndSetDefault() error { - if len(d.QueryName) == 0 { - return ErrDNSWithNoQueryName - } - if !strings.HasSuffix(d.QueryName, ".") { - d.QueryName += "." - } - if _, ok := dns.StringToType[d.QueryType]; !ok { - return ErrDNSWithInvalidQueryType - } - return nil -} - -func (d *DNS) query(url string, result *Result) { - if !strings.Contains(url, ":") { - url = fmt.Sprintf("%s:%d", url, dnsPort) - } - queryType := dns.StringToType[d.QueryType] - c := new(dns.Client) - m := new(dns.Msg) - m.SetQuestion(d.QueryName, queryType) - r, _, err := c.Exchange(m, url) - if err != nil { - result.AddError(err.Error()) - return - } - result.Connected = true - result.DNSRCode = dns.RcodeToString[r.Rcode] - for _, rr := range r.Answer { - switch rr.Header().Rrtype { - case dns.TypeA: - if a, ok := rr.(*dns.A); ok { - result.Body = []byte(a.A.String()) - } - case dns.TypeAAAA: - if aaaa, ok := rr.(*dns.AAAA); ok { - result.Body = []byte(aaaa.AAAA.String()) - } - case dns.TypeCNAME: - if cname, ok := rr.(*dns.CNAME); ok { - result.Body = []byte(cname.Target) - } - case dns.TypeMX: - if mx, ok := rr.(*dns.MX); ok { - result.Body = []byte(mx.Mx) - } - case dns.TypeNS: - if ns, ok := rr.(*dns.NS); ok { - result.Body = []byte(ns.Ns) - } - default: - result.Body = []byte("query type is not supported yet") - } - } -} diff --git a/core/dns_test.go b/core/dns_test.go deleted file mode 100644 index 29e57da1..00000000 --- a/core/dns_test.go +++ /dev/null @@ -1,126 +0,0 @@ -package core - -import ( - "testing" - "time" - - "github.com/TwiN/gatus/v5/pattern" -) - -func TestIntegrationQuery(t *testing.T) { - tests := []struct { - name string - inputDNS DNS - inputURL string - expectedDNSCode string - expectedBody string - isErrExpected bool - }{ - { - name: "test DNS with type A", - inputDNS: DNS{ - QueryType: "A", - QueryName: "example.com.", - }, - inputURL: "8.8.8.8", - expectedDNSCode: "NOERROR", - expectedBody: "93.184.215.14", - }, - { - name: "test DNS with type AAAA", - inputDNS: DNS{ - QueryType: "AAAA", - QueryName: "example.com.", - }, - inputURL: "8.8.8.8", - expectedDNSCode: "NOERROR", - expectedBody: "2606:2800:21f:cb07:6820:80da:af6b:8b2c", - }, - { - name: "test DNS with type CNAME", - inputDNS: DNS{ - QueryType: "CNAME", - QueryName: "en.wikipedia.org.", - }, - inputURL: "8.8.8.8", - expectedDNSCode: "NOERROR", - expectedBody: "dyna.wikimedia.org.", - }, - { - name: "test DNS with type MX", - inputDNS: DNS{ - QueryType: "MX", - QueryName: "example.com.", - }, - inputURL: "8.8.8.8", - expectedDNSCode: "NOERROR", - expectedBody: ".", - }, - { - name: "test DNS with type NS", - inputDNS: DNS{ - QueryType: "NS", - QueryName: "example.com.", - }, - inputURL: "8.8.8.8", - expectedDNSCode: "NOERROR", - expectedBody: "*.iana-servers.net.", - }, - { - name: "test DNS with fake type and retrieve error", - inputDNS: DNS{ - QueryType: "B", - QueryName: "example", - }, - inputURL: "8.8.8.8", - isErrExpected: true, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - dns := test.inputDNS - result := &Result{} - dns.query(test.inputURL, result) - if test.isErrExpected && len(result.Errors) == 0 { - t.Errorf("there should be errors") - } - if result.DNSRCode != test.expectedDNSCode { - t.Errorf("expected DNSRCode to be %s, got %s", test.expectedDNSCode, result.DNSRCode) - } - if test.inputDNS.QueryType == "NS" { - // Because there are often multiple nameservers backing a single domain, we'll only look at the suffix - if !pattern.Match(test.expectedBody, string(result.Body)) { - t.Errorf("got %s, expected result %s,", string(result.Body), test.expectedBody) - } - } else { - if string(result.Body) != test.expectedBody { - t.Errorf("got %s, expected result %s,", string(result.Body), test.expectedBody) - } - } - }) - time.Sleep(5 * time.Millisecond) - } -} - -func TestDNS_validateAndSetDefault(t *testing.T) { - dns := &DNS{ - QueryType: "A", - QueryName: "", - } - err := dns.validateAndSetDefault() - if err == nil { - t.Error("Should've returned an error because endpoint's dns didn't have a query name, which is a mandatory field for dns") - } -} - -func TestEndpoint_ValidateAndSetDefaultsWithInvalidDNSQueryType(t *testing.T) { - dns := &DNS{ - QueryType: "B", - QueryName: "example.com", - } - err := dns.validateAndSetDefault() - if err == nil { - t.Error("Should've returned an error because endpoint's dns query type is invalid, it needs to be a valid query name like A, AAAA, CNAME...") - } -} diff --git a/core/endpoint_status_test.go b/core/endpoint_status_test.go deleted file mode 100644 index 0eed7d29..00000000 --- a/core/endpoint_status_test.go +++ /dev/null @@ -1,19 +0,0 @@ -package core - -import ( - "testing" -) - -func TestNewEndpointStatus(t *testing.T) { - endpoint := &Endpoint{Name: "name", Group: "group"} - status := NewEndpointStatus(endpoint.Group, endpoint.Name) - if status.Name != endpoint.Name { - t.Errorf("expected %s, got %s", endpoint.Name, status.Name) - } - if status.Group != endpoint.Group { - t.Errorf("expected %s, got %s", endpoint.Group, status.Group) - } - if status.Key != "group_name" { - t.Errorf("expected %s, got %s", "group_name", status.Key) - } -} diff --git a/main.go b/main.go index c5994ac6..c1890451 100644 --- a/main.go +++ b/main.go @@ -80,8 +80,8 @@ func initializeStorage(cfg *config.Config) { } // Remove all EndpointStatus that represent endpoints which no longer exist in the configuration var keys []string - for _, endpoint := range cfg.Endpoints { - keys = append(keys, endpoint.Key()) + for _, ep := range cfg.Endpoints { + keys = append(keys, ep.Key()) } for _, externalEndpoint := range cfg.ExternalEndpoints { keys = append(keys, externalEndpoint.Key()) diff --git a/metrics/metrics.go b/metrics/metrics.go index 37027f24..42600c4b 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -3,7 +3,7 @@ package metrics import ( "strconv" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" ) @@ -50,24 +50,24 @@ func initializePrometheusMetrics() { // PublishMetricsForEndpoint publishes metrics for the given endpoint and its result. // These metrics will be exposed at /metrics if the metrics are enabled -func PublishMetricsForEndpoint(endpoint *core.Endpoint, result *core.Result) { +func PublishMetricsForEndpoint(ep *endpoint.Endpoint, result *endpoint.Result) { if !initializedMetrics { initializePrometheusMetrics() initializedMetrics = true } - endpointType := endpoint.Type() - resultTotal.WithLabelValues(endpoint.Key(), endpoint.Group, endpoint.Name, string(endpointType), strconv.FormatBool(result.Success)).Inc() - resultDurationSeconds.WithLabelValues(endpoint.Key(), endpoint.Group, endpoint.Name, string(endpointType)).Set(result.Duration.Seconds()) + endpointType := ep.Type() + resultTotal.WithLabelValues(ep.Key(), ep.Group, ep.Name, string(endpointType), strconv.FormatBool(result.Success)).Inc() + resultDurationSeconds.WithLabelValues(ep.Key(), ep.Group, ep.Name, string(endpointType)).Set(result.Duration.Seconds()) if result.Connected { - resultConnectedTotal.WithLabelValues(endpoint.Key(), endpoint.Group, endpoint.Name, string(endpointType)).Inc() + resultConnectedTotal.WithLabelValues(ep.Key(), ep.Group, ep.Name, string(endpointType)).Inc() } if result.DNSRCode != "" { - resultCodeTotal.WithLabelValues(endpoint.Key(), endpoint.Group, endpoint.Name, string(endpointType), result.DNSRCode).Inc() + resultCodeTotal.WithLabelValues(ep.Key(), ep.Group, ep.Name, string(endpointType), result.DNSRCode).Inc() } if result.HTTPStatus != 0 { - resultCodeTotal.WithLabelValues(endpoint.Key(), endpoint.Group, endpoint.Name, string(endpointType), strconv.Itoa(result.HTTPStatus)).Inc() + resultCodeTotal.WithLabelValues(ep.Key(), ep.Group, ep.Name, string(endpointType), strconv.Itoa(result.HTTPStatus)).Inc() } if result.CertificateExpiration != 0 { - resultCertificateExpirationSeconds.WithLabelValues(endpoint.Key(), endpoint.Group, endpoint.Name, string(endpointType)).Set(result.CertificateExpiration.Seconds()) + resultCertificateExpirationSeconds.WithLabelValues(ep.Key(), ep.Group, ep.Name, string(endpointType)).Set(result.CertificateExpiration.Seconds()) } } diff --git a/metrics/metrics_test.go b/metrics/metrics_test.go index 4a1bb35e..ecc1602a 100644 --- a/metrics/metrics_test.go +++ b/metrics/metrics_test.go @@ -5,18 +5,19 @@ import ( "testing" "time" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" + "github.com/TwiN/gatus/v5/config/endpoint/dns" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/testutil" ) func TestPublishMetricsForEndpoint(t *testing.T) { - httpEndpoint := &core.Endpoint{Name: "http-ep-name", Group: "http-ep-group", URL: "https://example.org"} - PublishMetricsForEndpoint(httpEndpoint, &core.Result{ + httpEndpoint := &endpoint.Endpoint{Name: "http-ep-name", Group: "http-ep-group", URL: "https://example.org"} + PublishMetricsForEndpoint(httpEndpoint, &endpoint.Result{ HTTPStatus: 200, Connected: true, Duration: 123 * time.Millisecond, - ConditionResults: []*core.ConditionResult{ + ConditionResults: []*endpoint.ConditionResult{ {Condition: "[STATUS] == 200", Success: true}, {Condition: "[CERTIFICATE_EXPIRATION] > 48h", Success: true}, }, @@ -43,11 +44,11 @@ gatus_results_total{group="http-ep-group",key="http-ep-group_http-ep-name",name= if err != nil { t.Errorf("Expected no errors but got: %v", err) } - PublishMetricsForEndpoint(httpEndpoint, &core.Result{ + PublishMetricsForEndpoint(httpEndpoint, &endpoint.Result{ HTTPStatus: 200, Connected: true, Duration: 125 * time.Millisecond, - ConditionResults: []*core.ConditionResult{ + ConditionResults: []*endpoint.ConditionResult{ {Condition: "[STATUS] == 200", Success: true}, {Condition: "[CERTIFICATE_EXPIRATION] > 47h", Success: false}, }, @@ -75,15 +76,15 @@ gatus_results_total{group="http-ep-group",key="http-ep-group_http-ep-name",name= if err != nil { t.Errorf("Expected no errors but got: %v", err) } - dnsEndpoint := &core.Endpoint{Name: "dns-ep-name", Group: "dns-ep-group", URL: "8.8.8.8", DNS: &core.DNS{ + dnsEndpoint := &endpoint.Endpoint{Name: "dns-ep-name", Group: "dns-ep-group", URL: "8.8.8.8", DNSConfig: &dns.Config{ QueryType: "A", QueryName: "example.com.", }} - PublishMetricsForEndpoint(dnsEndpoint, &core.Result{ + PublishMetricsForEndpoint(dnsEndpoint, &endpoint.Result{ DNSRCode: "NOERROR", Connected: true, Duration: 50 * time.Millisecond, - ConditionResults: []*core.ConditionResult{ + ConditionResults: []*endpoint.ConditionResult{ {Condition: "[DNS_RCODE] == NOERROR", Success: true}, }, Success: true, diff --git a/storage/store/memory/memory.go b/storage/store/memory/memory.go index 281e4a59..fac3fd3d 100644 --- a/storage/store/memory/memory.go +++ b/storage/store/memory/memory.go @@ -5,10 +5,9 @@ import ( "sync" "time" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" "github.com/TwiN/gatus/v5/storage/store/common" "github.com/TwiN/gatus/v5/storage/store/common/paging" - "github.com/TwiN/gatus/v5/util" "github.com/TwiN/gocache/v2" ) @@ -30,13 +29,13 @@ func NewStore() (*Store, error) { return store, nil } -// GetAllEndpointStatuses returns all monitored core.EndpointStatus -// with a subset of core.Result defined by the page and pageSize parameters -func (s *Store) GetAllEndpointStatuses(params *paging.EndpointStatusParams) ([]*core.EndpointStatus, error) { +// GetAllEndpointStatuses returns all monitored endpoint.Status +// with a subset of endpoint.Result defined by the page and pageSize parameters +func (s *Store) GetAllEndpointStatuses(params *paging.EndpointStatusParams) ([]*endpoint.Status, error) { endpointStatuses := s.cache.GetAll() - pagedEndpointStatuses := make([]*core.EndpointStatus, 0, len(endpointStatuses)) + pagedEndpointStatuses := make([]*endpoint.Status, 0, len(endpointStatuses)) for _, v := range endpointStatuses { - pagedEndpointStatuses = append(pagedEndpointStatuses, ShallowCopyEndpointStatus(v.(*core.EndpointStatus), params)) + pagedEndpointStatuses = append(pagedEndpointStatuses, ShallowCopyEndpointStatus(v.(*endpoint.Status), params)) } sort.Slice(pagedEndpointStatuses, func(i, j int) bool { return pagedEndpointStatuses[i].Key < pagedEndpointStatuses[j].Key @@ -45,17 +44,17 @@ func (s *Store) GetAllEndpointStatuses(params *paging.EndpointStatusParams) ([]* } // GetEndpointStatus returns the endpoint status for a given endpoint name in the given group -func (s *Store) GetEndpointStatus(groupName, endpointName string, params *paging.EndpointStatusParams) (*core.EndpointStatus, error) { - return s.GetEndpointStatusByKey(util.ConvertGroupAndEndpointNameToKey(groupName, endpointName), params) +func (s *Store) GetEndpointStatus(groupName, endpointName string, params *paging.EndpointStatusParams) (*endpoint.Status, error) { + return s.GetEndpointStatusByKey(endpoint.ConvertGroupAndEndpointNameToKey(groupName, endpointName), params) } // GetEndpointStatusByKey returns the endpoint status for a given key -func (s *Store) GetEndpointStatusByKey(key string, params *paging.EndpointStatusParams) (*core.EndpointStatus, error) { +func (s *Store) GetEndpointStatusByKey(key string, params *paging.EndpointStatusParams) (*endpoint.Status, error) { endpointStatus := s.cache.GetValue(key) if endpointStatus == nil { return nil, common.ErrEndpointNotFound } - return ShallowCopyEndpointStatus(endpointStatus.(*core.EndpointStatus), params), nil + return ShallowCopyEndpointStatus(endpointStatus.(*endpoint.Status), params), nil } // GetUptimeByKey returns the uptime percentage during a time range @@ -64,7 +63,7 @@ func (s *Store) GetUptimeByKey(key string, from, to time.Time) (float64, error) return 0, common.ErrInvalidTimeRange } endpointStatus := s.cache.GetValue(key) - if endpointStatus == nil || endpointStatus.(*core.EndpointStatus).Uptime == nil { + if endpointStatus == nil || endpointStatus.(*endpoint.Status).Uptime == nil { return 0, common.ErrEndpointNotFound } successfulExecutions := uint64(0) @@ -72,7 +71,7 @@ func (s *Store) GetUptimeByKey(key string, from, to time.Time) (float64, error) current := from for to.Sub(current) >= 0 { hourlyUnixTimestamp := current.Truncate(time.Hour).Unix() - hourlyStats := endpointStatus.(*core.EndpointStatus).Uptime.HourlyStatistics[hourlyUnixTimestamp] + hourlyStats := endpointStatus.(*endpoint.Status).Uptime.HourlyStatistics[hourlyUnixTimestamp] if hourlyStats == nil || hourlyStats.TotalExecutions == 0 { current = current.Add(time.Hour) continue @@ -93,14 +92,14 @@ func (s *Store) GetAverageResponseTimeByKey(key string, from, to time.Time) (int return 0, common.ErrInvalidTimeRange } endpointStatus := s.cache.GetValue(key) - if endpointStatus == nil || endpointStatus.(*core.EndpointStatus).Uptime == nil { + if endpointStatus == nil || endpointStatus.(*endpoint.Status).Uptime == nil { return 0, common.ErrEndpointNotFound } current := from var totalExecutions, totalResponseTime uint64 for to.Sub(current) >= 0 { hourlyUnixTimestamp := current.Truncate(time.Hour).Unix() - hourlyStats := endpointStatus.(*core.EndpointStatus).Uptime.HourlyStatistics[hourlyUnixTimestamp] + hourlyStats := endpointStatus.(*endpoint.Status).Uptime.HourlyStatistics[hourlyUnixTimestamp] if hourlyStats == nil || hourlyStats.TotalExecutions == 0 { current = current.Add(time.Hour) continue @@ -121,14 +120,14 @@ func (s *Store) GetHourlyAverageResponseTimeByKey(key string, from, to time.Time return nil, common.ErrInvalidTimeRange } endpointStatus := s.cache.GetValue(key) - if endpointStatus == nil || endpointStatus.(*core.EndpointStatus).Uptime == nil { + if endpointStatus == nil || endpointStatus.(*endpoint.Status).Uptime == nil { return nil, common.ErrEndpointNotFound } hourlyAverageResponseTimes := make(map[int64]int) current := from for to.Sub(current) >= 0 { hourlyUnixTimestamp := current.Truncate(time.Hour).Unix() - hourlyStats := endpointStatus.(*core.EndpointStatus).Uptime.HourlyStatistics[hourlyUnixTimestamp] + hourlyStats := endpointStatus.(*endpoint.Status).Uptime.HourlyStatistics[hourlyUnixTimestamp] if hourlyStats == nil || hourlyStats.TotalExecutions == 0 { current = current.Add(time.Hour) continue @@ -140,24 +139,24 @@ func (s *Store) GetHourlyAverageResponseTimeByKey(key string, from, to time.Time } // Insert adds the observed result for the specified endpoint into the store -func (s *Store) Insert(endpoint *core.Endpoint, result *core.Result) error { - key := endpoint.Key() +func (s *Store) Insert(ep *endpoint.Endpoint, result *endpoint.Result) error { + key := ep.Key() s.Lock() status, exists := s.cache.Get(key) if !exists { - status = core.NewEndpointStatus(endpoint.Group, endpoint.Name) - status.(*core.EndpointStatus).Events = append(status.(*core.EndpointStatus).Events, &core.Event{ - Type: core.EventStart, + status = endpoint.NewStatus(ep.Group, ep.Name) + status.(*endpoint.Status).Events = append(status.(*endpoint.Status).Events, &endpoint.Event{ + Type: endpoint.EventStart, Timestamp: time.Now(), }) } - AddResult(status.(*core.EndpointStatus), result) + AddResult(status.(*endpoint.Status), result) s.cache.Set(key, status) s.Unlock() return nil } -// DeleteAllEndpointStatusesNotInKeys removes all EndpointStatus that are not within the keys provided +// DeleteAllEndpointStatusesNotInKeys removes all Status that are not within the keys provided func (s *Store) DeleteAllEndpointStatusesNotInKeys(keys []string) int { var keysToDelete []string for _, existingKey := range s.cache.GetKeysByPattern("*", 0) { diff --git a/storage/store/memory/memory_test.go b/storage/store/memory/memory_test.go index ea0bd1cd..5d489677 100644 --- a/storage/store/memory/memory_test.go +++ b/storage/store/memory/memory_test.go @@ -4,30 +4,30 @@ import ( "testing" "time" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" "github.com/TwiN/gatus/v5/storage/store/common/paging" ) var ( - firstCondition = core.Condition("[STATUS] == 200") - secondCondition = core.Condition("[RESPONSE_TIME] < 500") - thirdCondition = core.Condition("[CERTIFICATE_EXPIRATION] < 72h") + firstCondition = endpoint.Condition("[STATUS] == 200") + secondCondition = endpoint.Condition("[RESPONSE_TIME] < 500") + thirdCondition = endpoint.Condition("[CERTIFICATE_EXPIRATION] < 72h") now = time.Now() - testEndpoint = core.Endpoint{ + testEndpoint = endpoint.Endpoint{ Name: "name", Group: "group", URL: "https://example.org/what/ever", Method: "GET", Body: "body", Interval: 30 * time.Second, - Conditions: []core.Condition{firstCondition, secondCondition, thirdCondition}, + Conditions: []endpoint.Condition{firstCondition, secondCondition, thirdCondition}, Alerts: nil, NumberOfFailuresInARow: 0, NumberOfSuccessesInARow: 0, } - testSuccessfulResult = core.Result{ + testSuccessfulResult = endpoint.Result{ Hostname: "example.org", IP: "127.0.0.1", HTTPStatus: 200, @@ -37,7 +37,7 @@ var ( Timestamp: now, Duration: 150 * time.Millisecond, CertificateExpiration: 10 * time.Hour, - ConditionResults: []*core.ConditionResult{ + ConditionResults: []*endpoint.ConditionResult{ { Condition: "[STATUS] == 200", Success: true, @@ -52,7 +52,7 @@ var ( }, }, } - testUnsuccessfulResult = core.Result{ + testUnsuccessfulResult = endpoint.Result{ Hostname: "example.org", IP: "127.0.0.1", HTTPStatus: 200, @@ -62,7 +62,7 @@ var ( Timestamp: now, Duration: 750 * time.Millisecond, CertificateExpiration: 10 * time.Hour, - ConditionResults: []*core.ConditionResult{ + ConditionResults: []*endpoint.ConditionResult{ { Condition: "[STATUS] == 200", Success: true, diff --git a/storage/store/memory/uptime.go b/storage/store/memory/uptime.go index ffb4863b..29259f6d 100644 --- a/storage/store/memory/uptime.go +++ b/storage/store/memory/uptime.go @@ -3,7 +3,7 @@ package memory import ( "time" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" ) const ( @@ -13,14 +13,14 @@ const ( // processUptimeAfterResult processes the result by extracting the relevant from the result and recalculating the uptime // if necessary -func processUptimeAfterResult(uptime *core.Uptime, result *core.Result) { +func processUptimeAfterResult(uptime *endpoint.Uptime, result *endpoint.Result) { if uptime.HourlyStatistics == nil { - uptime.HourlyStatistics = make(map[int64]*core.HourlyUptimeStatistics) + uptime.HourlyStatistics = make(map[int64]*endpoint.HourlyUptimeStatistics) } unixTimestampFlooredAtHour := result.Timestamp.Truncate(time.Hour).Unix() hourlyStats, _ := uptime.HourlyStatistics[unixTimestampFlooredAtHour] if hourlyStats == nil { - hourlyStats = &core.HourlyUptimeStatistics{} + hourlyStats = &endpoint.HourlyUptimeStatistics{} uptime.HourlyStatistics[unixTimestampFlooredAtHour] = hourlyStats } if result.Success { diff --git a/storage/store/memory/uptime_bench_test.go b/storage/store/memory/uptime_bench_test.go index 886ba4df..f055339a 100644 --- a/storage/store/memory/uptime_bench_test.go +++ b/storage/store/memory/uptime_bench_test.go @@ -4,17 +4,17 @@ import ( "testing" "time" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" ) func BenchmarkProcessUptimeAfterResult(b *testing.B) { - uptime := core.NewUptime() + uptime := endpoint.NewUptime() now := time.Now() now = time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), 0, 0, 0, now.Location()) // Start 12000 days ago timestamp := now.Add(-12000 * 24 * time.Hour) for n := 0; n < b.N; n++ { - processUptimeAfterResult(uptime, &core.Result{ + processUptimeAfterResult(uptime, &endpoint.Result{ Duration: 18 * time.Millisecond, Success: n%15 == 0, Timestamp: timestamp, diff --git a/storage/store/memory/uptime_test.go b/storage/store/memory/uptime_test.go index 071bb4ce..01670ad3 100644 --- a/storage/store/memory/uptime_test.go +++ b/storage/store/memory/uptime_test.go @@ -4,53 +4,53 @@ import ( "testing" "time" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" ) func TestProcessUptimeAfterResult(t *testing.T) { - endpoint := &core.Endpoint{Name: "name", Group: "group"} - status := core.NewEndpointStatus(endpoint.Group, endpoint.Name) + ep := &endpoint.Endpoint{Name: "name", Group: "group"} + status := endpoint.NewStatus(ep.Group, ep.Name) uptime := status.Uptime now := time.Now() now = time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), 0, 0, 0, now.Location()) - processUptimeAfterResult(uptime, &core.Result{Timestamp: now.Add(-7 * 24 * time.Hour), Success: true}) + processUptimeAfterResult(uptime, &endpoint.Result{Timestamp: now.Add(-7 * 24 * time.Hour), Success: true}) - processUptimeAfterResult(uptime, &core.Result{Timestamp: now.Add(-6 * 24 * time.Hour), Success: false}) + processUptimeAfterResult(uptime, &endpoint.Result{Timestamp: now.Add(-6 * 24 * time.Hour), Success: false}) - processUptimeAfterResult(uptime, &core.Result{Timestamp: now.Add(-8 * 24 * time.Hour), Success: true}) + processUptimeAfterResult(uptime, &endpoint.Result{Timestamp: now.Add(-8 * 24 * time.Hour), Success: true}) - processUptimeAfterResult(uptime, &core.Result{Timestamp: now.Add(-24 * time.Hour), Success: true}) - processUptimeAfterResult(uptime, &core.Result{Timestamp: now.Add(-12 * time.Hour), Success: true}) + processUptimeAfterResult(uptime, &endpoint.Result{Timestamp: now.Add(-24 * time.Hour), Success: true}) + processUptimeAfterResult(uptime, &endpoint.Result{Timestamp: now.Add(-12 * time.Hour), Success: true}) - processUptimeAfterResult(uptime, &core.Result{Timestamp: now.Add(-1 * time.Hour), Success: true, Duration: 10 * time.Millisecond}) + processUptimeAfterResult(uptime, &endpoint.Result{Timestamp: now.Add(-1 * time.Hour), Success: true, Duration: 10 * time.Millisecond}) checkHourlyStatistics(t, uptime.HourlyStatistics[now.Unix()-now.Unix()%3600-3600], 10, 1, 1) - processUptimeAfterResult(uptime, &core.Result{Timestamp: now.Add(-30 * time.Minute), Success: false, Duration: 500 * time.Millisecond}) + processUptimeAfterResult(uptime, &endpoint.Result{Timestamp: now.Add(-30 * time.Minute), Success: false, Duration: 500 * time.Millisecond}) checkHourlyStatistics(t, uptime.HourlyStatistics[now.Unix()-now.Unix()%3600-3600], 510, 2, 1) - processUptimeAfterResult(uptime, &core.Result{Timestamp: now.Add(-15 * time.Minute), Success: false, Duration: 25 * time.Millisecond}) + processUptimeAfterResult(uptime, &endpoint.Result{Timestamp: now.Add(-15 * time.Minute), Success: false, Duration: 25 * time.Millisecond}) checkHourlyStatistics(t, uptime.HourlyStatistics[now.Unix()-now.Unix()%3600-3600], 535, 3, 1) - processUptimeAfterResult(uptime, &core.Result{Timestamp: now.Add(-10 * time.Minute), Success: false}) + processUptimeAfterResult(uptime, &endpoint.Result{Timestamp: now.Add(-10 * time.Minute), Success: false}) - processUptimeAfterResult(uptime, &core.Result{Timestamp: now.Add(-120 * time.Hour), Success: true}) - processUptimeAfterResult(uptime, &core.Result{Timestamp: now.Add(-119 * time.Hour), Success: true}) - processUptimeAfterResult(uptime, &core.Result{Timestamp: now.Add(-118 * time.Hour), Success: true}) - processUptimeAfterResult(uptime, &core.Result{Timestamp: now.Add(-117 * time.Hour), Success: true}) - processUptimeAfterResult(uptime, &core.Result{Timestamp: now.Add(-10 * time.Hour), Success: true}) - processUptimeAfterResult(uptime, &core.Result{Timestamp: now.Add(-8 * time.Hour), Success: true}) - processUptimeAfterResult(uptime, &core.Result{Timestamp: now.Add(-30 * time.Minute), Success: true}) - processUptimeAfterResult(uptime, &core.Result{Timestamp: now.Add(-25 * time.Minute), Success: true}) + processUptimeAfterResult(uptime, &endpoint.Result{Timestamp: now.Add(-120 * time.Hour), Success: true}) + processUptimeAfterResult(uptime, &endpoint.Result{Timestamp: now.Add(-119 * time.Hour), Success: true}) + processUptimeAfterResult(uptime, &endpoint.Result{Timestamp: now.Add(-118 * time.Hour), Success: true}) + processUptimeAfterResult(uptime, &endpoint.Result{Timestamp: now.Add(-117 * time.Hour), Success: true}) + processUptimeAfterResult(uptime, &endpoint.Result{Timestamp: now.Add(-10 * time.Hour), Success: true}) + processUptimeAfterResult(uptime, &endpoint.Result{Timestamp: now.Add(-8 * time.Hour), Success: true}) + processUptimeAfterResult(uptime, &endpoint.Result{Timestamp: now.Add(-30 * time.Minute), Success: true}) + processUptimeAfterResult(uptime, &endpoint.Result{Timestamp: now.Add(-25 * time.Minute), Success: true}) } func TestAddResultUptimeIsCleaningUpAfterItself(t *testing.T) { - endpoint := &core.Endpoint{Name: "name", Group: "group"} - status := core.NewEndpointStatus(endpoint.Group, endpoint.Name) + ep := &endpoint.Endpoint{Name: "name", Group: "group"} + status := endpoint.NewStatus(ep.Group, ep.Name) now := time.Now() now = time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), 0, 0, 0, now.Location()) // Start 12 days ago timestamp := now.Add(-12 * 24 * time.Hour) for timestamp.Unix() <= now.Unix() { - AddResult(status, &core.Result{Timestamp: timestamp, Success: true}) + AddResult(status, &endpoint.Result{Timestamp: timestamp, Success: true}) if len(status.Uptime.HourlyStatistics) > numberOfHoursInTenDays { t.Errorf("At no point in time should there be more than %d entries in status.SuccessfulExecutionsPerHour, but there are %d", numberOfHoursInTenDays, len(status.Uptime.HourlyStatistics)) } @@ -59,7 +59,7 @@ func TestAddResultUptimeIsCleaningUpAfterItself(t *testing.T) { } } -func checkHourlyStatistics(t *testing.T, hourlyUptimeStatistics *core.HourlyUptimeStatistics, expectedTotalExecutionsResponseTime uint64, expectedTotalExecutions uint64, expectedSuccessfulExecutions uint64) { +func checkHourlyStatistics(t *testing.T, hourlyUptimeStatistics *endpoint.HourlyUptimeStatistics, expectedTotalExecutionsResponseTime uint64, expectedTotalExecutions uint64, expectedSuccessfulExecutions uint64) { if hourlyUptimeStatistics.TotalExecutionsResponseTime != expectedTotalExecutionsResponseTime { t.Error("TotalExecutionsResponseTime should've been", expectedTotalExecutionsResponseTime, "got", hourlyUptimeStatistics.TotalExecutionsResponseTime) } diff --git a/storage/store/memory/util.go b/storage/store/memory/util.go index 7ba757e1..961f740a 100644 --- a/storage/store/memory/util.go +++ b/storage/store/memory/util.go @@ -1,31 +1,31 @@ package memory import ( - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" "github.com/TwiN/gatus/v5/storage/store/common" "github.com/TwiN/gatus/v5/storage/store/common/paging" ) -// ShallowCopyEndpointStatus returns a shallow copy of a EndpointStatus with only the results +// ShallowCopyEndpointStatus returns a shallow copy of a Status with only the results // within the range defined by the page and pageSize parameters -func ShallowCopyEndpointStatus(ss *core.EndpointStatus, params *paging.EndpointStatusParams) *core.EndpointStatus { - shallowCopy := &core.EndpointStatus{ +func ShallowCopyEndpointStatus(ss *endpoint.Status, params *paging.EndpointStatusParams) *endpoint.Status { + shallowCopy := &endpoint.Status{ Name: ss.Name, Group: ss.Group, Key: ss.Key, - Uptime: core.NewUptime(), + Uptime: endpoint.NewUptime(), } numberOfResults := len(ss.Results) resultsStart, resultsEnd := getStartAndEndIndex(numberOfResults, params.ResultsPage, params.ResultsPageSize) if resultsStart < 0 || resultsEnd < 0 { - shallowCopy.Results = []*core.Result{} + shallowCopy.Results = []*endpoint.Result{} } else { shallowCopy.Results = ss.Results[resultsStart:resultsEnd] } numberOfEvents := len(ss.Events) eventsStart, eventsEnd := getStartAndEndIndex(numberOfEvents, params.EventsPage, params.EventsPageSize) if eventsStart < 0 || eventsEnd < 0 { - shallowCopy.Events = []*core.Event{} + shallowCopy.Events = []*endpoint.Event{} } else { shallowCopy.Events = ss.Events[eventsStart:eventsEnd] } @@ -49,16 +49,16 @@ func getStartAndEndIndex(numberOfResults int, page, pageSize int) (int, int) { return start, end } -// AddResult adds a Result to EndpointStatus.Results and makes sure that there are +// AddResult adds a Result to Status.Results and makes sure that there are // no more than MaximumNumberOfResults results in the Results slice -func AddResult(ss *core.EndpointStatus, result *core.Result) { +func AddResult(ss *endpoint.Status, result *endpoint.Result) { if ss == nil { return } if len(ss.Results) > 0 { // Check if there's any change since the last result if ss.Results[len(ss.Results)-1].Success != result.Success { - ss.Events = append(ss.Events, core.NewEventFromResult(result)) + ss.Events = append(ss.Events, endpoint.NewEventFromResult(result)) if len(ss.Events) > common.MaximumNumberOfEvents { // Doing ss.Events[1:] would usually be sufficient, but in the case where for some reason, the slice has // more than one extra element, we can get rid of all of them at once and thus returning the slice to a @@ -68,7 +68,7 @@ func AddResult(ss *core.EndpointStatus, result *core.Result) { } } else { // This is the first result, so we need to add the first healthy/unhealthy event - ss.Events = append(ss.Events, core.NewEventFromResult(result)) + ss.Events = append(ss.Events, endpoint.NewEventFromResult(result)) } ss.Results = append(ss.Results, result) if len(ss.Results) > common.MaximumNumberOfResults { diff --git a/storage/store/memory/util_bench_test.go b/storage/store/memory/util_bench_test.go index e832f149..36252829 100644 --- a/storage/store/memory/util_bench_test.go +++ b/storage/store/memory/util_bench_test.go @@ -3,14 +3,14 @@ package memory import ( "testing" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" "github.com/TwiN/gatus/v5/storage/store/common" "github.com/TwiN/gatus/v5/storage/store/common/paging" ) func BenchmarkShallowCopyEndpointStatus(b *testing.B) { - endpoint := &testEndpoint - status := core.NewEndpointStatus(endpoint.Group, endpoint.Name) + ep := &testEndpoint + status := endpoint.NewStatus(ep.Group, ep.Name) for i := 0; i < common.MaximumNumberOfResults; i++ { AddResult(status, &testSuccessfulResult) } diff --git a/storage/store/memory/util_test.go b/storage/store/memory/util_test.go index 8f88299e..1de445e7 100644 --- a/storage/store/memory/util_test.go +++ b/storage/store/memory/util_test.go @@ -4,16 +4,16 @@ import ( "testing" "time" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" "github.com/TwiN/gatus/v5/storage/store/common" "github.com/TwiN/gatus/v5/storage/store/common/paging" ) func TestAddResult(t *testing.T) { - endpoint := &core.Endpoint{Name: "name", Group: "group"} - endpointStatus := core.NewEndpointStatus(endpoint.Group, endpoint.Name) + ep := &endpoint.Endpoint{Name: "name", Group: "group"} + endpointStatus := endpoint.NewStatus(ep.Group, ep.Name) for i := 0; i < (common.MaximumNumberOfResults+common.MaximumNumberOfEvents)*2; i++ { - AddResult(endpointStatus, &core.Result{Success: i%2 == 0, Timestamp: time.Now()}) + AddResult(endpointStatus, &endpoint.Result{Success: i%2 == 0, Timestamp: time.Now()}) } if len(endpointStatus.Results) != common.MaximumNumberOfResults { t.Errorf("expected endpointStatus.Results to not exceed a length of %d", common.MaximumNumberOfResults) @@ -22,15 +22,15 @@ func TestAddResult(t *testing.T) { t.Errorf("expected endpointStatus.Events to not exceed a length of %d", common.MaximumNumberOfEvents) } // Try to add nil endpointStatus - AddResult(nil, &core.Result{Timestamp: time.Now()}) + AddResult(nil, &endpoint.Result{Timestamp: time.Now()}) } func TestShallowCopyEndpointStatus(t *testing.T) { - endpoint := &core.Endpoint{Name: "name", Group: "group"} - endpointStatus := core.NewEndpointStatus(endpoint.Group, endpoint.Name) + ep := &endpoint.Endpoint{Name: "name", Group: "group"} + endpointStatus := endpoint.NewStatus(ep.Group, ep.Name) ts := time.Now().Add(-25 * time.Hour) for i := 0; i < 25; i++ { - AddResult(endpointStatus, &core.Result{Success: i%2 == 0, Timestamp: ts}) + AddResult(endpointStatus, &endpoint.Result{Success: i%2 == 0, Timestamp: ts}) ts = ts.Add(time.Hour) } if len(ShallowCopyEndpointStatus(endpointStatus, paging.NewEndpointStatusParams().WithResults(-1, -1)).Results) != 0 { diff --git a/storage/store/sql/sql.go b/storage/store/sql/sql.go index cd94c824..ec25b564 100644 --- a/storage/store/sql/sql.go +++ b/storage/store/sql/sql.go @@ -9,10 +9,9 @@ import ( "strings" "time" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" "github.com/TwiN/gatus/v5/storage/store/common" "github.com/TwiN/gatus/v5/storage/store/common/paging" - "github.com/TwiN/gatus/v5/util" "github.com/TwiN/gocache/v2" _ "github.com/lib/pq" _ "modernc.org/sqlite" @@ -100,9 +99,9 @@ func (s *Store) createSchema() error { return s.createPostgresSchema() } -// GetAllEndpointStatuses returns all monitored core.EndpointStatus -// with a subset of core.Result defined by the page and pageSize parameters -func (s *Store) GetAllEndpointStatuses(params *paging.EndpointStatusParams) ([]*core.EndpointStatus, error) { +// GetAllEndpointStatuses returns all monitored endpoint.Status +// with a subset of endpoint.Result defined by the page and pageSize parameters +func (s *Store) GetAllEndpointStatuses(params *paging.EndpointStatusParams) ([]*endpoint.Status, error) { tx, err := s.db.Begin() if err != nil { return nil, err @@ -112,7 +111,7 @@ func (s *Store) GetAllEndpointStatuses(params *paging.EndpointStatusParams) ([]* _ = tx.Rollback() return nil, err } - endpointStatuses := make([]*core.EndpointStatus, 0, len(keys)) + endpointStatuses := make([]*endpoint.Status, 0, len(keys)) for _, key := range keys { endpointStatus, err := s.getEndpointStatusByKey(tx, key, params) if err != nil { @@ -127,12 +126,12 @@ func (s *Store) GetAllEndpointStatuses(params *paging.EndpointStatusParams) ([]* } // GetEndpointStatus returns the endpoint status for a given endpoint name in the given group -func (s *Store) GetEndpointStatus(groupName, endpointName string, params *paging.EndpointStatusParams) (*core.EndpointStatus, error) { - return s.GetEndpointStatusByKey(util.ConvertGroupAndEndpointNameToKey(groupName, endpointName), params) +func (s *Store) GetEndpointStatus(groupName, endpointName string, params *paging.EndpointStatusParams) (*endpoint.Status, error) { + return s.GetEndpointStatusByKey(endpoint.ConvertGroupAndEndpointNameToKey(groupName, endpointName), params) } // GetEndpointStatusByKey returns the endpoint status for a given key -func (s *Store) GetEndpointStatusByKey(key string, params *paging.EndpointStatusParams) (*core.EndpointStatus, error) { +func (s *Store) GetEndpointStatusByKey(key string, params *paging.EndpointStatusParams) (*endpoint.Status, error) { tx, err := s.db.Begin() if err != nil { return nil, err @@ -224,30 +223,30 @@ func (s *Store) GetHourlyAverageResponseTimeByKey(key string, from, to time.Time } // Insert adds the observed result for the specified endpoint into the store -func (s *Store) Insert(endpoint *core.Endpoint, result *core.Result) error { +func (s *Store) Insert(ep *endpoint.Endpoint, result *endpoint.Result) error { tx, err := s.db.Begin() if err != nil { return err } - endpointID, err := s.getEndpointID(tx, endpoint) + endpointID, err := s.getEndpointID(tx, ep) if err != nil { if errors.Is(err, common.ErrEndpointNotFound) { // Endpoint doesn't exist in the database, insert it - if endpointID, err = s.insertEndpoint(tx, endpoint); err != nil { + if endpointID, err = s.insertEndpoint(tx, ep); err != nil { _ = tx.Rollback() - log.Printf("[sql.Insert] Failed to create endpoint with group=%s; endpoint=%s: %s", endpoint.Group, endpoint.Name, err.Error()) + log.Printf("[sql.Insert] Failed to create endpoint with group=%s; endpoint=%s: %s", ep.Group, ep.Name, err.Error()) return err } } else { _ = tx.Rollback() - log.Printf("[sql.Insert] Failed to retrieve id of endpoint with group=%s; endpoint=%s: %s", endpoint.Group, endpoint.Name, err.Error()) + log.Printf("[sql.Insert] Failed to retrieve id of endpoint with group=%s; endpoint=%s: %s", ep.Group, ep.Name, err.Error()) return err } } // First, we need to check if we need to insert a new event. // // A new event must be added if either of the following cases happen: - // 1. There is only 1 event. The total number of events for a endpoint can only be 1 if the only existing event is + // 1. There is only 1 event. The total number of events for an endpoint can only be 1 if the only existing event is // of type EventStart, in which case we will have to create a new event of type EventHealthy or EventUnhealthy // based on result.Success. // 2. The lastResult.Success != result.Success. This implies that the endpoint went from healthy to unhealthy or @@ -256,38 +255,38 @@ func (s *Store) Insert(endpoint *core.Endpoint, result *core.Result) error { numberOfEvents, err := s.getNumberOfEventsByEndpointID(tx, endpointID) if err != nil { // Silently fail - log.Printf("[sql.Insert] Failed to retrieve total number of events for group=%s; endpoint=%s: %s", endpoint.Group, endpoint.Name, err.Error()) + log.Printf("[sql.Insert] Failed to retrieve total number of events for group=%s; endpoint=%s: %s", ep.Group, ep.Name, err.Error()) } if numberOfEvents == 0 { // There's no events yet, which means we need to add the EventStart and the first healthy/unhealthy event - err = s.insertEndpointEvent(tx, endpointID, &core.Event{ - Type: core.EventStart, + err = s.insertEndpointEvent(tx, endpointID, &endpoint.Event{ + Type: endpoint.EventStart, Timestamp: result.Timestamp.Add(-50 * time.Millisecond), }) if err != nil { // Silently fail - log.Printf("[sql.Insert] Failed to insert event=%s for group=%s; endpoint=%s: %s", core.EventStart, endpoint.Group, endpoint.Name, err.Error()) + log.Printf("[sql.Insert] Failed to insert event=%s for group=%s; endpoint=%s: %s", endpoint.EventStart, ep.Group, ep.Name, err.Error()) } - event := core.NewEventFromResult(result) + event := endpoint.NewEventFromResult(result) if err = s.insertEndpointEvent(tx, endpointID, event); err != nil { // Silently fail - log.Printf("[sql.Insert] Failed to insert event=%s for group=%s; endpoint=%s: %s", event.Type, endpoint.Group, endpoint.Name, err.Error()) + log.Printf("[sql.Insert] Failed to insert event=%s for group=%s; endpoint=%s: %s", event.Type, ep.Group, ep.Name, err.Error()) } } else { // Get the success value of the previous result var lastResultSuccess bool if lastResultSuccess, err = s.getLastEndpointResultSuccessValue(tx, endpointID); err != nil { - log.Printf("[sql.Insert] Failed to retrieve outcome of previous result for group=%s; endpoint=%s: %s", endpoint.Group, endpoint.Name, err.Error()) + log.Printf("[sql.Insert] Failed to retrieve outcome of previous result for group=%s; endpoint=%s: %s", ep.Group, ep.Name, err.Error()) } else { // If we managed to retrieve the outcome of the previous result, we'll compare it with the new result. // If the final outcome (success or failure) of the previous and the new result aren't the same, it means // that the endpoint either went from Healthy to Unhealthy or Unhealthy -> Healthy, therefore, we'll add // an event to mark the change in state if lastResultSuccess != result.Success { - event := core.NewEventFromResult(result) + event := endpoint.NewEventFromResult(result) if err = s.insertEndpointEvent(tx, endpointID, event); err != nil { // Silently fail - log.Printf("[sql.Insert] Failed to insert event=%s for group=%s; endpoint=%s: %s", event.Type, endpoint.Group, endpoint.Name, err.Error()) + log.Printf("[sql.Insert] Failed to insert event=%s for group=%s; endpoint=%s: %s", event.Type, ep.Group, ep.Name, err.Error()) } } } @@ -296,45 +295,45 @@ func (s *Store) Insert(endpoint *core.Endpoint, result *core.Result) error { // (since we're only deleting MaximumNumberOfEvents at a time instead of 1) if numberOfEvents > eventsCleanUpThreshold { if err = s.deleteOldEndpointEvents(tx, endpointID); err != nil { - log.Printf("[sql.Insert] Failed to delete old events for group=%s; endpoint=%s: %s", endpoint.Group, endpoint.Name, err.Error()) + log.Printf("[sql.Insert] Failed to delete old events for group=%s; endpoint=%s: %s", ep.Group, ep.Name, err.Error()) } } } // Second, we need to insert the result. if err = s.insertEndpointResult(tx, endpointID, result); err != nil { - log.Printf("[sql.Insert] Failed to insert result for group=%s; endpoint=%s: %s", endpoint.Group, endpoint.Name, err.Error()) + log.Printf("[sql.Insert] Failed to insert result for group=%s; endpoint=%s: %s", ep.Group, ep.Name, err.Error()) _ = tx.Rollback() // If we can't insert the result, we'll rollback now since there's no point continuing return err } // Clean up old results numberOfResults, err := s.getNumberOfResultsByEndpointID(tx, endpointID) if err != nil { - log.Printf("[sql.Insert] Failed to retrieve total number of results for group=%s; endpoint=%s: %s", endpoint.Group, endpoint.Name, err.Error()) + log.Printf("[sql.Insert] Failed to retrieve total number of results for group=%s; endpoint=%s: %s", ep.Group, ep.Name, err.Error()) } else { if numberOfResults > resultsCleanUpThreshold { if err = s.deleteOldEndpointResults(tx, endpointID); err != nil { - log.Printf("[sql.Insert] Failed to delete old results for group=%s; endpoint=%s: %s", endpoint.Group, endpoint.Name, err.Error()) + log.Printf("[sql.Insert] Failed to delete old results for group=%s; endpoint=%s: %s", ep.Group, ep.Name, err.Error()) } } } // Finally, we need to insert the uptime data. // Because the uptime data significantly outlives the results, we can't rely on the results for determining the uptime if err = s.updateEndpointUptime(tx, endpointID, result); err != nil { - log.Printf("[sql.Insert] Failed to update uptime for group=%s; endpoint=%s: %s", endpoint.Group, endpoint.Name, err.Error()) + log.Printf("[sql.Insert] Failed to update uptime for group=%s; endpoint=%s: %s", ep.Group, ep.Name, err.Error()) } // Clean up old uptime entries ageOfOldestUptimeEntry, err := s.getAgeOfOldestEndpointUptimeEntry(tx, endpointID) if err != nil { - log.Printf("[sql.Insert] Failed to retrieve oldest endpoint uptime entry for group=%s; endpoint=%s: %s", endpoint.Group, endpoint.Name, err.Error()) + log.Printf("[sql.Insert] Failed to retrieve oldest endpoint uptime entry for group=%s; endpoint=%s: %s", ep.Group, ep.Name, err.Error()) } else { if ageOfOldestUptimeEntry > uptimeCleanUpThreshold { if err = s.deleteOldUptimeEntries(tx, endpointID, time.Now().Add(-(uptimeRetention + time.Hour))); err != nil { - log.Printf("[sql.Insert] Failed to delete old uptime entries for group=%s; endpoint=%s: %s", endpoint.Group, endpoint.Name, err.Error()) + log.Printf("[sql.Insert] Failed to delete old uptime entries for group=%s; endpoint=%s: %s", ep.Group, ep.Name, err.Error()) } } } if s.writeThroughCache != nil { - cacheKeysToRefresh := s.writeThroughCache.GetKeysByPattern(endpoint.Key()+"*", 0) + cacheKeysToRefresh := s.writeThroughCache.GetKeysByPattern(ep.Key()+"*", 0) for _, cacheKey := range cacheKeysToRefresh { s.writeThroughCache.Delete(cacheKey) endpointKey, params, err := extractKeyAndParamsFromCacheKey(cacheKey) @@ -405,14 +404,14 @@ func (s *Store) Close() { } // insertEndpoint inserts an endpoint in the store and returns the generated id of said endpoint -func (s *Store) insertEndpoint(tx *sql.Tx, endpoint *core.Endpoint) (int64, error) { - //log.Printf("[sql.insertEndpoint] Inserting endpoint with group=%s and name=%s", endpoint.Group, endpoint.Name) +func (s *Store) insertEndpoint(tx *sql.Tx, ep *endpoint.Endpoint) (int64, error) { + //log.Printf("[sql.insertEndpoint] Inserting endpoint with group=%s and name=%s", ep.Group, ep.Name) var id int64 err := tx.QueryRow( "INSERT INTO endpoints (endpoint_key, endpoint_name, endpoint_group) VALUES ($1, $2, $3) RETURNING endpoint_id", - endpoint.Key(), - endpoint.Name, - endpoint.Group, + ep.Key(), + ep.Name, + ep.Group, ).Scan(&id) if err != nil { return 0, err @@ -421,7 +420,7 @@ func (s *Store) insertEndpoint(tx *sql.Tx, endpoint *core.Endpoint) (int64, erro } // insertEndpointEvent inserts en event in the store -func (s *Store) insertEndpointEvent(tx *sql.Tx, endpointID int64, event *core.Event) error { +func (s *Store) insertEndpointEvent(tx *sql.Tx, endpointID int64, event *endpoint.Event) error { _, err := tx.Exec( "INSERT INTO endpoint_events (endpoint_id, event_type, event_timestamp) VALUES ($1, $2, $3)", endpointID, @@ -435,7 +434,7 @@ func (s *Store) insertEndpointEvent(tx *sql.Tx, endpointID int64, event *core.Ev } // insertEndpointResult inserts a result in the store -func (s *Store) insertEndpointResult(tx *sql.Tx, endpointID int64, result *core.Result) error { +func (s *Store) insertEndpointResult(tx *sql.Tx, endpointID int64, result *endpoint.Result) error { var endpointResultID int64 err := tx.QueryRow( ` @@ -462,7 +461,7 @@ func (s *Store) insertEndpointResult(tx *sql.Tx, endpointID int64, result *core. return s.insertConditionResults(tx, endpointResultID, result.ConditionResults) } -func (s *Store) insertConditionResults(tx *sql.Tx, endpointResultID int64, conditionResults []*core.ConditionResult) error { +func (s *Store) insertConditionResults(tx *sql.Tx, endpointResultID int64, conditionResults []*endpoint.ConditionResult) error { var err error for _, cr := range conditionResults { _, err = tx.Exec("INSERT INTO endpoint_result_conditions (endpoint_result_id, condition, success) VALUES ($1, $2, $3)", @@ -477,7 +476,7 @@ func (s *Store) insertConditionResults(tx *sql.Tx, endpointResultID int64, condi return nil } -func (s *Store) updateEndpointUptime(tx *sql.Tx, endpointID int64, result *core.Result) error { +func (s *Store) updateEndpointUptime(tx *sql.Tx, endpointID int64, result *endpoint.Result) error { unixTimestampFlooredAtHour := result.Timestamp.Truncate(time.Hour).Unix() var successfulExecutions int if result.Success { @@ -514,12 +513,12 @@ func (s *Store) getAllEndpointKeys(tx *sql.Tx) (keys []string, err error) { return } -func (s *Store) getEndpointStatusByKey(tx *sql.Tx, key string, parameters *paging.EndpointStatusParams) (*core.EndpointStatus, error) { +func (s *Store) getEndpointStatusByKey(tx *sql.Tx, key string, parameters *paging.EndpointStatusParams) (*endpoint.Status, error) { var cacheKey string if s.writeThroughCache != nil { cacheKey = generateCacheKey(key, parameters) if cachedEndpointStatus, exists := s.writeThroughCache.Get(cacheKey); exists { - if castedCachedEndpointStatus, ok := cachedEndpointStatus.(*core.EndpointStatus); ok { + if castedCachedEndpointStatus, ok := cachedEndpointStatus.(*endpoint.Status); ok { return castedCachedEndpointStatus, nil } } @@ -528,7 +527,7 @@ func (s *Store) getEndpointStatusByKey(tx *sql.Tx, key string, parameters *pagin if err != nil { return nil, err } - endpointStatus := core.NewEndpointStatus(group, endpointName) + endpointStatus := endpoint.NewStatus(group, endpointName) if parameters.EventsPageSize > 0 { if endpointStatus.Events, err = s.getEndpointEventsByEndpointID(tx, endpointID, parameters.EventsPage, parameters.EventsPageSize); err != nil { log.Printf("[sql.getEndpointStatusByKey] Failed to retrieve events for key=%s: %s", key, err.Error()) @@ -564,7 +563,7 @@ func (s *Store) getEndpointIDGroupAndNameByKey(tx *sql.Tx, key string) (id int64 return } -func (s *Store) getEndpointEventsByEndpointID(tx *sql.Tx, endpointID int64, page, pageSize int) (events []*core.Event, err error) { +func (s *Store) getEndpointEventsByEndpointID(tx *sql.Tx, endpointID int64, page, pageSize int) (events []*endpoint.Event, err error) { rows, err := tx.Query( ` SELECT event_type, event_timestamp @@ -581,14 +580,14 @@ func (s *Store) getEndpointEventsByEndpointID(tx *sql.Tx, endpointID int64, page return nil, err } for rows.Next() { - event := &core.Event{} + event := &endpoint.Event{} _ = rows.Scan(&event.Type, &event.Timestamp) events = append(events, event) } return } -func (s *Store) getEndpointResultsByEndpointID(tx *sql.Tx, endpointID int64, page, pageSize int) (results []*core.Result, err error) { +func (s *Store) getEndpointResultsByEndpointID(tx *sql.Tx, endpointID int64, page, pageSize int) (results []*endpoint.Result, err error) { rows, err := tx.Query( ` SELECT endpoint_result_id, success, errors, connected, status, dns_rcode, certificate_expiration, domain_expiration, hostname, ip, duration, timestamp @@ -604,9 +603,9 @@ func (s *Store) getEndpointResultsByEndpointID(tx *sql.Tx, endpointID int64, pag if err != nil { return nil, err } - idResultMap := make(map[int64]*core.Result) + idResultMap := make(map[int64]*endpoint.Result) for rows.Next() { - result := &core.Result{} + result := &endpoint.Result{} var id int64 var joinedErrors string err = rows.Scan(&id, &result.Success, &joinedErrors, &result.Connected, &result.HTTPStatus, &result.DNSRCode, &result.CertificateExpiration, &result.DomainExpiration, &result.Hostname, &result.IP, &result.Duration, &result.Timestamp) @@ -618,7 +617,7 @@ func (s *Store) getEndpointResultsByEndpointID(tx *sql.Tx, endpointID int64, pag result.Errors = strings.Split(joinedErrors, arraySeparator) } // This is faster than using a subselect - results = append([]*core.Result{result}, results...) + results = append([]*endpoint.Result{result}, results...) idResultMap[id] = result } if len(idResultMap) == 0 { @@ -643,7 +642,7 @@ func (s *Store) getEndpointResultsByEndpointID(tx *sql.Tx, endpointID int64, pag } defer rows.Close() // explicitly defer the close in case an error happens during the scan for rows.Next() { - conditionResult := &core.ConditionResult{} + conditionResult := &endpoint.ConditionResult{} var endpointResultID int64 if err = rows.Scan(&endpointResultID, &conditionResult.Condition, &conditionResult.Success); err != nil { return @@ -734,9 +733,9 @@ func (s *Store) getEndpointHourlyAverageResponseTimes(tx *sql.Tx, endpointID int return hourlyAverageResponseTimes, nil } -func (s *Store) getEndpointID(tx *sql.Tx, endpoint *core.Endpoint) (int64, error) { +func (s *Store) getEndpointID(tx *sql.Tx, ep *endpoint.Endpoint) (int64, error) { var id int64 - err := tx.QueryRow("SELECT endpoint_id FROM endpoints WHERE endpoint_key = $1", endpoint.Key()).Scan(&id) + err := tx.QueryRow("SELECT endpoint_id FROM endpoints WHERE endpoint_key = $1", ep.Key()).Scan(&id) if err != nil { if errors.Is(err, sql.ErrNoRows) { return 0, common.ErrEndpointNotFound diff --git a/storage/store/sql/sql_test.go b/storage/store/sql/sql_test.go index cfd3c8bd..da611a4a 100644 --- a/storage/store/sql/sql_test.go +++ b/storage/store/sql/sql_test.go @@ -4,31 +4,31 @@ import ( "testing" "time" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" "github.com/TwiN/gatus/v5/storage/store/common" "github.com/TwiN/gatus/v5/storage/store/common/paging" ) var ( - firstCondition = core.Condition("[STATUS] == 200") - secondCondition = core.Condition("[RESPONSE_TIME] < 500") - thirdCondition = core.Condition("[CERTIFICATE_EXPIRATION] < 72h") + firstCondition = endpoint.Condition("[STATUS] == 200") + secondCondition = endpoint.Condition("[RESPONSE_TIME] < 500") + thirdCondition = endpoint.Condition("[CERTIFICATE_EXPIRATION] < 72h") now = time.Now() - testEndpoint = core.Endpoint{ + testEndpoint = endpoint.Endpoint{ Name: "name", Group: "group", URL: "https://example.org/what/ever", Method: "GET", Body: "body", Interval: 30 * time.Second, - Conditions: []core.Condition{firstCondition, secondCondition, thirdCondition}, + Conditions: []endpoint.Condition{firstCondition, secondCondition, thirdCondition}, Alerts: nil, NumberOfFailuresInARow: 0, NumberOfSuccessesInARow: 0, } - testSuccessfulResult = core.Result{ + testSuccessfulResult = endpoint.Result{ Hostname: "example.org", IP: "127.0.0.1", HTTPStatus: 200, @@ -38,7 +38,7 @@ var ( Timestamp: now, Duration: 150 * time.Millisecond, CertificateExpiration: 10 * time.Hour, - ConditionResults: []*core.ConditionResult{ + ConditionResults: []*endpoint.ConditionResult{ { Condition: "[STATUS] == 200", Success: true, @@ -53,7 +53,7 @@ var ( }, }, } - testUnsuccessfulResult = core.Result{ + testUnsuccessfulResult = endpoint.Result{ Hostname: "example.org", IP: "127.0.0.1", HTTPStatus: 200, @@ -63,7 +63,7 @@ var ( Timestamp: now, Duration: 750 * time.Millisecond, CertificateExpiration: 10 * time.Hour, - ConditionResults: []*core.ConditionResult{ + ConditionResults: []*endpoint.ConditionResult{ { Condition: "[STATUS] == 200", Success: true, @@ -100,7 +100,7 @@ func TestStore_InsertCleansUpOldUptimeEntriesProperly(t *testing.T) { now := time.Now().Truncate(time.Hour) now = time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), 0, 0, 0, now.Location()) - store.Insert(&testEndpoint, &core.Result{Timestamp: now.Add(-5 * time.Hour), Success: true}) + store.Insert(&testEndpoint, &endpoint.Result{Timestamp: now.Add(-5 * time.Hour), Success: true}) tx, _ := store.db.Begin() oldest, _ := store.getAgeOfOldestEndpointUptimeEntry(tx, 1) @@ -110,7 +110,7 @@ func TestStore_InsertCleansUpOldUptimeEntriesProperly(t *testing.T) { } // The oldest cache entry should remain at ~5 hours old, because this entry is more recent - store.Insert(&testEndpoint, &core.Result{Timestamp: now.Add(-3 * time.Hour), Success: true}) + store.Insert(&testEndpoint, &endpoint.Result{Timestamp: now.Add(-3 * time.Hour), Success: true}) tx, _ = store.db.Begin() oldest, _ = store.getAgeOfOldestEndpointUptimeEntry(tx, 1) @@ -120,7 +120,7 @@ func TestStore_InsertCleansUpOldUptimeEntriesProperly(t *testing.T) { } // The oldest cache entry should now become at ~8 hours old, because this entry is older - store.Insert(&testEndpoint, &core.Result{Timestamp: now.Add(-8 * time.Hour), Success: true}) + store.Insert(&testEndpoint, &endpoint.Result{Timestamp: now.Add(-8 * time.Hour), Success: true}) tx, _ = store.db.Begin() oldest, _ = store.getAgeOfOldestEndpointUptimeEntry(tx, 1) @@ -130,7 +130,7 @@ func TestStore_InsertCleansUpOldUptimeEntriesProperly(t *testing.T) { } // Since this is one hour before reaching the clean up threshold, the oldest entry should now be this one - store.Insert(&testEndpoint, &core.Result{Timestamp: now.Add(-(uptimeCleanUpThreshold - time.Hour)), Success: true}) + store.Insert(&testEndpoint, &endpoint.Result{Timestamp: now.Add(-(uptimeCleanUpThreshold - time.Hour)), Success: true}) tx, _ = store.db.Begin() oldest, _ = store.getAgeOfOldestEndpointUptimeEntry(tx, 1) @@ -141,7 +141,7 @@ func TestStore_InsertCleansUpOldUptimeEntriesProperly(t *testing.T) { // Since this entry is after the uptimeCleanUpThreshold, both this entry as well as the previous // one should be deleted since they both surpass uptimeRetention - store.Insert(&testEndpoint, &core.Result{Timestamp: now.Add(-(uptimeCleanUpThreshold + time.Hour)), Success: true}) + store.Insert(&testEndpoint, &endpoint.Result{Timestamp: now.Add(-(uptimeCleanUpThreshold + time.Hour)), Success: true}) tx, _ = store.db.Begin() oldest, _ = store.getAgeOfOldestEndpointUptimeEntry(tx, 1) @@ -313,7 +313,7 @@ func TestStore_InvalidTransaction(t *testing.T) { if _, err := store.insertEndpoint(tx, &testEndpoint); err == nil { t.Error("should've returned an error, because the transaction was already committed") } - if err := store.insertEndpointEvent(tx, 1, core.NewEventFromResult(&testSuccessfulResult)); err == nil { + if err := store.insertEndpointEvent(tx, 1, endpoint.NewEventFromResult(&testSuccessfulResult)); err == nil { t.Error("should've returned an error, because the transaction was already committed") } if err := store.insertEndpointResult(tx, 1, &testSuccessfulResult); err == nil { diff --git a/storage/store/store.go b/storage/store/store.go index f4d1d986..4dea6dcf 100644 --- a/storage/store/store.go +++ b/storage/store/store.go @@ -5,7 +5,7 @@ import ( "log" "time" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" "github.com/TwiN/gatus/v5/storage" "github.com/TwiN/gatus/v5/storage/store/common/paging" "github.com/TwiN/gatus/v5/storage/store/memory" @@ -14,15 +14,15 @@ import ( // Store is the interface that each store should implement type Store interface { - // GetAllEndpointStatuses returns the JSON encoding of all monitored core.EndpointStatus - // with a subset of core.Result defined by the page and pageSize parameters - GetAllEndpointStatuses(params *paging.EndpointStatusParams) ([]*core.EndpointStatus, error) + // GetAllEndpointStatuses returns the JSON encoding of all monitored endpoint.Status + // with a subset of endpoint.Result defined by the page and pageSize parameters + GetAllEndpointStatuses(params *paging.EndpointStatusParams) ([]*endpoint.Status, error) // GetEndpointStatus returns the endpoint status for a given endpoint name in the given group - GetEndpointStatus(groupName, endpointName string, params *paging.EndpointStatusParams) (*core.EndpointStatus, error) + GetEndpointStatus(groupName, endpointName string, params *paging.EndpointStatusParams) (*endpoint.Status, error) // GetEndpointStatusByKey returns the endpoint status for a given key - GetEndpointStatusByKey(key string, params *paging.EndpointStatusParams) (*core.EndpointStatus, error) + GetEndpointStatusByKey(key string, params *paging.EndpointStatusParams) (*endpoint.Status, error) // GetUptimeByKey returns the uptime percentage during a time range GetUptimeByKey(key string, from, to time.Time) (float64, error) @@ -34,9 +34,9 @@ type Store interface { GetHourlyAverageResponseTimeByKey(key string, from, to time.Time) (map[int64]int, error) // Insert adds the observed result for the specified endpoint into the store - Insert(endpoint *core.Endpoint, result *core.Result) error + Insert(ep *endpoint.Endpoint, result *endpoint.Result) error - // DeleteAllEndpointStatusesNotInKeys removes all EndpointStatus that are not within the keys provided + // DeleteAllEndpointStatusesNotInKeys removes all Status that are not within the keys provided // // Used to delete endpoints that have been persisted but are no longer part of the configured endpoints DeleteAllEndpointStatusesNotInKeys(keys []string) int diff --git a/storage/store/store_bench_test.go b/storage/store/store_bench_test.go index 67a8e06c..0ed4c2db 100644 --- a/storage/store/store_bench_test.go +++ b/storage/store/store_bench_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" "github.com/TwiN/gatus/v5/storage/store/common/paging" "github.com/TwiN/gatus/v5/storage/store/memory" "github.com/TwiN/gatus/v5/storage/store/sql" @@ -53,11 +53,11 @@ func BenchmarkStore_GetAllEndpointStatuses(b *testing.B) { for _, numberOfEndpointsToCreate := range numberOfEndpoints { // Create endpoints and insert results for i := 0; i < numberOfEndpointsToCreate; i++ { - endpoint := testEndpoint - endpoint.Name = "endpoint" + strconv.Itoa(i) + ep := testEndpoint + ep.Name = "endpoint" + strconv.Itoa(i) // Insert 20 results for each endpoint for j := 0; j < 20; j++ { - scenario.Store.Insert(&endpoint, &testSuccessfulResult) + scenario.Store.Insert(&ep, &testSuccessfulResult) } } // Run the scenarios @@ -123,7 +123,7 @@ func BenchmarkStore_Insert(b *testing.B) { b.RunParallel(func(pb *testing.PB) { n := 0 for pb.Next() { - var result core.Result + var result endpoint.Result if n%10 == 0 { result = testUnsuccessfulResult } else { @@ -136,7 +136,7 @@ func BenchmarkStore_Insert(b *testing.B) { }) } else { for n := 0; n < b.N; n++ { - var result core.Result + var result endpoint.Result if n%10 == 0 { result = testUnsuccessfulResult } else { diff --git a/storage/store/store_test.go b/storage/store/store_test.go index 8aec8182..6f18f446 100644 --- a/storage/store/store_test.go +++ b/storage/store/store_test.go @@ -1,11 +1,12 @@ package store import ( + "errors" "path/filepath" "testing" "time" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" "github.com/TwiN/gatus/v5/storage" "github.com/TwiN/gatus/v5/storage/store/common" "github.com/TwiN/gatus/v5/storage/store/common/paging" @@ -14,25 +15,25 @@ import ( ) var ( - firstCondition = core.Condition("[STATUS] == 200") - secondCondition = core.Condition("[RESPONSE_TIME] < 500") - thirdCondition = core.Condition("[CERTIFICATE_EXPIRATION] < 72h") + firstCondition = endpoint.Condition("[STATUS] == 200") + secondCondition = endpoint.Condition("[RESPONSE_TIME] < 500") + thirdCondition = endpoint.Condition("[CERTIFICATE_EXPIRATION] < 72h") now = time.Now().Truncate(time.Hour) - testEndpoint = core.Endpoint{ + testEndpoint = endpoint.Endpoint{ Name: "name", Group: "group", URL: "https://example.org/what/ever", Method: "GET", Body: "body", Interval: 30 * time.Second, - Conditions: []core.Condition{firstCondition, secondCondition, thirdCondition}, + Conditions: []endpoint.Condition{firstCondition, secondCondition, thirdCondition}, Alerts: nil, NumberOfFailuresInARow: 0, NumberOfSuccessesInARow: 0, } - testSuccessfulResult = core.Result{ + testSuccessfulResult = endpoint.Result{ Timestamp: now, Success: true, Hostname: "example.org", @@ -42,7 +43,7 @@ var ( Connected: true, Duration: 150 * time.Millisecond, CertificateExpiration: 10 * time.Hour, - ConditionResults: []*core.ConditionResult{ + ConditionResults: []*endpoint.ConditionResult{ { Condition: "[STATUS] == 200", Success: true, @@ -57,7 +58,7 @@ var ( }, }, } - testUnsuccessfulResult = core.Result{ + testUnsuccessfulResult = endpoint.Result{ Timestamp: now, Success: false, Hostname: "example.org", @@ -67,7 +68,7 @@ var ( Connected: true, Duration: 750 * time.Millisecond, CertificateExpiration: 10 * time.Hour, - ConditionResults: []*core.ConditionResult{ + ConditionResults: []*endpoint.ConditionResult{ { Condition: "[STATUS] == 200", Success: true, @@ -176,21 +177,21 @@ func TestStore_GetEndpointStatusForMissingStatusReturnsNil(t *testing.T) { t.Run(scenario.Name, func(t *testing.T) { scenario.Store.Insert(&testEndpoint, &testSuccessfulResult) endpointStatus, err := scenario.Store.GetEndpointStatus("nonexistantgroup", "nonexistantname", paging.NewEndpointStatusParams().WithEvents(1, common.MaximumNumberOfEvents).WithResults(1, common.MaximumNumberOfResults)) - if err != common.ErrEndpointNotFound { + if !errors.Is(err, common.ErrEndpointNotFound) { t.Error("should've returned ErrEndpointNotFound, got", err) } if endpointStatus != nil { t.Errorf("Returned endpoint status for group '%s' and name '%s' not nil after inserting the endpoint into the store", testEndpoint.Group, testEndpoint.Name) } endpointStatus, err = scenario.Store.GetEndpointStatus(testEndpoint.Group, "nonexistantname", paging.NewEndpointStatusParams().WithEvents(1, common.MaximumNumberOfEvents).WithResults(1, common.MaximumNumberOfResults)) - if err != common.ErrEndpointNotFound { + if !errors.Is(err, common.ErrEndpointNotFound) { t.Error("should've returned ErrEndpointNotFound, got", err) } if endpointStatus != nil { t.Errorf("Returned endpoint status for group '%s' and name '%s' not nil after inserting the endpoint into the store", testEndpoint.Group, "nonexistantname") } endpointStatus, err = scenario.Store.GetEndpointStatus("nonexistantgroup", testEndpoint.Name, paging.NewEndpointStatusParams().WithEvents(1, common.MaximumNumberOfEvents).WithResults(1, common.MaximumNumberOfResults)) - if err != common.ErrEndpointNotFound { + if !errors.Is(err, common.ErrEndpointNotFound) { t.Error("should've returned ErrEndpointNotFound, got", err) } if endpointStatus != nil { @@ -482,7 +483,7 @@ func TestStore_Insert(t *testing.T) { if len(ss.Results) != 2 { t.Fatalf("Endpoint '%s' should've had 2 results, got %d", ss.Name, len(ss.Results)) } - for i, expectedResult := range []core.Result{firstResult, secondResult} { + for i, expectedResult := range []endpoint.Result{firstResult, secondResult} { if expectedResult.HTTPStatus != ss.Results[i].HTTPStatus { t.Errorf("Result at index %d should've had a HTTPStatus of %d, got %d", i, ss.Results[i].HTTPStatus, expectedResult.HTTPStatus) } @@ -539,13 +540,13 @@ func TestStore_Insert(t *testing.T) { func TestStore_DeleteAllEndpointStatusesNotInKeys(t *testing.T) { scenarios := initStoresAndBaseScenarios(t, "TestStore_DeleteAllEndpointStatusesNotInKeys") defer cleanUp(scenarios) - firstEndpoint := core.Endpoint{Name: "endpoint-1", Group: "group"} - secondEndpoint := core.Endpoint{Name: "endpoint-2", Group: "group"} - result := &testSuccessfulResult + firstEndpoint := endpoint.Endpoint{Name: "endpoint-1", Group: "group"} + secondEndpoint := endpoint.Endpoint{Name: "endpoint-2", Group: "group"} + r := &testSuccessfulResult for _, scenario := range scenarios { t.Run(scenario.Name, func(t *testing.T) { - scenario.Store.Insert(&firstEndpoint, result) - scenario.Store.Insert(&secondEndpoint, result) + scenario.Store.Insert(&firstEndpoint, r) + scenario.Store.Insert(&secondEndpoint, r) if ss, _ := scenario.Store.GetEndpointStatusByKey(firstEndpoint.Key(), paging.NewEndpointStatusParams()); ss == nil { t.Fatal("firstEndpoint should exist, got", ss) } @@ -631,7 +632,7 @@ func TestInitialize(t *testing.T) { store.Close() // Try to initialize it again err = Initialize(scenario.Cfg) - if err != scenario.ExpectedErr { + if !errors.Is(err, scenario.ExpectedErr) { t.Errorf("expected %v, got %v", scenario.ExpectedErr, err) return } diff --git a/watchdog/alerting.go b/watchdog/alerting.go index 29898016..09e73a40 100644 --- a/watchdog/alerting.go +++ b/watchdog/alerting.go @@ -6,48 +6,48 @@ import ( "os" "github.com/TwiN/gatus/v5/alerting" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" ) // HandleAlerting takes care of alerts to resolve and alerts to trigger based on result success or failure -func HandleAlerting(endpoint *core.Endpoint, result *core.Result, alertingConfig *alerting.Config, debug bool) { +func HandleAlerting(ep *endpoint.Endpoint, result *endpoint.Result, alertingConfig *alerting.Config, debug bool) { if alertingConfig == nil { return } if result.Success { - handleAlertsToResolve(endpoint, result, alertingConfig, debug) + handleAlertsToResolve(ep, result, alertingConfig, debug) } else { - handleAlertsToTrigger(endpoint, result, alertingConfig, debug) + handleAlertsToTrigger(ep, result, alertingConfig, debug) } } -func handleAlertsToTrigger(endpoint *core.Endpoint, result *core.Result, alertingConfig *alerting.Config, debug bool) { - endpoint.NumberOfSuccessesInARow = 0 - endpoint.NumberOfFailuresInARow++ - for _, endpointAlert := range endpoint.Alerts { +func handleAlertsToTrigger(ep *endpoint.Endpoint, result *endpoint.Result, alertingConfig *alerting.Config, debug bool) { + ep.NumberOfSuccessesInARow = 0 + ep.NumberOfFailuresInARow++ + for _, endpointAlert := range ep.Alerts { // If the alert hasn't been triggered, move to the next one - if !endpointAlert.IsEnabled() || endpointAlert.FailureThreshold > endpoint.NumberOfFailuresInARow { + if !endpointAlert.IsEnabled() || endpointAlert.FailureThreshold > ep.NumberOfFailuresInARow { continue } if endpointAlert.Triggered { if debug { - log.Printf("[watchdog.handleAlertsToTrigger] Alert for endpoint=%s with description='%s' has already been TRIGGERED, skipping", endpoint.Name, endpointAlert.GetDescription()) + log.Printf("[watchdog.handleAlertsToTrigger] Alert for endpoint=%s with description='%s' has already been TRIGGERED, skipping", ep.Name, endpointAlert.GetDescription()) } continue } alertProvider := alertingConfig.GetAlertingProviderByAlertType(endpointAlert.Type) if alertProvider != nil { - log.Printf("[watchdog.handleAlertsToTrigger] Sending %s alert because alert for endpoint=%s with description='%s' has been TRIGGERED", endpointAlert.Type, endpoint.Name, endpointAlert.GetDescription()) + log.Printf("[watchdog.handleAlertsToTrigger] Sending %s alert because alert for endpoint=%s with description='%s' has been TRIGGERED", endpointAlert.Type, ep.Name, endpointAlert.GetDescription()) var err error if os.Getenv("MOCK_ALERT_PROVIDER") == "true" { if os.Getenv("MOCK_ALERT_PROVIDER_ERROR") == "true" { err = errors.New("error") } } else { - err = alertProvider.Send(endpoint, endpointAlert, result, false) + err = alertProvider.Send(ep, endpointAlert, result, false) } if err != nil { - log.Printf("[watchdog.handleAlertsToTrigger] Failed to send an alert for endpoint=%s: %s", endpoint.Name, err.Error()) + log.Printf("[watchdog.handleAlertsToTrigger] Failed to send an alert for endpoint=%s: %s", ep.Name, err.Error()) } else { endpointAlert.Triggered = true } @@ -57,10 +57,10 @@ func handleAlertsToTrigger(endpoint *core.Endpoint, result *core.Result, alertin } } -func handleAlertsToResolve(endpoint *core.Endpoint, result *core.Result, alertingConfig *alerting.Config, debug bool) { - endpoint.NumberOfSuccessesInARow++ - for _, endpointAlert := range endpoint.Alerts { - if !endpointAlert.IsEnabled() || !endpointAlert.Triggered || endpointAlert.SuccessThreshold > endpoint.NumberOfSuccessesInARow { +func handleAlertsToResolve(ep *endpoint.Endpoint, result *endpoint.Result, alertingConfig *alerting.Config, debug bool) { + ep.NumberOfSuccessesInARow++ + for _, endpointAlert := range ep.Alerts { + if !endpointAlert.IsEnabled() || !endpointAlert.Triggered || endpointAlert.SuccessThreshold > ep.NumberOfSuccessesInARow { continue } // Even if the alert provider returns an error, we still set the alert's Triggered variable to false. @@ -71,14 +71,14 @@ func handleAlertsToResolve(endpoint *core.Endpoint, result *core.Result, alertin } alertProvider := alertingConfig.GetAlertingProviderByAlertType(endpointAlert.Type) if alertProvider != nil { - log.Printf("[watchdog.handleAlertsToResolve] Sending %s alert because alert for endpoint=%s with description='%s' has been RESOLVED", endpointAlert.Type, endpoint.Name, endpointAlert.GetDescription()) - err := alertProvider.Send(endpoint, endpointAlert, result, true) + log.Printf("[watchdog.handleAlertsToResolve] Sending %s alert because alert for endpoint=%s with description='%s' has been RESOLVED", endpointAlert.Type, ep.Name, endpointAlert.GetDescription()) + err := alertProvider.Send(ep, endpointAlert, result, true) if err != nil { - log.Printf("[watchdog.handleAlertsToResolve] Failed to send an alert for endpoint=%s: %s", endpoint.Name, err.Error()) + log.Printf("[watchdog.handleAlertsToResolve] Failed to send an alert for endpoint=%s: %s", ep.Name, err.Error()) } } else { log.Printf("[watchdog.handleAlertsToResolve] Not sending alert of type=%s despite being RESOLVED, because the provider wasn't configured properly", endpointAlert.Type) } } - endpoint.NumberOfFailuresInARow = 0 + ep.NumberOfFailuresInARow = 0 } diff --git a/watchdog/alerting_test.go b/watchdog/alerting_test.go index 358412d8..914355e4 100644 --- a/watchdog/alerting_test.go +++ b/watchdog/alerting_test.go @@ -20,7 +20,7 @@ import ( "github.com/TwiN/gatus/v5/alerting/provider/telegram" "github.com/TwiN/gatus/v5/alerting/provider/twilio" "github.com/TwiN/gatus/v5/config" - "github.com/TwiN/gatus/v5/core" + "github.com/TwiN/gatus/v5/config/endpoint" ) func TestHandleAlerting(t *testing.T) { @@ -37,7 +37,7 @@ func TestHandleAlerting(t *testing.T) { }, } enabled := true - endpoint := &core.Endpoint{ + ep := &endpoint.Endpoint{ URL: "https://example.com", Alerts: []*alert.Alert{ { @@ -51,23 +51,23 @@ func TestHandleAlerting(t *testing.T) { }, } - verify(t, endpoint, 0, 0, false, "The alert shouldn't start triggered") - HandleAlerting(endpoint, &core.Result{Success: false}, cfg.Alerting, cfg.Debug) - verify(t, endpoint, 1, 0, false, "The alert shouldn't have triggered") - HandleAlerting(endpoint, &core.Result{Success: false}, cfg.Alerting, cfg.Debug) - verify(t, endpoint, 2, 0, true, "The alert should've triggered") - HandleAlerting(endpoint, &core.Result{Success: false}, cfg.Alerting, cfg.Debug) - verify(t, endpoint, 3, 0, true, "The alert should still be triggered") - HandleAlerting(endpoint, &core.Result{Success: false}, cfg.Alerting, cfg.Debug) - verify(t, endpoint, 4, 0, true, "The alert should still be triggered") - HandleAlerting(endpoint, &core.Result{Success: true}, cfg.Alerting, cfg.Debug) - verify(t, endpoint, 0, 1, true, "The alert should still be triggered (because endpoint.Alerts[0].SuccessThreshold is 3)") - HandleAlerting(endpoint, &core.Result{Success: true}, cfg.Alerting, cfg.Debug) - verify(t, endpoint, 0, 2, true, "The alert should still be triggered (because endpoint.Alerts[0].SuccessThreshold is 3)") - HandleAlerting(endpoint, &core.Result{Success: true}, cfg.Alerting, cfg.Debug) - verify(t, endpoint, 0, 3, false, "The alert should've been resolved") - HandleAlerting(endpoint, &core.Result{Success: true}, cfg.Alerting, cfg.Debug) - verify(t, endpoint, 0, 4, false, "The alert should no longer be triggered") + verify(t, ep, 0, 0, false, "The alert shouldn't start triggered") + HandleAlerting(ep, &endpoint.Result{Success: false}, cfg.Alerting, cfg.Debug) + verify(t, ep, 1, 0, false, "The alert shouldn't have triggered") + HandleAlerting(ep, &endpoint.Result{Success: false}, cfg.Alerting, cfg.Debug) + verify(t, ep, 2, 0, true, "The alert should've triggered") + HandleAlerting(ep, &endpoint.Result{Success: false}, cfg.Alerting, cfg.Debug) + verify(t, ep, 3, 0, true, "The alert should still be triggered") + HandleAlerting(ep, &endpoint.Result{Success: false}, cfg.Alerting, cfg.Debug) + verify(t, ep, 4, 0, true, "The alert should still be triggered") + HandleAlerting(ep, &endpoint.Result{Success: true}, cfg.Alerting, cfg.Debug) + verify(t, ep, 0, 1, true, "The alert should still be triggered (because endpoint.Alerts[0].SuccessThreshold is 3)") + HandleAlerting(ep, &endpoint.Result{Success: true}, cfg.Alerting, cfg.Debug) + verify(t, ep, 0, 2, true, "The alert should still be triggered (because endpoint.Alerts[0].SuccessThreshold is 3)") + HandleAlerting(ep, &endpoint.Result{Success: true}, cfg.Alerting, cfg.Debug) + verify(t, ep, 0, 3, false, "The alert should've been resolved") + HandleAlerting(ep, &endpoint.Result{Success: true}, cfg.Alerting, cfg.Debug) + verify(t, ep, 0, 4, false, "The alert should no longer be triggered") } func TestHandleAlertingWhenAlertingConfigIsNil(t *testing.T) { @@ -81,7 +81,7 @@ func TestHandleAlertingWithBadAlertProvider(t *testing.T) { defer os.Clearenv() enabled := true - endpoint := &core.Endpoint{ + ep := &endpoint.Endpoint{ URL: "http://example.com", Alerts: []*alert.Alert{ { @@ -95,11 +95,11 @@ func TestHandleAlertingWithBadAlertProvider(t *testing.T) { }, } - verify(t, endpoint, 0, 0, false, "The alert shouldn't start triggered") - HandleAlerting(endpoint, &core.Result{Success: false}, &alerting.Config{}, false) - verify(t, endpoint, 1, 0, false, "The alert shouldn't have triggered") - HandleAlerting(endpoint, &core.Result{Success: false}, &alerting.Config{}, false) - verify(t, endpoint, 2, 0, false, "The alert shouldn't have triggered, because the provider wasn't configured properly") + verify(t, ep, 0, 0, false, "The alert shouldn't start triggered") + HandleAlerting(ep, &endpoint.Result{Success: false}, &alerting.Config{}, false) + verify(t, ep, 1, 0, false, "The alert shouldn't have triggered") + HandleAlerting(ep, &endpoint.Result{Success: false}, &alerting.Config{}, false) + verify(t, ep, 2, 0, false, "The alert shouldn't have triggered, because the provider wasn't configured properly") } func TestHandleAlertingWhenTriggeredAlertIsAlmostResolvedButendpointStartFailingAgain(t *testing.T) { @@ -116,7 +116,7 @@ func TestHandleAlertingWhenTriggeredAlertIsAlmostResolvedButendpointStartFailing }, } enabled := true - endpoint := &core.Endpoint{ + ep := &endpoint.Endpoint{ URL: "https://example.com", Alerts: []*alert.Alert{ { @@ -132,8 +132,8 @@ func TestHandleAlertingWhenTriggeredAlertIsAlmostResolvedButendpointStartFailing } // This test simulate an alert that was already triggered - HandleAlerting(endpoint, &core.Result{Success: false}, cfg.Alerting, cfg.Debug) - verify(t, endpoint, 2, 0, true, "The alert was already triggered at the beginning of this test") + HandleAlerting(ep, &endpoint.Result{Success: false}, cfg.Alerting, cfg.Debug) + verify(t, ep, 2, 0, true, "The alert was already triggered at the beginning of this test") } func TestHandleAlertingWhenTriggeredAlertIsResolvedButSendOnResolvedIsFalse(t *testing.T) { @@ -151,7 +151,7 @@ func TestHandleAlertingWhenTriggeredAlertIsResolvedButSendOnResolvedIsFalse(t *t } enabled := true disabled := false - endpoint := &core.Endpoint{ + ep := &endpoint.Endpoint{ URL: "https://example.com", Alerts: []*alert.Alert{ { @@ -166,8 +166,8 @@ func TestHandleAlertingWhenTriggeredAlertIsResolvedButSendOnResolvedIsFalse(t *t NumberOfFailuresInARow: 1, } - HandleAlerting(endpoint, &core.Result{Success: true}, cfg.Alerting, cfg.Debug) - verify(t, endpoint, 0, 1, false, "The alert should've been resolved") + HandleAlerting(ep, &endpoint.Result{Success: true}, cfg.Alerting, cfg.Debug) + verify(t, ep, 0, 1, false, "The alert should've been resolved") } func TestHandleAlertingWhenTriggeredAlertIsResolvedPagerDuty(t *testing.T) { @@ -183,7 +183,7 @@ func TestHandleAlertingWhenTriggeredAlertIsResolvedPagerDuty(t *testing.T) { }, } enabled := true - endpoint := &core.Endpoint{ + ep := &endpoint.Endpoint{ URL: "https://example.com", Alerts: []*alert.Alert{ { @@ -198,11 +198,11 @@ func TestHandleAlertingWhenTriggeredAlertIsResolvedPagerDuty(t *testing.T) { NumberOfFailuresInARow: 0, } - HandleAlerting(endpoint, &core.Result{Success: false}, cfg.Alerting, cfg.Debug) - verify(t, endpoint, 1, 0, true, "") + HandleAlerting(ep, &endpoint.Result{Success: false}, cfg.Alerting, cfg.Debug) + verify(t, ep, 1, 0, true, "") - HandleAlerting(endpoint, &core.Result{Success: true}, cfg.Alerting, cfg.Debug) - verify(t, endpoint, 0, 1, false, "The alert should've been resolved") + HandleAlerting(ep, &endpoint.Result{Success: true}, cfg.Alerting, cfg.Debug) + verify(t, ep, 0, 1, false, "The alert should've been resolved") } func TestHandleAlertingWhenTriggeredAlertIsResolvedPushover(t *testing.T) { @@ -219,7 +219,7 @@ func TestHandleAlertingWhenTriggeredAlertIsResolvedPushover(t *testing.T) { }, } enabled := true - endpoint := &core.Endpoint{ + ep := &endpoint.Endpoint{ URL: "https://example.com", Alerts: []*alert.Alert{ { @@ -234,11 +234,11 @@ func TestHandleAlertingWhenTriggeredAlertIsResolvedPushover(t *testing.T) { NumberOfFailuresInARow: 0, } - HandleAlerting(endpoint, &core.Result{Success: false}, cfg.Alerting, cfg.Debug) - verify(t, endpoint, 1, 0, true, "") + HandleAlerting(ep, &endpoint.Result{Success: false}, cfg.Alerting, cfg.Debug) + verify(t, ep, 1, 0, true, "") - HandleAlerting(endpoint, &core.Result{Success: true}, cfg.Alerting, cfg.Debug) - verify(t, endpoint, 0, 1, false, "The alert should've been resolved") + HandleAlerting(ep, &endpoint.Result{Success: true}, cfg.Alerting, cfg.Debug) + verify(t, ep, 0, 1, false, "The alert should've been resolved") } func TestHandleAlertingWithProviderThatReturnsAnError(t *testing.T) { @@ -389,7 +389,7 @@ func TestHandleAlertingWithProviderThatReturnsAnError(t *testing.T) { for _, scenario := range scenarios { t.Run(scenario.Name, func(t *testing.T) { - endpoint := &core.Endpoint{ + ep := &endpoint.Endpoint{ URL: "https://example.com", Alerts: []*alert.Alert{ { @@ -403,33 +403,33 @@ func TestHandleAlertingWithProviderThatReturnsAnError(t *testing.T) { }, } _ = os.Setenv("MOCK_ALERT_PROVIDER_ERROR", "true") - HandleAlerting(endpoint, &core.Result{Success: false}, scenario.AlertingConfig, true) - verify(t, endpoint, 1, 0, false, "") - HandleAlerting(endpoint, &core.Result{Success: false}, scenario.AlertingConfig, true) - verify(t, endpoint, 2, 0, false, "The alert should have failed to trigger, because the alert provider is returning an error") - HandleAlerting(endpoint, &core.Result{Success: false}, scenario.AlertingConfig, true) - verify(t, endpoint, 3, 0, false, "The alert should still not be triggered, because the alert provider is still returning an error") - HandleAlerting(endpoint, &core.Result{Success: false}, scenario.AlertingConfig, true) - verify(t, endpoint, 4, 0, false, "The alert should still not be triggered, because the alert provider is still returning an error") + HandleAlerting(ep, &endpoint.Result{Success: false}, scenario.AlertingConfig, true) + verify(t, ep, 1, 0, false, "") + HandleAlerting(ep, &endpoint.Result{Success: false}, scenario.AlertingConfig, true) + verify(t, ep, 2, 0, false, "The alert should have failed to trigger, because the alert provider is returning an error") + HandleAlerting(ep, &endpoint.Result{Success: false}, scenario.AlertingConfig, true) + verify(t, ep, 3, 0, false, "The alert should still not be triggered, because the alert provider is still returning an error") + HandleAlerting(ep, &endpoint.Result{Success: false}, scenario.AlertingConfig, true) + verify(t, ep, 4, 0, false, "The alert should still not be triggered, because the alert provider is still returning an error") _ = os.Setenv("MOCK_ALERT_PROVIDER_ERROR", "false") - HandleAlerting(endpoint, &core.Result{Success: false}, scenario.AlertingConfig, true) - verify(t, endpoint, 5, 0, true, "The alert should've been triggered because the alert provider is no longer returning an error") - HandleAlerting(endpoint, &core.Result{Success: true}, scenario.AlertingConfig, true) - verify(t, endpoint, 0, 1, true, "The alert should've still been triggered") + HandleAlerting(ep, &endpoint.Result{Success: false}, scenario.AlertingConfig, true) + verify(t, ep, 5, 0, true, "The alert should've been triggered because the alert provider is no longer returning an error") + HandleAlerting(ep, &endpoint.Result{Success: true}, scenario.AlertingConfig, true) + verify(t, ep, 0, 1, true, "The alert should've still been triggered") _ = os.Setenv("MOCK_ALERT_PROVIDER_ERROR", "true") - HandleAlerting(endpoint, &core.Result{Success: true}, scenario.AlertingConfig, true) - verify(t, endpoint, 0, 2, false, "The alert should've been resolved DESPITE THE ALERT PROVIDER RETURNING AN ERROR. See Alert.Triggered for further explanation.") + HandleAlerting(ep, &endpoint.Result{Success: true}, scenario.AlertingConfig, true) + verify(t, ep, 0, 2, false, "The alert should've been resolved DESPITE THE ALERT PROVIDER RETURNING AN ERROR. See Alert.Triggered for further explanation.") _ = os.Setenv("MOCK_ALERT_PROVIDER_ERROR", "false") // Make sure that everything's working as expected after a rough patch - HandleAlerting(endpoint, &core.Result{Success: false}, scenario.AlertingConfig, true) - verify(t, endpoint, 1, 0, false, "") - HandleAlerting(endpoint, &core.Result{Success: false}, scenario.AlertingConfig, true) - verify(t, endpoint, 2, 0, true, "The alert should have triggered") - HandleAlerting(endpoint, &core.Result{Success: true}, scenario.AlertingConfig, true) - verify(t, endpoint, 0, 1, true, "The alert should still be triggered") - HandleAlerting(endpoint, &core.Result{Success: true}, scenario.AlertingConfig, true) - verify(t, endpoint, 0, 2, false, "The alert should have been resolved") + HandleAlerting(ep, &endpoint.Result{Success: false}, scenario.AlertingConfig, true) + verify(t, ep, 1, 0, false, "") + HandleAlerting(ep, &endpoint.Result{Success: false}, scenario.AlertingConfig, true) + verify(t, ep, 2, 0, true, "The alert should have triggered") + HandleAlerting(ep, &endpoint.Result{Success: true}, scenario.AlertingConfig, true) + verify(t, ep, 0, 1, true, "The alert should still be triggered") + HandleAlerting(ep, &endpoint.Result{Success: true}, scenario.AlertingConfig, true) + verify(t, ep, 0, 2, false, "The alert should have been resolved") }) } @@ -449,7 +449,7 @@ func TestHandleAlertingWithProviderThatOnlyReturnsErrorOnResolve(t *testing.T) { }, } enabled := true - endpoint := &core.Endpoint{ + ep := &endpoint.Endpoint{ URL: "https://example.com", Alerts: []*alert.Alert{ { @@ -463,38 +463,38 @@ func TestHandleAlertingWithProviderThatOnlyReturnsErrorOnResolve(t *testing.T) { }, } - HandleAlerting(endpoint, &core.Result{Success: false}, cfg.Alerting, cfg.Debug) - verify(t, endpoint, 1, 0, true, "") + HandleAlerting(ep, &endpoint.Result{Success: false}, cfg.Alerting, cfg.Debug) + verify(t, ep, 1, 0, true, "") _ = os.Setenv("MOCK_ALERT_PROVIDER_ERROR", "true") - HandleAlerting(endpoint, &core.Result{Success: true}, cfg.Alerting, cfg.Debug) - verify(t, endpoint, 0, 1, false, "") + HandleAlerting(ep, &endpoint.Result{Success: true}, cfg.Alerting, cfg.Debug) + verify(t, ep, 0, 1, false, "") _ = os.Setenv("MOCK_ALERT_PROVIDER_ERROR", "false") - HandleAlerting(endpoint, &core.Result{Success: false}, cfg.Alerting, cfg.Debug) - verify(t, endpoint, 1, 0, true, "") + HandleAlerting(ep, &endpoint.Result{Success: false}, cfg.Alerting, cfg.Debug) + verify(t, ep, 1, 0, true, "") _ = os.Setenv("MOCK_ALERT_PROVIDER_ERROR", "true") - HandleAlerting(endpoint, &core.Result{Success: true}, cfg.Alerting, cfg.Debug) - verify(t, endpoint, 0, 1, false, "") + HandleAlerting(ep, &endpoint.Result{Success: true}, cfg.Alerting, cfg.Debug) + verify(t, ep, 0, 1, false, "") _ = os.Setenv("MOCK_ALERT_PROVIDER_ERROR", "false") // Make sure that everything's working as expected after a rough patch - HandleAlerting(endpoint, &core.Result{Success: false}, cfg.Alerting, cfg.Debug) - verify(t, endpoint, 1, 0, true, "") - HandleAlerting(endpoint, &core.Result{Success: false}, cfg.Alerting, cfg.Debug) - verify(t, endpoint, 2, 0, true, "") - HandleAlerting(endpoint, &core.Result{Success: true}, cfg.Alerting, cfg.Debug) - verify(t, endpoint, 0, 1, false, "") - HandleAlerting(endpoint, &core.Result{Success: true}, cfg.Alerting, cfg.Debug) - verify(t, endpoint, 0, 2, false, "") + HandleAlerting(ep, &endpoint.Result{Success: false}, cfg.Alerting, cfg.Debug) + verify(t, ep, 1, 0, true, "") + HandleAlerting(ep, &endpoint.Result{Success: false}, cfg.Alerting, cfg.Debug) + verify(t, ep, 2, 0, true, "") + HandleAlerting(ep, &endpoint.Result{Success: true}, cfg.Alerting, cfg.Debug) + verify(t, ep, 0, 1, false, "") + HandleAlerting(ep, &endpoint.Result{Success: true}, cfg.Alerting, cfg.Debug) + verify(t, ep, 0, 2, false, "") } -func verify(t *testing.T, endpoint *core.Endpoint, expectedNumberOfFailuresInARow, expectedNumberOfSuccessInARow int, expectedTriggered bool, expectedTriggeredReason string) { - if endpoint.NumberOfFailuresInARow != expectedNumberOfFailuresInARow { - t.Errorf("endpoint.NumberOfFailuresInARow should've been %d, got %d", expectedNumberOfFailuresInARow, endpoint.NumberOfFailuresInARow) +func verify(t *testing.T, ep *endpoint.Endpoint, expectedNumberOfFailuresInARow, expectedNumberOfSuccessInARow int, expectedTriggered bool, expectedTriggeredReason string) { + if ep.NumberOfFailuresInARow != expectedNumberOfFailuresInARow { + t.Errorf("endpoint.NumberOfFailuresInARow should've been %d, got %d", expectedNumberOfFailuresInARow, ep.NumberOfFailuresInARow) } - if endpoint.NumberOfSuccessesInARow != expectedNumberOfSuccessInARow { - t.Errorf("endpoint.NumberOfSuccessesInARow should've been %d, got %d", expectedNumberOfSuccessInARow, endpoint.NumberOfSuccessesInARow) + if ep.NumberOfSuccessesInARow != expectedNumberOfSuccessInARow { + t.Errorf("endpoint.NumberOfSuccessesInARow should've been %d, got %d", expectedNumberOfSuccessInARow, ep.NumberOfSuccessesInARow) } - if endpoint.Alerts[0].Triggered != expectedTriggered { + if ep.Alerts[0].Triggered != expectedTriggered { if len(expectedTriggeredReason) != 0 { t.Error(expectedTriggeredReason) } else { diff --git a/watchdog/watchdog.go b/watchdog/watchdog.go index 2557b413..3fcb977a 100644 --- a/watchdog/watchdog.go +++ b/watchdog/watchdog.go @@ -9,8 +9,8 @@ import ( "github.com/TwiN/gatus/v5/alerting" "github.com/TwiN/gatus/v5/config" "github.com/TwiN/gatus/v5/config/connectivity" + "github.com/TwiN/gatus/v5/config/endpoint" "github.com/TwiN/gatus/v5/config/maintenance" - "github.com/TwiN/gatus/v5/core" "github.com/TwiN/gatus/v5/metrics" "github.com/TwiN/gatus/v5/storage/store" ) @@ -37,17 +37,17 @@ func Monitor(cfg *config.Config) { } // monitor a single endpoint in a loop -func monitor(endpoint *core.Endpoint, alertingConfig *alerting.Config, maintenanceConfig *maintenance.Config, connectivityConfig *connectivity.Config, disableMonitoringLock, enabledMetrics, debug bool, ctx context.Context) { +func monitor(ep *endpoint.Endpoint, alertingConfig *alerting.Config, maintenanceConfig *maintenance.Config, connectivityConfig *connectivity.Config, disableMonitoringLock, enabledMetrics, debug bool, ctx context.Context) { // Run it immediately on start - execute(endpoint, alertingConfig, maintenanceConfig, connectivityConfig, disableMonitoringLock, enabledMetrics, debug) + execute(ep, alertingConfig, maintenanceConfig, connectivityConfig, disableMonitoringLock, enabledMetrics, debug) // Loop for the next executions for { select { case <-ctx.Done(): - log.Printf("[watchdog.monitor] Canceling current execution of group=%s; endpoint=%s", endpoint.Group, endpoint.Name) + log.Printf("[watchdog.monitor] Canceling current execution of group=%s; endpoint=%s", ep.Group, ep.Name) return - case <-time.After(endpoint.Interval): - execute(endpoint, alertingConfig, maintenanceConfig, connectivityConfig, disableMonitoringLock, enabledMetrics, debug) + case <-time.After(ep.Interval): + execute(ep, alertingConfig, maintenanceConfig, connectivityConfig, disableMonitoringLock, enabledMetrics, debug) } } // Just in case somebody wandered all the way to here and wonders, "what about ExternalEndpoints?" @@ -55,7 +55,7 @@ func monitor(endpoint *core.Endpoint, alertingConfig *alerting.Config, maintenan // periodically like they are for normal endpoints. } -func execute(endpoint *core.Endpoint, alertingConfig *alerting.Config, maintenanceConfig *maintenance.Config, connectivityConfig *connectivity.Config, disableMonitoringLock, enabledMetrics, debug bool) { +func execute(ep *endpoint.Endpoint, alertingConfig *alerting.Config, maintenanceConfig *maintenance.Config, connectivityConfig *connectivity.Config, disableMonitoringLock, enabledMetrics, debug bool) { if !disableMonitoringLock { // By placing the lock here, we prevent multiple endpoints from being monitored at the exact same time, which // could cause performance issues and return inaccurate results @@ -68,32 +68,32 @@ func execute(endpoint *core.Endpoint, alertingConfig *alerting.Config, maintenan return } if debug { - log.Printf("[watchdog.execute] Monitoring group=%s; endpoint=%s", endpoint.Group, endpoint.Name) + log.Printf("[watchdog.execute] Monitoring group=%s; endpoint=%s", ep.Group, ep.Name) } - result := endpoint.EvaluateHealth() + result := ep.EvaluateHealth() if enabledMetrics { - metrics.PublishMetricsForEndpoint(endpoint, result) + metrics.PublishMetricsForEndpoint(ep, result) } - UpdateEndpointStatuses(endpoint, result) + UpdateEndpointStatuses(ep, result) if debug && !result.Success { - log.Printf("[watchdog.execute] Monitored group=%s; endpoint=%s; success=%v; errors=%d; duration=%s; body=%s", endpoint.Group, endpoint.Name, result.Success, len(result.Errors), result.Duration.Round(time.Millisecond), result.Body) + log.Printf("[watchdog.execute] Monitored group=%s; endpoint=%s; success=%v; errors=%d; duration=%s; body=%s", ep.Group, ep.Name, result.Success, len(result.Errors), result.Duration.Round(time.Millisecond), result.Body) } else { - log.Printf("[watchdog.execute] Monitored group=%s; endpoint=%s; success=%v; errors=%d; duration=%s", endpoint.Group, endpoint.Name, result.Success, len(result.Errors), result.Duration.Round(time.Millisecond)) + log.Printf("[watchdog.execute] Monitored group=%s; endpoint=%s; success=%v; errors=%d; duration=%s", ep.Group, ep.Name, result.Success, len(result.Errors), result.Duration.Round(time.Millisecond)) } if !maintenanceConfig.IsUnderMaintenance() { // TODO: Consider moving this after the monitoring lock is unlocked? I mean, how much noise can a single alerting provider cause... - HandleAlerting(endpoint, result, alertingConfig, debug) + HandleAlerting(ep, result, alertingConfig, debug) } else if debug { log.Println("[watchdog.execute] Not handling alerting because currently in the maintenance window") } if debug { - log.Printf("[watchdog.execute] Waiting for interval=%s before monitoring group=%s endpoint=%s again", endpoint.Interval, endpoint.Group, endpoint.Name) + log.Printf("[watchdog.execute] Waiting for interval=%s before monitoring group=%s endpoint=%s again", ep.Interval, ep.Group, ep.Name) } } // UpdateEndpointStatuses updates the slice of endpoint statuses -func UpdateEndpointStatuses(endpoint *core.Endpoint, result *core.Result) { - if err := store.Get().Insert(endpoint, result); err != nil { +func UpdateEndpointStatuses(ep *endpoint.Endpoint, result *endpoint.Result) { + if err := store.Get().Insert(ep, result); err != nil { log.Println("[watchdog.UpdateEndpointStatuses] Failed to insert result in storage:", err.Error()) } } @@ -101,8 +101,8 @@ func UpdateEndpointStatuses(endpoint *core.Endpoint, result *core.Result) { // Shutdown stops monitoring all endpoints func Shutdown(cfg *config.Config) { // Disable all the old HTTP connections - for _, endpoint := range cfg.Endpoints { - endpoint.Close() + for _, ep := range cfg.Endpoints { + ep.Close() } cancelFunc() }